WebGL基础教程(十四):投影矩阵深度解析——正交 vs 透视,彻底搞懂3D视觉魔法

还在为 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 矩阵(模型-视图-投影)中的最后一环:

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

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

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

关键公式

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 场景。

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

相关推荐
LateFrames8 小时前
5 种 3D 模型文件格式比对( .asc / .stl / .obj / .ply / .3mf )
3d
dgaf9 小时前
DX12 快速教程(17) —— 立体图标与合并渲染
c语言·c++·3d·图形渲染·d3d12
千鼎数字孪生-可视化11 小时前
webGPU即将到来,和原生GPU有啥区别呢?
webgl·网页3d
动恰客流管家12 小时前
动恰3DV3丨客流统计系统:旺季人手不够淡季闲人太多?客流统计帮你科学优化人力成本
大数据·运维·人工智能·3d
接着奏乐接着舞14 小时前
3D Tiles tileset.jso 数据格式
运维·服务器·3d
神探小白牙16 小时前
echarts,3d堆叠图
android·3d·echarts
zhangrelay16 小时前
三分钟云课实践速通--工程制图基础-3D--FreeCAD
笔记·学习·3d
qq_3874595816 小时前
浩辰CAD看图王轻松绘制CAD局部放大图
图像处理·3d·cad·cad看图·cad看图软件·cad看图王·浩辰cad看图王
三维频道17 小时前
3C电子制造破局:高精度蓝光3D扫描仪在形位公差分析中的应用
3d·制造·3c电子制造·新拓三维·xtom·形位公差分析·蓝光3d扫描仪
我是大聪明.17 小时前
大模型Tokenizer原理:BPE、WordPiece与子词编码的核心机制深度解析
人工智能·线性代数·算法·机器学习·矩阵