WebGL第四十二课:3D前置知识点之Perspective矩阵

361 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

本文标题:WebGL第四十二课:3D前置知识点之Perspective矩阵

友情提示

这篇文章是WebGL课程专栏的第42篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。

矩阵的作用

一般来说,在程序里或者在公式里,矩阵的作用可以看成是一个输入输出系统。

这个系统的输入是一个向量,输出是另一个向量,像下图这样:

image.png

研究某一个特定矩阵的效果,就要看,这个输出向量和输入向量的关系。

这十分类似于程序里的函数。

举个特定的例子来说明,比如说我们有一个2D的旋转矩阵,作用是逆时针旋转30°,那么对于任意输入向量来说,得到的输出向量,皆是该输入向量逆时针旋转30°的结果,如下图:

image.png

我们这里注意一下,一个矩阵的输入向量,必须是该维度内,任意的向量。

比如说上述的旋转矩阵,那么对于任意2D的输入向量,必须都能完成这个旋转效果才行。

这一点需要留个心眼,下面会再次提及。

用穷举法获取矩阵效果

既然这里把矩阵看做一个输入输出系统,我们不妨这样来研究某个特定矩阵的效果:

大量穷举输入向量,去观测相应的输出向量,总结输出向量与输入向量的关系,即可。

意思就是,如果我们不清楚一个矩阵的效果是什么,我们就可以随便找几个输入向量,然后计算出输出向量,然后观察一下效果,然后试图总结一下整个矩阵的效果。

穷举法示例1

有一个2D的矩阵:[2003]\begin{bmatrix} 2 & 0\\ 0& 3\end{bmatrix}, 试着总结一下这个矩阵的效果。

我们用三个输入向量来实验一下:

  • [2003]\begin{bmatrix} 2 & 0\\ 0& 3\end{bmatrix} * (10)\left(\begin{array}{cc} 1\\ 0\end{array}\right) = (20)\left(\begin{array}{cc} 2\\ 0\end{array}\right)

  • [2003]\begin{bmatrix} 2 & 0\\ 0& 3\end{bmatrix} * (0.50.5)\left(\begin{array}{cc} 0.5\\ 0.5\end{array}\right) = (11.5)\left(\begin{array}{cc} 1\\ 1.5\end{array}\right)

  • [2003]\begin{bmatrix} 2 & 0\\ 0& 3\end{bmatrix} * (01)\left(\begin{array}{cc} 0\\ 1\end{array}\right) = (03)\left(\begin{array}{cc} 0\\ 3\end{array}\right)

我们来总结一下规律,该矩阵可能的效果是:

  • 将输入向量的x分量变成原来的2倍
  • 将输入向量的y分量变成原来的3倍

我们再列一个任意向量,来验证一下我们的总结是否正确:

  • [2003]\begin{bmatrix} 2 & 0\\ 0& 3\end{bmatrix} * (56)\left(\begin{array}{cc} 5\\ 6\end{array}\right) = (1018)\left(\begin{array}{cc} 10\\ 18\end{array}\right)

验证了一下,确实是这个效果,那么大概率我们总结的效果就是正确的了。

穷举法示例2

看这个矩阵: [1000]\begin{bmatrix} 1 & 0\\ 0& 0\end{bmatrix}

列举如下:

  • [1000]\begin{bmatrix} 1 & 0\\ 0& 0\end{bmatrix} * (10)\left(\begin{array}{cc} 1\\ 0\end{array}\right) = (10)\left(\begin{array}{cc} 1\\ 0\end{array}\right)

  • [1000]\begin{bmatrix} 1 & 0\\ 0& 0\end{bmatrix} * (0.50.5)\left(\begin{array}{cc} 0.5\\ 0.5\end{array}\right) = (0.50)\left(\begin{array}{cc} 0.5\\ 0\end{array}\right)

  • [1000]\begin{bmatrix} 1 & 0\\ 0& 0\end{bmatrix} * (01)\left(\begin{array}{cc} 0\\ 1\end{array}\right) = (00)\left(\begin{array}{cc} 0\\ 0\end{array}\right)

总结规律:

保留输入向量的x分量,让y分量变成0。

穷举画图

总结规律最好的办法就是在坐标系中画出输入输出向量坐标,能更加清晰的找到规律。

