WebGL - 绘制三角形
WebGL - 绘制一个三角形
Section titled “WebGL - 绘制一个三角形”绘制一个简单的形状(如三角形)通常是图形编程的“Hello, World!”。它包含了 WebGL 应用程序的所有基本步骤:设置 Canvas、定义几何体、编写着色器、将数据发送到 GPU,以及触发绘制调用。
本章演示了如何使用索引缓冲区(index buffer,drawElements)绘制一个三角形,这是一种常用且高效的方法,对于顶点在多个三角形之间共享的更复杂模型尤其如此。
绘制三角形的核心步骤
Section titled “绘制三角形的核心步骤”以下是必要的步骤分解:
步骤 1: 准备 Canvas 并获取 WebGL Context
Section titled “步骤 1: 准备 Canvas 并获取 WebGL Context”首先,你需要一个 HTML <canvas> 元素。然后,使用 JavaScript 获取它的引用,并请求一个 WebGL 渲染上下文(Rendering Context)。
<canvas id="glCanvas" width="640" height="480"></canvas>const canvas = document.getElementById('glCanvas');// Try getting the standard WebGL context firstconst gl = canvas.getContext('webgl');
if (!gl) { console.error("Unable to initialize WebGL. Your browser or machine may not support it."); // Optionally try 'experimental-webgl' for older browsers // gl = canvas.getContext('experimental-webgl'); // if (!gl) { ... handle error ... }}
// If context is obtained, proceed...步骤 2: 定义几何体并存储在缓冲区中
Section titled “步骤 2: 定义几何体并存储在缓冲区中”定义顶点位置和组成三角形的索引。然后,创建 WebGL 缓冲区对象(buffer objects)(ARRAY_BUFFER 用于顶点,ELEMENT_ARRAY_BUFFER 用于索引),并将数据加载到其中。
// Define vertex positions (X, Y, Z)const positions = [ -0.5, -0.5, 0.0, // Vertex 0: Bottom-left (顶点 0: 左下) 0.5, -0.5, 0.0, // Vertex 1: Bottom-right (顶点 1: 右下) 0.0, 0.5, 0.0 // Vertex 2: Top-center (顶点 2: 中上)];
// Define the indices that form the triangle (using vertex indices 0, 1, 2)const indices = [0, 1, 2]; // 定义组成三角形的索引(使用顶点索引 0, 1, 2)
// --- Create and load position buffer ---const positionBuffer = gl.createBuffer(); // 创建位置缓冲区gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // 绑定为当前 ARRAY_BUFFERgl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); // 将数据加载到缓冲区// Note: It's good practice to unbind after configuration, but not strictly necessary// gl.bindBuffer(gl.ARRAY_BUFFER, null); // 注意:配置后解除绑定是个好习惯,但非强制要求
// --- Create and load index buffer ---const indexBuffer = gl.createBuffer(); // 创建索引缓冲区gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); // 绑定为当前 ELEMENT_ARRAY_BUFFERgl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); // 将数据加载到缓冲区// gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);gl.STATIC_DRAW 是给 WebGL 的一个提示,表明数据不会经常改变。
步骤 3: 创建、编译和链接着色器程序
Section titled “步骤 3: 创建、编译和链接着色器程序”用 GLSL 编写顶点和片元着色器代码,然后使用 WebGL 函数编译它们,并将它们链接成一个可用的着色器程序。记住要包含错误检查!
顶点着色器 (GLSL):
attribute vec3 a_position; // Receive vertex position attribute (接收顶点位置属性)
void main() { // Pass the position directly to the output (no transformation) // We use vec4 because gl_Position requires (x, y, z, w) gl_Position = vec4(a_position, 1.0); // 将位置直接传递给输出(无变换),使用 vec4 因为 gl_Position 需要 (x, y, z, w)}片元着色器 (GLSL):
precision mediump float; // Necessary precision declaration (必要的精度声明)
void main() { // Set the fragment color to opaque orange (将片元颜色设置为不透明橙色) gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0);}JavaScript(编译/链接 - 概念性,使用辅助函数):
// Assume vsSource and fsSource contain the GLSL code strings// Assume loadShader and initShaderProgram helper functions exist (as shown previously)const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); // 假设 vsSource 和 fsSource 包含 GLSL 代码字符串const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); // 假设 loadShader 和 initShaderProgram 辅助函数存在(如前所示)const shaderProgram = initShaderProgram(gl, vertexShader, fragmentShader);
// If shaderProgram is null, an error occurred during setupif (!shaderProgram) { // 如果 shaderProgram 为 null,则设置过程中发生错误 // Handle error - likely reported by helper functions return; // 处理错误 - 可能由辅助函数报告}步骤 4: 将着色器属性与缓冲区数据关联
Section titled “步骤 4: 将着色器属性与缓冲区数据关联”获取着色器程序中 attribute 变量(a_position)的位置。然后,告诉 WebGL 如何从当前绑定的 ARRAY_BUFFER(我们的 positionBuffer)中为该属性提取数据。
// Get the location of the attributeconst positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position'); // 获取属性位置
// Bind the position buffer again (if not already bound)gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // 再次绑定位置缓冲区(如果尚未绑定)
// Tell WebGL how to pull data out of the position bufferconst numComponents = 3; // (X, Y, Z) (分量数量)const type = gl.FLOAT; // Data type (数据类型)const normalize = false; // Don't normalize (不归一化)const stride = 0; // Bytes to skip between elements (0 = tightly packed) (元素之间的跳过字节数,0 = 紧密打包)const offset = 0; // Bytes offset from the beginning (从开头偏移的字节数)gl.vertexAttribPointer( positionAttributeLocation, numComponents, type, normalize, stride, offset);
// Enable the vertex attribute arraygl.enableVertexAttribArray(positionAttributeLocation); // 启用顶点属性数组步骤 5: 设置渲染状态并绘制三角形
Section titled “步骤 5: 设置渲染状态并绘制三角形”清除 Canvas,设置视口,告诉 WebGL 使用着色器程序,确保索引缓冲区已绑定,最后调用 drawElements。
// Set clear color to light gray and clear the canvasgl.clearColor(0.8, 0.8, 0.8, 1.0); // 设置清除颜色为浅灰色gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清除颜色和深度缓冲区
// Set the viewport to match the canvas sizegl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // 设置视口匹配 Canvas 大小
// Tell WebGL to use our shader programgl.useProgram(shaderProgram); // 告诉 WebGL 使用我们的着色器程序
// Bind the index buffer (important before drawElements)gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); // 绑定索引缓冲区(在 drawElements 之前很重要)
// Draw the triangle!const primitiveType = gl.TRIANGLES; // Draw triangles (绘制三角形)const count = indices.length; // Number of indices to draw (要绘制的索引数量)const indexType = gl.UNSIGNED_SHORT; // Type of data in index buffer (索引缓冲区中的数据类型)const drawOffset = 0; // Start at the beginning of the index buffer (从索引缓冲区开头开始)gl.drawElements(primitiveType, count, indexType, drawOffset);完整示例(概念性结构)
Section titled “完整示例(概念性结构)”下面是 HTML 和 JavaScript 文件的概念性结构。你将把上面步骤中的代码片段填充到其中,包括健壮的错误检查。
<!doctype html><html><head> <title>WebGL Triangle</title> <meta charset="utf-8"></head><body> <canvas id="glCanvas" width="640" height="480"></canvas> <script> // --- Helper function: loadShader --- function loadShader(gl, type, source) { /* ... see previous chapter ... */ } // 辅助函数:加载着色器
// --- Helper function: initShaderProgram --- function initShaderProgram(gl, vsSource, fsSource) { /* ... see previous chapter ... */ } // 辅助函数:初始化着色器程序
// --- Main function --- function main() { // 主函数 // Step 1: Get Canvas & Context const canvas = document.getElementById('glCanvas'); // 步骤 1: 获取 Canvas 和 Context const gl = canvas.getContext('webgl'); if (!gl) { /* handle error */ return; } // 处理错误
// Step 3: Define Shader Sources const vsSource = `...`; // Vertex shader GLSL code (步骤 3: 定义着色器源) const fsSource = `...`; // Fragment shader GLSL code (片元着色器 GLSL 代码)
// Step 3: Compile and Link Shaders const shaderProgram = initShaderProgram(gl, vsSource, fsSource); // 步骤 3: 编译和链接着色器 if (!shaderProgram) { /* handle error */ return; } // 处理错误
// Step 2: Define Geometry const positions = [/* ... vertex data ... */]; // 步骤 2: 定义几何体 const indices = [/* ... index data ... */]; // 索引数据
// Step 2: Create and Load Buffers const positionBuffer = gl.createBuffer(); // 步骤 2: 创建和加载缓冲区 /* ... bind and bufferData for positions ... */ // 绑定并加载位置数据 const indexBuffer = gl.createBuffer(); /* ... bind and bufferData for indices ... */ // 绑定并加载索引数据
// Step 4: Associate Shaders and Buffers const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position'); // 步骤 4: 关联着色器和缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.vertexAttribPointer(/* ... parameters ... */); // 设置属性指针 gl.enableVertexAttribArray(positionAttributeLocation);
// Step 5: Set Up Render State and Draw gl.clearColor(0.8, 0.8, 0.8, 1.0); // 步骤 5: 设置渲染状态并绘制 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); gl.useProgram(shaderProgram); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); }
// Run the main function when the page loads window.onload = main; // 页面加载时运行主函数 </script></body></html>在支持 WebGL 的浏览器中运行此代码,应该会显示一个水平居中、尖端朝上的纯橙色三角形,背景为浅灰色。