目录

透视投影


目录

对透视投影的深入思考

投影变换是整个渲染管线里,设计得最复杂的,也最巧妙的一次变换。

前因

在GAMES101中,我只是大概了解了透视投影矩阵所做出的变换,将视椎体中的点全部映射到近平面上,再将近平面缩放成规则观察体(CVV空间)。

而在tinyrenderer中,我见到了不一样的透视投影矩阵。

关于透视投影的插值矫正,tinyrenderer直接忽略了这点,GAMES101中也没有做更详细的讲解,以至于我在看到透视除法等一系列操作时摸不到头脑。

于是,我去开始查找资料,为什么要做透视投影的插值矫正?但网上得到的答案大多都很简短、类似。类似于:重心坐标插值要在线性空间中插值,世界空间、观察空间中的坐标都是线性的,而经过透视投影变换后,是非线性的。于是以屏幕坐标插值出的结果是畸形的,当线性坐标下(例如观察坐标)的插值结果才是正确的。

这番解释虽然大概说明了要做透视插值矫正的必要性,但距离实际应用还是一头雾水。我到底该怎么从屏幕坐标这个非线性空间,插值得到线性空间坐标?透视投影变换的步骤究竟是什么?透视除法除以的w分量又究竟是什么,为什么要这么做?

于是,我展开了深入的理解。

理解前的统一定义

首先,为了统一坐标系、矩阵等问题,我会从以下方式思考:

  • 向量默认为列向量
  • 矩阵默认为列主序
  • 变换默认为矩阵左乘
  • 坐标系默认为右手系
  • 摄像机默认处于原点,lookat方向-z,向上方向+y

投影变换的目的

坐标转换到观察空间后,由于直接使用摄像机的平截头体进行裁剪比较复杂(平截头体的边界方程求交困难),所以需要将其转化到裁剪空间(Clip空间 )。

从观察空间到裁剪空间的变换叫做投影变换。Project

裁剪空间变换的思路是,对视椎体进行缩放,使远裁剪面缩放至近裁剪面大小,然后将这个长方体缩放至标准立方体移动到原点,使坐标的w分量表示裁剪范围,此时,只需要简单的比较x,y,z和w分量的大小即可裁剪图元。

虽然叫做投影变换,但是投影变换并没有进行真正的投影。

这一篇主要讲透视投影

透视投影变换步骤

  1. 从视椎体内一点投影到近裁剪面
  2. 由近裁剪面缩放至规则观察体(CVV空间),得到clip坐标(此时clip坐标还没有除以w变成3D坐标,仍是齐次坐标)

对相机空间中的顶点,如果在视锥体中,则变换后就在CVV中。如果在视锥体外,变换后就在CVV外。CVV本身的规则性对于多边形的裁剪很有利。

透视投影简易模型理解

首先,我们明确我们的需求:将观察空间中视椎体中的坐标点 (x, y, z, 1),通过透视投影,转化到近平面上的点 (x’, y’, z’, 1) 中去。

https://learnopengl-cn.github.io/img/01/08/perspective_frustum.png

那么我们可以很简单的得到 z’ = Near,那么由于近平面值Near是固定的,先不妨假设为1,则

由相似三角形可得:

  • z’ = 1 (z’ = Near)
  • x’ = x/z (x’ = x * Near / z)
  • y’ = y/z (y’ = y * Near / z)
  • w = 1

那么就可以推出tinyrenderer中的透视矩阵:

https://pic3.zhimg.com/80/v2-ad0ea8a748d9edf9d549a5fb3f951e0a_720w.jpg

因采用右手系,这里红色的1应该是-1

注意到M的最后一行不是(0 0 0 1)而是(0 0 -1 0),因此可以看出透视变换不是一种仿射变换,它是非线性的。

经过这个矩阵变换,就可以得到(x’ = x, y’ = y, z’ = z, w’ = z),经过齐次除法,xy映射到x’ = x/z,y’ = y/z,z’ = 1。这正是我们想要的变换。

我们将透视投影变换的系数储存在了w分量里,只要进行透视除法,就是非线性的(x’, y’, z’)坐标,而不考虑w分量,(x, y, z)仍是线性可插值的。

数学推导

https://img-blog.csdnimg.cn/20190902235800653.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9oYXBweWZpcmUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70

推荐看这篇文章:图形学基础之透视校正插值

经过推导,我们发现:

当我们从视角空间到透视投影到屏幕空间后,我们的线性变换并不是x, y, z的线性变换,而是x, y, 1/z的线性变换。因此在屏幕空间上的插值需要做透视校正,否则会出现不正确的情况。

为什么是1/z的线性变换呢,在透视投影中,我们的视椎体是一个不规则立方体,将点(x, y, z)中的x, y映射到屏幕(近平面)上时会根据其z的值进行压缩,其公式是:

  • x’ = Near/z * x
  • y’ = Near/z * y

可以发现x’, y’和x, y的关系是1/z的关系。

在齐次坐标中 https://www.zhihu.com/equation?tex=%28P_x%2C+P_y%2C+P_z%2C+1%29+%3D%3D+%28kP_x%2C+kPy%2C+kPz%2C+k%29 ,因此我们将z保存在w的位置,将x, y, z在透视投影下的线性变换都乘以z。这样 https://www.zhihu.com/equation?tex=%28P_xP_z%2C+P_yP_z%2C+P_zP_z%2CP_z%29+%5C%5C+

就是关于x, y, z的线性变换。

因此所谓的透视除法,就是在透视投影后还原x, y, z真实的屏幕空间值的过程。

真实模型理解

下面来看看网上最常见的透视矩阵书写形式:

https://pic3.zhimg.com/80/v2-b987fbb765291b6d4014a643a0d76542_720w.jpg

其实这么写反而妨碍了我们的直观理解,由Aspect、Fov与w(width)、h(height)、N(Near)、F(Far)的关系,我们应当写成如下形式:

https://pic4.zhimg.com/80/v2-49f30cf6dd8b979218c1b7c0a0cbeff7_720w.jpg

发现了吗,和透视矩阵之母Pure Perspective Projection的区别,第一列第一行的1变为了N*(2/w)这么一个缩放值。

2/w很好理解,是将近平面的宽缩放到长度2;

乘上N ,是因为考虑到齐次除法xyz分量要除以w分量,矩阵转化后的w分量等于转化前的z分量相反数。

故x’/w’= x/z, y’/w’ = y/z,是我们要的投影到近平面为1的值,这个值乘上N就相当于映射到了近平面,即true x’ =(N/z)*(2/w)。 相应的,mat11的值1变化为2N/h。

总结

关于透视投影的矫正,通过将原始的z值存储在w分量中。

透视校正插值使用线性的深度值非线性空间下的插值系数s来计算出线性空间下的插值系数t,并用t来对顶点属性进行插值。

最后得到的(x’, y’, z’, w)中,x‘、y’已经是xy乘过Near了,再除以z不就是屏幕空间吗。

  • x’ = x * Near / z
  • y’ = y * Near / z
  • z = Near
  • w = 1