比如说旋转矩阵,我们只要列出几个向量,然后画出输出向量,非常容易得知,输出向量是由输入向量旋转得来。

开始分析Perspective矩阵

对输入向量进行约束

我们先把上文中的一个重要注意点放到这里:

一个矩阵的输入向量,必须是该维度内,任意的向量。

由于该Perspective矩阵是4D的,那么根据这个注意点,得出一个东西:

输入向量必须是任意4D向量。

但是很明显,对于Perspective矩阵来说,这个输入向量不能是任意的4D向量。

这是由于齐次空间的原因,如果对于齐次空间不理解的读者,可以先看WebGL第四十一课:3D前置知识点之齐次空间 - 掘金 (juejin.cn)

对于Perspective矩阵来说,这里给出输入向量必须满足的条件:

形如:(xyz1.0)\left(\begin{array}{cc} x\\ y\\ z\\ 1.0\end{array}\right)的向量,即可作为Perspective矩阵的输入。

也就是说,前面三个分量无所谓,最后一个分量必须是1.0。

这里做这样的约束,并不是因为,矩阵本身不能接受任意4D向量,而是我们图形学应用的时候,第四个分量只会是1.0,这样能大大缩减我们穷举时的数据量,能够更快分析出来规律。

先给出Perspective的数学形式

想要生成一个Perspective矩阵,我们一般有几个参数:

  • 近面 near
  • 远面 far
  • 宽高比 whr
  • 视场角 fov

根据以上参数,可以得出一个Perspective矩阵:

[1whrtan(fov/2)00001tan(fov/2)0000far+nearfarnear2farnearnearfar0010]\begin{bmatrix} \frac{1}{whr * tan(fov/2)} & 0 &0 &0 \\ 0 & \frac{1}{tan(fov/2)} &0 &0 \\ 0 & 0 & -\frac{far+near}{far-near} & \frac{2 * far * near}{near-far} \\ 0 & 0 &-1 &0 \end{bmatrix}

我们剩下来的事情就是要研究,对于任意 (xyz1.0)\left(\begin{array}{cc} x\\ y\\ z\\ 1.0\end{array}\right)向量,经过这个矩阵运算之后,输出向量会是什么。

根据矩阵乘法的相关性质(WebGL第十四课:从向量到矩阵 - 掘金 (juejin.cn)),我们可以立即得出一个结论:

输出向量的 w=zw = -z, 也就是说:

[1whrtan(fov/2)00001tan(fov/2)0000far+nearfarnear2farnearnearfar0010]\begin{bmatrix} \frac{1}{whr * tan(fov/2)} & 0 &0 &0 \\ 0 & \frac{1}{tan(fov/2)} &0 &0 \\ 0 & 0 & -\frac{far+near}{far-near} & \frac{2 * far * near}{near-far} \\ 0 & 0 &-1 &0 \end{bmatrix} * (xyz1.0)\left(\begin{array}{cc} x\\ y\\ z\\ 1.0\end{array}\right) = (???z)\left(\begin{array}{cc} ?\\ ?\\ ?\\ -z\end{array}\right)

前面三个分量暂时处于未知状态。

研究一个图形学中矩阵的效果,一定要看输入向量的物理意义是什么。

看下图:

image.png

提出一个问题:

Perspective矩阵的输入向量,是图中的

  • OA (黑色向量)?
  • VA (绿色向量)?

到底是哪一个?

很明显是绿色的向量,因为Perspective矩阵是MVP矩阵中,最后进行运算的矩阵。

在经过View矩阵的时候,已经将黑色向量转成了绿色向量了。

在这里再提一个额外的知识:

View矩阵不仅仅是将黑色向量变成绿色向量,并且连带坐标系,也一并变换了,就是说,这个View矩阵有两个效果:

    1. 将黑色向量变成绿色向量
    1. 将绿色向量用相机的三个轴形成的坐标系,输出到结果

重点:Perspective矩阵的输入,正是上面两步之后的结果。

也就是说,我们此时考虑的坐标系,就应该是Camera坐标系,而非世界坐标系。

接下来,我们在Camera坐标系中,将 near far whr fov 画出来,这就是我们常见的视椎体:

image.png

然后,我们将注意力集中在这个视椎体内部的点Camera位置连线所形成的一堆向量上。

