varying 傳遞著色資訊

文章推薦指數: 80 %
投票人數:10人

至今為止的範例,片段著色器的gl_FragColor 都是寫死的,然而attribute 只能用在頂點著色器中,若想自行指定顏色資訊,應當如何傳遞給片段著色器? 著色器的設計, ... 回WebGL 至今為止的範例,片段著色器的gl_FragColor都是寫死的,然而attribute只能用在頂點著色器中,若想自行指定顏色資訊,應當如何傳遞給片段著色器? 著色器的設計,基本上應該只針對各個頂點與像素,不能直接指定資訊給片段著色器,是可以理解的,想要傳遞給片段著色器的資訊,可以透過varying變數,它是頂點著色器的輸出,片段著色器的輸入。

最單純的做法是,將顏色資訊指定給頂點著色器,然後透過varying傳遞至片段著色器,例如: uniformfloataspect; attributevec3position; attributevec4color; varyingvec4fColor; voidmain(void){ gl_Position=vec4(position.x,position.y*aspect,position.z,1.0); gl_PointSize=5.0; fColor=color; } precisionmediumpfloat; varyingvec4fColor; voidmain(void){ gl_FragColor=fColor; } 在頂點著色器中,使用varying宣告了fColor變數,color會接受JavaScript指定的資訊,並在執行時指定給fColor。

頂點著色器會有預設的精度highp,然而片段著色器沒有預設精度,因此在片段著色器必須指定精度,precisionmediumpfloat的指定方式會適用整個片段著色器,也可以使用varyingmediumpvec4fColor的方式個別指定,可以指定的精度有highp、mediump與lowp,越高的精度負擔越大,在某些效能不好的行動裝置上可能會有問題,然而低精度可能有損繪製的效果呈現,通常mediump是個風險較低的權衡做法。

片段著色器的varying變數與頂點著色器的varying名稱相同,這表示頂點著色器中被指定的值,會傳遞到片段著色器中的同名變數。

來寫個隨機畫彩色線的例子作為示範,先建立頂點以及顏色資訊: //頂點數 constn=20; constverteices=[]; for(leti=0;i<3*n;i+=3){ verteices[i]=Math.random()-0.5;//x verteices[i+1]=Math.random()-0.5;//y verteices[i+2]=Math.random()-0.5;//z } //頂點Buffer constvertexBuffer=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(verteices),gl.STATIC_DRAW); constcolors=[]; for(leti=0;i<4*n;i+=4){ colors[i]=Math.random();//R colors[i+1]=Math.random();//G colors[i+2]=Math.random();//B colors[i+3]=Math.random();//Alpha } //顏色Buffer constcolorBuffer=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer); gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(colors),gl.STATIC_DRAW); 顏色的資訊是隨機產生的,接下來,指定頂點與顏色等資訊給各個attribute變數並繪製: //指定給各attribute gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer); constattr_position=gl.getAttribLocation(prog,'position'); gl.vertexAttribPointer(attr_position,3,gl.FLOAT,false,0,0); gl.enableVertexAttribArray(attr_position); gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer); constattr_color=gl.getAttribLocation(prog,'color'); gl.vertexAttribPointer(attr_color,4,gl.FLOAT,false,0,0); gl.enableVertexAttribArray(attr_color); gl.uniform1f( gl.getUniformLocation(prog,'aspect'), gl.canvas.clientWidth/gl.canvas.clientHeight ); //繪製 gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.LINES,0,20); 可以按一下範例網頁來看看效果,這邊也擷個圖來展示一下: varying之所為varying,是因為在片段著色器中,varying變數的值是會變化的,也就是片段著色器會自動根據兩個頂點座標處指定的varying變數值,為每個像素計算出插值來執行演算內容,最後指定給gl_FragColor,這也就是為何你會看到有漸層色的線段。

另一種常見的著色方式,是根據頂點資訊來做些顏色資訊的演算,例如,來個正四面體旋轉: 正四面體的四個面都是正三角,只不過,該怎麼計算頂點呢?拿出三角函數是一個方式,不過,其實正四面體可以連接正立方體的四個頂點來畫出來: 那麼要用drawArray還是drawElement呢?也就是,該用無索引頂點還是搭配頂點索引陣列呢?對正四面體來說都可以!這邊先示範使用無索引頂點,也就是使用drawArray的方式,正四面體可以有共用邊,將之展開的話就可以清楚看出: 這邊打算讓正四面體的四個面呈現漸層色,漸層的資訊是根據頂點而來: uniformfloataspect; attributevec3position; varyingvec3vPosition; voidmain(void){ gl_Position=vec4(position.x,position.y*aspect,position.z,1.0); vPosition=position; } precisionmediumpfloat; varyingvec3vPosition; voidmain(void){ gl_FragColor=vec4((vPosition+vec3(1.0,1.0,1.0))*0.5,1.0); } 由於頂點會是-1.0~1.0,然而顏色值會是0.0~1.0,vPosition會先轉換為0.0~1.0的值,加上alpha再指定給gl_FragColor。

在JavaScript的部份,就只要指定頂點等資訊: //正四面體 constn=0.25; constverteices=[ n,-n,-n, -n,-n,n, n,n,n, -n,n,-n, n,-n,-n, -n,-n,n ]; rotateXY(verteices,Math.PI/3,0); //頂點Buffer constvertexBuffer=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(verteices),gl.DYNAMIC_DRAW); constattr_position=gl.getAttribLocation(prog,'position'); gl.vertexAttribPointer(attr_position,3,gl.FLOAT,false,0,0); gl.enableVertexAttribArray(attr_position); gl.uniform1f( gl.getUniformLocation(prog,'aspect'), gl.canvas.clientWidth/gl.canvas.clientHeight ); 接下來就是繪製的部份,結合了滑鼠事件以及requestAnimationFrame,因為繪製時會有共用邊,因此drawArrays時搭配的是gl.TRIANGLE_STRIP: letanimation=false; functiondrawTetrahedron(){ gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_STRIP,0,6); rotateXY(verteices,0,0.025); gl.bufferSubData(gl.ARRAY_BUFFER,0,newFloat32Array(verteices)); if(animation){ requestAnimationFrame(drawTetrahedron); } } gl.canvas.addEventListener('mousedown',()=>{ animation=!animation; if(animation){ drawTetrahedron(); } }); drawTetrahedron(); 你可以按一下範例網頁看看效果,看起來好像不錯,不過其實並不是正確的繪製結果,仔細看的話,會發現應該是看不見的背面也被繪製了,這有兩種方式可以解決,一是啟用深度測試讓較深的點不會繪製,二是啟用面剔除(Faceculling),不繪製背面,這就留待下一篇來說明。



請為這篇文章評分?