使用Buffer

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

在〈使用attribute 變數〉中,每次只傳遞一個向量或浮點數,如果有多個頂點要繪製呢?這時可以透過Buffer,使用JavaScript 將資料放入Buffer,然後著色器從Buffer... 回WebGL 在〈使用attribute變數〉中,每次只傳遞一個向量或浮點數,如果有多個頂點要繪製呢?這時可以透過Buffer,使用JavaScript將資料放入Buffer,然後著色器從Buffer取得資料。

來看看怎麼畫兩個點好了,這只需要用到簡單的著色器: attributevec3position; voidmain(void){ gl_Position=vec4(position,1.0); gl_PointSize=5.0; } voidmain(void){ gl_FragColor=vec4(1.0,0.0,0.0,1.0); } 在這邊只有一個attribute,頂點著色器會從Buffer中逐一取出頂點指定給position,然後執行main;JavaScript的部份,使用陣列作為Buffer,例如: import{getGLContext,shaderSourceById,installProgram}from'./js/gl-comm-1.js'; constgl=getGLContext(document.getElementById('glCanvas')); constprog=installProgram(gl, shaderSourceById('vertex-shader'), shaderSourceById('fragment-shader') ); //兩個頂點 constverteices=[ 0.5,0.0,0.0, -0.5,0.0,0.0 ]; //建立、指定、綁定Buffer constbuffer=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,buffer); gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(verteices),gl.STATIC_DRAW); 在這邊可以看到,因為getGLContext、shaderSourceById、installProgram經常使用,就放在模組然後匯入了,createBuffer用來建立Buffer,bindBuffer用來指定gl接下來的操作會是針對哪個Buffer,bufferData會在被綁定的Buffer中將指定的資料置入。

因為這邊不做動畫,指定了gl.STATIC_DRAW,這是個最佳化提示,表示會經常使用Buffer,然而不常去變動它,在MDN的bufferData文件中,可以查看到其他提示,像是gl.DYNAMIC_DRAW、gl.STREAM_DRAW等。

接下來可以指定position來取用這個Buffer: constattr_position=gl.getAttribLocation(prog,'position'); gl.vertexAttribPointer(attr_position,3,gl.FLOAT,false,0,0); gl.enableVertexAttribArray(attr_position); vertexAttribPointer的參數中3與g.FLOAT表示,每次從Buffer中取用三個單位的資料,型態是浮點數;false是normalized指定,簡單來說,若設定為true,會把整數轉換為-1~1的值,因為這邊設定為g.FLOAT了,這個參數基本上就是false(這時設成true也沒作用);下一個0是stride,用來指定每單位資料是幾個位元組,被設成0的話,就表示使用指定的型態;最後一個0表示從Buffer哪裏開始讀取,這邊是從陣列開頭開始。

接下來就可以畫兩個點了: letmode=gl.POINTS; gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(mode,0,2); mode如果改成gl.LINES的話,就會每兩個點連接起來,繪製出一條線來,例如,希望按下滑鼠時可以切換點與線的繪製: gl.canvas.addEventListener('mousedown',()=>{ mode=mode===gl.POINTS?gl.LINES:gl.POINTS; gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(mode,0,2); }); 可以點一下範例網頁看看效果,因為會有兩個頂點,頂點著色器會被呼叫兩次,然而卻畫出了一條線,這是片段著色器的功勞,它會自動根據兩個頂點座標處指定的varying變數值,計算出插值來執行演算內容,最後指定給gl_FragColor,這個過程叫柵格化(Rasterisation),就目前來說,並沒有用到varying變數,片段著色器的顏色是寫死的,因此兩個點間的像素顏色也就固定了,之後會看到,頂點著色器如何使用varying變數傳遞資訊給片段著色器。

除了gl.POINTS、g.LINES之外,還有gl.LINE_STRIP依序連接每個點,gl.LINE_LOOP依序連接每個點外,最後一個點還會連接第一個點,在上頭的範例因為只有兩個點,設成這兩個效果都一樣,來看看正三角形的繪製好了: //正三角頂點 constverteices=[ 0.25,0.0,0.0, 0.0,0.433,0.0, -0.25,0.0,0.0 ]; //建立、指定、綁定Buffer constbuffer=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,buffer); gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(verteices),gl.STATIC_DRAW); constattr_position=gl.getAttribLocation(prog,'position'); gl.vertexAttribPointer(attr_position,3,gl.FLOAT,false,0,0); gl.enableVertexAttribArray(attr_position); //繪製 leti=0; letmodes=[gl.LINE_STRIP,gl.LINE_LOOP,gl.TRIANGLES]; gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(modes[i],0,3); gl.canvas.addEventListener('mousedown',()=>{ i=(i+1)%3; gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(modes[i],0,3); }); 按下滑鼠的話,會依序展示gl.LINE_STRIP、gl.LINE_LOOP與gl.TRIANGLES的效果,gl.TRIANGLES是每次取三個點,畫出一個三角形,你可以先點一下範例網頁看看效果,嗯?不是要畫正三角嗎?你看到的不是正三角?只是個等腰三角?頂點設錯了嗎? 如果單看verteices中的三個頂點值,確實是正三角沒錯,不過別忘了,在〈認識著色器〉談到: 如果要將Canvas的顯示空間對應至裁剪空間,那麼通常可以將Canvas顯示空間最左邊對應至裁剪空間的x的-1.0,最右邊對應至1.0,顯示空間最下方對應至裁剪空間y的-1.0,最上方對應至1.0。

這表示,如果你的Canvas寬高並不是一比一的話,畫出來的東西是會變形的,若不想如此,必須依Canvas寬高比例做出修正: uniformfloataspect; attributevec3position; voidmain(void){ gl_Position=vec4(position.x,position.y*aspect,position.z,1.0); gl_PointSize=5.0; } 這邊看到了uniform修飾,它表示在繪製過程中,aspect的值是固定的,配合這個變數,JavaScript的部份加入變數設定: gl.uniform1f( gl.getUniformLocation(prog,'aspect'), gl.canvas.clientWidth/gl.canvas.clientHeight ); 要取得uniform變數的位置,必須使用getUniformLocation方法,而設定值時,使用的是uniform1f這類開頭為uniform的方法,點一下範例網頁看看效果,應該可以看到正三角了。

來順便看一下gl.TRIANGLE_STRIP與gl.TRIANGLE_FAN好了。

gl.TRIANGLE_STRIP可以共用頂點,從第三個頂點開始,每個頂點與前兩個頂點連成三角形,也就是後續的三角形,會使用前兩個頂點形成的邊作為共用邊。

例如: constverteices=[ 0.25,0.0,0.0, 0.0,0.433,0.0, -0.25,0.0,0.0, -0.5,0.433,0.0 ]; ...略 gl.drawArrays(gl.TRIANGLE_STRIP,0,4); 看一下範例網頁,你會看到兩個正三角形成的平行四邊形,下圖列出了頂點關係: 至於gl.TRIANGLE_FAN,正如其名稱所示,適合繪製扇形,因為它從第三個頂點開始,都會與第一個和上個頂點相連,例如: //圓的頂點 constr=0.25; constfn=24; constverteices=[0.0,0.0,0.0]; for(leti=0,step=2*Math.PI/fn;i{ gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_FAN,0,3+i); i=(i+1)%fn; }); 看一下範例網頁,每點一次就會增加扇形的面積,直到畫出一個圓。



請為這篇文章評分?