先研究输出向量的z分量

先把得到的向量写出来:

[1whrtan(fov/2)00001tan(fov/2)0000far+nearfarnear2farnearnearfar0010]\begin{bmatrix} \frac{1}{whr * tan(fov/2)} & 0 &0 &0 \\ 0 & \frac{1}{tan(fov/2)} &0 &0 \\ 0 & 0 & -\frac{far+near}{far-near} & \frac{2 * far * near}{near-far} \\ 0 & 0 &-1 &0 \end{bmatrix} * (xyz1.0)\left(\begin{array}{cc} x\\ y\\ z\\ 1.0\end{array}\right) = (??zoutz)\left(\begin{array}{cc} ?\\ ?\\ z_{out}\\ -z\end{array}\right)

zout=zfar+nearfarnear+2farnearnfz_{out} = -z * \frac{far+near}{far-near} + \frac{2* far* near}{n-f}

结论一:可以看出,far 和 near 都是常量,随着z的变化,zoutz_{out}是线性变化的。

我们不妨取

  • 输入z = -near
  • 输入z = -far 两个极端情况来代入试试:

当输入 z=-near 时,zoutz_{out} = -near // 很简单的中学化简得到的结果
当输入 z=-far 时,zoutz_{out} = far

又由于,这个变换过程是线性的,我们可以得知,
当输入的z在[ -near, -far]间变化时,输出的zoutz_{out}就会在[ -near, far]之间线性变化。

再研究输出向量的x分量

从矩阵乘法算式中可以一眼看出:

输出向量xout=xwhrtan(fov/2)x_{out} = \frac{x}{whr * tan(fov/2)}

这仅仅是输入向量x带了一个乘积系数而已,非常简单的关系。

输出向量的y分量与x分量类似

输出向量yout=ytan(for/2)y_{out} = \frac{y}{tan(for/2)}

也仅仅是输入向量y带了一个乘积系数。

疑团

从网上的资料中,我们知道,Perspective 矩阵可以将视椎体内的向量,变到NDC的范围中去。也就是xyz个分量的范围都应该是[-1,1]。

但上面我们看出,zoutz_{out}的范围似乎不是[-1,1], 而是[-near, far]。

这是为啥?

这里就要再次提一下齐次空间了WebGL第四十一课:3D前置知识点之齐次空间 - 掘金 (juejin.cn)

输出向量的xyz最终都要除以w,也就是说:

最终得到的结果是一个3D的向量:

(xoutwyoutwzoutw)\left(\begin{array}{cc} \frac{x_{out}}{w}\\ \frac{y_{out}}{w} \\ \frac{z_{out}}{w} \end{array}\right)

研究这个式子:

zout/wz_{out}/w = zout/(z)z_{out}/(-z) = far+nearfarnear+2farnear(nearfar)(z) \frac{far+near}{far-near} + \frac{2* far* near}{(near-far)(-z)}

这就很明显了,

  • 将z=-near代入,得到 zout/wz_{out}/w = -1
  • 将z=-far代入,得到 zout/wz_{out}/w = 1

并且,观察式子,z处于一个分母的位置,很明显,随着z的变化,最终得到的结果不是一个线性的,而是呈下图的样子:

image.png

在z靠近-near的时候,变化极其剧烈
在z靠近-far的时候,变化极其平缓。

最终的xy

我们不妨来观察一下xout/wx_{out}/w这个式子:

xout/wx_{out}/w = xout/(z)x_{out}/(-z) = xwhrtan(fov/2)(z)\frac{x}{whr * tan(fov/2) * (-z)}

我们将式子中的常量写成一个整体系数,得到 Cxz\frac{C * x}{-z}

那么输入向量x在[zC,zC][\frac{z}{C}, \frac{-z}{C}]之间取值的时候,最终的结果就会是[-1,1]之间。

我们发现,输入向量z越小,我们输入向量x就必须更大,才能更靠近[-1, 1]。

这不就是透视效果吗!!!

y的分析与x一样,就不赘述了。

总结

本篇从数值角度分析了Perspective矩阵的效果和相关特性,希望对诸位有帮助。

以下是deepseek对这篇文章的总结,非常好,复制过来

分步解释:Perspective矩阵的作用与原理

1. 矩阵的基本作用

