使用 WebGL 创建 3D 对象
让我们将方形平面扩展到三维,添加另外五个面来创建一个立方体。为了高效地做到这一点,我们将从直接调用 gl.drawArrays() 方法通过顶点绘制,切换为将顶点数组用作表格,并引用该表格中的单个顶点来定义每个面的顶点位置,通过调用 gl.drawElements()。
考虑一下:每个面需要四个顶点来定义,但每个顶点被三个面共享。我们可以通过构建一个包含所有 24 个顶点的数组来传递更少的数据,然后通过顶点在数组中的索引来引用每个顶点,而不是移动整个坐标集。如果您想知道为什么我们需要 24 个顶点,而不是仅仅 8 个,那是因为每个角属于三个不同颜色的面,而单个顶点需要具有单一的特定颜色;因此,我们将为每个顶点创建三个副本,分别用三种不同的颜色,每个面一个。
定义立方体顶点的坐标
首先,让我们通过更新 initBuffers() 中的代码来构建立方体的顶点坐标缓冲区。这与方形平面时的基本相同,但因为有 24 个顶点(每条边 4 个),所以代码会稍长一些。
在您的 "init-buffers.js" 模块的 initPositionBuffer() 函数中,用以下代码替换 positions 声明
const positions = [
// Front face
-1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,
// Back face
-1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0,
// Top face
-1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0,
// Bottom face
-1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,
// Right face
1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0,
// Left face
-1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0,
];
由于我们为顶点添加了 z 分量,因此需要将 vertexPosition 属性的 numComponents 更新为 3。
在您的 "draw-scene.js" 模块的 setPositionAttribute() 函数中,将 numComponents 常量从 2 更改为 3
const numComponents = 3;
定义顶点的颜色
我们还需要为 24 个顶点中的每个顶点构建一个颜色数组。这段代码首先为每个面定义一种颜色,然后使用循环将所有顶点的颜色组装成一个数组。
在您的 "init-buffers.js" 模块的 initColorBuffer() 函数中,用以下代码替换 colors 声明
const faceColors = [
[1.0, 1.0, 1.0, 1.0], // Front face: white
[1.0, 0.0, 0.0, 1.0], // Back face: red
[0.0, 1.0, 0.0, 1.0], // Top face: green
[0.0, 0.0, 1.0, 1.0], // Bottom face: blue
[1.0, 1.0, 0.0, 1.0], // Right face: yellow
[1.0, 0.0, 1.0, 1.0], // Left face: purple
];
// Convert the array of colors into a table for all the vertices.
let colors = [];
for (const c of faceColors) {
// Repeat each color four times for the four vertices of the face
colors = colors.concat(c, c, c, c);
}
定义元素数组
生成顶点数组后,我们需要构建元素数组。
在您的 "init-buffer.js" 模块中,添加以下函数
function initIndexBuffer(gl) {
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// This array defines each face as two triangles, using the
// indices into the vertex array to specify each triangle's
// position.
// prettier-ignore
const indices = [
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23, // left
];
// Now send the element array to GL
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array(indices),
gl.STATIC_DRAW,
);
return indexBuffer;
}
indices 数组将每个面定义为一对三角形,将每个三角形的顶点指定为立方体顶点数组的索引。因此,立方体被描述为 12 个三角形的集合。
接下来,您需要从 initBuffers() 调用这个新函数,并返回它创建的缓冲区。
在您的 "init-buffers.js" 模块的 initBuffers() 函数的末尾,添加以下代码,替换现有的 return 语句
function initBuffers(gl) {
// …
const indexBuffer = initIndexBuffer(gl);
return {
position: positionBuffer,
color: colorBuffer,
indices: indexBuffer,
};
}
绘制立方体
接下来,我们需要在 drawScene() 函数中添加代码,使用立方体的索引缓冲区进行绘制,添加新的 gl.bindBuffer() 和 gl.drawElements() 调用。
在您的 drawScene() 函数中,在 gl.useProgram 行之前添加以下代码
// Tell WebGL which indices to use to index the vertices
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
在您的 "draw-scene.js" 模块的 drawScene() 函数中,替换位于两个 gl.uniformMatrix4fv 调用之后、包含 gl.drawArrays() 行的块,用以下块替换
{
const vertexCount = 36;
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
}
由于我们的立方体的每个面由两个三角形组成,每条边有 6 个顶点,或者说立方体共有 36 个顶点,尽管其中许多是重复的。
最后,让我们将变量 squareRotation 替换为 cubeRotation,并添加绕 x 轴的第二个旋转。
在您的 "webgl-demo.js" 文件的开头,用以下行替换 squareRotation 声明
let cubeRotation = 0.0;
在您的 drawScene() 函数声明中,将 squareRotation 替换为 cubeRotation
function drawScene(gl, programInfo, buffers, cubeRotation) {
// …
}
在您的 drawScene() 函数中,用以下代码替换 mat4.rotate 调用
mat4.rotate(
modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation, // amount to rotate in radians
[0, 0, 1],
); // axis to rotate around (Z)
mat4.rotate(
modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation * 0.7, // amount to rotate in radians
[0, 1, 0],
); // axis to rotate around (Y)
mat4.rotate(
modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation * 0.3, // amount to rotate in radians
[1, 0, 0],
); // axis to rotate around (X)
在您的 main() 函数中,替换调用 drawScene() 并更新 squareRotation 的代码,改用传递和更新 cubeRotation
drawScene(gl, programInfo, buffers, cubeRotation);
cubeRotation += deltaTime;
此时,我们已经有了一个动画立方体在旋转,它的六个面颜色非常鲜艳。