开启辅助访问
 找回密码
 立即注册

Hand writing 家用投影矩阵

a3760014 回答数1 浏览数652
观卿尤似周公瑾,闲茶无事做文章


       透视家用投影推导过程很巧妙,使用很简单线性代数知识就可以,由于在渲染中有时候也需要通过屏幕空间坐标反推出世界坐标,理解它对这个也有帮助。今天打算手推一下这个矩阵,最后也会简单分析一下UE中家用投影矩阵的写法。
作用:

包含裁剪和屏幕坐标家用投影显示。



1 三维空间中,家用投影矩阵用来把摄像机坐标转为裁剪空间,继而通过透视除法,用来计算出物体在屏幕中的位置。家用投影的屏幕位置记做家用投影面。



2 用来做裁剪,也就是这个功能才让家用投影矩阵变的复杂起来。细想摄像机显示范围是有限的,而且屏幕的显示的范围也是有限的,应该让摄像机平阶头体范围内的物才显示才对,这样才符合实际情况,前面的世界坐标变化矩阵,摄像机坐标变化矩阵都不能完成这个任务,那么这个艰巨的任务只能而且只能由透视家用投影矩阵完成了。
因为要求在摄像机坐标点乘以家用投影矩阵后的xy坐标在一台限定了XYZ大小的范围内的才会渲染到屏幕上,其余的都被剔除了,这个范围也组成了所谓的裁剪空间,裁剪空间为了裁剪的时候计算省事,要求把它设置位一台立方体。在opengl XYZ方向都是【-w,+w】,而direcx3D XY 方向也跟opengl一样,只有Z轴上是【0 - w】,而透视家用投影的计算过程把这个过程合并到一起进行考虑,计算方法通过把摄像机平截头体的六个边(上下左右前后(也是一台立方体))映射到NDC 空间-1到1或者0到1当中。最终的NDC空间形状是一台2x2x2或者2x2x1立方体盒子。下图以opengl为例。


说明

在三维空间中,物体空间的坐标是一台XYZ,三个方向的点,家用投影矩阵一台4X4的矩阵(携带缩放,旋转,平移信息),根据矩阵乘法规则,与之相乘的坐标必须是一台4位的点,而通常三位坐标都是三个方向点顶值XYZ,为了不影响最后的计算结果,最后一位通常会设置为1,也就是组成所谓的齐次坐标。有了这些知识铺垫,配合上初中的三角形相似原理,和大学的简单的线性代数知识就可以推导家用投影矩阵了。
接下来先仔细介绍opengl 透视家用投影矩阵的推导过程,家用投影矩阵的最终计算结果是在NDC空间,opengl坐标系遵从右手定则,因此z轴正方面朝向屏幕向外,而摄像机的朝向是与z轴方向向反从指向屏幕里面,因此能看到物体的摄像机空间中z轴坐标都是负值,因此透视家用投影面的z坐标也是负值。

行距阵与列矩阵

由于opengl 与dx 对坐标系统的使用规则,矩阵的存储上也呈现转置关系。
行距阵表示:

矩阵按照行顺序来表示坐标轴:如下 红 绿 蓝 三个颜色分别代表X Y Z 三个坐标轴方向。


当进行齐次坐标变化的过程中,在矩阵乘法中按照向量*矩阵顺序即矩阵后置规则。

列矩阵表示:

矩阵按照列顺序来表示坐标轴:如下 红 绿 蓝 三个颜色分别代表X Y Z 三个坐标轴方向。


当进行齐次坐标变化的过程中,在矩阵乘法中按照矩阵*向量顺序,即矩阵前置规则。

值得一提的是不管使用行距在或者列矩阵,最后得到的结果必然是一样的,这可以通过改变矩阵的每一项目的存储顺序来实现,opengl 使用按照列优先存储,dx按照行优先存储,似乎矩阵前置更有利于CPU cache(因为每次取出的都是矩阵中连续的数据与向量相乘),但是在现代c++编译器优化的前提下,并不会有性能损失。
透视矩阵

家用投影计算:



把上图近平面看作家用投影面,需要的计算的家用投影点为E (Xe,Ye,Ze)家用投影后的点为P(Xp,Yp,Zp),家用投影面z坐标值为-n(这里n并不一定表示近平面的z值,n>0),在Y轴与Z轴组成的平面当中,链接家用投影点与家用投影后的点必然相交于原点o由此形成的了两个三角形ΔYeZeO和ΔYpZeO根据三角形近似原理,Ye/Yp =Ze/Zp,进而得出,又 Zp= -n, 由于Zp是负值,Yp>0, 所以Yp=-n* Ye/Ze.同理也可以推导出Xp=-n* Xe/Ze.
根据opengl矩阵列矩阵相乘规则:矩阵前置先乘, P = M * E.




因此可以将家用投影点的Z坐标当作Wclip。
那么,矩阵M的最后一行就可以确定了:


下面就是计算M矩阵前三行的方法了,无它,唯映射尔,而透视家用投影矩阵的复杂计算也就是浓缩在这个映射过程里面了,这个过程涉及到的数学可以说很简单,只需要初中的水平就可以了。
首先因为最终的NDC坐标空间XYZ范围为[-1,1],那么使用线性映射方法:
根据图1,左面截面和右边截面,对应的X值坐标l,r 经过家用投影后对应的坐标分别为-1和1,那么经过家用投影后的P点会被映射到NDC空间。


l≤Px≤r.
下面是计算过程,这个计算过程很巧妙,不觉梦回初中,想起了初中同学为了靠近我,故意装作是请教我数学题的场景,那是一台深秋的夜晚,皓月当空,空荡荡的教室里…
差点跑题,我写下



