使用 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.
var colors = [];
for (var j = 0; j < faceColors.length; ++j) {
const c = faceColors[j];
// 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.
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
语句
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.drawArrays()
行的两个 gl.uniformMatrix4fv
调用之后的代码块
{
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()
函数中,用传递并更新 cubeRotation
而不是 squareRotation
的代码替换调用 drawScene()
并更新 squareRotation
的代码
drawScene(gl, programInfo, buffers, cubeRotation);
cubeRotation += deltaTime;
在这一点上,我们现在拥有一个动画立方体,它正在旋转,六个面以非常鲜明的颜色呈现。