视截体简单的说就是摄像机的可视区域。
一个正交视图的摄像机,他的视截体是一个长方体。而一个使用了透视视图的摄像机,它的视截体则是一个正棱台。
视截体由多个因素决定。
1.field of view:视野区域角度,一般是只要知道竖直方向的角度。因为水平方向的视野可以通过aspect参数推出。如上图的∠UOD.
2.aspect:正棱台的宽高比。W / H。
3.far:视截体的远截面距离摄像机的距离。
4.near:视截体的近截面距离摄像机的距离。
通过投影矩阵,可以把观察空间里的正棱台视截体形变拉伸为一个长方体的裁剪空间。
裁剪空间在经由透视除法之后,才会转化到宽高取值范围为[-1,1],深度取值为[-1,0]的长方体方体(Direct3D平台)。这种投影变换称为透视投影。转化后的空间叫标准化设备坐标(NDC)。
下面尝试推导下投影矩阵。
假设视截体里的一个点P(x,y,z),经过透视投影后转变为NDC坐标的P1(x1, y1, z1)。
因为NDC空间的高度正是2,并且在NDC空间中,PP1//AA1。那么上图的OP与蓝色投影线相交的P1点的高度值y1,就是转化到NDC空间后的P1点的y1值。
现在我们知道摄像机的参数有 纵角度FoY,远近截面f,n,宽高比aspect。
∠ U O A 1 = F o Y ∠UOA_{1} = FoY\\ ∠UOA1=FoY
∣ U A 1 → ∣ = 1 \left| \overrightarrow{UA_{1}}\right| = 1\\ ∣∣∣UA1∣∣∣=1
z 1 = − ∣ A 1 O → ∣ = − c t a n ( ∠ U O A 1 2 ) = − c t a n ( F o Y 2 ) z_{1} = -\left| \overrightarrow{A_{1}O}\right| = -ctan(\dfrac{∠UOA_{1}}{2}) = -ctan(\dfrac{FoY}{2}) z1=−∣∣∣A1O∣∣∣=−ctan(2∠UOA1)=−ctan(2FoY)
因为△OPA和△OP1A1相似。有
y 1 z 1 = y z \dfrac{y_{1}}{z_{1}}=\dfrac{y}{z} z1y1=zy
可以先求得投影后的y1值为
y 1 = y z . z 1 = − y z . c t a n ( F o Y 2 ) . . . . . . . . . . ① y_{1}=\dfrac{y}{z}. z_{1} =- \dfrac{y}{z}.ctan(\dfrac{FoY}{2})..........① y1=zy.z1=−zy.ctan(2FoY)..........①
同理,我们可以把符号y更换为符号x,符号FoY更换为FoX,就可以求得x1的等式。
x 1 = x z . z 1 = − x z . c t a n ( F o X 2 ) . . . . . . . . . . ② x_{1}=\dfrac{x}{z}. z_{1} =- \dfrac{x}{z}.ctan(\dfrac{FoX}{2})..........② x1=zx.z1=−zx.ctan(2FoX)..........②
因为FoX参数未知,我们可以通过已知的aspect和FoY参数,替换掉它。如下图
a s p e c t = W H = c t a n ( F o Y 2 ) . z 1 c t a n ( F o X 2 ) . z 1 aspect=\dfrac{W}{H}=\dfrac{ctan(\dfrac{FoY}{2}).z_{1}}{ctan(\dfrac{FoX}{2}).z_{1}}\\ aspect=HW=ctan(2FoX).z1ctan(2FoY).z1
c t a n ( F o X 2 ) = c t a n ( F o Y 2 ) a s p e c t . . . . . . . . . . ③ ctan(\dfrac{FoX}{2}) = \dfrac{ctan(\dfrac{FoY}{2})}{aspect}..........③ ctan(2FoX)=aspectctan(2FoY)..........③
③式代入②式后
x 1 = x z . z 1 = − x z . c t a n ( F o Y 2 ) a s p e c t x_{1}=\dfrac{x}{z}. z_{1} =- \dfrac{x}{z}.\dfrac{ctan(\dfrac{FoY}{2})}{aspect} x1=zx.z1=−zx.aspectctan(2FoY)
P1的x1,y1的表达式已经有了,P1新的坐标表示为
P 1 = [ − x z . c t a n ( F o Y 2 ) a s p e c t , − y z . c t a n ( F o Y 2 ) , z 1 ] P_{1}=[- \dfrac{x}{z}.\dfrac{ctan(\dfrac{FoY}{2})}{aspect},- \dfrac{y}{z}.ctan(\dfrac{FoY}{2}),z_{1}] P1=[−zx.aspectctan(2FoY),−zy.ctan(2FoY),z1]
齐次化的P1的坐标
P 1 = [ − x z . c t a n ( F o Y 2 ) a s p e c t , − y z . c t a n ( F o Y 2 ) , z 1 , 1 ] P_{1}=[- \dfrac{x}{z}.\dfrac{ctan(\dfrac{FoY}{2})}{aspect},- \dfrac{y}{z}.ctan(\dfrac{FoY}{2}),z_{1}, 1] P1=[−zx.aspectctan(2FoY),−zy.ctan(2FoY),z1,1]
根据齐次坐标的性质,如果第四维w≠0,则(x,y,z,1)等价于(wx,wy,wz,w)。现在把P1 的坐标值乘以−z后
P 1 = [ x . c t a n ( F o Y 2 ) a s p e c t , y . c t a n ( F o Y 2 ) , − z . z 1 , − z ] . . . . . . . . . . ④ P_{1}=[{x}.\dfrac{ctan(\dfrac{FoY}{2})}{aspect},y.ctan(\dfrac{FoY}{2}),-z.z_{1}, -z]..........④ P1=[x.aspectctan(2FoY),y.ctan(2FoY),−z.z1,−z]..........④
通过观察P1表达式的特点,P1可以拆分为点(x,y,z,1)右乘一个矩阵Mp
P 1 = [ c t a n ( F o Y 2 ) a s p e c t 1 1 1 1 c t a n ( F o Y 2 ) 1 1 m 1 m 2 m 3 m 4 0 0 − 1 0 ] . [ x y z 1 ] P_{1}=\begin{bmatrix} \dfrac{ctan(\dfrac{FoY}{2})}{aspect} & 1 & 1 & 1 \\ 1 & ctan(\dfrac{FoY}{2}) & 1 & 1 \\ m1 & m2 & m3 & m4 \\ 0 & 0 & -1 & 0 \end{bmatrix}.\begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} P1=⎣⎢⎢⎢⎢⎢⎢⎢⎡aspectctan(2FoY)1m101ctan(2FoY)m2011m3−111m40⎦⎥⎥⎥⎥⎥⎥⎥⎤.⎣⎢⎢⎡xyz1⎦⎥⎥⎤
M p = [ c t a n ( F o Y 2 ) a s p e c t 1 1 1 1 c t a n ( F o Y 2 ) 1 1 m 1 m 2 m 3 m 4 0 0 − 1 0 ] M_{p}=\begin{bmatrix} \dfrac{ctan(\dfrac{FoY}{2})}{aspect} & 1 & 1 & 1 \\ 1 & ctan(\dfrac{FoY}{2}) & 1 & 1 \\ m1 & m2 & m3 & m4 \\ 0 & 0 & -1 & 0 \end{bmatrix} Mp=⎣⎢⎢⎢⎢⎢⎢⎢⎡aspectctan(2FoY)1m101ctan(2FoY)m2011m3−111m40⎦⎥⎥⎥⎥⎥⎥⎥⎤
矩阵Mp中的m1 m2 m3 m4是未知的,且有一下表达式成立
− z . z 1 = m 1. x + m 2. y + m 3. z + m 4 -z.z_{1}=m1.x + m2.y + m3.z+m4 −z.z1=m1.x+m2.y+m3.z+m4
通过观察式子,可以猜想m1 = 0, m2 = 0(因为式子的左边不存在x和y)。这个也是必然的。因为对于P点,投影到投影面后,z1值都必定一致且和xy的取值无关。
所以上式进一步简化
− z . z 1 = m 3. z + m 4 -z.z_{1}=m3.z+m4 −z.z1=m3.z+m4
投影后z1的取值范围是[−1,0],显然远截面的z(即−f)投影后的z1 为−1,近截面的z(即−n)投影后z1 为0。把值代入上式解二元一次方程
{ − f ⋅ m 3 + m 4 = f ( − 1 ) m 3 ( − n ) + m 4 = n ( 0 ) . \begin{aligned}\begin{cases}-f\cdot m_{3}+m_{4}=f\left( -1\right) \\ m_{3}\left( -n\right) +m_{4}=n\left( 0\right) \end{cases}\\ .\end{aligned} {−f⋅m3+m4=f(−1)m3(−n)+m4=n(0).
求得m3 m4的表达式
m 3 = f f − n m_{3}=\dfrac{f}{f-n} m3=f−nf
m 4 = n f f − n m_{4}=\dfrac{nf}{f-n} m4=f−nnf
最后求得Mp的表达式为
M p = [ c t a n ( F o Y 2 ) a s p e c t 1 1 1 1 c t a n ( F o Y 2 ) 1 1 0 0 f f − n n f f − n 0 0 − 1 0 ] M_{p}=\begin{bmatrix} \dfrac{ctan(\dfrac{FoY}{2})}{aspect} & 1 & 1 & 1 \\ 1 & ctan(\dfrac{FoY}{2}) & 1 & 1 \\ 0 & 0 & \dfrac{f}{f-n} & \dfrac{nf}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} Mp=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡aspectctan(2FoY)1001ctan(2FoY)0011f−nf−111f−nnf0⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
Mp就是我们的投影矩阵。
最后有一个需要注意的,我们的观察空间使用的是右手坐标系,而裁剪空间采用左手坐标系,要把Mp右乘一个倒转z轴的矩阵才能得到最终的投影矩阵Mp。
M p = [ 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ] . [ c t a n ( F o Y 2 ) a s p e c t 1 1 1 1 c t a n ( F o Y 2 ) 1 1 0 0 f f − n n f f − n 0 0 − 1 0 ] M_{p}=\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 &-1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}.\begin{bmatrix} \dfrac{ctan(\dfrac{FoY}{2})}{aspect} & 1 & 1 & 1 \\ 1 & ctan(\dfrac{FoY}{2}) & 1 & 1 \\ 0 & 0 & \dfrac{f}{f-n} & \dfrac{nf}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} Mp=⎣⎢⎢⎡1000010000−100001⎦⎥⎥⎤.⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡aspectctan(2FoY)1001ctan(2FoY)0011f−nf−111f−nnf0⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
M p = [ c t a n ( F o Y 2 ) a s p e c t 1 1 1 1 c t a n ( F o Y 2 ) 1 1 0 0 f n − f n f n − f 0 0 − 1 0 ] M_{p}=\begin{bmatrix} \dfrac{ctan(\dfrac{FoY}{2})}{aspect} & 1 & 1 & 1 \\ 1 & ctan(\dfrac{FoY}{2}) & 1 & 1 \\ 0 & 0 & \dfrac{f}{n-f} & \dfrac{nf}{n-f} \\ 0 & 0 & -1 & 0 \end{bmatrix} Mp=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡aspectctan(2FoY)1001ctan(2FoY)0011n−ff−111n−fnf0⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
因为在④的时候为了转化齐次坐标,乘了-z值。所以投影矩阵Mp乘以P点得到到的裁剪空间的P1坐标的w值并非为1,也即使说,P1点没有做透视除法。最后要把的到的P1点的所有坐标值除以它的w值(也是P点的z值),后才是NDC中的坐标值。
P N D C = M p . P w P_{NDC}= \dfrac{M_{p}.P}{w} PNDC=wMp.P
推导完结了。
现在有一个物体Cube,它呆在一个使用透视的摄像机前。我们可以在Unity通过坐标转化的方式,模拟它投影到屏幕前的样子。
所用到的场景中的物体有
一个透视投影的摄像机 MainCamera
一个任你摆布的模型cube Cube
一个表示NDC空间原点的空物体 ndcspace,这个空物体摆在世界坐标的原点。
步骤:
1.取得模型上的所有顶点
2.顶点坐标需要转化->世界坐标->观察空间坐标P。
3.P点右乘Mp投影矩阵,就可以的得到一个裁剪空间上的坐标P1。
3.P1还需要做透视除法处理:除以P1的w分量。
4.把所有的生成的P1点通过mesh连接起来,生成一个新的cube,放置在ndcspace节点的位置。
5.把Game视图的窗口设置为1:1,因为要和我们生成的NDC空间大小要一致,方便查看效果。
6.在scene视图,选择正交模式,沿着z轴的正方向观察ndcspace节点。
移动cube,观察生成的mesh和屏幕上的cube的投影。
在scene视图正交模式观察的mesh的表现,和投影在屏幕的cube的表现完全一致。
把cube拖到右上角,和屏幕的右上对其,mesh也和NDC的边缘对齐。
工程地址
矩阵的推导部分思路来自 《unity3d内建着色器源码剖析》一书,有解读的不对的地方望大佬们指出。