前言
原文地址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⎦⎥⎥⎤