WebGL - 平移
WebGL - 平移
Section titled “WebGL - 平移”本章探讨平移,这是一种基本的变换,它可以在 3D 场景中移动物体,而不改变其朝向或大小。
平移涉及沿着 X、Y 和 Z 轴将特定距离(偏移量)添加到物体中每个顶点的坐标。如果一个顶点位于 [x, y, z],并且期望的平移是 [tx, ty, tz],则新位置变为 [x + tx, y + ty, z + tz]。
在现代 WebGL 中,平移通常使用矩阵乘法来实现。在顶点着色器中,会创建一个平移矩阵,并将其与顶点位置向量相乘。
一个标准的 4x4 平移矩阵如下所示:
1 0 0 tx0 1 0 ty0 0 1 tz0 0 0 1当顶点位置 (x, y, z, 1) 乘以这个矩阵时,结果是 (x+tx, y+ty, z+tz, 1),有效地平移了该点。
平移的核心步骤
Section titled “平移的核心步骤”实现平移涉及修改顶点着色器并从 JavaScript 传递一个平移矩阵:
步骤 1: 设置 Canvas 和 WebGL 上下文
标准初始化:获取 canvas,获取 webgl 上下文,检查错误。
步骤 2: 定义几何体和缓冲区
定义物体的顶点(例如,一个三角形)并将它们加载到 VBO 中。
const vertices = new Float32Array([ -0.5, 0.5, 0.0, // 左上 -0.5, -0.5, 0.0, // 左下 0.5, -0.5, 0.0 // 右下]);// ... (创建 VBO 和 bufferData) ...步骤 3: 创建和编译着色器
编写、编译和链接着色器,包含错误检查。
- 顶点着色器: 定义一个顶点位置属性 (
a_position) 和一个用于平移矩阵的uniform mat4(u_translationMatrix)。计算最终位置:gl_Position = u_translationMatrix * vec4(a_position, 1.0);。 - 片段着色器: 设置片段颜色 (
gl_FragColor)。
// 顶点着色器 (GLSL ES 1.00)attribute vec3 a_position;uniform mat4 u_translationMatrix;
void main() { // 将平移矩阵应用于顶点位置 gl_Position = u_translationMatrix * vec4(a_position, 1.0);}// 片段着色器 (GLSL ES 1.00)precision mediump float;
void main() { gl_FragColor = vec4(0.2, 0.5, 0.8, 1.0); // 蓝色}步骤 4: 链接着色器和缓冲区
获取属性位置 (a_position) 和 Uniform 位置 (u_translationMatrix)。配置属性指针。
步骤 5: 定义和应用平移矩阵
在 JavaScript 中,定义平移距离 (tx、ty、tz)。创建一个 4x4 平移矩阵作为 Float32Array(记住 WebGL 使用列主序 (column-major order))。获取 Uniform 位置并使用 gl.uniformMatrix4fv 传递矩阵。
步骤 6: 绘制物体
清除 canvas,设置视口,并调用 gl.drawArrays 渲染平移后的物体。
示例:平移一个三角形
Section titled “示例:平移一个三角形”这个例子绘制了一个蓝色三角形,向右平移了 0.5 单位,向上平移了 0.3 单位。
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>WebGL Translation Example</title> <style> body { margin: 0; overflow: hidden; background-color: #eee; } canvas { display: block; } </style></head><body> <canvas id="webgl-canvas" width="400" height="400"></canvas>
<script> // --- 着色器源码 --- const vsSource = ` attribute vec3 a_position; uniform mat4 u_translationMatrix; void main() { gl_Position = u_translationMatrix * vec4(a_position, 1.0); } `;
const fsSource = ` precision mediump float; void main() { gl_FragColor = vec4(0.2, 0.5, 0.8, 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 vertices = new Float32Array([ -0.5, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0 ]); const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, 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); gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
// --- Uniform 设置 --- const translationMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_translationMatrix'); if (!translationMatrixLocation) console.error("未找到 Uniform 'u_translationMatrix'");
const tx = 0.5; // 向右平移 0.5 单位 const ty = 0.3; // 向上平移 0.3 单位 const tz = 0.0; // Z 轴无平移
// 平移矩阵(列主序) const translationMatrix = new Float32Array([ 1.0, 0.0, 0.0, 0.0, // 列 1 0.0, 1.0, 0.0, 0.0, // 列 2 0.0, 0.0, 1.0, 0.0, // 列 3 tx, ty, tz, 1.0 // 列 4(平移值) ]);
gl.useProgram(shaderProgram); // 激活程序 gl.uniformMatrix4fv(translationMatrixLocation, false, translationMatrix);
// --- 绘制 --- gl.clearColor(0.9, 0.9, 0.9, 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.drawArrays(gl.TRIANGLES, 0, 3);
</script></body></html>预期输出:
这段代码将渲染一个蓝色三角形。它不会像原始顶点数据那样围绕原点居中,而是在裁剪空间 (clip space) 中向右移动 0.5 单位、向上移动 0.3 单位。
关于变换顺序的注意事项:
Section titled “关于变换顺序的注意事项:”组合多个变换(如缩放 (scaling)、旋转 (rotation) 和平移)时,矩阵的乘法顺序至关重要。标准顺序通常是 缩放 -> 旋转 -> 平移,应用于顶点时表达式为 gl_Position = Projection * View * Translation * Rotation * Scale * VertexPosition;。使用像 gl-matrix 这样的数学库可以极大地简化这些变换的管理。
进一步探索:
Section titled “进一步探索:”- 尝试不同的
tx、ty和tz值。 - 尝试通过在
requestAnimationFrame循环中更新tx或ty值,并在绘制每一帧之前重新应用 Uniform 矩阵来制作平移动画。 - 将此平移矩阵与缩放或旋转矩阵组合(需要在将结果作为单个 Uniform 发送之前将矩阵相乘)。