透视投影是非线性变换
透视投影
相较于正交投影(不管相距多远,物体大小不变),透视投影更符合人眼观察世界的模式(近大远小)。
首先我们推导下投影到
z
=
−
d
,
d
>
0
z=-d,d>0
z=−d,d>0 平面上的透视投影矩阵。
假设相机位于坐标原点(一般经过视图变换也确实位于坐标原点),我们希望将点
p
\bold{p}
p 投影到平面
z
=
−
d
,
d
>
0
z=-d,d>0
z=−d,d>0 上,最终生成一个新的顶点
q
=
(
q
x
,
q
y
,
−
d
)
\bold{q}=(q_x,q_y,-d)
q=(qx,qy,−d),这个过程如上图所示。
通过图中的相似三角形,我们可以推导出点 q \bold{q} q的 x x x分量,如下所示
q x p x = − d p z ⟺ q x = − d p x p z ( 1 ) \frac{q_x}{p_x}=\frac{-d}{p_z} \Longleftrightarrow q_x=-d\frac{p_x}{p_z} (1) pxqx=pz−d⟺qx=−dpzpx(1)
同理也可以推出点 q \bold{q} q的 y y y分量为 q y = − d p y p z q_y=-d\frac{p_y}{p_z} qy=−dpzpy,将上述公式整合在一起,就可以获得这个透视投影矩阵 M p \bold{M}_p Mp,具体如下所示
M p = ( 1 0 0 0 0 1 0 0 0 0 1 0 0 0 − 1 / d 0 ) ( 2 ) \bold{M}_p=\begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & -1/d & 0 \end{pmatrix} (2) Mp= 10000100001−1/d0000 (2)
貌似与我们常见的透视投影矩阵不太一样,是因为我们经常使用的透视投影,并没有真正地将所有物体都投影到一个平面上(这个过程是不可逆的,会丢失深度值,后面的zbuffer没法处理),而是将视锥体变换成了一个规则观察体。
我们会假设视锥体从
z
=
n
z=n
z=n 开始,并在
z
=
f
z = f
z=f 结束,其中
0
>
n
>
f
0>n>f
0>n>f。在
z
=
n
z=n
z=n 平面上,视锥体的截面是一个长方形,其最小角(左下角)是
(
l
,
b
,
n
)
(l,b,n)
(l,b,n),最大角(右上角)是
(
r
,
t
,
f
)
(r,t,f)
(r,t,f),同理
z
=
f
z=f
z=f 平面上,视锥体的截面是一个更大的长方形,其最小角是
(
l
,
b
,
f
)
(l,b,f)
(l,b,f),最大角是
(
r
,
t
,
f
)
(r,t,f)
(r,t,f)。
透视投影的整个过程,可以理解为:
- 将视锥体的远裁剪平面按一定规则,缩放到与近裁剪平面一样的尺寸,即将视锥体变成一个长方体;
- 然后再按照正交投影的方式,将其变换成一个规则观察体。
下面推导下视锥体变化为长方体的变换矩阵 M p e r s p → o r t h o M_{persp \rightarrow ortho} Mpersp→ortho
考虑经过视图变换后的点坐标为 ( x v , y v , z v , 1 ) T (x_v,y_v,z_v,1)^T (xv,yv,zv,1)T,那么经过 M p e r s p → o r t h o M_{persp \to ortho} Mpersp→ortho 我们希望这个点为 ( n x v / z , n y v / z , ? , 1 ) T (nx_v/z,ny_v/z,?,1)^T (nxv/z,nyv/z,?,1)T 与其相同的齐次坐标为 ( n x v , n y v , ? , z ) T (nx_v,ny_v,?,z)^T (nxv,nyv,?,z)T (考虑每一项乘上 z v z_v zv),具体公式可以写为
M p e r s p → o r t h o ( 4 × 4 ) ( x y z 1 ) = ( n x n y ? z ) ( 3 ) M_{persp \rightarrow ortho}^{(4 \times 4)}\left(\begin{array}{l} x \\ y \\ z \\ 1 \end{array}\right)=\left(\begin{array}{c} n x \\ n y \\ ? \\ z \end{array}\right) (3) Mpersp→ortho(4×4) xyz1 = nxny?z (3)
那么我们可以推出 M p e r s p → o r t h o M_{persp \rightarrow ortho} Mpersp→ortho 的部分项,即
M p e r s p → o r t h o = ( n 0 0 0 0 n 0 0 ? ? ? ? 0 0 1 0 ) ( 4 ) M_{persp \rightarrow ortho}=\left(\begin{array}{cccc} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ? & ? \\ 0 & 0 & 1 & 0 \end{array}\right) (4) Mpersp→ortho= n0?00n?000?100?0 (4)
再考虑所有在近平面的点都不会改变
远平面的在
z
z
z 轴上的点也不会改变
那么两变量,两个等式,就能求解出
A
A
A 和
B
B
B
变换矩阵最终形式如下
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
)
(
5
)
M_{persp \rightarrow ortho}=\left(\begin{array}{cccc} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{array}\right) (5)
Mpersp→ortho=
n0000n0000n+f100−nf0
(5)
当然我们可以乘上正交投影矩阵,得到最后的透视投影矩阵,在投影变换之后,还会进行裁剪操作和齐次化操作,最终将其转换到 NDC 空间中
M
p
e
r
s
p
=
M
o
r
t
h
o
M
p
e
r
s
p
→
o
r
t
h
o
=
(
2
r
−
l
0
0
r
+
l
l
−
r
0
2
t
−
b
0
t
+
b
b
−
t
0
0
2
n
−
f
n
+
f
f
−
n
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
l
−
r
0
0
2
n
t
−
b
t
+
b
b
−
t
0
0
0
n
+
f
n
−
f
2
n
f
f
−
n
0
0
1
0
)
(
6
)
M_{persp}=M_{ortho}M_{persp \rightarrow ortho} = \left(\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & \frac{r+l}{l-r} \\ 0 & \frac{2}{t-b} & 0 & \frac{t+b}{b-t} \\ 0 & 0 & \frac{2}{n-f} & \frac{n+f}{f-n} \\ 0 & 0 & 0 & 1 \end{array}\right) \left(\begin{array}{cccc} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{array}\right) = \left(\begin{array}{cccc} \frac{2n}{r-l} & 0 & \frac{r+l}{l-r} & 0 \\ 0 & \frac{2n}{t-b} & \frac{t+b}{b-t} & 0 \\ 0 & 0 & \frac{n+f}{n-f} & \frac{2nf}{f-n} \\ 0 & 0 & 1 & 0 \end{array}\right) (6)
Mpersp=MorthoMpersp→ortho=
r−l20000t−b20000n−f20l−rr+lb−tt+bf−nn+f1
n0000n0000n+f100−nf0
=
r−l2n0000t−b2n00l−rr+lb−tt+bn−fn+f100f−n2nf0
(6)
类似地,我们可以推出 OpenGL 的透视投影矩阵,需要注意的是,OpenGL 的视图空间是右手系,而齐次裁剪空间是左手系,所以其正交投影矩阵需要在一般的正交投影矩阵左乘一个镜像变换矩阵 M o = ( 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ) \bold{M}_o=\begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & -1 & 0\\ 0 & 0 & 0 & 1 \end{pmatrix} Mo= 1000010000−100001 ,最终的正交投影矩阵如下
M o r t h o O p e n G L = ( 2 r − l 0 0 r + l l − r 0 2 t − b 0 t + b b − t 0 0 2 f − n n + f n − f 0 0 0 1 ) ( 7 ) M_{orthoOpenGL} = \left(\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & \frac{r+l}{l-r} \\ 0 & \frac{2}{t-b} & 0 & \frac{t+b}{b-t} \\ 0 & 0 & \frac{2}{f-n} & \frac{n+f}{n-f} \\ 0 & 0 & 0 & 1 \end{array}\right) (7) MorthoOpenGL= r−l20000t−b20000f−n20l−rr+lb−tt+bn−fn+f1 (7)
同时,我们使用 n ′ = − n n'=-n n′=−n 以及 f ′ = − f f'=-f f′=−f 来替换,因为正的深度值更符合用户习惯,可以得到 OpenGL 的透视投影矩阵,远平面的点最终会投影到 z = 1 z=1 z=1 平面,近平面的点最终会投影到 z = − 1 z=-1 z=−1 平面
M p e r s p O p e n G L = ( 2 n ′ l − r 0 r + l l − r 0 0 2 n ′ b − t t + b b − t 0 0 0 f ′ + n ′ f ′ − n ′ 2 f ′ n ′ f ′ − n ′ 0 0 1 0 ) ( 8 ) M_{perspOpenGL} = \left(\begin{array}{cccc} \frac{2n'}{l-r} & 0 & \frac{r+l}{l-r} & 0 \\ 0 & \frac{2n'}{b-t} & \frac{t+b}{b-t} & 0\\ 0 & 0 & \frac{f'+n'}{f'-n'} & \frac{2f'n'}{f'-n'} \\ 0 & 0 & 1 & 0 \end{array}\right) (8) MperspOpenGL= l−r2n′0000b−t2n′00l−rr+lb−tt+bf′−n′f′+n′100f′−n′2f′n′0 (8)
这个矩阵对点的作用其实是和 RealTime-Rendering-4th 中提到的 OpenGL 的透视投影矩阵是一样的,可能OpenGL里实际使用的是下面这种?
如果远裁剪平面设置在无穷远处
f
→
∞
f \rightarrow \infty
f→∞,那么
M
p
e
r
s
p
M_{persp}
Mpersp将变成如下形式
M p e r s p I n f t y = ( 2 n r − l 0 r + l l − r 0 0 2 n t − b t + b b − t 0 0 0 − 1 2 n 0 0 1 0 ) ( 9 ) M_{perspInfty} = \left(\begin{array}{cccc} \frac{2n}{r-l} & 0 & \frac{r+l}{l-r} & 0 \\ 0 & \frac{2n}{t-b} & \frac{t+b}{b-t} & 0 \\ 0 & 0 & -1& 2n \\ 0 & 0 & 1 & 0 \end{array}\right) (9) MperspInfty= r−l2n0000t−b2n00l−rr+lb−tt+b−11002n0 (9)
一般来说,对于透视投影,不提供 l , r , b , t l,r,b,t l,r,b,t,而是提供垂直视场角(FOV) ϕ \phi ϕ 和 宽高比 a = w h a=\frac{w}{h} a=hw(代表了屏幕分辨率),那么 P O p e n G L P_{OpenGL} POpenGL 可以改为如下形式,其中 c = 1 t a n ( ϕ / 2 ) c=\frac{1}{tan(\phi /2)} c=tan(ϕ/2)1
投影之后转换为 NDC 空间下,深度值其实是非线性变化的,导致远处点难以比较深度,可以通过反向z-buffer的手段等来提高深度值的精度
透视投影矫正
在使用光栅化的图形学方法中,法线,颜色,纹理坐标这些属性通常是绑定在图元的顶点上的,当我们需要除顶点外处的属性时,要通过插值的方式得到。
在3D空间中,这些属性值在图元应该是线性变换的。但是当3D顶点被透视投影到2D屏幕,如果在2D投影面上对属性进行线性插值,其对应的属性在3D空间中却不是线性变化的,下图可以明显看到, c c c 是 a a a 和 b b b 的中点,但 C C C 明显更靠近 A A A,这是因为透视投影是非线性变换
深度值插值
我们先以插值深度值为例,根据之前在透视投影的内容,我们知道 X = Z u d X=\frac{Zu}{d} X=dZu,定义直线 AB 方程为 a x + b z = c , c ≠ 0 ax+bz=c,c\ne0 ax+bz=c,c=0
将 X t = Z t u d X_t = \frac{Z_tu}{d} Xt=dZtu 代入AB的方程:
a
(
Z
t
u
d
)
+
b
Z
t
=
c
(
10
)
a(Z_t\frac{u}{d})+bZ_t=c (10)
a(Ztdu)+bZt=c(10)
Z
t
(
a
u
d
+
b
)
=
c
(
11
)
Z_t(a\frac{u}{d}+b)=c(11)
Zt(adu+b)=c(11)
1
Z
t
=
a
u
d
c
+
b
c
(
12
)
\frac{1}{Z_t}=\frac{au}{dc}+\frac{b}{c}(12)
Zt1=dcau+cb(12)
因为 u = u 1 + s ( u 2 − u 1 ) = u 1 ( 1 − s ) + u 2 s u=u_1+s(u_2-u_1)=u_1(1-s)+u_2s u=u1+s(u2−u1)=u1(1−s)+u2s,代入(12)
1
Z
t
=
a
u
1
(
1
−
s
)
d
c
+
a
u
2
s
d
c
+
b
c
=
a
u
1
d
c
(
1
−
s
)
+
a
u
2
d
c
s
+
b
c
(
1
−
s
)
+
b
c
s
(
13
)
\frac{1}{Z_t}=\frac{au_1(1-s)}{dc}+\frac{au_2s}{dc}+\frac{b}{c}=\frac{au_1}{dc}(1-s)+\frac{au_2}{dc}s+\frac{b}{c}(1-s)+\frac{b}{c}s(13)
Zt1=dcau1(1−s)+dcau2s+cb=dcau1(1−s)+dcau2s+cb(1−s)+cbs(13)
1
Z
t
=
(
a
u
1
d
c
+
b
c
)
(
1
−
s
)
+
(
a
u
2
d
c
+
b
c
)
s
(
14
)
\frac{1}{Z_t}=(\frac{au_1}{dc}+\frac{b}{c})(1-s)+(\frac{au_2}{dc}+\frac{b}{c})s(14)
Zt1=(dcau1+cb)(1−s)+(dcau2+cb)s(14)
根据 (12) 有
1
Z
1
=
a
u
1
d
c
+
b
c
(
13
)
\frac{1}{Z_1}=\frac{au_1}{dc}+\frac{b}{c}(13)
Z11=dcau1+cb(13)
1
Z
2
=
a
u
2
d
c
+
b
c
(
14
)
\frac{1}{Z_2}=\frac{au_2}{dc}+\frac{b}{c}(14)
Z21=dcau2+cb(14)
代入(14)得
1 Z t = 1 Z 1 ( 1 − s ) + 1 Z 2 s ( 15 ) \frac{1}{Z_t}=\frac{1}{Z_1}(1-s)+\frac{1}{Z_2}s(15) Zt1=Z11(1−s)+Z21s(15)
可以看出, Z t Z_t Zt 的倒数是 Z 1 Z_1 Z1 倒数和 Z 2 Z_2 Z2 倒数的线性插值
考虑之前的透视投影矩阵 P O p e n G L P_{OpenGL} POpenGL (这里 r = − l , t = − b r=-l,t=-b r=−l,t=−b,将第三列化简)我们将其应用到一个点可以得到
P O p e n G L ( x y z 1 ) = ( 2 n ′ r − l x 2 n ′ t − b y − f ′ + n ′ f ′ − n ′ z − 2 f ′ n ′ f ′ − n ′ − z ) ( 16 ) P_{OpenGL}\begin{pmatrix} x\\ y\\ z\\ 1 \end{pmatrix}=\begin{pmatrix} \frac{2n'}{r-l}x\\ \frac{2n'}{t-b}y\\ -\frac{f'+n'}{f'-n'}z-\frac{2f'n'}{f'-n'}\\ -z \end{pmatrix}(16) POpenGL xyz1 = r−l2n′xt−b2n′y−f′−n′f′+n′z−f′−n′2f′n′−z (16)
z分量除以w分量,可以得到NDC空间下的z为 A z + B \frac{A}{z}+B zA+B,其中 A = 2 f ′ n ′ f ′ − n ′ A=\frac{2f'n'}{f'-n'} A=f′−n′2f′n′, B = f ′ + n ′ f ′ − n ′ B=\frac{f'+n'}{f'-n'} B=f′−n′f′+n′
所以NDC空间下的z,是和视图空间下的z成反比关系,可以直接在光栅化时进行插值。
我们将线性插值扩展到重心坐标插值,可以得到
1 Z t = 1 Z 1 ( 1 − α − β ) + 1 Z 2 α + 1 Z 3 β ( 17 ) \frac{1}{Z_t}=\frac{1}{Z_1}(1-\alpha-\beta)+\frac{1}{Z_2}\alpha+\frac{1}{Z_3}\beta(17) Zt1=Z11(1−α−β)+Z21α+Z31β(17)
顶点属性插值
对于顶点属性,我们可以有下面的比例关系
I
t
−
I
1
I
2
−
I
1
=
Z
t
−
Z
1
Z
2
−
Z
1
(
18
)
\frac{I_t-I_1}{I_2-I_1}=\frac{Z_t-Z_1}{Z_2-Z_1}(18)
I2−I1It−I1=Z2−Z1Zt−Z1(18)
而根据 (15)
Z t = 1 1 Z 1 ( 1 − s ) + 1 Z 2 s ( 19 ) Z_t = \frac{1}{\frac{1}{Z_1}(1-s)+\frac{1}{Z_2}s}(19) Zt=Z11(1−s)+Z21s1(19)
代入到(18),解出 I t I_t It
I t − I 1 I 2 − I 1 = 1 1 Z 1 ( 1 − s ) + 1 Z 2 s − Z 1 Z 2 − Z 1 = 1 1 + ( 1 − s ) Z 2 s Z 1 = Z 1 s Z 1 s + Z 2 ( 1 − s ) ( 20 ) \frac{I_t-I_1}{I_2-I_1}=\frac{\frac{1}{\frac{1}{Z_1}(1-s)+\frac{1}{Z_2}s}-Z_1}{Z_2-Z_1}=\frac{1}{1+\frac{(1-s)Z_2}{sZ_1}}=\frac{Z_1s}{Z_1s+Z_2(1-s)}(20) I2−I1It−I1=Z2−Z1Z11(1−s)+Z21s1−Z1=1+sZ1(1−s)Z21=Z1s+Z2(1−s)Z1s(20)
I t = I 1 Z 2 ( 1 − s ) + I 2 Z 1 s Z 1 s + Z 2 ( 1 − s ) ( 21 ) I_t = \frac{I_1Z_2(1-s)+I_2Z_1s}{Z_1s+Z_2(1-s)}(21) It=Z1s+Z2(1−s)I1Z2(1−s)+I2Z1s(21)
上下同除以 Z 1 Z 2 Z_1 Z_2 Z1Z2 得
I t = ( 1 − s ) I 1 Z 1 + s I 2 Z 2 ( 1 − s ) 1 Z 1 + s 1 Z 2 ( 22 ) I_t=\frac{(1-s)\frac{I_1}{Z_1}+s\frac{I_2}{Z_2}}{(1-s)\frac{1}{Z_1}+s\frac{1}{Z_2}}(22) It=(1−s)Z11+sZ21(1−s)Z1I1+sZ2I2(22)
我们可以在透视投影之后的 w 分量取到视图空间中的 z 值,而不用单独保存下来
同样可以推广到重心坐标插值
I t = I 1 Z 1 ( 1 − α − β ) + I 2 Z 2 α + I 3 Z 3 β 1 Z 1 ( 1 − α − β ) + 1 Z 2 α + 1 Z 3 β ( 23 ) I_t=\frac{\frac{I_1}{Z_1}(1-\alpha-\beta)+\frac{I_2}{Z_2}\alpha+\frac{I_3}{Z_3}\beta}{\frac{1}{Z_1}(1-\alpha-\beta)+\frac{1}{Z_2}\alpha+\frac{1}{Z_3}\beta}(23) It=Z11(1−α−β)+Z21α+Z31βZ1I1(1−α−β)+Z2I2α+Z3I3β(23)
参考资料
Real-Time-Rendering-4th-CN
Games101
计算机图形学六:透视矫正插值和图形渲染管线总结-知乎
图形学基础知识:重心坐标 (Barycentric Coordinates) - CSDN博客
图形学 - 关于透视矫正插值那些事 - 知乎
图形学基础之透视校正插值 - CSDN博客