还在为 WebGL 中物体“近大远小”的效果感到神奇?或者苦恼于正交投影和透视投影到底该怎么选?为什么 3D 物体总被裁剪看不见?

今天这篇教程,带你彻底搞懂 WebGL 中的投影矩阵。我们将从数学公式推导开始,一步步拆解正交投影和透视投影的矩阵来源,再到实战代码,并详细解析每个参数的含义与调试技巧,让你不仅会用,更懂其原理。

 今天这篇教程,带你彻底搞懂 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 矩阵(模型-视图-投影)中的最后一环:

  1. 模型矩阵:将物体从局部坐标变换到世界坐标(平移/旋转/缩放)。

  2. 视图矩阵:将世界坐标变换到观察者(相机)坐标(定义相机位置和朝向)。

  3. 投影矩阵:将观察坐标变换到裁剪坐标,并定义可视范围(视景体)。

关键公式

glsl

gl_Position = 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标;

3.投影矩阵数学推导(重点!)

3.1 透视投影矩阵推导

透视投影的目标:将视景体(平截头体)映射到裁剪空间立方体 [-1,1]^3,并实现“近大远小”。

步骤 1:建立坐标系

  • 相机位于原点,看向 -Z 方向(OpenGL 惯例)

  • 近平面在 z = -near,远平面在 z = -far

  • 近平面上可视范围:[left, right] × [bottom, top]

步骤 2:透视除法原理
透视的核心是通过 z 坐标控制缩放。对于空间中一点 (x, y, z),投影到近平面上的坐标为:

text

x' = (near * x) / (-z)
y' = (near * y) / (-z)

这里 -z 是点到相机的距离,距离越大,投影坐标越小——这就是“近大远小”的数学本质。

步骤 3:构建齐次坐标变换
我们希望用一个 4x4 矩阵 M_proj 使得:

text

M_proj * (x, y, z, 1)^T = (x * near, y * near, A*z + B, -z)^T

其中 A 和 B 用于将 z 映射到 [-1,1],而 -z 作为 w 分量,为后续透视除法做准备。

根据近平面和远平面的映射条件:

  • 当 z = -near 时,输出 z_clip = -1

  • 当 z = -far 时,输出 z_clip = 1

代入 z_clip = (A*z + B) / (-z),得到方程组:

text

(A*(-near) + B) / near = -1  →  -A*near + B = -near
(A*(-far) + B) / far = 1     →  -A*far + B = far

解得:

text

A = -(far + near) / (far - near)
B = -2*far*near / (far - near)

步骤 4:考虑视野和宽高比
通常我们不直接指定 left/right/bottom/top,而是通过:

  • 视野角 fov(垂直方向)

  • 宽高比 aspect

关系为:

text

top = near * tan(fov/2)
bottom = -top
right = top * aspect
left = -right

代入步骤 3 的矩阵,得到最终透视投影矩阵:

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          ]
3.2 正交投影矩阵推导

正交投影的目标:将长方体视景体映射到裁剪空间立方体 [-1,1]^3无透视缩放

步骤 1:建立映射关系
视景体:[left, right] × [bottom, top] × [-near, -far](注意 Z 轴负方向)

我们需要将 x ∈ [left, right] 线性映射到 x' ∈ [-1, 1]

text

x' = (2 / (right - left)) * x - (right + left) / (right - left)

同理 y 和 z

步骤 2:写成矩阵形式
正交投影矩阵为:

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                          ]

注意 Z 轴负方向导致 -2/(far-near) 和 -(far+near)/(far-near) 的出现,确保 z = -near 映射到 -1z = -far 映射到 1

4.投影矩阵参数详解(调试必读)

4.1 透视投影参数

透视投影矩阵通过 mat4.perspective(fov, aspect, near, far) 创建,各参数含义如下:

参数 类型 含义 取值范围 调试建议
fov number(弧度) 垂直视野角(Field of View) 0° ~ 180°,常用 45°~60° fov 越大,视野越广,物体越小(广角效果);fov 越小,视野越窄,物体越大(长焦效果)。fov > 120° 会产生明显畸变。
aspect number 宽高比 = 画布宽度/高度 通常 >0 必须与 canvas 的 width/height 一致,否则画面拉伸。窗口 resize 时需重新计算。
near number 近平面距离(相机到近平面的距离) >0,通常 0.01~1.0 过小(如 0.0001)会导致深度缓冲精度不足,引起 Z-fighting(画面闪烁);过大(如 10)会使近处物体被裁剪。
far number 远平面距离 > near,通常 100~1000 过小会使远处物体被裁剪;过大会浪费深度精度,同样引起 Z-fighting。建议 far/near ≤ 1000 以保证深度精度。

调试技巧

  • 物体被裁剪:增大 far 或减小 near

  • 画面闪烁/深度冲突:缩小 far/near 比值,或将 near 适当调大

  • 物体太小/太大:调整 fov(增大 fov 物体变小,减小 fov 物体变大)

4.2 正交投影参数

