Skip to content

WebGL - 几何图形

要在 WebGL 中渲染任何对象(或图元),首先需要定义其几何体。这通常涉及指定顶点的位置,以及(可选地)如何使用索引将这些顶点连接起来形成形状。还可以包含其他逐顶点数据,如颜色或纹理坐标。

这些几何数据源自你的 JavaScript 代码,通常存储在类型化数组中,并且必须传输到 GPU 的内存中供着色器访问。这个传输过程通过 WebGL 缓冲区对象(Buffer Objects)完成。

使用 JavaScript 类型化数组定义形状的顶点(和可选的索引),类型化数组提供原始二进制数据视图,这对于 WebGL 至关重要。

顶点定义了角点。对于坐标为 (0.0, 0.5)、(-0.5, -0.5) 和 (0.5, -0.5) 的三角形:

const triangleVertices = new Float32Array([
0.0, 0.5, // Vertex 0 (Top) - 顶点 0(顶部)
-0.5, -0.5, // Vertex 1 (Bottom Left) - 顶点 1(左下)
0.5, -0.5 // Vertex 2 (Bottom Right) - 顶点 2(右下)
]);

如果直接使用 gl.drawArrays 绘制这个三角形,你并不严格需要索引。但是,如果使用 gl.drawElements 或定义更复杂的形状,则会使用索引。

一个正方形可以由两个共享顶点的三角形组成。顶点:0=(-0.5, 0.5),1=(0.5, 0.5),2=(0.5, -0.5),3=(-0.5, -0.5)。

const squareVertices = new Float32Array([
-0.5, 0.5, // Vertex 0 - 顶点 0
0.5, 0.5, // Vertex 1 - 顶点 1
0.5, -0.5, // Vertex 2 - 顶点 2
-0.5, -0.5 // Vertex 3 - 顶点 3
]);
// Indices define two triangles: (0, 3, 1) and (1, 3, 2) - 索引定义了两个三角形:(0, 3, 1) 和 (1, 3, 2)
const squareIndices = new Uint16Array([
0, 3, 1, // First triangle (Top-left, Bottom-left, Top-right) - 第一个三角形(左上,左下,右上)
1, 3, 2 // Second triangle (Top-right, Bottom-left, Bottom-right) - 第二个三角形(右上,左下,右下)
]);

使用索引(Uint16Array 或 Uint32Array)允许重用顶点数据,节省内存并可能提高性能。

缓冲区对象 (Buffer Objects) (VBOs 和 IBOs)

Section titled “缓冲区对象 (Buffer Objects) (VBOs 和 IBOs)”

WebGL 缓冲区对象是分配在 GPU 上的内存,用于存储渲染数据。它们对于将几何体(和其他数据)从 CPU (JavaScript) 高效地传输到 GPU (着色器) 至关重要。

用于几何体的两种主要类型是:

  • **顶点缓冲区对象 (Vertex Buffer Object, VBO):**存储逐顶点数据,如坐标、颜色、纹理坐标、法线。绑定到 gl.ARRAY_BUFFER 目标。
  • **索引缓冲区对象 (Index Buffer Object, IBO):**存储用于索引绘制(gl.drawElements)的顶点索引。绑定到 gl.ELEMENT_ARRAY_BUFFER 目标。

使用缓冲区的工作流程:

  1. **创建缓冲区:**使用 gl.createBuffer() 获取一个空的缓冲区对象引用。
  2. **绑定缓冲区:**使用 gl.bindBuffer(target, buffer) 将缓冲区绑定到特定目标(VBO 用 gl.ARRAY_BUFFER,IBO 用 gl.ELEMENT_ARRAY_BUFFER)。之后所有针对该目标的操作都会影响绑定的缓冲区。
  3. **上传数据:**使用 gl.bufferData(target, data, usage) 分配内存并将数据从类型化数组(data)复制到当前绑定的缓冲区中。usage 提示(例如 gl.STATIC_DRAW、gl.DYNAMIC_DRAW)告诉 WebGL 你打算如何使用这些数据,可能允许进行优化。
  4. **解绑缓冲区(可选但推荐):**使用 gl.bindBuffer(target, null) 解除缓冲区与目标的绑定。这可以防止意外修改。
  5. **配置属性(针对 VBO):**稍后,你需要再次绑定 VBO,并使用 gl.vertexAttribPointer 告诉 WebGL 着色器中的顶点属性如何从其中读取数据。

gl.bufferData 的 usage 参数有助于 WebGL 驱动程序优化内存放置和访问:

  • gl.STATIC_DRAW:数据设置一次并使用多次(例如,静态模型几何体)。最常见。
  • gl.DYNAMIC_DRAW:数据频繁更新并使用多次(例如,动画角色的顶点)。
  • gl.STREAM_DRAW:数据设置一次且仅使用几次(例如,粒子效果数据)。

这些只是提示;实际实现可能将它们处理得类似,但提供正确的提示是良好的实践。

// Assume 'gl' is the WebGLRenderingContext - 假设 'gl' 是 WebGL 渲染上下文
// Assume 'triangleVertices' and 'squareIndices' TypedArrays exist - 假设 'triangleVertices' 和 'squareIndices' 类型化数组存在
// --- Vertex Buffer (VBO) ---
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);
// gl.bindBuffer(gl.ARRAY_BUFFER, null); // Optional unbind - 可选的解绑
// --- Index Buffer (IBO) ---
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, squareIndices, gl.STATIC_DRAW);
// gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); // Optional unbind - 可选的解绑

顶点数组对象 (Vertex Array Objects, VAOs)

Section titled “顶点数组对象 (Vertex Array Objects, VAOs)”

设置顶点属性需要绑定正确的 VBO,并为每个属性调用 gl.vertexAttribPointer 和 gl.enableVertexAttribArray。这种设置可能很复杂,特别是对于具有多个属性的模型。

顶点数组对象 (VAO) 存储了所有这些配置状态:绑定了哪些 VBO,每个属性如何从其 VBO 读取数据,以及哪些属性已启用。

使用 VAOs 的工作流程:

  1. 创建 VAO:const vao = gl.createVertexArray();(注意:在 WebGL 1 中,需要 OES_vertex_array_object 扩展:gl.createVertexArrayOES())
  2. 绑定 VAO:gl.bindVertexArray(vao);
  3. **设置缓冲区和属性:**执行对象所需的所有 gl.bindBuffer、gl.bufferData、gl.vertexAttribPointer、gl.enableVertexAttribArray 调用。如果使用 IBO,也需要绑定 IBO (gl.ELEMENT_ARRAY_BUFFER)。
  4. 解绑 VAO:gl.bindVertexArray(null);(以防止意外更改)
  5. **进行绘制:**只需绑定 VAO (gl.bindVertexArray(vao);),然后进行绘制调用 (gl.drawArrays 或 gl.drawElements)。所有相关的缓冲区绑定和属性配置都会自动恢复。

VAOs 显著简化了渲染循环,并通过减少每帧的 WebGL 状态更改次数,通常能提高性能。它们是 WebGL 2 中的标准且强制要求的功能。

以下是关键的缓冲区相关方法的总结: