GAMES101投影矩阵推导详解和分析
GAMES101投影矩阵推导详解和分析
- 前言
- GAMES101投影矩阵相关坐标系的约定
- 正交投影矩阵的推导
- 和OpenGL正交投影矩阵的比较
- 正交投影clip space中顶点的w值
- 透视投影矩阵的推导
- 推导透视投影frustum挤压到正交投影视景体的矩阵
- 最终的GAMES101透视投影矩阵
- 和OpenGL透视投影矩阵的比较
- 透视投影clip space中顶点的w值
- GAMES101思考题:视景体挤压后z值为(n+f)/2的点会挤向n还是f
前言
之前推导过OpenGL的投影矩阵,学了GAMES101之后,发现老师的推导方式很有意思,且GAMES101的坐标系约定和OpenGL不一样。最近在填新坑URasterizer的过程中,发现了一些问题,比如透视投影在clip space做裁剪时为啥w必须取反,以及之前GAMES101作业中做深度测试时为啥z值要取反的问题,因此重新推导GAMES101的投影矩阵并分析一下。由于GAMES101的推导思路很有趣,因此整个过程会轻松愉快很多。
GAMES101投影矩阵相关坐标系的约定
GAMES101使用右手坐标系,包括View space和NDC space(Clip space)。
- 对于View space,camera的位置为原点,看向负Z轴,这和OpenGL一致。GAMES101推导投影矩阵时,近裁面坐标n和远裁面坐标f都是使用的是坐标值,因此有 f < n < 0。
- 对于NDC space, GAMES101的NDC中,x,y,z的坐标范围都是[-1,1]。虽然OpenGL也是[-1,1],但是需要注意的是,GAMES101中,由于View space和NDC space都是右手系,所以变换后z轴方向并没有变化,因此n被映射到了1,而f被映射到了-1,仍然符合 f < n,且。GAMES101讲义上也说了,near and far not intuitive (n>f) ,而OpenGL使用左手系的NDC更加直觉一些。
- 下图是右手系的view space(以正交投影视景体为例),由于camera从原点看向负Z轴,因此n更靠近正Z,f更靠近负Z。
- 当变换到NDC后,由于还是右手系,因此 n 映射到了 +1,f 映射到了 -1
而对于OpenGL,NDC是左手坐标系,因此n被映射到-1,f被映射到1。(图就免了,上图反转Z轴)
正交投影矩阵的推导
由于正交投影只是把一个长方体变换到一个中心位于原点,坐标范围[-1,1]的立方体,因此通过移动加缩放即可完成变换。借用讲义中的图:
首先把长方体的原点移动到坐标系原点,由于view space中长方体的中心点为Po=[(r+l)/2,(t+b)/2,(n+f)/2]Po = [(r+l)/2, (t+b)/2, (n+f)/2]Po=[(r+l)/2,(t+b)/2,(n+f)/2],因此移动到原点只要移动−Po-Po−Po即可,写成平移矩阵就是:
缩放也很简单,视景体在x,y,z三个轴上的长度分别是(r−l)(r-l)(r−l), (t−b)(t-b)(t−b)和(n−f)(n-f)(n−f),注意这儿都是大数减小数,上面说过n>f,虽然它们都是小于0的负数,但(n−f)(n-f)(n−f)仍然是正数。而NDC的标准立方体的长宽高都是2。因此x,y,z轴的缩放系数分别是2/(r−l)2/(r-l)2/(r−l),2/(t−b)2/(t-b)2/(t−b)和2/(n−f)2/(n-f)2/(n−f),写成缩放矩阵就是:
将上面两个矩阵相乘,注意GAMES101和OpenGL一样,都是矩阵在左,向量在右进行向量变换的,因此矩阵连乘时,先起作用的矩阵在右边(更靠近向量),所以最终的正交投影矩阵就是:
手动乘了一下,结果是:
Mortho=[2r−l00−r+lr−l02t−b0−t+bt−b002n−f−f+nn−f0001]M_{ortho} = \left[\begin{matrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{n-f} & -\frac{f+n}{n-f} \\ 0 & 0 & 0 & 1 \end{matrix}\right] Mortho=⎣⎢⎢⎡r−l20000t−b20000n−f20−r−lr+l−t−bt+b−n−ff+n1⎦⎥⎥⎤
和OpenGL正交投影矩阵的比较
如上所述,GAMES101的投影坐标系约定和OpenGL基本一致,区别就在于NDC手向性不同。在参数方面,经典的OpenGL函数glOrtho(left,right,top,bottom.near,far)glOrtho(left, right, top, bottom. near, far)glOrtho(left,right,top,bottom.near,far),前四个参数是左右上下剪裁面的坐标值,这和GAMES101一致。但near, far并不是坐标值(负数),而是距离near/far plane的距离值:
Specify the distances to the nearer and farther depth clipping planes. These distances are negative if the plane is to be behind the viewer.
既然是距离值,正常情况就是正数了。但是glOrtho中也可使用负数near,far,但其含义并不是坐标值,而是表示平面在视点后面(这有什么意义?)。
综合以上两点差异,对于NDC的差异,就是z轴方向反了,那么只要将GAMES101的变换矩阵再乘一个Z轴缩放-1的矩阵就可以右手系变左手系,而n,f参数意义的差异,只要n和f各自取负就行。那么:
Mglortho=[1000010000−100001]∗[2r−l00−r+lr−l02t−b0−t+bt−b002f−nf+nf−n0001]=[2r−l00−r+lr−l02t−b0−t+bt−b00−2f−n−f+nf−n0001]M_{glortho} = \left [\begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & 1 \end{matrix}\right] * \left[\begin{matrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{f-n} & \frac{f+n}{f-n} \\ 0 & 0 & 0 & 1 \end{matrix}\right] = \left[\begin{matrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{-2}{f-n} & -\frac{f+n}{f-n} \\ 0 & 0 & 0 & 1 \end{matrix}\right] Mglortho=⎣⎢⎢⎡1000010000−100001⎦⎥⎥⎤∗⎣⎢⎢⎡r−l20000t−b20000f−n20−r−lr+l−t−bt+bf−nf+n1⎦⎥⎥⎤=⎣⎢⎢⎡r−l20000t−b20000f−n−20−r−lr+l−t−bt+b−f−nf+n1⎦⎥⎥⎤
乘式右边的矩阵是GAMES101正交投影矩阵n和f分别取负的结果,最终得到了OpenGL的正交投影矩阵。
正交投影clip space中顶点的w值
我们知道投影矩阵的作用是将顶点从view space变换到clip space(而不是NDC),NDC是由clip space经过透视除法(x/w,y/w,z/w)(x/w,y/w,z/w)(x/w,y/w,z/w)得到的。对于正交投影,其实不存在透视除法,但是为了流水线的统一,还是需要经过一个除以w的过程。而我们推导的正交投影矩阵将顶点从view space变换到clip space后,其坐标的w值是1(因为矩阵最后一行是(0,0,0,1)(0,0,0,1)(0,0,0,1)),因此可兼容于流水线。
为啥要强调一下w为1呢?我们思考一下这个问题,我们经常说NDC中的坐标范围为[-1,1],而clip space是[-w,w]。所以如果我们在clip space中判断一个点是否在视景体内,只要判断 -w <= p <= w 是否成立。但是以上式子成立的条件是 w > 0,因为如果 w < 0, 那么clip space的坐标范围就是[w, -w]了,比较也要反过来。而正交投影时,w为1,是个正数,所以天然满足 -w <= p <= w,但是下面推导透视投影矩阵后会发现,在GAMES101的约定下,透视投影后,clip space的w值是一个负数。
透视投影矩阵的推导
GAMES101的推导方法很有趣,首先将透视投影的Frustum挤压成一个正交投影那样的长方体视景体,然后对长方体进行正交投影,得到NDC立方体:
所以主要的工作就是推导这个挤压矩阵。
推导透视投影frustum挤压到正交投影视景体的矩阵
看上面的图,需要分别沿着x轴和y轴进行挤压。先看y轴的情况,x轴可以类比。
这是讲义上的示例图,但要注意并不是说把(x,y,z)(x,y,z)(x,y,z)点挤压到(x′,y′,z′)(x',y',z')(x′,y′,z′),如果点在远裁面上,那么挤压后应该是在绿色点的位置,如果点在近裁面上,那么不用挤压了,就是(x′,y′,z′)(x',y',z')(x′,y′,z′)。那么中间的点呢?显然挤压后是在绿色的虚线上,但是其z坐标如何变化,是向nnn移动,还是向fff移动?这是GAMES101的思考题,下面再说。
虽然示例图标识的不是挤压后点的位置,但是挤压后,y坐标确实变成了y’,利用相似三角形是可以得到y’和y的关系:
y′=nzyy' = \frac{n}{z}y y′=zny
同样类比可得,挤压后x’和x的关系:
x′=nzxx' = \frac{n}{z}x x′=znx
这里出现了除z,而我们使用的是齐次坐标,因此可以将变换后的点乘以z,得到表示同一个点的齐次坐标:
这样,我们需要推导的Persp to ortho矩阵的作用就是将view space 的顶点变换到这样的齐次坐标:
从x,y坐标的对应关系:x=>nx,y=>nyx => nx, y => nyx=>nx,y=>ny,可以填入矩阵的前两行:
为了解出最后一行,需要用到z坐标的映射关系,当z为n时,挤压后的点的z也是n,因此齐次坐标的z就是n2n^2n2:
设第三行为(0,0,A,B)(0, 0, A, B)(0,0,A,B),有:
同样,位于远裁面的点,z值为f,挤压后仍然为f,代入方程,可得:
利用上面两个关于A和B的方程式,可计算出A和B:
这就得到了从透视投影变换到正交投影的矩阵:
Mpersp−>ortho=[n0000n0000n+f−nf0010]M_{persp->ortho} = \left[\begin{matrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{matrix}\right] Mpersp−>ortho=⎣⎢⎢⎡n0000n0000n+f100−nf0⎦⎥⎥⎤
最终的GAMES101透视投影矩阵
使用上面推导的正交投影矩阵乘上这个矩阵就得到最终的透视投影矩阵了:
Mpersp=MorthoMpersp−>ortho=[2r−l00−r+lr−l02t−b0−t+bt−b002n−f−f+nn−f0001][n0000n0000n+f−nf0010]=M_{persp} = M_{ortho}M_{persp->ortho} = \left[\begin{matrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{n-f} & -\frac{f+n}{n-f} \\ 0 & 0 & 0 & 1 \end{matrix}\right] \left[\begin{matrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{matrix}\right] = Mpersp=MorthoMpersp−>ortho=⎣⎢⎢⎡r−l20000t−b20000n−f20−r−lr+l−t−bt+b−n−ff+n1⎦⎥⎥⎤⎣⎢⎢⎡n0000n0000n+f100−nf0⎦⎥⎥⎤=
[2nr−l0−r+lr−l002nt−b−t+bt−b000n+fn−f−2nfn−f0010]\left[\begin{matrix} \frac{2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{n+f}{n-f} & -\frac{2nf}{n-f} \\ 0 & 0 & 1 & 0 \end{matrix}\right] ⎣⎢⎢⎡r−l2n0000t−b2n00−r−lr+l−t−bt+bn−fn+f100−n−f2nf0⎦⎥⎥⎤
和OpenGL透视投影矩阵的比较
如前所述,区别首先在于NDC的手向性,另外就是OpenGL的经典函数glFrustum(left,right,bottom,top,nearVal,farVal)glFrustum(left, right, bottom, top, nearVal, farVal)glFrustum(left,right,bottom,top,nearVal,farVal), 前4个参数都是坐标值,而后两个是距离值(正值)。那么我们像正交投影一样操作,将n和f取负,并且左乘一个翻转z轴的缩放矩阵:
MglPersp=[1000010000−100001]∗[−2nr−l0−r+lr−l00−2nt−b−t+bt−b000−n+ff−n−2nff−n0010]=M_{glPersp} = \left [\begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & 1 \end{matrix}\right] * \left[\begin{matrix} \frac{-2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{-2n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & -\frac{n+f}{f-n} & -\frac{2nf}{f-n} \\ 0 & 0 & 1 & 0 \end{matrix}\right]= MglPersp=⎣⎢⎢⎡1000010000−100001⎦⎥⎥⎤∗⎣⎢⎢⎡r−l−2n0000t−b−2n00−r−lr+l−t−bt+b−f−nn+f100−f−n2nf0⎦⎥⎥⎤=
[−2nr−l0−r+lr−l00−2nt−b−t+bt−b000f+nf−n2nff−n0010]\left[\begin{matrix} \frac{-2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{-2n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{f+n}{f-n} & \frac{2nf}{f-n} \\ 0 & 0 & 1 & 0 \end{matrix}\right] ⎣⎢⎢⎡r−l−2n0000t−b−2n00−r−lr+l−t−bt+bf−nf+n100f−n2nf0⎦⎥⎥⎤
但是OpenGL的透视投影矩阵并不是这样的啊?别的不说,至少最后一行明明是(0,0,−1,0)(0,0,-1,0)(0,0,−1,0)。这是因为OpenGL设计出来的矩阵,clip space的w坐标值为−Zview-Z_{view}−Zview。而GAMES101的w值是ZviewZ_{view}Zview。这是上面没有说的第3个差异。所以我们需要w值取反,对于齐次坐标来说,所有元素乘以同一个系数和原来的坐标是一样的,因此为了让w取反,只要所有坐标同时乘-1就行。那么我们在前面再乘一个x,y,z,w都缩放-1的矩阵:
MglPersp=[−10000−10000−10000−1]∗[−2nr−l0−r+lr−l00−2nt−b−t+bt−b000f+nf−n2nff−n0010]=M_{glPersp} = \left [\begin{matrix} -1 & 0 & 0 & 0 \\ 0 & -1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & -1 \end{matrix}\right] * \left[\begin{matrix} \frac{-2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{-2n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{f+n}{f-n} & \frac{2nf}{f-n} \\ 0 & 0 & 1 & 0 \end{matrix}\right]= MglPersp=⎣⎢⎢⎡−10000−10000−10000−1⎦⎥⎥⎤∗⎣⎢⎢⎡r−l−2n0000t−b−2n00−r−lr+l−t−bt+bf−nf+n100f−n2nf0⎦⎥⎥⎤=
[2nr−l0r+lr−l002nt−bt+bt−b000−f+nf−n−2nff−n00−10]\left[\begin{matrix} \frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\ 0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2nf}{f-n} \\ 0 & 0 & -1 & 0 \end{matrix}\right] ⎣⎢⎢⎡r−l2n0000t−b2n00r−lr+lt−bt+b−f−nf+n−100−f−n2nf0⎦⎥⎥⎤
这就对了。所以约定真的是对投影矩阵的影响很大,随便搞错一个地方,得到的矩阵就不对,算出来的坐标就需要在哪儿莫名其妙的取个负。
透视投影clip space中顶点的w值
如前所述,在GAMES101的约定下,最终clip space的w值为ZviewZ_{view}Zview,由于Camera从原点看向-Z轴,所以view space中,在Frustum内的顶点的ZviewZ_{view}Zview应该是一个负数,既w是负数。因此在clip space中使用w值判断点是否在Frustum内时,需要判断的区间为[w,-w]。是不是很不直觉?我是在实现URasterizer的clip功能时发现的这个问题,一开始我就是直接判断 -w <= P <= w,结果是所有的点都被裁剪掉了,什么都没剩下。上面也说了,OpenGL设计的w值为−Zview-Z_{view}−Zview,由于OpenGL的约定下,符合条件的点的ZviewZ_{view}Zview也是负数,因此w就是正数,这样就科学多了。
GAMES101思考题:视景体挤压后z值为(n+f)/2的点会挤向n还是f
在GAMES101课上,闫老师提出一个思考题,在挤压之后,近裁面和远裁面上的点的z坐标保持不变,那么中间的点的z坐标如何变化呢,是向nnn移动,还是向fff移动,以z值为(n+f)/2(n+f)/2(n+f)/2的点为例。
这个问题很难用脑子想,反正我是想不通,所以直接算一下。设原来的点为(x,y,(n+f)/2)(x,y,(n+f)/2)(x,y,(n+f)/2),那么挤压后的点为:
P=[n0000n0000n+f−nf0010]∗[xyn+f21]=[nxnyn2+f22n+f2]=[−−−−n2+f2n+f1]P = \left[\begin{matrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{matrix}\right] * \left[\begin{matrix} x\\ y \\ \frac{n+f}{2} \\ 1 \end{matrix}\right] = \left[\begin{matrix} nx\\ ny \\ \frac{n^2+f^2}{2} \\ \frac{n+f}{2} \end{matrix}\right] = \left[\begin{matrix} --\\ -- \\ \frac{n^2+f^2}{n+f} \\ 1 \end{matrix}\right] P=⎣⎢⎢⎡n0000n0000n+f100−nf0⎦⎥⎥⎤∗⎣⎢⎢⎡xy2n+f1⎦⎥⎥⎤=⎣⎢⎢⎡nxny2n2+f22n+f⎦⎥⎥⎤=⎣⎢⎢⎡−−−−n+fn2+f21⎦⎥⎥⎤
所以z值从(n+f)/2(n+f)/2(n+f)/2挤压到了(n2+f2)/(n+f)(n^2+f^2)/(n+f)(n2+f2)/(n+f), 用前值减去后值,化解之后得到:
−(n−f)2/2(n+f)-(n-f)^2/2(n+f)−(n−f)2/2(n+f)
由于n和f是负数,所以整个结果是正数,这说明原来的z坐标值大于挤压后的z坐标,因此中间点向近裁面移动了。
GAMES101投影矩阵推导详解和分析相关推荐
- 深入理解OpenGL之投影矩阵推导
深入理解OpenGL之投影矩阵推导 OpenGL流水线中的投影矩阵以及坐标变换 OpenGL中,投影矩阵在Vertex shader中使用,用于变换顶点.一般和Model, View矩阵结合成MVP矩 ...
- 【相机标定与三维重建原理及实现】学习笔记1——相机模型数学推导详解
目录 前言 一.小孔成像模型 二.坐标系的变换 1.世界坐标系到相机坐标系的变换(刚体变换)[xw^→xc^\boldsymbol {\hat{x_{w}}}\rightarrow \boldsymb ...
- c语言编程 输入螺旋数组,C语言 经典题目螺旋矩阵 实例详解
C语言 经典题目螺旋矩阵 实例详解 C语言 经典题目螺旋矩阵 //N阶螺旋矩阵 #include #include int main() { int N,i,j,n,num=1; int a[10][ ...
- LMDI 理论推导详解【从理论到Python-MATLAB实现(理论)】
LMDI 理论推导详解[从理论到Python-MATLAB实现(理论)] 影响因素分解分析方法能够有效地反映任意时段上各个影响因素对目标变量变化的影响程度 大多数模型虽然可以定量反映各影响因素对目标变 ...
- x264 代码重点详解 详细分析
eg mplayer x264 代码重点详解 详细分析 分类: ffmpeg 2012-02-06 09:19 4229人阅读 评论(1) 收藏 举报 h.264codecflv优化initializ ...
- Web 三维矩阵matrix3d详解
文章目录 前言 一.3D平面的矩阵由来? 二.平移 公式推导 推导矩阵 三.缩放 推导矩阵 四.旋转 单位圆 公式推导 推导矩阵 绕Z轴旋转 绕X轴旋转 绕Y轴旋转 结论 五.CSS3中matrix3 ...
- 安卓通知栏管理详解及分析 NotificationListenerService
NotificationListenerService 安卓通知栏管理详解及分析 一. 方法概述 在api 18前可以通过辅助功能'AccessibilityEvent.TYPE_NOTIFICATI ...
- Diffusion model(二): 训练推导详解
接上文 Diffusion model(一): 公式推导详解 Diffusion model(二): 训练推导详解 Diffusion model(三): 公式结论 Diffusion的训练推导 1. ...
- mysql之explain详解(分析索引最佳使用)
mysql之explain详解(分析索引最佳使用) mysql explain用于分析sql 语句的执行及数据库索引的使用.本文将致力于帮助大家充分理解explain所返回的各项参数,从而使大家快速掌 ...
- 相机小孔成像模型(逐步推导详解)
先搞清楚为什么可以简化成小孔成像模型 相机小孔成像模型推导 原则:先简单后复杂,先理想后实际 [理想情况,相机无畸变] 一.明确四个坐标系:这个是推导的前提! 说明: 1.图像坐标系的坐标原点是成像平 ...
最新文章
- 一文读懂支持向量机SVM(附实现代码、公式)
- sql CHECK ,UNIQUE 约束(mysql)
- Redis 内存用完会怎样?
- db2 jdbc驱动参数_JDBC详细整理(一)
- Java ForkJoin 框架初探
- C++的性能C#的产能?! - .Net Native 系列向导
- Java项目:医院药品管理系统设计和实现(java+Springboot+ssm+mysql+jsp+maven)
- Laravel开发的一元交友盲盒源码存取小纸条盲盒交友匹配交友趣味交友同城交友流量
- html的abbr标签,html标签里有个abbr 请问这个标签是肿么使用的
- 【uni-app】uni-app 封装 uni.request 携带请求头
- 【无标题】电自2104吕薇202130310206
- 如何在iPhone和iPad上使用Group FaceTime
- 自习室预约小程序有哪些功能?
- 来聊聊软件测试-静态测试
- Android View scrollTo()和scroll()By()学习备忘
- 星环科技数据安全管理平台 Defensor重磅发布
- 计算机毕设Node.js+Vue兴澜幼儿园管理系统(程序+LW+部署)
- Wildfly10 部署MDB
- npm install 无响应解决方案
- Android开发学习总结day1-1