WebGL學習之從着色器入門 原

Intro

不論是WebGL、OpenGL、OpenGL ES、Vulkan、DirectX,這些聽起來就十分“底層”、“高性能”、“難寫”的東西似乎是和我一個後端開發都沒什麼關係。(遠處傳來聲音:別tm擅自改臺詞!)

咳,迴歸正題。

我看了好多的 OpenGL 入門書,固定管線的比較好懂,但過時已久。新的可編程管線則十分晦澀——沒有人試圖解釋一個繪製三角形的簡單 OpenGL 程序有哪些細節,紅寶書也是,上手便是一個"Framework",面對大片幾十上百行代碼,試圖理解的人總會遇到各種“這個常量是什麼意思?glBind是在幹嘛?三角形是怎麼畫出來的?”

實在太魔術了。

所以本博文試圖從一個更全局的角度,去解釋一個最簡單的 畫三角形 WebGL 程序都做了什麼。

零、縱觀全局

一個OpenGL的Hello World無非是做了這些事情。

  1. 創建shader
  2. 編譯shader
  3. 鏈接shader爲program
  4. 創建/初始化頂點buffer
  5. 將頂點buffer傳遞給頂點着色器,完成渲染

一、着色器

着色器是一段在GPU上運行的程序,它不是腳本,有使用過編譯語言的同學應該明白,C要編譯成一個exe需要經過編譯、鏈接這兩個步驟,同樣的,着色器程序也需要這兩個步驟。

對於單個着色器我們需要先使用gl.compileShader(shader)來編譯,這個api不會直接返回錯誤,而是設置了一個全局的錯誤代碼,通過gl.getError來取得。十分老派的posix/unix/c風格錯誤處理api,不是嗎。

着色器編譯不會帶來任何可見的改變,我們持有的shader對象本質上是一個指向黑箱的索引,編譯好shader之後我們使用gl.createProgramgl.attachShadergl.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)}`);

大多教程都封裝了這個加載、創建着色器程序的細節,通常來說像是叫做createShaderloadShader的做的事情和以上代碼做的差不多。

GLSL

glsl是OpenGL的着色器語言,可以理解爲和C語言類似的編譯型語言,不過因爲是在GPU上跑的緣故,所以有諸多限制,比如說不能遞歸。

從一個簡單的頂點着色器說起。

#version 120
attribute vec4 position;

void main() {
    gl_Position = position;
}

上面這個頂點着色器和一個普通C程序類似,但有三個差異點。

差異一:#version

#version 120指定GLSL的版本,GLSL版本和OpenGL版本之間有個對照表。因爲OpenGL細節是由驅動實現的,驅動支持到哪個版本的OpenGLGLSL最多也就是跟進到那個版本而已。

差異二:attribute

attribute只能在頂點着色器裏,去聲明一個可以被GLSL外的環境修改的變量,由於現在說的是WebGL,所以這個_GLSL外的環境_指的就是js了。

attribute賦值的方式是通過兩個api:gl.getAttribLocationgl.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

按照網上解釋,gl_Position是當前在處理的那個頂點在處理結束之後的位置,是一個GLSL內置的變量,它的存在可以用C裏面extern vec4 gl_Position的聲明來類比。不過在GLSL裏,gl_Position是不需要聲明的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章