原文地址http://www.songho.ca/opengl/gl_projectionmatrix.html,英语好的小伙伴可以去阅读原文,可能理解的更好一些,这里只是按我的理解来翻译的,以方便自己日后查看,文中所用到的图片均来自原文。
我们都知道设备屏幕都是2D的,3D的内容最终都会投射到2D的屏幕上去,GL_PROJECTION
矩阵就是做这种变换的,他首先将观察坐标转换到裁切坐标,再通过每个坐标除以裁切坐标中的w参数(齐次坐标)映射到设备标准坐标(NDC),所以GL_PROJECTION
集成了以上两种变换。
要注意在除以 w c w_c wc(下表c表示在clip裁切坐标中)之前,平截锥体已经完成了裁切的功能。裁切坐标中的 x c x_c xc, y c y_c yc, z c z_c zc都是通过和 w c w_c wc做对比来做裁切的。如果裁切坐标小于- w c w_c wc或者大于 w c w_c wc,那么顶点就会被丢弃。
- w c w_c wc< x c x_c xc, y c y_c yc, z c z_c zc < w c w_c wc
opengl会重构那些边缘被裁切掉的多边形结构
在透视投影中,平截锥体中3D的点会被映射到NDC的立方体坐标中;其中x的坐标范围[l,r]在[-1.1]之间;y的坐标范围[b,t]在[-1,1]之间;z的坐标范围[-n,-f]在[-1,1]范围之间。
要注意观察坐标采用的是右手坐标系而NDC(normalized device coordinates,标准设备坐标系)中使用的是左手坐标系(从上图蓝色z坐标指向能看出来是相反的),所以在观察坐标中,在原点的摄像机总是看向-Z的方向,而在NDC中是看向Z的方向。glFrustum
只能接受near和far之间距离的正值参数,所以我们需要在构造GL_PROJECTION矩阵的时候对他们取反。
在OpenGL中,观察空间中的3D的点是投影到近平面上(near)的。下面的图展示观察坐标中( x e x_e xe, y e y_e ye, z e z_e ze)坐标是如何投影到近平面(near)上对应得( x p x_p xp, y p y_p yp, z p z_p zp)坐标的。
(顶视图)
(侧面图)
从平截锥体顶视图上,我们可以看到通过相似三角形原理观察坐标的 x e x_e xe被映射到了 x p x_p xp坐标上:
x p x e = − n z e \frac{x_p}{x_e} = \frac{-n}{z_e} xexp=ze−n
x p = x e ⋅ n − z e x_p =\frac{x_e·n}{-z_e} xp=−zexe⋅n
从侧面图上,我们也可以看到同样的方法 y e y_e ye投射到了 y p y_p yp坐标上
y p y e = − n z e \frac{y_p}{y_e}=\frac{-n}{z_e} yeyp=ze−n
y p = y e ⋅ n − z e y_p=\frac{y_e·n}{-z_e} yp=−zeye⋅n
我们会发现 x p x_p xp和 y p y_p yp都会依赖于 z e z_e ze他们和 − z e -z_e −ze成反比。观察坐标通过GL_PROJECTION
矩阵变换后,裁切坐标依然是一个齐次坐标。最终他通过除以裁切坐标的w分量转换到NDC空间。
[ x c l i p y c l i p z c l i p w c l i p ] = M p r o j e c t i o n ⋅ [ x e y e y e y e z e y e w e y e ] \left[ \begin{array}{c} x_{clip}\\ y_{clip}\\ z_{clip}\\ w_{clip} \end{array} \right] =M_{projection}·\left[ \begin{array}{c} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye} \end{array} \right] ⎣⎢⎢⎡xclipyclipzclipwclip⎦⎥⎥⎤=Mprojection⋅⎣⎢⎢⎡xeyeyeyezeyeweye⎦⎥⎥⎤
[ x n d c y n d c z n d c ] = [ x c l i p / w c l i p y c l i p / w c l i p z c l i p / w c l i p ] \left[ \begin{array}{c} x_{ndc}\\ y_{ndc}\\ z_{ndc} \end{array} \right] =\left[ \begin{array}{c} x_{clip}/w_{clip}\\ y_{clip}/w_{clip}\\ z_{clip}/w_{clip} \end{array} \right] ⎣⎡xndcyndczndc⎦⎤=⎣⎡xclip/wclipyclip/wclipzclip/wclip⎦⎤
因此,我们把裁切坐标系的w分量当做- z e z_e ze。把GL_PROJECTION
矩阵第四行设置为(0,0,-1,0)
接下来,我们通过线性关系将 x p x_p xp和 y p y_p yp映射到NDC里面的 x n x_n xn和 y n y_n yn;
[l,r]->[-1,1],[b,t]->[-1,1]。
然后,我们把 x p x_p xp和 y p 1 y_p1 yp1带入以上等式
注意,我们让等式的每一项都除以- z e z_e ze来实现透视结果( x c / w c x_c/w_c xc/wc, y c / w c y_c/w_c yc/wc)。而我们之前就设定过 w c w_c wc和- z c z_c zc一致,括号中的项就是裁切坐标的 x c x_c xc和 y c y_c yc。
从以上等式,我们得出了GL_PROJECTION
矩阵的第一行和第二行内容。
现在我们只有GL_PROJECTION
矩阵的第三行还未完成。我们发现 z n z_n zn和别的项有点区别因为 z e z_e ze在观察空间是投影到-n的近平面上的。但是我们需要唯一的z值来做裁切和深度测试。因而,我们应该对该值取消投影变换。因为我们知道z不依赖于x和y值,我们借助w分量来寻找 z n z_n zn和 z e z_e ze之间的关系。因此,我们可以假设第三行的内容如下:
在观察空间, w e w_e we值为1.因此,等式就变成了
z n = A z e + B − z e z_n=\frac{Az_e+B}{-z_e} zn=−zeAze+B
为了得出系数值,A和B,我们使用( z e , z n z_e,z_n ze,zn)关系;(-n,1)和(-f,1),将他们带入以上等式。
为了解出等式,我们重写下(1)为B的等式;
B=An-n (1*)
我们将(1*)B的等式带入(2)方程,得到A的等式
将A代入(1)得出B
我们得出了A和B。因此, z e z_e ze和 z n z_n zn的关系就是:
z n = − f + n f − n z e − 2 f n f − n − z e z_n=\frac{-\frac{f+n}{f-n}z_e-\frac{2fn}{f-n}}{-z_e} zn=−ze−f−nf+nze−f−n2fn (3)
最终我们得出了整个GL_PROJECTION
的矩阵。完整的投影矩阵如下:
(OpenGL Perspective Projection Matrix)
该投影矩阵属于常规平截锥体。如果观察参数都是对称的,r=-l,t=-b,那么就有简单的方程组:
{ r + l = 0 r − l = 2 r ( w i d t h ) , { t + b = 0 t − b = 2 t ( h e i g h t ) \begin{cases}r+l=0\\r-l=2r(width)\end{cases},\begin{cases}t+b=0\\t-b=2t(height)\end{cases} {r+l=0r−l=2r(width),{t+b=0t−b=2t(height)
[ 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 ] \left[ \begin{array}{cccc} \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{array} \right] ⎣⎢⎢⎡rn0000tn0000f−n−(f+n)−100f−n−2fn0⎦⎥⎥⎤
在我们继续学习之前,我们再看下等式(3),看下 z e z_e ze和 z n z_n zn之间的联系。你注意到这是一个有理函数并且 z e z_e ze和 z n z_n zn之间是非线性关系。也就是说在近平面他有很高的精度而在远平面则有较低的精度。如果[-n,-f]之间距离很大,就会造成深度精准问题(z-fighting);在远平面附近 z e z_e ze点微小的变化是不会影响到 z n z_n zn的值得。n和f之间的距离应该尽可能的小来减少深度缓存的精度问题。
构建正射投影的GL_PROJECTION
要比透视投影的矩阵简单的多。
所有观察空间的 x e x_e xe, y e y_e ye和 z e z_e ze分量都可以线性的映射到NDC上。我们只需要缩放一个固定值到这个正方体上,然后移动到坐标原地。让我们用线性关系来找出GL_PROJECTION
的各个元素。
因为w分量对正射投影来说不是必须的,所以GL_PROEJCTION
第四行就保持为(0,0,0,1)。因此,完整的正射投影GL_PROJECTION
的矩阵为
[ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 − 2 f − n − f + n f − n 0 0 0 1 ] \left[ \begin{array}{cccc} \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{array} \right] ⎣⎢⎢⎡r−l20000t−b20000f−n−20−r−lr+l−t−bt+b−f−nf+n1⎦⎥⎥⎤
(OpenGL Orthographic Projection Matrix)
如果观察参数都是对称的这个矩阵还可以更简单(r=-l,t=-b);
{ r + l = 0 r − l = 2 r ( w i d t h ) , { t + b = 0 t − b = 2 t ( h e i g h t ) \begin{cases}r+l=0\\r-l=2r(width)\end{cases},\begin{cases}t+b=0\\t-b=2t(height)\end{cases} {r+l=0r−l=2r(width),{t+b=0t−b=2t(height)
[ 1 r 0 0 0 0 1 t 0 0 0 0 − 2 f − n − f + n f − n 0 0 0 1 ] \left[ \begin{array}{cccc} \frac{1}{r}&0&0&0\\ 0&\frac{1}{t}&0&0\\ 0&0&\frac{-2}{f-n}&-\frac{f+n}{f-n}\\ 0&0&0&1 \end{array} \right] ⎣⎢⎢⎡r10000t10000f−n−2000−f−nf+n1⎦⎥⎥⎤