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

如果用原來(lái)的 gl.POINTS,并設(shè)置好 gl_PointSize 指定點(diǎn)大小,則會(huì)繪制出下面的效果:

代碼
下面是完整的代碼實(shí)現(xiàn)。
/** @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);
}
`;
/**** 渲染器生成處理 ****/
// 創(chuàng)建頂點(diǎn)渲染器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
// 創(chuàng)建片元渲染器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
// 程序?qū)ο?/span>
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.program = program;
// 頂點(diǎn)數(shù)據(jù)
const vertices = new Float32Array([
// 第一個(gè)點(diǎn)
0,
0.5,
// 第二個(gè)點(diǎn)
-0.5,
-0.5,
// 第三個(gè)點(diǎn)
0.5,
-0.5
]);
// 創(chuàng)建緩存對(duì)象
const vertexBuffer = gl.createBuffer();
// 綁定緩存對(duì)象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向緩存區(qū)寫入數(shù)據(jù)
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 獲取 a_Position 變量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
// 將緩沖區(qū)對(duì)象分配給 a_Position 變量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 允許訪問(wèn)緩存區(qū)
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。