关于透视投影变换及相关应用的详细推导

lutianfei 发表于 2022/06/10 15:53:52 2022/06/10
【摘要】 一、透视投影过程推导由于GAMES101与LearnOpenGL课程中,对透视投影变换、视口变换、法线变换等介绍较少,因此这里通过总结大量资料,手推了相关公式,可以说这是网上能看到的最全面、严谨的推导过程之一吧(谦虚)。关于一个模型投影到屏幕上所要经历过程如下:关于模型变换、和视变换相对比较简单,GAMES101课程第四课也有详细的推导,可参考如下笔记结合视频课程进行学习:https://...

由于GAMES101与LearnOpenGL课程中,对透视投影变换、视口变换、法线变换等介绍相对简单,因此本文通过总结大量资料,手推了相关公式,可以说这是网上能看到的最详实、严谨的推导过程之一(谦虚)。

关于一个模型投影到屏幕上所要经历过程如下:
image.png

一、透视投影过程推导

1.1 正交投影矩阵推导

正交投影过程主要为先将中心点平移到原点,再缩放至 c u b e [ 1 , 1 ] 3 cube[-1,1]^3

正交投影本身使用相对较少,这里推导的目的为后续透视投影变换做准备。

1.1.1 纯右手坐标系的推导过程

image.png

说明:

  1. 这里统一都是用右手坐标系,即z轴始终朝外。
  2. 这里n、f都在z轴负半轴上的坐标值,都是负数
  3. 为了行文方便下面统一称这种为标准右手坐标系,GAMES101即是在这种情况下推导的。

其中,平移变换矩阵为:

T = [ 1 0 0 r + l 2 0 1 0 t + b 2 0 0 1 f + n 2 0 0 0 1 ] (平移变换矩阵) T= \begin{bmatrix} 1 & 0 & 0 & -\frac{r+l}{2} \\ 0 & 1 & 0 & -\frac{t+b}{2} \\ 0 & 0 & 1 & -\frac{f+n}{2} \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{平移变换矩阵}

缩放变换矩阵(注意这里边长为2)为:

S = [ 2 r l 0 0 0 0 2 t b 0 0 0 0 2 n f 0 0 0 0 1 ] (缩放变换矩阵) S= \begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t - b} & 0 & 0 \\ 0 & 0 & \frac{2}{n - f} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{缩放变换矩阵}

因此,正交投影矩阵 M o r t h o = S T M_{ortho} = S*T :

M o r t h o = [ 2 r l 0 0 r + l r l 0 2 t b 0 t + b t b 0 0 2 n f f + n n f 0 0 0 1 ] (标准右手正交投影矩阵) M_{ortho} = \begin{bmatrix} \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{bmatrix} \tag{标准右手正交投影矩阵}

如果视椎体是上下、左右对称(r=-l, t=-b, r+l=0, t+b=0)的,则 M o r t h o M_{ortho} 矩阵可以简化为:

M o r t h o = [ 1 r 0 0 0 0 1 t 0 0 0 0 2 n f f + n n f 0 0 0 1 ] (简化版右手正交投影矩阵) M_{ortho} = \begin{bmatrix} \frac{1}{r} & 0 & 0 & 0 \\ 0 & \frac{1}{t} & 0 & 0 \\ 0 & 0 & \frac{2}{n - f} & -\frac{f+n}{n - f} \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{简化版右手正交投影矩阵}

1.1.2 OpenGL中的正交投影变换推导

image.png

在OpenGL中需要特别注意:

  1. 观察空间坐标系为右手系,而NDC中+z轴向内,为左手系。 因此在做缩放时不再是将n映射到1,f映射到-1而是[f,n]映射到NDC中的[1,-1]
  2. 在OpenGL中n、f分别定义为近平面远平面的距离值,都是正数,因此这里代入矩阵前需要加负号以表示正确的坐标值。
  3. 为了行文方便下面统一称这种情况为OpenGL坐标系

此时平移变换矩阵变为:

