WebGL 中的矩阵变换
在 3D 图形渲染中,矩阵变换用于将顶点从一个坐标空间转换到另一个坐标空间。WebGL 使用 4×4 矩阵进行各种变换操作。
基本变换矩阵
1. 平移矩阵(Translation Matrix)
将物体沿 X、Y、Z 轴移动
shell| 1 0 0 tx | | 0 1 0 ty | | 0 0 1 tz | | 0 0 0 1 |
javascriptfunction createTranslationMatrix(tx, ty, tz) { return new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1 ]); }
2. 缩放矩阵(Scale Matrix)
沿各轴缩放物体大小
shell| sx 0 0 0 | | 0 sy 0 0 | | 0 0 sz 0 | | 0 0 0 1 |
3. 旋转矩阵(Rotation Matrix)
绕 X 轴旋转
shell| 1 0 0 0 | | 0 cosθ -sinθ 0 | | 0 sinθ cosθ 0 | | 0 0 0 1 |
绕 Y 轴旋转
shell| cosθ 0 sinθ 0 | | 0 1 0 0 | | -sinθ 0 cosθ 0 | | 0 0 0 1 |
绕 Z 轴旋转
shell| cosθ -sinθ 0 0 | | sinθ cosθ 0 0 | | 0 0 1 0 | | 0 0 0 1 |
MVP 矩阵详解
MVP 矩阵是三个矩阵的乘积,用于将顶点从模型空间转换到裁剪空间:
MVP = P × V × M
M - 模型矩阵(Model Matrix)
作用:将顶点从模型空间(局部空间)转换到世界空间
javascript// 模型矩阵 = 平移 × 旋转 × 缩放 const modelMatrix = mat4.create(); mat4.translate(modelMatrix, modelMatrix, [x, y, z]); mat4.rotateX(modelMatrix, modelMatrix, angleX); mat4.rotateY(modelMatrix, modelMatrix, angleY); mat4.scale(modelMatrix, modelMatrix, [sx, sy, sz]);
应用场景:
- 物体在世界中的位置
- 物体的旋转角度
- 物体的大小缩放
V - 视图矩阵(View Matrix)
作用:将顶点从世界空间转换到相机空间(观察空间)
javascript// 使用 lookAt 函数创建视图矩阵 const viewMatrix = mat4.create(); mat4.lookAt( viewMatrix, [0, 0, 5], // 相机位置(眼睛) [0, 0, 0], // 观察目标点 [0, 1, 0] // 上方向向量 );
视图矩阵的本质:
- 将世界坐标系原点移动到相机位置
- 旋转坐标系使相机朝向 -Z 方向
- 相机位于原点,看向 -Z 轴方向
P - 投影矩阵(Projection Matrix)
作用:将顶点从相机空间转换到裁剪空间
透视投影(Perspective Projection)
模拟人眼视觉效果,近大远小
javascriptconst projectionMatrix = mat4.create(); mat4.perspective( projectionMatrix, Math.PI / 4, // 视野角度(FOV) canvas.width / canvas.height, // 宽高比 0.1, // 近平面 100.0 // 远平面 );
正交投影(Orthographic Projection)
保持物体大小不变,常用于 2D 游戏或 CAD 软件
javascriptmat4.ortho( projectionMatrix, -2, 2, // 左右 -2, 2, // 下上 0.1, 100 // 近远 );
坐标空间转换流程
shell模型空间(局部空间) ↓ [模型矩阵 M] 世界空间 ↓ [视图矩阵 V] 相机空间(观察空间) ↓ [投影矩阵 P] 裁剪空间 ↓ [透视除法] 标准化设备坐标(NDC) ↓ [视口变换] 屏幕空间
顶点着色器中的 MVP 应用
glsl// 顶点着色器 attribute vec3 a_position; attribute vec3 a_color; uniform mat4 u_modelMatrix; uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; varying vec3 v_color; void main() { // 方法1:分别应用三个矩阵 // vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0); // vec4 viewPos = u_viewMatrix * worldPos; // gl_Position = u_projectionMatrix * viewPos; // 方法2:使用预计算的 MVP 矩阵(推荐) mat4 mvp = u_projectionMatrix * u_viewMatrix * u_modelMatrix; gl_Position = mvp * vec4(a_position, 1.0); v_color = a_color; }
JavaScript 中的矩阵计算
javascript// 使用 gl-matrix 库 import { mat4, vec3 } from 'gl-matrix'; class Camera { constructor() { this.projectionMatrix = mat4.create(); this.viewMatrix = mat4.create(); this.mvpMatrix = mat4.create(); } setPerspective(fov, aspect, near, far) { mat4.perspective(this.projectionMatrix, fov, aspect, near, far); } lookAt(eye, center, up) { mat4.lookAt(this.viewMatrix, eye, center, up); } getMVPMatrix(modelMatrix) { // MVP = P × V × M mat4.multiply(this.mvpMatrix, this.viewMatrix, modelMatrix); mat4.multiply(this.mvpMatrix, this.projectionMatrix, this.mvpMatrix); return this.mvpMatrix; } } // 使用示例 const camera = new Camera(); camera.setPerspective(Math.PI / 4, 16/9, 0.1, 100); camera.lookAt([0, 0, 5], [0, 0, 0], [0, 1, 0]); const modelMatrix = mat4.create(); mat4.translate(modelMatrix, modelMatrix, [1, 0, 0]); const mvp = camera.getMVPMatrix(modelMatrix); gl.uniformMatrix4fv(mvpLocation, false, mvp);
常见问题与注意事项
1. 矩阵乘法顺序
矩阵乘法不满足交换律,顺序非常重要:
- 正确:
MVP = P × V × M,应用于顶点:MVP × 顶点 - 先应用的变换在乘法链的右侧
2. 行主序 vs 列主序
- WebGL 使用列主序存储矩阵
gl.uniformMatrix4fv的第三个参数transpose必须为false
3. 齐次坐标
使用 4D 向量 (x, y, z, w):
- 顶点位置:
w = 1 - 方向向量:
w = 0(不受平移影响)
4. 性能优化
- 在 CPU 端预计算 MVP 矩阵,而不是在着色器中分别相乘
- 使用 uniform 传递预计算的矩阵