正交投影矩阵通过 mat4.ortho(left, right, bottom, top, near, far) 创建,各参数含义如下:

参数 类型 含义 取值范围 调试建议
left number 视景体左边界 X 坐标 left < right 范围越小,物体在屏幕上显示越大;范围越大,物体越小。类似于“缩放”。
right number 视景体右边界 X 坐标 right > left 与 left 共同决定水平可视范围。通常保持 right = -left 使物体居中。
bottom number 视景体下边界 Y 坐标 bottom < top 与 top 共同决定垂直可视范围。通常保持 top = -bottom 使物体居中。
top number 视景体上边界 Y 坐标 top > bottom 控制垂直方向显示范围。
near number 近平面距离 通常 0.1~10 物体必须在 near 和 far 之间才能显示。与透视投影不同,正交投影的 near/far 不影响物体大小,只决定深度测试范围。
far number 远平面距离 > near 同样只影响深度测试范围。建议保持 far/near 比值在合理范围(如 1000 以内)以保证深度精度。

调试技巧

  • 物体被裁剪:检查物体是否在 [left,right] × [bottom,top] × [near,far] 范围内

  • 物体太小/太大:调整 left/right/bottom/top 的范围(范围越小,物体越大)

  • 画面拉伸:检查 aspect 是否通过 left/right/bottom/top 正确反映(通常设置 right/left = aspect * (top/bottom)

5.实战:透视投影 vs 正交投影(同屏对比)

以下代码在一个页面中左右分屏展示两种投影效果,并支持鼠标拖拽同步旋转视角,让你直观感受差异。

html

            const proj = mat4.create();
            const aspect = width / height;
            // 使用可调的 fov 参数
            mat4.perspective(proj, perspFov * Math.PI / 180, 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);

6.正交 vs 透视:核心区别一览表

对比维度 正交投影 透视投影
视觉效果 物体大小不随距离变化,无“近大远小” 近大远小,符合人眼视觉
视景体 长方体(平行投影) 平截头体(锥体被切去顶部)
平行线 始终保持平行 在远处交汇于灭点
数学核心 线性映射,无除法 透视除法(除以 -z)实现缩放
参数控制 left/right/bottom/top 直接控制显示范围 fov 控制视野广度,aspect 防拉伸
深度精度 near/far 影响深度精度,但不影响大小 near/far 影响深度精度,且 far/near 比值过大会导致 Z-fighting
典型用途 2D游戏、UI界面、工程制图、CAD 3D游戏、虚拟场景、模型展示

7.投影矩阵参数调优指南

透视投影参数调优
现象 可能原因 解决方案
物体被裁剪 near 太大 或 far 太小 减小 near(如 0.1),增大 far(如 1000)
远处物体闪烁 far/near 比值过大(>1000) 减小 far 或增大 near,压缩比值
物体太小/太大 fov 不合适 减小 fov 物体变大,增大 fov 物体变小
画面拉伸 aspect 与 canvas 不匹配 窗口 resize 时重新计算 aspect
正交投影参数调优
现象 可能原因 解决方案
物体被裁剪 left/right/bottom/top 范围过小,或 near/far 不合适 扩大范围,或检查物体坐标是否在范围内
物体太小/太大 left/right/bottom/top 范围不合适 缩小范围物体变大,扩大范围物体变小
画面拉伸 left/right 与 bottom/top 比例与 canvas 宽高比不一致 保持 (right-left)/(top-bottom) = aspect

🎁 新手投影避坑指南

  • 物体看不见:检查物体是否在 near/far 范围内。可以临时将 near 设小、far 设大来测试。

  • 画面拉伸:透视投影中,aspect 必须与 canvas 的宽高比一致,并在窗口改变时更新。

  • 深度冲突(Z-fighting):near/far 范围太大或物体靠太近时,表面会闪烁。尽量缩小范围,或提高深度缓冲区精度。

  • 矩阵顺序投影 × 视图 × 模型 的顺序千万不能搞反,矩阵乘法不满足交换律。

  • 透视投影 fov 陷阱:fov 是垂直视野角,不是水平。宽屏下水平视野会自动更宽,这是正常的。

8.总结

  • 投影矩阵 是 WebGL 3D 渲染的最后一步,决定了视觉效果是“近大远小”还是“保持真实大小”。

  • 透视投影 通过透视除法和平截头体实现景深,正交投影 通过线性映射和长方体实现精确尺寸。

  • 从公式推导可以看出,两者本质区别在于是否引入与 z 相关的除法,这也决定了它们完全不同的视觉特性。

  • 参数调试时,near/far 的比值是影响深度精度的关键,fov 和 left/right/bottom/top 分别控制视野范围。

9.完整代码,关注后可以下载。

💡 下期预告

投影矩阵学完,MVP 矩阵就完整了!下期我们将深入 光照模型,教你如何让物体拥有明暗变化、阴影和高光,彻底告别扁平感,打造真正逼真的 3D 场景。

关注我,下期手把手教你用矩阵+光照,新手也能轻松拿捏🎊!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