T = [ 1 0 0 r + l 2 0 1 0 t + b 2 0 0 1 f + n 2 0 0 0 1 ] (OpenGL平移变换矩阵) T= \begin{bmatrix} 1 & 0 & 0 & -\frac{r+l}{2} \\ 0 & 1 & 0 & -\frac{t+b}{2} \\ 0 & 0 & 1 & \frac{f+n}{2} \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{OpenGL平移变换矩阵}

缩放变换矩阵为:

注意这里f、n有一个翻转,即由 2 ( n ) ( f ) \frac{2}{(-n)-(-f)} 翻转为 2 ( n ) ( f ) -\frac{2}{(-n)-(-f)} ,最终结果如下所示:

S = [ 2 r l 0 0 0 0 2 t b 0 0 0 0 2 n f 0 0 0 0 1 ] (OpenGL缩放变换矩阵) S= \begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t - b} & 0 & 0 \\ 0 & 0 & \frac{2}{n - f} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{OpenGL缩放变换矩阵}

这里亦可以用更数学的形式理解为z轴做了一次翻转,相当于变换过程中左乘了一个翻转矩阵 [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

因此,OpenGL正交投影矩阵 M o r t h o = S T M_{ortho} = S*T :

M o r t h o = [ 2 r l 0 0 r + l r l 0 2 t b 0 t + b t b 0 0 2 n f f + n n f 0 0 0 1 ] (OpenGL正交投影矩阵) M_{ortho} = \begin{bmatrix} \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{bmatrix} \tag{OpenGL正交投影矩阵}

同样可推出简化版OpenGL正交投影坐标系

M o r t h o = [ 1 r 0 0 0 0 1 t 0 0 0 0 2 n f f + n n f 0 0 0 1 ] (简化OpenGL正交投影矩阵) M_{ortho} = \begin{bmatrix} \frac{1}{r} & 0 & 0 & 0 \\ 0 & \frac{1}{t} & 0 & 0 \\ 0 & 0 & \frac{2}{n - f} & \frac{f+n}{n - f} \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{简化OpenGL正交投影矩阵}

1.2 透视投影矩阵推导

对于透视投影,分成两步操作:

  • 首先,“压扁”视锥体成一个长方体(n->n,f->f)( M p e r s p > o r t h o M_{persp−>ortho} );

    注意:这里是在右手坐标系下进行推导,不再考虑坐标变换问题,但根据n、f的正负情况仍然有两种推导情况。

    image.png

  • 然后,利用上面的正交投影矩阵得到最终结果。

这里主要以n、f为正数为主,用-n、-f代入坐标的情况进行推导,过程如下:

在把视锥体压缩成长方体的过程中,我们规定三个原则:

  1. 近平面的所有点坐标不变

  2. 远平面的所有点坐标z值不变 都是-f

  3. 远平面的中心点坐标值不变 为(0,0,-f)

下图表示观察空间中的点 ( x e , y e , z e ) (x_e, y_e, z_e) 是投影到近平面 ( x p , y p , z p ) (x_p, y_p, z_p) 上的情况,

image.png

从视锥体的顶视图(Top View of Frustum)看,观察空间的 x e x_e 映射到 x p x_p ,利用相似三角形的比值计算可得:

x p x e = n z e , x p = n x e z e \frac{x_p}{x_e} = \frac{-n}{z_e}, x_p = \frac{n \cdot x_e}{-z_e}

同理通过侧视图(Side View of Frustum)可以得到:

y p y e = n z e , y p = n y e z e \frac{y_p}{y_e} = \frac{-n}{z_e} , y_p = \frac{n \cdot y_e}{-z_e}

故对于视椎体中任一点 ( x e , y e , z e , 1 ) (x_e, y_e, z_e, 1) ,它在视椎体压缩成长方体之后的坐标应该是 ( n x e z e , n y e z e , u n k n o w n , 1 ) (\frac{n*x_e}{-z_e}, \frac{n*y_e}{-z_e}, unknown,1) ,即我们需要一个矩阵 M p e r s p > o r t h o M_{persp->ortho} 使得下面公式成立:

[ n x e z e n y e z e u n k n o w n 1 ] = M p e r s p > o r t h o [ x e y e z e 1 ] \begin{bmatrix} \frac{n*x_e}{-z_e} \\ \frac{n*y_e}{-z_e}\\ unknown \\ 1 \end{bmatrix} = M_{persp->ortho} * \begin{bmatrix} x_{e} \\ y_{e} \\ z_{e} \\ 1 \end{bmatrix}

假设 M p e r s p > o r t h o M_{persp->ortho} 矩阵的第一行为A,B,C,D。可以得到等式: A x e + B y e + C z e + D = n x e z e Ax_e+By_e+Cz_e+D = \frac{n*x_e}{-z_e} ,这个等式并不容易求解,如果让A = n z e \frac{n}{-z_e} ,其他等于0,的确可以得到结果,但是矩阵的值应该是常数,而 n z e \frac{n}{-z_e} 是个变量。

同理对于矩阵的第二行求 y e y_e 时也会遇到如上问题。

因此,这里需要利用其次坐标的性质来进行求解。已知 ( x , y , z , 1 ) ( k x , k y , k z , k ! = 0 ) (x,y,z,1)与(kx,ky,kz,k!=0) 这两个点是完全等价的关系,所以这里将上面等式左边统一乘以 z e -z_e 可改写为如下形式:

[ n x e n y e u n k n o w n z e ] = M p e r s p > o r t h o [ x e y e z e 1 ] \begin{bmatrix} {nx_e} \\ {ny_e} \\ unknown \\ -z_e \end{bmatrix} = M_{persp->ortho} * \begin{bmatrix} x_{e} \\ y_{e} \\ z_{e} \\ 1 \end{bmatrix}

此时可以很容易推出:

M p e r s p > o r t h o M_{persp->ortho} 矩阵的第一行:Ax+By+Cz+D = nx,求出 A=n,B=C=D=0

第二行:Ex+Fy+Gz+H = ny,求出F=n,E=G=H=0

第四行:Mx+Ny+Oz+P = z,求出O=-1,M=N=P=0

至此,仅第三行元素还是未知状态, 此时再回过头来思考上面所规定的三个原则:

  • 第一个原则:近平面的所有点坐标不变

    也就是点 ( x e , y e , n , 1 ) (x_e,y_e,-n,1) 通过矩阵 M p e r s p > o r t h o M_{persp->ortho} 变换后,应该还是等于 ( x e , y e , n , 1 ) (x_e,y_e,-n,1) 。代入等式可得:

[ x e y e n 1 ] = [ n 0 0 0 0 n 0 0 ? ? ? ? 0 0 1 0 ] [ x e y e n 1 ] \begin{bmatrix} {x_e} \\ {y_e} \\ -n \\ 1 \end{bmatrix} = \begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ? & ? \\ 0 & 0 & -1 & 0 \end{bmatrix} * \begin{bmatrix} x_{e} \\ y_{e} \\ -n \\ 1 \end{bmatrix}

对于第一、二、四行,我们写出推导等式:

nx+0y+0n+0*1=x

0x+ny+0n+0*1=y

0x+0y+n+0*1=1

这里,n应该是任意常数,但是现在只有在n等于1时,一、二、四行的运算才成立,这并不是一个合理的解。

因此这里再次利用齐次坐标系的性质将等式左边再乘以n,得到:

[ n x e n y e n 2 n ] = [ n 0 0 0 0 n 0 0 ? ? ? ? 0 0 1 0 ] [ x e y e n 1 ] \begin{bmatrix} n{x_e} \\ n{y_e} \\ -n^2 \\ n \end{bmatrix} = \begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ? & ? \\ 0 & 0 & -1 & 0 \end{bmatrix} * \begin{bmatrix} x_{e} \\ y_{e} \\ -n \\ 1 \end{bmatrix}

此时再对第一、二、四行,写出推导等式:

nx+0y+0n+0*1=nx,

0x+ny+0n+0*1=ny,

0x+0y+n+0*1=n

发现上面三个等式左右两边相等,且对n没有任何限制,此时设第三行的四个数分别为A、B、C、D可得:Ax+By-Cn+D = -n^2,

不难发现等式右边的x,y与结果没有任何关系,因此可求出:A=0, B=0, 且:

C n D = n 2 (1) C*n-D = n^2 \tag{1}

  • 第三个原则:远平面的中心点坐标值不变 为(0,0,-f)

同样为了保证之前求的矩阵一、二、四行成立,这里亦需要利用齐次坐标系的性质把(0,0,-f,1)写成(0,0,-f*f,f),得到如下等式:

[ 0 0 f 2 f ] = [ n 0 0 0 0 n 0 0 0 0 C D 0 0 1 0 ] [ 0 0 f 1 ] \begin{bmatrix} 0 \\ 0 \\ -f^2 \\ f \end{bmatrix} = \begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & C & D \\ 0 & 0 & -1 & 0 \end{bmatrix} * \begin{bmatrix} 0 \\ 0 \\ -f \\ 1 \end{bmatrix}

C f D = f 2 (2) C*f-D = f^2 \tag{2}

联合(1) (2)可以很容易求得

C = n + f

D = nf

至此,我们终于顺利推导出 M p e r s p > o r t h o M_{persp->ortho} 矩阵:

M p e r s p > o r t h o = [ n 0 0 0 0 n 0 0 0 0 n + f n f 0 0 1 0 ] (n,f为正值情况) M_{persp->ortho} = \begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & nf \\ 0 & 0 & -1 & 0 \end{bmatrix} \tag{n,f为正值情况}

注意:若上面一开始设定n、f为负值则: x p = n x e z e , y p = n y e z e x_p = \frac{n \cdot x_e}{z_e}, y_p = \frac{n \cdot y_e}{z_e} ,后面的推导与上述过程完全一致,最终结果为:

M p e r s p > o r t h o = [ n 0 0 0 0 n 0 0 0 0 n + f n f 0 0 1 0 ] (n,f为负值情况) M_{persp->ortho} = \begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{bmatrix} \tag{n,f为负值情况}

此时即可得到透视投影变换矩阵的最终形式 M p e r s p = M o r t h o M p e r s p > o r t h o M_{persp}=M_{ortho} * M_{persp->ortho}

M p e r s p = [ 2 r l 0 0 r + l r l 0 2 t b 0 t + b t b 0 0 2 n f f + n n f 0 0 0 1 ] [ n 0 0 0 0 n 0 0 0 0 n + f n f 0 0 1 0 ] = [ 2 n r l 0 r + l r l 0 0 2 n t b t + b t b 0 0 0 f + n f n 2 f n f n 0 0 1 0 ] (OpenGL透视投影矩阵) M_{persp}= \begin{bmatrix} \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{bmatrix} * \begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & nf \\ 0 & 0 & -1 & 0 \end{bmatrix} = \begin{bmatrix} \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{2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \tag{OpenGL透视投影矩阵}

M p e r s p = [ n r 0 0 0 0 n t 0 0 0 0 f + n f n 2 f n f n 0 0 1 0 ] (简化OpenGL透视投影矩阵) M_{persp}= \begin{bmatrix} \frac{n}{r} & 0 & 0 & 0 \\ 0 & \frac{n}{t} & 0 & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \tag{简化OpenGL透视投影矩阵}

这里通过查阅GLM库中glm::perspective发现,源码与我们手推的结果完全一致。

// glm/gtc/matrix_transform.inl:222
// 存储方式是列优先
Result[0][0] = (static_cast<T>(2) * nearVal) / (right - left);
Result[1][1] = (static_cast<T>(2) * nearVal) / (top - bottom);
Result[2][0] = (right + left) / (right - left);
Result[2][1] = (top + bottom) / (top - bottom);
Result[2][2] = -(farVal + nearVal) / (farVal - nearVal);
Result[2][3] = static_cast<T>(-1);
Result[3][2] = -(static_cast<T>(2) * farVal * nearVal) / (farVal - nearVal);

1.3 透视除法与NDC空间

符号术语说明:

  • x c , y c , z c , w c x_c, y_c, z_c, w_c 表示剪裁空间(clip space)坐标

  • x e , y e , z e , w e x_e, y_e, z_e, w_e 表示观察空间(eye\view space)坐标

  • x n , y n , z n x_n, y_n, z_n 表示NDC空间坐标

裁剪坐标系与观察坐标系的关系:

[ x c y c z c w c ] = M p e r s p [ x e y e z e w e ] \begin{bmatrix} x_{c} \\ y_{c} \\ z_{c} \\ w_c \end{bmatrix} = M_{persp} * \begin{bmatrix} x_{e} \\ y_{e} \\ z_{e} \\ w_{e} \end{bmatrix}

根据齐次坐标的定义我们知道 (x,y,z,w) 与 (x/w,y/w,z/w,1) 是等价的,即把裁剪空间下的(x,y,z,w)分别除以w,这一步我们称之为透视除法(Perspective Divide)

[ x n y n z n ] = [ x c / w c y c / w c z c / w c ] \begin{bmatrix} x_{n} \\ y_{n} \\ z_{n} \end{bmatrix} = \begin{bmatrix} x_{c} / w_{c} \\ y_{c} / w_{c} \\ z_{c} / w_{c} \end{bmatrix}

由前文推导可知,透视投影变换会把视椎体压缩成一个标准立方体,那么视椎体内的顶点 (x/w,y/w,z/w,1),其取值范围即为:

{ 1 < x / w < 1 = > w < x < w , 1 < y / w < 1 = > w < y < w , 1 < z / w < 1 = > w < z < w \begin{cases} -1 < x/w < 1 => -w < x < w, \\ -1 < y/w < 1 => -w < y < w, \\ -1 < z/w < 1 => -w < z < w \end{cases}

因此在裁剪空间中任意顶点(x,y,z,w)如果不满足 (-w<x<w && -w<y<w && -w<z<w) 的条件即说明不在视椎体内,需要被裁剪。

裁剪后剩下的顶点,我们做一次透视除法,即可把它们从裁剪空间变换到NDC空间。

1.4 Zfighting问题

由透视投影矩阵以及1.3节中的坐标变换关系可知:

z n = z c / w c w c = z e z c = f + n f n z e 2 f n f n z n = z c / w c = f + n f n z e 2 f n f n z e = f + n f n + 2 f n f n / z e z_n = z_c/w_c \\ w_c = -z_e \\ z_c = {-\frac{f+n}{f-n}z_e - \frac{2fn}{f-n}} \\ \\ z_n = z_c/w_c = \frac {-\frac{f+n}{f-n}z_e - \frac{2fn}{f-n}} {-z_e} = \frac{f+n}{f-n} + \frac{2fn}{f-n}/{z_e}

从上面 z n z_n 公式可画出下图坐标映射关系图,可以看到,在近平面附近值变化比较大,精确度较好;而在远平面附近,有一段距离内,近乎持平,精确度不好。

当增大远近裁剪平面的范围后,如下右图所示,我们可以看到在远平面附近, z e z_e 坐标值投影到 z n z_n 坐标后值几乎相同,精确度低的现象更为明显,这种深度的精确度引起的问题称之为zFighting

因此要尽量减小[-n,-f]的范围,以减轻zFighting问题。

image.png

1.5 使用FOV推导透视投影

另外一种经常使用 的方式是通过视角(Fov),宽高比(Aspect)来指定透视投影。

其中指定fovy指定视角,aspect指定宽高比,zNear和zFar指定剪裁平面。fovy的理解如下图所示:

image.png

这些参数指定的是一个对称的视见体,如下图所示:

image.png

由这些参数,代入上面推导出的OpenGL透视投影矩阵 (简化版)可以得到:

t = n t a n ( f o v / 2 ) r t = w h = a s p e c t = > r = a s p e c t t = a s p e c t n t a n ( f o v / 2 ) t = n * tan(fov/2) \\ \frac{r}{t} = \frac{w}{h} = aspect => r = aspect*t = aspect * n * tan(fov/2) \\

代入上述透视变换矩阵可得Fov透视投影矩阵为:

P = [ c o t ( θ 2 ) a s p e c t 0 0 0 0 c o t ( θ 2 ) 0 0 0 0 ( f + n ) f n 2 f n f n 0 0 1 0 ] (Fov透视投影矩阵) P= \begin{bmatrix} \frac{cot(\frac{\theta}{2})}{aspect} & 0 & 0 & 0 \\ 0 & cot(\frac{\theta}{2}) & 0 & 0 \\ 0& 0 & \frac{-(f+n)}{f-n} & \frac{-2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \tag{Fov透视投影矩阵}

二、视口变换过程推导

视口变换是将NDC坐标转换为显示屏幕坐标的过程,如下图所示:

image.png

{ 1 x 1 1 y 1 0 z 1 \begin{cases} -1 \leq x \leq 1 \\ -1 \leq y \leq 1 \\ 0 \leq z \leq 1 \end{cases}

说明:

  • x n y n , z n x_n,y_n, z_n 表示NDC空间坐标值
  • x s , y s , z s x_s, y_s, z_s 表示屏幕空间坐标值
  • n s , f s n_s, f_s 表示屏幕空间近平面、远平面的位置
  • S x , S y S_x, S_y 表示屏幕的起始像素点
  • W s , H s W_s, H_s 表示屏幕的像素宽、高

x n ( 1 ) 2 = x s S x W s = > x s = W s 2 x n + W s 2 + S x y n ( 1 ) 2 = y s S y H s = > y s = H s 2 y n + H s 2 + S y z n ( 1 ) 2 = z s n s f s n s = > z s = f s n s 2 + n s + f s 2 \frac{x_n - (-1)}{2} = \frac{x_s - S_x}{W_s} => x_s = \frac{W_s}{2}x_n + \frac{W_s}{2} + S_x \\ \frac{y_n - (-1)}{2} = \frac{y_s - S_y}{H_s} => y_s = \frac{H_s}{2}y_n + \frac{H_s}{2} + S_y \\ \frac{z_n - (-1)}{2} = \frac{z_s - n_s}{f_s - n_s} => z_s = \frac{f_s - n_s}{2} + \frac{n_s + f_s}{2}

则由上述式子得到视口变换矩阵为:

v i e w P o r t = [ W s 2 0 0 S x + W s 2 0 H s 2 0 S y + H s 2 0 0 f s n s 2 n s + f s 2 0 0 0 1 ] (视口变换矩阵) viewPort=\begin{bmatrix} \frac{W_{s}}{2} & 0 & 0 & S_{x}+\frac{W_{s}}{2} \\ 0 & \frac{H_{s}}{2} & 0 & S_{y}+\frac{H_{s}}{2} \\ 0 & 0 & \frac{f_{s}-n_{s}}{2} & \frac{n_{s} + f_{s}}{2} \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{视口变换矩阵}

在OpenGL中,一般情况下 S x = 0 , S y = 0 , n s = 0 , f s = 1 S_x=0, S_y=0, n_s=0, f_s=1 ,因此viewPort可简化为:

v i e w P o r t = [ W s 2 0 0 W s 2 0 H s 2 0 H s 2 0 0 1 2 1 2 0 0 0 1 ] (简化视口变换矩阵) viewPort=\begin{bmatrix} \frac{W_{s}}{2} & 0 & 0 & \frac{W_{s}}{2} \\ 0 & \frac{H_{s}}{2} & 0 & \frac{H_{s}}{2} \\ 0 & 0 & \frac{1}{2} & \frac{1}{2} \\ 0 & 0 & 0 & 1 \end{bmatrix} \tag{简化视口变换矩阵}

三、线性与非线性深度之间的变换关系

3.1 从NDC空间深度值反推观察空间下的线性深度

已知在1.4节中已经推导出 z n z e z_n 与z_e 之间的变换关系:

z n = z c / w c = f + n f n z e 2 f n f n z e = f + n f n + 2 f n f n z e z_n = z_c/w_c = \frac {-\frac{f+n}{f-n}z_e - \frac{2fn}{f-n}} {-z_e} = \frac{f+n}{f-n} + \frac{2fn}{(f-n)z_e}

因此很容易可反推出:

z e = 2 f n f + n z n ( f n ) z_e = \frac{-2fn}{f+n - z_n(f-n)}

这里 z e z_e 表示观察空间(右手坐标系)下的坐标值,是负数;

而深度这个概念是针对屏幕空间(左手坐标系)来定义的,这里对 z e z_e 取反,可从NDC空间反推出观察空间下的线性深度:

l i n e a r D e p t h = z e = 2 n f z n ( f n ) ( f + n ) linearDepth = -z_e = \frac{2nf}{z_n(f-n) - (f+n)}

3.2 从观察空间的线性深度值推导屏幕空间下的非线性深度值

根据5.2 中推出的视口变换矩阵可以很容易得到:

z s = 0.5 z n + 0.5 z n = z c / w c z_s = 0.5z_n + 0.5 \\ z_n = z_c/w_c \\

代入可得:

image.png

\begin{equation*} \begin{aligned} z_s &= \frac{1}{2}(z_n + 1) \\ &= \frac{1}{2}(\frac{f+n}{f-n} + \frac{2fn}{z_e(f-n)} + 1)\\ &= \frac{f - \frac{nf}{z_e}}{f - n} \\ &= \frac{\frac{1}{-z_e} - \frac{1}{n}}{\frac{1}{f} - \frac{1}{n}} \end{aligned} \end{equation*}

同上文,这里 z e z_e 表示观察空间(右手坐标系)下的坐标值

但在深度测试文中,定义的 z是代表了近、远平面之间的距离值,它们都是正数,因此这里也要对 z e z_e 取反。

最终可以从观察空间推导出屏幕空间的非线性深度值为:

F d e p t h = z s = 1 z 1 n 1 f 1 n F_{depth} = z_s = \frac{\frac{1}{z} - \frac{1}{n}}{\frac{1}{f} - \frac{1}{n}}

3.3 通过深度信息还原位置和法线信息

参考资料:

https://zhuanlan.zhihu.com/p/367257314

https://mynameismjp.wordpress.com/2010/09/05/position-from-depth-3

以上两篇资料推理过程比较详尽,这里不再赘述。

四、法线变换说明

参考资料:https://blog.csdn.net/u012419410/article/details/42174839

image.png

由上面两图可知,当模型存在缩放变换时,法线向量将会被破坏,因此不能简单使用 M v i e w M m o d e l M_{view}*M_{model} 矩阵来变换法线向量。

法线变换矩阵推导过程如下:

假设Model space中的某条切线向量是T,法线向量是N。

那么由他们是垂直的可得到: T T N = 0 T^TN=0

假设他们变换到Eye space中后分别是 T T' N N' 。那么他们应该仍然是相互垂直的: T T N = 0 T’^TN’=0

假设切线向量T和法线向量N的变换矩阵为M、G。则有: ( M T ) T ( G N ) = 0 (MT)^T(GN)=0

进一步推出: T T M T G N = 0 T^TM^TGN=0

由于 T T N = 0 T^TN=0 ,因此我们猜想 M T G = I M^TG=I

最终求得: G = ( M 1 ) T G=(M^{-1})^T

即:应用于法线向量的变换矩阵是顶点变换矩阵的逆转置矩阵。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请发送邮件至:cloudbbs@huaweicloud.com;如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。