WebGL - 绘制四边形
WebGL - 绘制四边形
Section titled “WebGL - 绘制四边形”虽然 WebGL 的基本绘制原始类型是点、线和三角形,但我们经常需要绘制更复杂的形状,如四边形 (quad) 或正方形。本章演示了如何使用索引缓冲区 (index buffer),通过将四边形分解为两个三角形来绘制它。
用三角形构建四边形
Section titled “用三角形构建四边形”四边形可以表示为两个共享一条边的相邻三角形。为了在 WebGL 中高效地绘制它,我们定义四边形的四个独特顶点,然后使用索引缓冲区 (ELEMENT_ARRAY_BUFFER) 来指定哪些顶点组成了这两个三角形。
考虑一个顶点为 0、1、2、3 的四边形:
0-------3 | \ | | \ | | \ | | \ | | \ | 1-------2我们可以定义顶点,然后使用索引来形成三角形 1(顶点 0、1、2)和三角形 2(顶点 0、2、3)。
使用索引绘制四边形的步骤
Section titled “使用索引绘制四边形的步骤”这个过程与使用索引绘制单个三角形非常相似,但几何体数据不同:
步骤 1: 设置 Canvas 和 WebGL 上下文
标准初始化。
步骤 2: 定义几何体(顶点和索引)和缓冲区
在 Float32Array 中定义四边形的 4 个独特顶点。在 Uint16Array(或类似类型)中定义 6 个索引(每个三角形 3 个)。创建两个 WebGL 缓冲区:一个用于顶点的 VBO (gl.ARRAY_BUFFER) 和一个用于索引的 IBO (gl.ELEMENT_ARRAY_BUFFER)。使用 gl.bufferData 将相应的数据加载到每个缓冲区中。
// 定义四边形的 4 个顶点 (X, Y, Z)const quadVertices = new Float32Array([ -0.5, 0.5, 0.0, // 顶点 0:左上 -0.5, -0.5, 0.0, // 顶点 1:左下 0.5, -0.5, 0.0, // 顶点 2:右下 0.5, 0.5, 0.0 // 顶点 3:右上]);
// 定义构成四边形的两个三角形的索引const quadIndices = new Uint16Array([ 0, 1, 2, // 第一个三角形 0, 2, 3 // 第二个三角形]);
// ... (创建 VBO 和 IBO,并为两者填充 bufferData) ...步骤 3: 创建和编译着色器
基本的顶点着色器和片段着色器就足够了。顶点着色器需要接收位置属性并输出 gl_Position。片段着色器设置 gl_FragColor。
// 顶点着色器(简单直传)attribute vec3 a_position;void main() { gl_Position = vec4(a_position, 1.0);}// 片段着色器(纯色)precision mediump float;void main() { gl_FragColor = vec4(0.3, 0.8, 0.4, 1.0); // 偏绿色}步骤 4: 链接着色器和缓冲区
获取属性位置,启用位置属性数组,并配置 gl.vertexAttribPointer 以从 VBO 中读取数据。
步骤 5: 绘制四边形
清除 canvas,设置视口等。关键是,在绘制之前将索引缓冲区对象 (IBO) 绑定到 gl.ELEMENT_ARRAY_BUFFER 目标。然后,使用 gl.drawElements(gl.TRIANGLES, count, type, offset)。
mode:gl.TRIANGLEScount:要绘制的索引数量(由两个三角形组成的四边形为 6)。type:IBO 中索引的数据类型(例如,gl.UNSIGNED_SHORT)。offset:IBO 中的起始偏移量(通常为 0)。
// 在绘制前绑定索引缓冲区gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
// 使用索引绘制四边形gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);示例:绘制一个四边形
Section titled “示例:绘制一个四边形”这个例子使用上述步骤绘制了一个纯偏绿色的四边形。
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>WebGL Drawing a Quad</title> <style> body { margin: 0; overflow: hidden; background-color: #444; } canvas { display: block; border: 1px solid lightblue; } </style></head><body> <canvas id="webgl-canvas" width="500" height="500"></canvas>
<script> // --- 着色器源码 --- const vsSource = ` attribute vec3 a_position; void main() { gl_Position = vec4(a_position, 1.0); } `; const fsSource = ` precision mediump float; void main() { gl_FragColor = vec4(0.3, 0.8, 0.4, 1.0); // 偏绿色 } `;
// --- 工具函数:createShader, createProgram (假定已定义) --- function createShader(gl, type, source) { /* ... */ const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error(`着色器编译错误 (${type === gl.VERTEX_SHADER ? 'Vertex' : 'Fragment'}):`, gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function createProgram(gl, vsSource, fsSource) { /* ... */ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource); const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource); if (!vertexShader || !fragmentShader) return null; const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('程序链接错误:', gl.getProgramInfoLog(program)); gl.deleteProgram(program); return null; } return program; }
// --- 主 WebGL --- const canvas = document.getElementById('webgl-canvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('WebGL 不受支持。'); throw new Error('WebGL 不受支持'); }
const shaderProgram = createProgram(gl, vsSource, fsSource); if (!shaderProgram) throw new Error('创建程序失败。');
// --- 几何体 --- const quadVertices = new Float32Array([ -0.5, 0.5, 0.0, // 0:左上 -0.5, -0.5, 0.0, // 1:左下 0.5, -0.5, 0.0, // 2:右下 0.5, 0.5, 0.0 // 3:右上 ]); const quadIndices = new Uint16Array([ 0, 1, 2, // 第一个三角形 0, 2, 3 // 第二个三角形 ]);
// --- 缓冲区 --- const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, quadVertices, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices, gl.STATIC_DRAW);
// --- 属性设置 --- const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position'); if (positionAttribLocation < 0) console.error("未找到属性 'a_position'"); gl.enableVertexAttribArray(positionAttribLocation); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // 设置指针前先绑定 VBO gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
// --- 绘制 --- gl.useProgram(shaderProgram); // 激活程序
gl.clearColor(0.25, 0.25, 0.25, 1.0); // 深灰色背景 gl.enable(gl.DEPTH_TEST); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// ** 绑定索引缓冲区 ** gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// ** 使用索引绘制 ** gl.drawElements( gl.TRIANGLES, // 原始类型 6, // 要绘制的索引数量 gl.UNSIGNED_SHORT, // 索引类型(与 Uint16Array 匹配) 0 // 缓冲区中的偏移量 );
// 良好实践:如果在其他地方需要,绘制后解绑缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
</script></body></html>预期输出:
这段代码将渲染一个纯偏绿色的正方形,居中显示在 canvas 上,填充裁剪空间中从 (-0.5, -0.5) 到 (0.5, 0.5) 的区域。
进一步探索:
Section titled “进一步探索:”- 修改顶点坐标以创建一个矩形而不是正方形。
- 改变索引顺序(例如,
1, 2, 3, 1, 3, 0),看看它如何影响绘制的三角形(它应该仍然构成相同的四边形)。 - 使用其他章节中的技术为四边形应用颜色或变换。
- 尝试使用
gl.TRIANGLE_STRIP模式和drawArrays绘制四边形,只使用正确排序的 4 个顶点(例如,1、0、2、3)。比较所需的设置。