跳到主要内容
3D Playground
WebGL 与着色器第 4 / 12 节

MVP 矩阵:亲手搭建 3D

进阶WebGL 2

三个矩阵,三次换坐标系

顶点在模型文件里的坐标是局部坐标。要画到屏幕上,要连乘三个 4×4 矩阵:

glsl
gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
  • Model(模型矩阵):局部 → 世界。物体的缩放、旋转、位置。
  • View(视图矩阵):世界 → 相机。它是“相机摆放”的逆:相机往右移,等价于整个世界往左移。lookAt(eye, target, up) 用三次叉乘构造出相机的正交基。
  • Projection(投影矩阵):相机 → 裁剪空间。透视投影把视锥体压成一个立方体,并把深度放进 w,GPU 随后做“透视除法” x/w、y/w,于是远处的东西变小。

列主序与乘法顺序

GLSL 的矩阵是列主序:数组里先存第一列,平移量位于第 12、13、14 个元素。矩阵乘法不满足交换律,M = 公转 × 平移 × 自转 × 缩放 读起来要从右往左:先缩放,再自转,再推出去,最后绕原点公转。本例的小立方体“月亮”就是这样组合的,换个顺序试试,结果完全不同。

深度测试

不开深度测试时,后画的三角形总是盖住先画的,不管谁离相机更近。gl.enable(gl.DEPTH_TEST) 让 GPU 为每个像素记录深度,只保留最近的片元。每帧记得 gl.clear(... | gl.DEPTH_BUFFER_BIT)。

背面剔除

封闭物体的背面永远看不见,gl.enable(gl.CULL_FACE) 直接丢掉它们,省下一半片元。判断正反面靠顶点顺序:从外面看是逆时针(CCW)的三角形为正面。关掉剔除和深度测试,片元着色器里用 gl_FrontFacing 把背面涂成品红色,你会看到它们“透”出来。

glsl
if (!gl_FrontFacing) c = vec3(0.9, 0.1, 0.35) * 0.6;

法线也要变换

光照需要世界空间的法线。只有旋转和统一缩放时,可以直接用 mat3(uModel);有非均匀缩放时要用法线矩阵(模型矩阵逆的转置)。

核心 API

uniformMatrix4fvperspectivelookAtgl.enable(gl.DEPTH_TEST)gl.enable(gl.CULL_FACE)gl.DEPTH_BUFFER_BITgl_FrontFacing

动手试试

  1. 1关掉 DEPTH_TEST,保持 CULL_FACE,看小立方体何时“穿”到大立方体前面。
  2. 2把月亮矩阵里的平移和公转对调顺序,观察运动变化。
  3. 3把 fov 拉到 110 并缩短相机距离,感受强烈的透视畸变。
运行中