
三角形可太重要了,再復雜的三維模型都是由一個個小三角形組合而成,越多越精細越真實。
繪制三角形
這次繪制三角形,要繪制的點就有三個了,不再是一個。為此我們需要用到緩存區對象(buffer object)。
通過緩存區對象,我們可以一次性向頂點著色器傳入多個頂點數據。
Float32Array
首先我們來用 Float32Array 數組保存需要用到的三個點的位置信息。
// 頂點數據
const vertices = new Float32Array([
// 第一個點
0, 0.5,
// 第二個點
-0.5, -0.5,
// 第三個點
0.5, -0.5
]);
為了更簡單一些,這里先不考慮 z 維度,每個頂點只用了 x 和 y 分量。到時候傳遞到頂點著色器的 gl_Position 時,z 會自動填充默認的 0。
Float32Array 是一個比較特殊的 JavaScript 數組,最初也是為了和 WebGL 配合使用的,它可以創建一個 32 位浮點數的強類型數組。
普通的 JS 數組沒有類型的概念,數組元素可能是數字、字符串、對象的混合體,傳給 WebGL,要處理也麻煩,不太合適。
需要注意的是這種強類型數組的 API 和普通數組不一樣,比如不能用 push 方法。
緩沖區對象的創建和數據寫入
// 創建緩沖區對象
const vertexBuffer = gl.createBuffer();
// 綁定緩沖區對象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向緩沖區對象寫入數據
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
首先是用 gl.createBuffer 方法創建一個緩沖區對象。
然后用 gl.bindBuffer(target, buffer) 將緩存區綁定到 gl 上下文指定目標(gl.ARRAY_BUFFER)中。target 參數還有另一個值是 gl.ELEMENT_ARRAY_BUFFER,這個我們后面章節講如何繪制立方體的時候會用到哈。
buffer 參數就是剛剛創建的緩沖區對象。
最后往緩沖區對象寫入我們剛剛的數組數據。最后一個參數 gl.STATIC_DRAW,表示數據寫入后不會再被修改,去繪制 靜態 場景。
綁定到頂點著色器上
接著就是讓緩沖區對象對接上頂點著色器的變量。
// 獲取 a_Position 變量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
// 將緩沖區對象分配給 a_Position 變量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 允許訪問緩存區
gl.enableVertexAttribArray(a_Position);
首先通過 gl.getAttribLocation 拿到頂點著色器中聲明的 a_Position 變量的地址。
然后是比較復雜的 gl.vertexAttribPointer 方法。參數說明:
- location,待分配的 attribute 變量地址;
- size,每個頂點分量分量個數,就是一次從中取幾個作為一個頂點數據,不夠的補缺省值。
- type,指定數據格式,gl.FLOAT 表示使用的是 Float32Array 的類型。
- normalize,是否將浮點數歸一化到 [0, 1] 或 [-1, 1] 。
- stride,相鄰兩個頂點之間的字節數,以后用數組保存多種信息數據時會用到。
- offset,指定緩沖區對象中的偏移量。
目前我們只需要知道 location 和 size 就行了。最后兩個參數會在繪制顏色漸變的三角形用到,這里不細說。
最后是用調用 gl.enableVertexAttribArray(a_Position),表示 a_Position 變量對應的緩沖區被開啟了。開啟后,我們就不能再用原來的 gl.vertexAttrib3f 來傳遞數據了,WebGL 會從緩存區一個個拿。如果你想關閉分配,可以調用
繪制
/*** 繪制 ***/
// 清空畫布,并指定顏色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 繪制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
最后是清空畫布,然后繪制三角形。
這里 gl.drawArrays 方法的第一個 mode 參數換成了 gl.TRIANGLES(三角形圖元),不再是原來的 gl.POINTS。
繪制效果:

如果用原來的 gl.POINTS,并設置好 gl_PointSize 指定點大小,則會繪制出下面的效果:

代碼
下面是完整的代碼實現。
/** @type {HTMLCanvasElement} */
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
const vertexShaderSrc = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
}
`;
const fragmentShaderSrc = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
/**** 渲染器生成處理 ****/
// 創建頂點渲染器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
// 創建片元渲染器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
// 程序對象
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.program = program;
// 頂點數據
const vertices = new Float32Array([
// 第一個點
0,
0.5,
// 第二個點
-0.5,
-0.5,
// 第三個點
0.5,
-0.5
]);
// 創建緩存對象
const vertexBuffer = gl.createBuffer();
// 綁定緩存對象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向緩存區寫入數據
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 獲取 a_Position 變量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
// 將緩沖區對象分配給 a_Position 變量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 允許訪問緩存區
gl.enableVertexAttribArray(a_Position);
/*** 繪制 ***/
// 清空畫布,并指定顏色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 繪制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
在線 demo:
https://codesandbox.io/s/gbh1xf?file=/index.js。