一起學(xué) WebGL:三角形加上漸變色
大家好,我是前端西瓜哥。之前教大家繪制一個(gè)紅色的三角形,這次我們來(lái)畫個(gè)有漸變的三角形。
原來(lái)的寫法,顏色是在片元著色器中寫死的,這次我們來(lái)像傳頂點(diǎn)數(shù)據(jù)一樣,聲明一個(gè)顏色數(shù)據(jù)傳遞過(guò)去。
顏色需要在片元著色器中賦值給內(nèi)部變量 gl_FragColor,但 attribute 動(dòng)態(tài)類型卻不能在片元著色器中使用。
這時(shí)候就要用到一個(gè)新的類型 varying 了。(意思為:“變化的“)
varying 用于從頂點(diǎn)著色器中將變量傳遞到片元著色器中。
兩個(gè)緩沖區(qū)對(duì)象的寫法
著色器代碼:
const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
這里我們需要在兩種著色器中同時(shí)聲明 varing 變量,后面的類型也必須是相同的 vec4,變量名也要一致,只能說(shuō)是完全相同了。
頂點(diǎn)著色器中需要通過(guò) v_Color = a_Color; 賦值。然后在片元著色器中,再將同步過(guò)來(lái)的 v_Color 賦值給 gl_FragColor。
然后是新增的顏色數(shù)組的聲明,以及對(duì)應(yīng)緩存區(qū)的創(chuàng)建。
/**** 顏色數(shù)據(jù) ****/
// prettier-ignore
const colors = new Float32Array([
1, 0, 0, // 紅色
0, 1, 0, // 綠色
0, 0, 1, // 藍(lán)色
])
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color);
貼一下完整代碼:
/** @type {HTMLCanvasElement} */
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
/**** 渲染器生成處理 ****/
// 創(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ū)ο?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ù)
// prettier-ignore
const vertices = new Float32Array([
0, 0.5, // 第一個(gè)點(diǎn)
-0.5, -0.5, // 第二個(gè)點(diǎn)
0.5, -0.5, // 第三個(gè)點(diǎn)
]);
// 創(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);
// 允許訪問緩存區(qū)
gl.enableVertexAttribArray(a_Position);
/**** 顏色數(shù)據(jù) ****/
// prettier-ignore
const colors = new Float32Array([
1, 0, 0, // 紅色
0, 1, 0, // 綠色
0, 0, 1, // 藍(lán)色
])
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color);
/*** 繪制 ***/
// 清空畫布,并指定顏色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 繪制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
demo 地址:
https://codesandbox.io/s/uqbjsu?file=/index.js。
渲染結(jié)果:
我們其實(shí)只是給三個(gè)頂點(diǎn)設(shè)置了紅、綠、藍(lán)三個(gè)顏色,然后 WebGL 會(huì)基于它們計(jì)算出中間的過(guò)內(nèi)插顏色,將它們填充滿三個(gè)點(diǎn)圍成區(qū)域的像素點(diǎn)。
單緩沖區(qū)的實(shí)現(xiàn)
前面的實(shí)現(xiàn)用了兩個(gè)緩沖區(qū)對(duì)象分別保存位置信息和顏色信息。
但實(shí)際上可以將它們組合在一起,讓數(shù)據(jù)更緊湊放在一個(gè)緩沖區(qū)里。
浮點(diǎn)數(shù)數(shù)組為:
// prettier-ignore
const verticesColors = new Float32Array([
0, 0.5, 1, 0, 0, // 點(diǎn) 1 的位置和顏色信息
-0.5, -0.5, 0, 1, 0, // 點(diǎn) 2
0.5, -0.5, 0, 0, 1, // 點(diǎn) 3
]);
然后是和前一種寫法有一些不同的地方:
// 每個(gè)數(shù)組元素的字節(jié)數(shù)
const SIZE = verticesColors.BYTES_PER_ELEMENT;
// 獲取 a_Position 變量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, SIZE * 5, 0);
gl.enableVertexAttribArray(a_Position);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, SIZE * 5, SIZE * 2);
gl.enableVertexAttribArray(a_Color);
主要是 gl.vertexAttribPointer 方法的最后兩個(gè)參數(shù) stride 和 offset。
我們看下面這個(gè):
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, SIZE * 5, SIZE * 2);
stride 為 SIZE * 5(單位為字節(jié),所以要乘以一個(gè)數(shù)組元素的字節(jié)大?。?,表示 5 個(gè)數(shù)組元素為一趟,然后 offset 為 SIZE * 2,表示從第 2 個(gè)元素,取 3 個(gè)元素作為這一趟的數(shù)據(jù)內(nèi)容。
完整代碼實(shí)現(xiàn):
/** @type {HTMLCanvasElement} */
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
/**** 渲染器生成處理 ****/
// 創(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ū)ο?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ù)
// prettier-ignore
const verticesColors = new Float32Array([
0, 0.5, 1, 0, 0, // 點(diǎn) 1 的位置和顏色信息
-0.5, -0.5, 0, 1, 0, // 點(diǎn) 2
0.5, -0.5, 0, 0, 1, // 點(diǎn) 3
]);
// 每個(gè)數(shù)組元素的字節(jié)數(shù)
const SIZE = verticesColors.BYTES_PER_ELEMENT;
// 創(chuàng)建緩存對(duì)象
const vertexColorBuffer = gl.createBuffer();
// 綁定緩存對(duì)象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
// 向緩存區(qū)寫入數(shù)據(jù)
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
// 獲取 a_Position 變量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, SIZE * 5, 0);
gl.enableVertexAttribArray(a_Position);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, SIZE * 5, SIZE * 2);
gl.enableVertexAttribArray(a_Color);
/*** 繪制 ***/
// 清空畫布,并指定顏色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 繪制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
demo 地址:
https://codesandbox.io/s/bvkv20?file=/index.js。
結(jié)尾
本節(jié)講了 varying 的能力:將頂點(diǎn)著色器中的變量傳遞給片元著色器。并演示了使用兩個(gè)緩沖區(qū)對(duì)象,位置數(shù)據(jù)和顏色數(shù)據(jù),以及將它們組合成一個(gè)緩沖區(qū)對(duì)象的實(shí)現(xiàn)。