認識著色器
文章推薦指數: 80 %
void main(void) { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }. 這個片段著色器實際上也沒有從屬性或緩衝區取得資料來進行計算,只是單純地生成固定的的顏色值(1.0, ...
回WebGL
在〈準備WebGLCanvas〉中談到,WebGL的組成中需要著色器程式,初學WebGL時,著色器程式中基本上會有頂點著色器(Vertexshader)及片段著色器(Fragmentshader),前者主要負責頂點的運算,將頂點對應至畫面上的二維座標,後者則是計算出需要繪製的像素顏色值。
著色器使用GLSL撰寫,並透過WebGL的JavaScriptAPI編譯、繫結(attach)、鏈結(link)等動作成為著色器程式,接下來會藉由在Canvas的正中央繪製一個點,認識一下這個流程。
頂點著色器的作用是產生裁剪空間(Clipspace)座標,例如,底下的頂點著色器始終生成裁剪空間中心座標:
voidmain(void){
gl_Position=vec4(0.0,0.0,0.0,1.0);
}
在被要求計算裁剪空間座標時,這個頂點著色器實際上沒有從屬性(Attribute)或緩衝區(Buffer)取得資料來進行計算,只是單純地生成固定的座標值(0.0,0.0,0.0),分別代表(x,y,z),裁剪空間三維座標的分量值必定介於-1.0~1.0,超過範圍的資料會被裁剪不被繪製,至於(x,y,z)的正方向如下(中心為(0,0,0)):
如果要將Canvas的顯示空間對應至裁剪空間,那麼通常可以將Canvas顯示空間最左邊對應至裁剪空間的x的-1.0,最右邊對應至1.0,顯示空間最下方對應至裁剪空間y的-1.0,最上方對應至1.0,而z代表著深度,繪製像素時,z的資訊會轉換為0~1寫入深度緩衝,在啟用深度測試的情況下,預設像素的深度輸入值小於深度緩衝中對應位置的值才會進行繪製,也就是說,近物遮蓋遠物(可以改變這個行為)。
在GLSL中,gl_開頭的變數意謂著保留的變數,gl_Position用來指定裁剪空間中的座標,它需要指定GLSL中vec4型態的值,也就是具有四個分量的浮點數,實際上座標只需要用到前三個分量,第四個分量就座標本身來說用不上,然而慣例上會設為1.0,這在一些向量計算時會比較方便。
那麼一個片段著色器,看起來會是如何呢?
voidmain(void){
gl_FragColor=vec4(1.0,0.0,0.0,1.0);
}
這個片段著色器實際上也沒有從屬性或緩衝區取得資料來進行計算,只是單純地生成固定的的顏色值(1.0,0.0,0.0),也就是RGB,三個分量必定介於-1.0~1.0,同樣地,在這邊vec4的第四個分量慣例上會設為1.0。
在這邊要先知道的是,在被要求渲染時,這邊頂點的頂點著色器直接設定裁剪空間座標為空間的中心,之後片段著色器將直接將之繪製為紅色,之後會看到如何從屬性或緩衝區取得資料進行計算。
著色器要寫在哪呢?可以是個(透過Ajax或FetchAPI)下載的檔案,或者是寫在JavaScript字串裏(透過ES6的模版字串會比較方便),或者是個寫在裏的文字。
例如:
例如,在shader-1.js中寫個函式: functionshaderSourceById(id){ returndocument.getElementById(id).textContent; } 要編譯著色器的話很簡單,寫個如下的函式,glContext是WebGLRenderingContext實例,type會是個常數VERTEX_SHADER或FRAGMENT_SHADER,表示要建立哪種著色器,source是著色器程式的原始碼字串,而API名稱應該是很清楚地提示了它在做些什麼: functionshader(glContext,type,source){ constshader=glContext.createShader(type); glContext.shaderSource(shader,source); glContext.compileShader(shader); if(!glContext.getShaderParameter(shader,glContext.COMPILE_STATUS)){ throw'編譯著色器時發生錯誤:'+glContext.getShaderInfoLog(shader); } returnshader; } 接下來,必須將建立的著色器組合為一個著色器程式,並指定給WebGLRenderingContext實例使用: functioninstallProgram(glContext,vertexSource,fragSource){ constvertexShader=shader(glContext,glContext.VERTEX_SHADER,vertexSource); constfragShader=shader(glContext,glContext.FRAGMENT_SHADER,fragSource); constprog=glContext.createProgram(); glContext.attachShader(prog,vertexShader); glContext.attachShader(prog,fragShader); glContext.linkProgram(prog); if(!glContext.getProgramParameter(prog,glContext.LINK_STATUS)){ throw'編譯著色器時發生錯誤:'+glContext.getProgramInfoLog(prog); } glContext.useProgram(prog); returnprog; } 為了方便,將〈準備WebGLCanvas〉中取得WebGLRenderingContext實例的程式碼也封裝為一個函式: functiongetGLContext(glCanvas){ glCanvas.width=glCanvas.clientWidth; glCanvas.height=glCanvas.clientHeight; constgl=glCanvas.getContext('webgl'); if(!gl){ throw'無法初始化WebGL,您的瀏覽器不支援'; } gl.clearColor(0.0,0.0,0.0,1.0); gl.clear(gl.COLOR_BUFFER_BIT); returngl; } 接下來,只要取得WebGLRenderingContext實例,安裝著色器程式,就可以來畫個點了: constgl=getGLContext(document.getElementById('glCanvas')); installProgram(gl, shaderSourceById('vertex-shader'), shaderSourceById('fragment-shader') ); gl.drawArrays(gl.POINTS,0,1); WebGLRenderingContext的drawArrays基於頂點的向量陣列資料來繪製,第一個參數指定了POINTS常數,這表示只繪製頂點,第二個參數指定從陣列中哪個索引開始,第三個參數指定要繪製幾筆資料,每取得一筆資料,就會執行一次頂點著色器,計算出裁剪空間中的座標,要進行顏色渲染時,就會執行一次片段著色器。
實際上,至今並沒有指定過陣列資料,而著色器本身的程式碼也沒有要求資料,需要用到的值都是直接寫死在著色器,因此第二個參數實際上沒做用,這邊只要求繪製一個點,因此第三個參數設定為1。
費盡千辛萬苦,點一下範例網頁吧!你會看到全黑背景正中央有個紅點,嗯?長的像正方形?一個像素點就是一個正方形啊!
延伸文章資訊
- 1一起幫忙解決難題,拯救IT 人的一天
昨天提到fragment shader 輸出gl_FragColor gl_FragColor // vec4 ... gl_FragColor = vec4(1.0, 1.0, 0.0, 1....
- 2Using gl_FragColor vs. out vec4 color? - Stack Overflow
Writing to gl_FragColor specifies the fragment color that will be used by the subsequent fixed fu...
- 3找gl_FragColor相關社群貼文資訊| 服飾貼文懶人包-2022年1月
關於「gl_FragColor」標籤,搜尋引擎有相關的訊息討論:. 使用shaders 在WebGL 上色- Web APIs | MDN。 ARRAY_BUFFER, new Float32A...
- 4GLSL着色器- 游戏开发环境 - MDN Web Docs
这个着色器的作用是设置 gl_FragColor 变量, 也是一个GLSL内置变量: void main() { gl_FragColor = makeCalculationsToHaveCol...
- 5varying 傳遞著色資訊
至今為止的範例,片段著色器的gl_FragColor 都是寫死的,然而attribute 只能用在頂點著色器中,若想自行指定顏色資訊,應當如何傳遞給片段著色器? 著色器的設計, ...