看到这里如果大脑可以飞速运转的话对着M矩阵,按照先行号后列号的顺序,从0到4,应该可以想到


因为包含了透视除法的原因,上面的M00应该乘以-Ze(w=-Ze),所以最终M00应该为2n/(r-l)
同理:通过平截头体上部和下部,b与t计算出
矩阵M的第二行为M10 =0,M11 =(2n/(t-b)),M12=(t+b)/(t-b),M13=0;
到这里矩阵的前两行和最后一行都得出了。


剩下的只有矩阵的第三行的信息米有推导出来了,也不难,根据近平面远平面z坐标,最终范围为-1到1,并且z坐标的计算与X坐标和Y坐标无关,因此可以先写作


根据下面公式



得出矩阵为


又因为r与l对称,t与b对称,并且都是关于摄像机原点对称,因此r =-l,t =-b,

得出矩阵为:



家用投影矩阵正确使用姿势

但是正在的引擎当中很少通过设置t 、b、 l、 r、 n、 f,六个值去使用透视家用投影矩阵的,而是使用叫做FOV的东西和n 、f、 与屏幕的宽高比简写Asp,来控制使用家用投影矩阵
由于t与b表示屏幕的高度范围,l与r表示屏幕的宽度范围。FOV表示摄像机当前范围内的视野,它一般通过结合近平面距离来计算到t和b,那么通过屏幕的宽高比再计算另外的两边l和r或者先计算l和r然后通过屏幕的宽高比再计算另外两边t和b,前者叫做FOVY 后者叫做FOVH,绝大多数情况下都是使用FOVY。由于FOVH与FOVY原理一样,只不过是计算竖直方向与水平角度张开角度问题,那么就只介绍FOVH就可以。
FOVH


家用投影面的侧面,上面为top,底部为bottom。


根据三角函数知识,


在保持n 不变的情况下,它影响着top的值,也就是当前的平截头向上和向下张开的口径有多大。同时由于对称性质,bottom = -top.
所以:



屏幕宽高比



剩下还有l与r的值需要求出,其实也很简单,知道了屏幕的宽高比之后,就很容易算出表示宽度的两边 l 与r 了。


r = top∗aspectratio;
l =−top∗aspectratio
把t、b、r、l带入家用投影矩阵当中,最终的矩阵可以写为:



Z轴精度问题:

由于透视家用投影映射的关系, Pz =-A + B/-Ze,家用投影后的点与家用投影点倒数成正比,函数图如下:


随着z值增加在近平面范围内变化明显,精度比较高,在远平面变化不明显,精度比较低,因此远近平面差距越小越好。
Z fighting

在游戏中一台场景会很大,尤其是开放世界游戏中,远近平面距离距离会很远,甚至几万,在视线远方会出现剧烈的抖动,也就是我们熟知的Z fighting。 也正在由于这种情况的存在,在实际过程中经常解决方法是使用: Z reversed,也就是交换远近平面,NDC坐标中,近处是1,远处是0.
UE4 家用投影矩阵:

由于UE4的z坐标范围跟dx一样,从0到1。同时由于Z轴方向也不再是指向屏幕里面,Z值也不再是负值,因此矩阵左后一行那个-1需要换成1, 这样上面的A与B的计算如下:


同时由于与opengl矩阵乘转置关系,颠倒上面矩阵的行和列,因此最终矩阵为



UE4 透视家用投影矩阵

UE4中透视家用投影矩阵在 PerspectiveMatrix.h中.
UE4中有几种家用投影矩阵,除了反转z轴减少zfightting的,其他一些在屏幕的宽高比,FOV等做文章,其中的使用很有讲究,其中的细节目前刚接触还么有深入研究,先记录一下留待以后再详细说明,目前发现UE4 中按照是否反转z轴有两大类透视家用投影矩阵,其中每一台类中又根据参数类型不同分为三种。 其中两大类的三小种除了反转z轴其中它参数都一样,反转z轴,就是颠倒一下进平面与原平面。
对于矩阵第三行第三列值,和第四行第三列,中,当进平面与远平面的距离相等分母为0的情况,不反转Z轴时候,设置值为1.0 减去一台很小值Z_PRECISIO(默认为0),同时保证要和common.usf中的值匹配。反转z轴时候直接设置值为0.
不反转z轴



反转Z轴



透视矩阵1



MinZ 表示进平面,MaxZ 表示远平面,FPlan(16位对齐,占16字节)继承与FVector(4位对齐,共有12字节),同时多了一位w,用来保存x*x + y *y + z*z 的大小。使用了水平FOV和竖直FOV。
实际使用中发现 UE4中使用该透视矩阵的时候必须设置进平面等于远平面距离,否则渲染出是黑屏,这点我是很想不到,应该是我有什么地方还没有了解到。他除了设置水平fov,竖直fov(几乎两者是相等的)它还设置MultFOV和竖直MultFOV两个参数用来更加精细控制屏幕宽高比.


透视矩阵2



这里是使用当前的viewport的宽度和高度来决定屏幕的宽高比把上面推导的家用投影矩阵中asp 改为高度/宽度,其他没什么可解释的。
透视矩阵3



也是根据高度/宽度来决定屏幕的宽高比,是第一种透视矩阵的特殊情况下(即HalfFOVX =HalfFOVY,  Minz = Maxz情况下)的矩阵。

Ref
http://www.songho.ca/opengl/gl_projectionmatrix.html#ortho
https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/projection-matrices-what-you-need-to-know-first
https://github.com/EpicGames/UnrealEngine
当贝投影