三角形可太重要了,再复杂的三维模型都是由一个个小三角形组合而成,越多越精细越真实。
绘制三角形
这次绘制三角形,要绘制的点就有三个了,不再是一个。为此我们需要用到缓存区对象(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。