不管是WebGL、OpenGL、OpenGL ES、Vulkan、DirectX,這些聽起來就十分「底層」、「高性能」、「難寫」的東西彷佛是和我一個後端開發都沒什麼關係。(遠處傳來聲音:別tm擅自改臺詞!)編程
咳,迴歸正題。後端
我看了好多的 OpenGL 入門書,固定管線的比較好懂,但過期已久。新的可編程管線則十分晦澀——沒有人試圖解釋一個繪製三角形的簡單 OpenGL 程序有哪些細節,紅寶書也是,上手即是一個"Framework",面對大片幾十上百行代碼,試圖理解的人總會遇到各類「這個常量是什麼意思?glBind是在幹嗎?三角形是怎麼畫出來的?」api
實在太魔術了。函數
因此本博文試圖從一個更全局的角度,去解釋一個最簡單的 畫三角形 WebGL 程序都作了什麼。性能
一個OpenGL的Hello World無非是作了這些事情。unix
着色器是一段在GPU上運行的程序,它不是腳本,有使用過編譯語言的同窗應該明白,C要編譯成一個exe須要通過編譯、連接這兩個步驟,一樣的,着色器程序也須要這兩個步驟。code
對於單個着色器咱們須要先使用gl.compileShader(shader)
來編譯,這個api不會直接返回錯誤,而是設置了一個全局的錯誤代碼,經過gl.getError
來取得。十分老派的posix/unix/c風格錯誤處理api,不是嗎。對象
着色器編譯不會帶來任何可見的改變,咱們持有的shader
對象本質上是一個指向黑箱的索引,編譯好shader以後咱們使用gl.createProgram
、gl.attachShader
和gl.linkProgram
來創造一個可用的着色器程序,就像是咱們把一段c代碼編譯成了能夠在gpu上跑的exe。教程
看一個示例遞歸
const vs = gl.createShader(gl.VERTEX_SHADER); // 頂點着色器 const fs = gl.createShader(gl.FRAGMENT_SHADER); // 片元着色器 gl.shaderSource(vs, `... 頂點着色器代碼略`); // 指定 shader 源碼 gl.shaderSource(fs, `... 片元着色器代碼略`); // 指定 shader 源碼 gl.compileShader(vs); // 編譯頂點着色器 if(!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) console.log(`頂點着色器編譯錯誤:${gl.getShaderInfoLog(vs)}`); gl.compileShader(fs); // 編譯片元着色器 if(!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) console.log(`片元着色器編譯錯誤:${gl.getShaderInfoLog(fs)}`); const program = gl.createProgram(); // 建立一個着色器程序 gl.attachShader(program, vs); // 指定要連接的 頂點着色器 gl.attachShader(program, fs); // 指定要連接的 片元着色器 gl.linkProgram(program); // 連接着色器程序 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) console.log(`着色器程序連接失敗:${gl.getProgramInfoLog(program)}`);
大多教程都封裝了這個加載、建立着色器程序的細節,一般來講像是叫作createShader
、loadShader
的作的事情和以上代碼作的差很少。
glsl
是OpenGL的着色器語言,能夠理解爲和C語言相似的編譯型語言,不過由於是在GPU上跑的緣故,因此有諸多限制,好比說不能遞歸。
從一個簡單的頂點着色器提及。
#version 120 attribute vec4 position; void main() { gl_Position = position; }
上面這個頂點着色器和一個普通C程序相似,但有三個差別點。
#version 120
指定GLSL
的版本,GLSL
版本和OpenGL
版本之間有個對照表。由於OpenGL細節是由驅動實現的,驅動支持到哪一個版本的OpenGL
,GLSL
最多也就是跟進到那個版本而已。
attribute
是只能在頂點着色器裏,去聲明一個能夠被GLSL外的環境修改的變量,因爲如今說的是WebGL,因此這個_GLSL外的環境_指的就是js了。
爲attribute
賦值的方式是經過兩個api:gl.getAttribLocation
和gl.vertexAttribPointer
。
其中gl.getAttribLocation
能夠得到指定attribute
名字的位置——用函數參數打比方的話,就是這個attribute
是第幾個參數。
const posAttribLocation = gl.getAttribLocation(program, "position");
有了這個posAttribLocation
,咱們就能用gl.vertexAttribPointer
來賦值數據了。
const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Flaot32Buffer([ 0.75, 0.75, 0, 1.0, 0.75, -0.75, 0, 1.0, -0.75, -0.75, 0, 1.0, ]), gl.STATIC_DRAW); gl.vertexAttribPointer(posAttribLocation, 4, gl.FLOAT, false, 0, 0);
還記得OpenGL是狀態機模型吧?
gl.vertexAttribPointer
之因此不須要傳給他要用哪一個buffer
,是由於咱們前面先作了一個gl.bindBuffer
。
按照網上解釋,gl_Position是當前在處理的那個頂點在處理結束以後的位置,是一個GLSL
內置的變量,它的存在能夠用C裏面extern vec4 gl_Position
的聲明來類比。不過在GLSL
裏,gl_Position
是不須要聲明的。