矩阵在图形学中用于坐标变换。Perspective矩阵属于投影矩阵的一种,负责将3D相机空间中的点投影到2D屏幕,同时保持透视效果(近大远小)。

2. 输入向量的约束

  • 形式:输入向量为齐次坐标 (xyz1.0)\left(\begin{array}{cc} x\\ y\\ z\\ 1.0\end{array}\right),表示3D空间中的点。
  • 来源:经过模型矩阵(Model)和视图矩阵(View)变换后,点已位于相机坐标系,此时应用Perspective矩阵。

3. Perspective矩阵的结构

矩阵参数:

  • near:近裁剪面距离(正值)。
  • far:远裁剪面距离(正值)。
  • fov:垂直视场角(决定视野高度)。
  • whr:宽高比(视图宽度/高度)。

矩阵形式: M=[1whrtan(fov/2)00001tan(fov/2)0000far+nearfarnear2farnearnearfar0010]M=\begin{bmatrix} \frac{1}{whr * tan(fov/2)} & 0 &0 &0 \\ 0 & \frac{1}{tan(fov/2)} &0 &0 \\ 0 & 0 & -\frac{far+near}{far-near} & \frac{2 * far * near}{near-far} \\ 0 & 0 &-1 &0 \end{bmatrix}

4. 输出向量计算

输入向量 (xyz1.0)\left(\begin{array}{cc} x\\ y\\ z\\ 1.0\end{array}\right) 经过矩阵乘法:

(xyz1.0)=M(xyz1.0)\left(\begin{array}{cc} x'\\ y'\\ z'\\ 1.0\end{array}\right) = M \cdot \left(\begin{array}{cc} x\\ y\\ z\\ 1.0\end{array}\right)

具体分量:

  • x'xwhrtan(fov/2)\frac{x}{\text{whr} \cdot \tan(\text{fov}/2)}

  • y'ytan(fov/2)\frac{y}{\tan(\text{fov}/2)}

  • z'far+nearfarnearz+2farnearnearfar-\frac{\text{far} + \text{near}}{\text{far} - \text{near}} \cdot z + \frac{2 \cdot \text{far} \cdot \text{near}}{\text{near} - \text{far}}

  • w':(-z)

5. 透视除法(关键步骤)

将齐次坐标转换为3D标准化设备坐标(NDC): NDC=(xw,yw,zw)\text{NDC} = \left( \frac{x'}{w'}, \frac{y'}{w'}, \frac{z'}{w'} \right)

  • x和y分量

    xw=xwhrtan(fov/2)z\frac{x'}{w'} = \frac{x}{-\text{whr} \cdot \tan(\text{fov}/2) \cdot z}

    yw=ytan(fov/2)z\frac{y'}{w'} = \frac{y}{-\tan(\text{fov}/2) \cdot z}

    这实现了近大远小:当 (z) 增大(远离相机),分母增大,坐标值缩小。

  • z分量zw=far+nearfarnearz+2farnearnearfarz\frac{z'}{w'} = \frac{-\frac{\text{far} + \text{near}}{\text{far} - \text{near}} \cdot z + \frac{2 \cdot \text{far} \cdot \text{near}}{\text{near} - \text{far}}}{-z}

    化简后:

    zw=far+nearfarnear+2farnearz(nearfar)\frac{z'}{w'} = \frac{\text{far} + \text{near}}{\text{far} - \text{near}} + \frac{2 \cdot \text{far} \cdot \text{near}}{z \cdot (\text{near} - \text{far})}

    • z=nearz = -\text{near}(近裁剪面),结果为 1-1
    • z=farz = -\text{far}(远裁剪面),结果为 11。非线性映射优化了深度缓冲的精度分布。

6. 视椎体到NDC的映射

  • x和y:被缩放并受zz影响,确保视椎体内的点映射到NDC的[1,1][-1, 1]范围。
  • z:非线性映射到[1,1][-1, 1],保证深度测试正确性。

7. 参数影响

  • fov:角度越大,视野越广,物体显得更小。
  • near/far:决定可见深度范围,影响深度精度和裁剪。

8. 总结

Perspective矩阵通过缩放和平移,将相机空间的视椎体转换为NDC立方体,再通过视口变换映射到屏幕。关键步骤是矩阵乘法后的透视除法,实现了3D到2D的投影及深度处理。