在〈貼圖座標〉中談到,貼圖是上色時採樣的依據,這表示著色器的撰寫上,必須傳入貼圖座標:
<script id="vertex-shader" type="x-shader/x-vertex">
uniform float aspect;
attribute vec3 vertexPosition;
attribute vec2 texturePosition;
varying vec2 vTexturePosition;
void main(void) {
gl_Position = vec4(vertexPosition.x, vertexPosition.y * aspect, vertexPosition.z, 1.0);
vTexturePosition = texturePosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D sampler;
varying vec2 vTexturePosition;
void main(void) {
gl_FragColor = texture2D(uSampler, vTexturePosition);
}
</script>
在頂點著色器中,傳入的貼圖座標 texturePosition
指定給 vTexturePosition
,因此片段著色器中就可以利用貼圖座標來上色,貼圖本身的資訊會設定給 sampler
,型態為 sampler2D
,顯然地,採樣的來源是個 2D 圖片,texture2D
函式會從指定的採樣器中依座標取得顏色資訊作為像素的顏色。
為了能在圖片下載後繪製,這邊將程式寫在 load
事件之中:
// 共用的 Buffer 設置函式
function enableBufferOfAttr(gl, prog, attrName, size, data) {
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
const position = gl.getAttribLocation(prog, attrName);
gl.vertexAttribPointer(position, size, gl.FLOAT, false, 0 , 0);
gl.enableVertexAttribArray(position);
}
const gl = getGLContext(document.getElementById('glCanvas'));
const prog = installProgram(gl,
shaderSourceById('vertex-shader'),
shaderSourceById('fragment-shader')
);
const image = new Image();
image.addEventListener('load', () => {
// 在這邊進行繪製
...
});
image.src = "images/caterpillar.jpg";
接下來,啟用、建立、綁定貼圖:
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
activeTexture
表示使用哪個貼圖單元,createTexture
建立的物件會作為貼圖操作時的資料存儲與狀態記錄之用,在這邊,bindTexture
會將之綁定在 gl.TEXTURE0
。
接著把下載的圖片置入:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB , gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
texImage2D
用來設置貼圖來源與相關參數,它有多個重載版本,在這邊,gl.TEXTURE_2D
表示這是個二維貼圖,0 表示這是原始圖片(具體來說,mipmap 的第 0 級,之後文件會說明),第一個 gl.RGB
表示貼圖來源的顏色資訊,因為這是個 JPG,不會有 Alpha 資訊,因此指定為 gl.RGB
就可以了,第二個 gl.RGB
是 Texel(Texture 與 Pixel 的合成字)資料格式,也就是貼圖讀入為位元組之後,如何看待這些位元組資訊,對於 WebGL 1,必須與前一個參數值相同,gl.UNSIGNED_BYTE
是 Texel 的資料型態,這個版本的 texImage2D
會自動依 image
判斷圖片寬高。
在這邊暫且不用關心 generateMipmap
,只需要先知道,如果圖片的長、寬都是 2 的次方,那麼 generateMipmap
可以自動處理貼圖縮放的問題,當然,也可以自己控制,對於圖片的長、寬不是 2 次方的圖片,不能使用 generateMipmap
,也得自行控制縮放相關參數。
在這邊,caterpillar.jpg 是 256x256,因此可以使用 generateMipmap
,之後會再說明它的作用。
接下來指定採樣器要使用哪個貼圖單元:
gl.uniform1i(
gl.getUniformLocation(prog, "sampler"),
0
);
由於著色器中全域變數的預設值為 0,在只有一個貼圖的情況下,gl.activeTexture(gl.TEXTURE0)
與上頭採樣器的設置不寫,其實也是可以運作的,不過這邊還是將之寫出來比較清楚。
剩下的就是頂點、貼圖座標等的設置了,這邊要畫出完整的圖片,因此要使用兩個三角形:
enableBufferOfAttr(gl, prog, 'vertexPosition', 3, [
-0.25, 0.25, 0.0,
0.25, 0.25, 0.0,
-0.25, -0.25, 0.0,
-0.25, -0.25, 0.0,
0.25, 0.25, 0.0,
0.25, -0.25, 0.0
]);
enableBufferOfAttr(gl, prog, 'texturePosition', 2, [
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
]);
gl.uniform1f(
gl.getUniformLocation(prog, 'aspect'),
gl.canvas.clientWidth / gl.canvas.clientHeight
);
// 繪製
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6);
可以按一下範例網頁,費盡千辛萬苦,只為了畫一張圖?當然不是這樣的,因為使用三角形構成,只要改變一下頂點座標,就可以完成某些效果,例如:
enableBufferOfAttr(gl, prog, 'vertexPosition', 3, [
-0.5, 0.25, 0.0,
0.25, 0.25, 0.0,
-0.25, -0.25, 0.0,
-0.25, -0.25, 0.0,
0.5, 0.25, 0.0,
0.25, -0.25, 0.0
]);
就可以把挖土機切割了:
想像一下,多規劃幾個三角形,修改一下程式,就可以完成玻璃心碎掉的效果了。
另一方面,因為片段著色器中,texture2D
傳回 vec4
,如果重新組合 RGBA 的資訊,就可以達到變化色彩的效果了,例如:
gl_FragColor = texture2D(sampler, vTexturePosition).gbra;
就可以把挖土機變色了:
知道這些之後,基本上,要貼圖到立體方塊上,只要頂點、貼圖座標與索引陣列不要搞錯就好了,例如這個範例網頁有個轉動的立體方塊,詳細程式碼就自行從中探索了: