还在为 WebGL 中物体"近大远小"的效果感到神奇?或者苦恼于正交投影和透视投影到底该怎么选?为什么 3D 物体总被裁剪看不见?
今天这篇教程,带你彻底搞懂 WebGL 中的投影矩阵。我们从数学原理到实战代码,手把手教你区分正交与透视投影,并理解 MVP 矩阵中投影矩阵的核心作用。

左图为正交,六个面为平行四边形,右图为透视,六个面近大远小

左图为正交,六个面为平行四边形,右图为透视,六个面近大远小
1.先搞懂:为什么需要投影矩阵?
在 WebGL 中,顶点着色器输出的 gl_Position 坐标范围是 裁剪空间 (四个分量 x,y,z,w 都在 -w 到 w 之间)。但我们的世界是三维的,屏幕是二维的,投影矩阵 的作用就是:
将 3D 空间中的物体,变换到 2D 屏幕上,并决定"近大远小"的视觉感。
没有投影矩阵,你只能看到物体在 [-1,1] 范围内的正交投影,毫无立体感。投影矩阵是连接 3D 世界与 2D 屏幕的魔法桥梁。
核心优势(投影矩阵版)
-
透视投影:产生景深感,符合人眼视觉,是 3D 游戏/场景的首选。
-
正交投影:保持物体实际大小,无透视变形,适合 2D 游戏、UI 界面、工程制图。
-
GPU 统一处理:无论哪种投影,都通过 4x4 矩阵乘法完成,性能高效。
2.WebGL + 投影矩阵工作原理
投影矩阵是 MVP 矩阵(模型-视图-投影)中的最后一环:
-
模型矩阵:将物体从局部坐标变换到世界坐标(平移/旋转/缩放)。
-
视图矩阵:将世界坐标变换到观察者(相机)坐标(定义相机位置和朝向)。
-
投影矩阵 :将观察坐标变换到裁剪坐标,并定义可视范围(视景体)。
关键公式:
glsl
gl_Position = 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标;
3.投影矩阵核心原理
3.1 透视投影(Perspective Projection)
透视投影模拟人眼成像,遵循"近大远小"规则。它定义了一个 平截头体 视景体:近平面大、远平面小,物体越靠近近平面,在屏幕上投影越大。
透视矩阵原理(重点!)
标准透视矩阵(由视野、宽高比、近平面、远平面构建):

text
[ 1/(aspect*tan(fov/2)) 0 0 0 ]
[ 0 1/tan(fov/2) 0 0 ]
[ 0 0 -(far+near)/(far-near) -2*far*near/(far-near) ]
[ 0 0 -1 0 ]
-
fov:视野角度(Field of View),角度越大,可见范围越广。
-
aspect:canvas 宽高比,防止画面拉伸。
-
near/far:近平面和远平面距离,在此范围内的物体才会被渲染。
矩阵乘法效果 :顶点的 z 坐标被用于控制缩放比例,实现"近大远小"。
3.2 正交投影(Orthographic Projection)
正交投影无视物体远近,在屏幕上保持相同大小。它定义了一个长方体视景体,适合精确的 2D 布局、UI 或技术绘图。
正交矩阵原理
text
[ 2/(right-left) 0 0 -(right+left)/(right-left) ]
[ 0 2/(top-bottom) 0 -(top+bottom)/(top-bottom) ]
[ 0 0 -2/(far-near) -(far+near)/(far-near) ]
[ 0 0 0 1 ]
参数直接定义左右上下远近的范围,物体在此范围内则显示,且大小不随距离变化。
4.实战:透视投影 vs 正交投影(同屏对比)
以下代码在一个页面中左右分屏展示两种投影效果,并支持鼠标拖拽同步旋转视角,让你直观感受差异。
html
// 渲染正交投影(左侧)
const canvas = orthoGL.gl.canvas;
canvas.width = width;
canvas.height = height;
orthoGL.gl.viewport(0, 0, width, height);
const view = mat4.create();
const camDist = 5.0;
const cx = camDist * Math.sin(rotY) * Math.cos(rotX);
const cy = camDist * Math.sin(rotX);
const cz = camDist * Math.cos(rotY) * Math.cos(rotX);
mat4.lookAt(view, [cx, cy, cz], [0,0,0], [0,1,0]);
const proj = mat4.create();
const aspect = width / height;
let left, right, bottom, top;
if (aspect >= 1) {
left = -orthoRange * aspect;
right = orthoRange * aspect;
bottom = -orthoRange;
top = orthoRange;
} else {
left = -orthoRange;
right = orthoRange;
bottom = -orthoRange / aspect;
top = orthoRange / aspect;
}
mat4.ortho(proj, left, right, bottom, top, 0.5, 20);
const mvp = mat4.create();
mat4.multiply(mvp, proj, view);
orthoGL.gl.uniformMatrix4fv(orthoGL.mvpLoc, false, mvp);
orthoGL.gl.clearColor(0.08, 0.08, 0.12, 1);
orthoGL.gl.clear(orthoGL.gl.COLOR_BUFFER_BIT | orthoGL.gl.DEPTH_BUFFER_BIT);
orthoGL.gl.drawArrays(orthoGL.gl.TRIANGLES, 0, 36);
// 渲染透视投影(右侧)
const canvas = perspGL.gl.canvas;
canvas.width = width;
canvas.height = height;
perspGL.gl.viewport(0, 0, width, height);
const view = mat4.create();
const cx = perspDist * Math.sin(rotY) * Math.cos(rotX);
const cy = perspDist * Math.sin(rotX);
const cz = perspDist * Math.cos(rotY) * Math.cos(rotX);
mat4.lookAt(view, [cx, cy, cz], [0,0,0], [0,1,0]);
const proj = mat4.create();
const aspect = width / height;
mat4.perspective(proj, Math.PI / 4, aspect, 0.3, 30);
const mvp = mat4.create();
mat4.multiply(mvp, proj, view);
perspGL.gl.uniformMatrix4fv(perspGL.mvpLoc, false, mvp);
perspGL.gl.clearColor(0.08, 0.08, 0.12, 1);
perspGL.gl.clear(perspGL.gl.COLOR_BUFFER_BIT | perspGL.gl.DEPTH_BUFFER_BIT);
perspGL.gl.drawArrays(perspGL.gl.TRIANGLES, 0, 36);
5.正交 vs 透视:核心区别一览表
| 对比维度 | 正交投影 | 透视投影 |
|---|---|---|
| 视觉效果 | 物体大小不随距离变化,无"近大远小" | 近大远小,符合人眼视觉 |
| 视景体 | 长方体(平行投影) | 平截头体(锥体被切去顶部) |
| 平行线 | 始终保持平行 | 在远处交汇于灭点 |
| 典型用途 | 2D游戏、UI界面、工程制图、CAD | 3D游戏、虚拟场景、模型展示 |
| 矩阵函数 | mat4.ortho(left, right, bottom, top, near, far) |
mat4.perspective(fov, aspect, near, far) |
| 参数含义 | 直接定义可视范围的左右上下边界 | 定义视野角度、宽高比、近远平面 |
6.投影矩阵核心知识点(必记!)
-
MVP 矩阵顺序固定 :
gl_Position = 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标,顺序不可颠倒。 -
透视投影调节:
-
fov:越大,看到的范围越广,物体越小(类似广角镜头)。
-
aspect:必须与 canvas 的宽高比一致,否则图形会拉伸。
-
near/far:物体必须在两者之间才能显示,过近或过远会被裁剪。
-
-
正交投影调节:
- left/right/bottom/top:决定了可视范围的大小,范围越大,物体在屏幕上显得越小。
-
深度测试必须开启 :
gl.enable(gl.DEPTH_TEST),否则远近遮挡关系会错误。
🎁 新手投影避坑指南
-
物体看不见:检查物体是否在 near/far 范围内。可以临时将 near 设小、far 设大来测试。
-
画面拉伸:透视投影中,aspect 必须与 canvas 的宽高比一致,并在窗口改变时更新。
-
深度冲突(Z-fighting):near/far 范围太大或物体靠太近时,表面会闪烁。尽量缩小范围,或提高深度缓冲区精度。
-
纹理扭曲 :WebGL 会自动进行透视校正插值,但必须通过
varying传递纹理坐标,不要手动计算。
7.总结
-
投影矩阵 是 WebGL 3D 渲染的最后一步,决定了视觉效果是"近大远小"还是"保持真实大小"。
-
透视投影 通过平截头体实现景深,正交投影 通过长方体实现精确尺寸。
-
MVP 矩阵 的构建顺序至关重要,工业级开发推荐使用
gl-matrix库。
💡 下期预告
投影矩阵学完,MVP 矩阵就完整了!下期我们将深入 光照模型,教你如何让物体拥有明暗变化、阴影和高光,彻底告别扁平感,打造真正逼真的 3D 场景。
关注我,下期手把手教你用矩阵+光照,新手也能轻松拿捏🎊!