引子
JavaScript WebGL 設定顏色效果始終有限,這時候就會想到使用圖片,這就涉及到 WebGL 中的紋理使用,比預想中要麻煩的多。
使用圖片
紋理(texture)可以用來新增模擬物體的細節,在 3D 遊戲中各種模擬的物體都使用了紋理。在繪製矩形的基礎上主要有以下幾個方面的變化:
- 資料
- 頂點著色器
- 片元著色器
- 緩衝紋理座標資料
- 載入並建立紋理
- 繪製
資料
先準備一張圖片,然後為了把紋理對映到對應的矩形上,需要指定矩形每個頂點各自對應紋理的那個位置。
紋理二維座標在 x 和 y 軸上,範圍為 0 到 1 之間。紋理座標起於(0, 0),對應圖片的左下角,終於(1, 1),對應圖片的右上角。所以對應的紋理座標為:
const texCoords = [
1.0,
1.0, // 右上角
0.0,
1.0, // 左上角
0.0,
0.0, // 左下角
1.0,
0.0, // 右下角
];
頂點著色器
紋理座標需要進行緩衝並進行傳遞,在頂點著色器中增加了變數 aVertexTextureCoord
,其值會傳遞到片元著色器。
const source = `
attribute vec3 aVertexPos;
attribute vec2 aVertexTextureCoord;
varying highp vec2 vTextureCoord;
void main(void){
gl_Position = vec4(aVertexPos, 1);
vTextureCoord = aVertexTextureCoord;
}
`;
片元著色器
在片元著色器中接受紋理座標,定義紋理取樣器 uSampler
,注意這個是全域性變數,在任何階段都可以訪問,目前還沒有值。內建的方法 texture2D
獲得最終的顏色。
const fragmentSource = `
varying highp vec2 vTextureCoord;
uniform sampler2D uSampler;
void main(void){
gl_FragColor = texture2D(uSampler, vTextureCoord);
}
`;
緩衝紋理座標資料
紋理座標資料同樣需要進入緩衝。
/**
* 緩衝紋理座標資料並啟用
* @param {*} gl WebGL 上下文
* @param {*} shaderProgram 著色器程式
* @param {*} data 紋理座標資料
*/
function setTextureBuffers(gl, shaderProgram, data) {
// 建立空白的緩衝物件
const buffer = gl.createBuffer();
// 繫結目標
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// WebGL 不支援直接使用 JavaScript 原始陣列型別,需要轉換
const dataFormat = new Float32Array(data);
// 初始化資料儲存
gl.bufferData(gl.ARRAY_BUFFER, dataFormat, gl.STATIC_DRAW);
// 獲取對應資料索引
const texCoord = gl.getAttribLocation(
shaderProgram,
"aVertexTextureCoord"
);
// 解析頂點資料
gl.vertexAttribPointer(texCoord, 2, gl.FLOAT, false, 0, 0);
// 啟用頂點屬性,頂點屬性預設是禁用的。
gl.enableVertexAttribArray(texCoord);
}
載入並建立紋理
要保證圖片載入完成後才能使用。得到圖片資料後需要建立紋理物件。
function loadImage(gl) {
var img = new Image();
img.onload = (e) => {
createTexture(gl, e.target);
};
img.src = "./1.jpg";
}
function createTexture(gl, source) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// 反轉圖片 Y 軸方向
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
// 紋理座標水平填充 s
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// 紋理座標垂直填充 t
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 紋理放大處理
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// 紋理縮小處理
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 圖片資料賦給紋理物件
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
source
);
// 啟用紋理
gl.activeTexture(gl.TEXTURE0);
}
createTexture 建立紋理物件,接著使用 bindTexture 並繫結到對應的目標,這裡是二維的圖片,第一個引數值取 gl.TEXTURE_2D
表示二維紋理,第二個引數是紋理物件,當為 null
時表示取消繫結。繫結之後才能對紋理進行進一步操作。
pixelStorei 方法對影像進行反轉 Y 方向座標,這是因為圖片的座標系統和紋理參考的座標系不一樣。
texParameteri 方法設定紋理的各種引數,這裡需要特地說明一下,如果希望使用各種尺寸的圖片,需要對水平和垂直填充進行上面的設定,否則只能顯示特定尺寸的圖片。
texImage2D 方法把紋理源賦值給紋理物件,這裡就是把影像的畫素資料傳給紋理物件,這樣才能在繪製紋理的時候看到影像。
activeTexture 方法啟用指定的紋理,紋理單元的範圍是 0 到 gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1
,這裡只有一個,取值為 gl.TEXTUREI0
。預設的第一個紋理單元總是啟用的,所以這行程式碼可以去掉。
繪製
在片元著色器中宣告的全域性變數,繪製時使用 uniform1i 方法指定對應的值,第二個參數列示紋理單元,這裡 0 就是第一個紋理單元。
/**
* 繪製
* @param {*} gl WebGL 上下文
* @param {*} shaderProgram 著色器程式
*/
function draw(gl, shaderProgram) {
// 獲取紋理取樣器
const samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
// 指定全域性變數關聯的紋理單元
gl.uniform1i(samplerUniform, 0);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
效果
這是示例, 效果如下:
如果對比原圖的話,可以發現這張圖片變形了,並沒有自適應。