Projection in Computer Graphics
Perspective projection vs. orthographic projection
Orthographic Projection 正交投影
方法一
A simple way of understanding
- Camera located at origin, looking at -Z, up at Y (looks familiar?)
- Translate and scale the resulting rectangle to [−1,1]2
感兴趣可以参考
Catlikecoding Render 1 中 Orthographic Camera 部分。
方法二
In general, we want to map a cuboid [l, r] x [b, t] x [
f, n] to the “canonical (正则、规范、标准)” cube
[−1,1]3 我们在
x 轴上定义左和右
[l,r] (左比右小),
y 轴上定义下和上
[b,t](下比上小),
z 轴上定义远和近
[f,n](远比近小)。
不管 x, y 多大,都将其映射到
[−1,1] 之间。这也是个约定俗成的事情,能方便计算。这样任何空间中的长方体,都可以映射成一个标准的立方体。
这也是标准化设备坐标(NDC)的定义。
💡
上面的左比右小是相对于
x 轴来说的,下比上小是相对于
y 轴说的,但
z 轴上不太直观,因为我们推导的 NDC 是右手坐标系,(相机)看的是
−z 方向,因此一个面离我们远,说明
z 值更小。离我们近,说明
z 值更大。
💡
在标准化设备坐标系中 OpenGL 使用的是左手坐标系,因为左手系在这一点上会比较方便,其
z 值离相机越近越小,也就是
n<f。但也会造成别的问题,
x×y=zSlightly different orders (to the “simple way”)
- Center cuboid by translating 移到原点
- Scale into “canonical” cube 映射到 [-1, 1],也就是缩放
Translate (center to origin) first, then scale (length/width/height to 2) 因为 -1 到 1 的长度就是 2。
因此我们可以用一个平移矩阵和缩放矩阵来求出正交投影矩阵,先平移,再缩放:
Mortho=⎣⎡r−l20000t−b20000n−f200001⎦⎤⎣⎡100001000010−2r+l−2t+b−2n+f1⎦⎤=⎣⎡r−l20000t−b20000n−f20−r−lr+l−t−bt+b−n−fn+f1⎦⎤
💡
如果把长方体范围缩成立方体,物体不会被拉伸吗?
会,这就涉及到另外一个变换。在所有变换做完之后,还要做一个视口变换,还要做一次拉伸。
Perspective Projection 透视投影
- Most common in Computer Graphics, art, visual system
- Further objects are smaller
- Parallel lines not parallel; converge to single point
平行线就是永不相交的两条线,但照片上铁轨是平行的,却交于一点。透视投影的情况下,一个平面相当于被投影到了另外一个平面上,这种情况下就不是平行线了。
Recall
- Recall: property of homogeneous coordinates
- (x,y,z,1),(kx,ky,kz,k!=0),(xz,yz,z2,z!=0) all represent the same point (x, y, z) in 3D
- 只要一个点乘于一个不为零的 k,那么它们还是一个点。那么我们还可以将其乘以 z,其表示的点还是空间中同样的点。下面我们会用到。
- e.g. (1, 0, 0, 1) and (2, 0, 0, 2) both represent (1, 0, 0)
怎么做透视投影
How to do perspective projection
- First “squish” the frustum into a cuboid (n→n,f→f)(Mpersp→ortho)
- Do orthographic projection ( Mortho, already known!)
透视投影的视锥体中,远的平面比近的平面要大。
我们可以把远的平面往里“挤”,“挤”到同一高度且同近平面大小,“挤”成空间中的长方体,再做正交投影就解决了。
我们已经知道正交投影怎么做了,因此剩下的就是“挤”这个操作。
在这个过程中,需要规定:
求出任何一个点挤压后的 x′,y′ 值
要做“挤”的操作,首先要知道任何一个点的
x,y 值是怎么变化的。因为我们任何一个面都要挤成近平面大小,我们也可以将
(x,y,z)投影到近平面上求出变换后的
x′,y′ 值。对于
x,y 值来说,这种变换是线性的。
因此,在视锥体的上面一部分中,我们可以通过相似三角形求出变换后的
x′,y′ 值。(
z′ 值不是线性变化的,后面会提到)
上图中,
n 为近平面的
z 值
z’,
z 为任何一个点
(x,y,z)中的
z 值。
挤压后的
y’ 值,我们可以通过相似三角形原理得出:
y′=znyx′=znx在齐次坐标系中,对于变换后的
(x’,y’,z’) 我们只剩下
z’ 未知。
这里给矩阵乘了
z,其表示的点还是空间中同样的点。
⎝⎛xyz1⎠⎞⇒⎝⎛x’y’z’1⎠⎞=⎝⎛nx/zny/zunknown1⎠⎞=multbyz=⎝⎛nxnystill unknownz⎠⎞也就是说
(x,y,z,1) 经过
Mpersp→ortho(4×4) 矩阵“挤压”后,会被映射到
(nx,ny,??,z):
Mpersp→ortho(4×4)⎝⎛xyz1⎠⎞=⎝⎛nxny unknown z⎠⎞根据上式,我们可以得出部分的
Mpersp→ortho 矩阵:
Mpersp→ortho=⎝⎛n0?00n?000?100?0⎠⎞对于
z,我们不知道挤压后
z 会怎么变,我们
只规定了近的平面上和远的平面上
z 不变。
Observation: the third row is responsible for z’
- Any point on the near plane will not change
- 近平面的点不变,对于任何 (x,y,n,1) 运算完了一定还是 (x,y,n,1)
- Any point’s z on the far plane will not change
- 远平面的点,虽然 x,y 会变化,但是 z 没有变。
求出任何一个点挤压后的 z’ 值
由“近平面的点不变,对于任何
(x,y,n,1) 运算完了一定还是
(x,y,n,1)”可得:
这里给矩阵乘了 n,其表示的点还是空间中同样的点。
⎝⎛xyn1⎠⎞⇒⎝⎛xyn1⎠⎞=multbyn=⎝⎛nxnyn2n⎠⎞因此
Mpersp→ortho 第三行一定是
(0,0,A,B) 的形式,因为:
(00AB)⎝⎛xyn1⎠⎞=n2由上式可得:
An+B=n2前面我们已经知道第三行前两个数是 0。
💡
我们前面已经规定了远平面的中心经过
Mpersp→ortho 变换后也不会发生变化。
另外一个等式可以用远平面可以用其特殊的中心点得出,给中心点再乘个
f 可得:
⎝⎛00f1⎠⎞⇒⎝⎛00f1⎠⎞=multbyf=⎝⎛00f2f⎠⎞由上式可得:
Af+B=f2根据两式可得
Mpersp→ortho :
An+B=n2Af+B=f2⇒A=n+fB=−nfMpersp→ortho=⎝⎛n0000n0000n+f100−nf0⎠⎞Mpersp=MorthoMpersp→ortho💡
平截头体(Frustum)被压缩成长方体以后,内部的点的
z 值是更偏向于近平面还是更偏向于远平面?
🕓
推导 z 值 (1)定义视锥
前面提到了长方体近平面的 l, r, b, t (left, right, bottom, top),有没有更好的方法去定义这些呢?
vertical field-of-view (fovY) and aspect ratio
我们现实中相机有视角的定义,也就是可以看到的角度的范围,也就是 field of view。广角相机就是可视角度比较大,对于视锥体来说,就是张的比较开。
垂直的可视角度就是 fovY。而相机的长宽比就是 aspect ratio。
💡
我们也可以通过 fovY 和 aspect ratio,来推出水平的可视角度。
How to convert from fovY and aspect to l, r, b, t?
上图中,夹角为
2fovY,红点坐标为
(0,t,n),
t 为屏幕高度的一半,那么根据三角函数的定义就可以得出:
tan2fovYcot2fovYaspect=∣n∣t=t∣n∣=tr完成推导正交投影矩阵
正交投影没有 fovY,在 Unity 中,正交投影的参数由 Camera 组件中的参数 Size, Near, Far(Viewport Rect 暂时忽略)和 Game 视图的横纵比(aspect ratio)共同决定。
这里的 Near 是近裁面的距离,也就是
−n,Far 同理,等于
−f。
Size 属性用来更改视锥体竖直方向上高度的一半,也就是前面近平面的高度
t。
由此可得正交投影近远平面的高度
t−b 为:
2⋅Size=2⋅tAspect⋅近远平面的高度=2⋅Aspect⋅Size=2⋅Aspect⋅t Mortho=⎣⎡r−l20000t−b20000n−f20−r−lr+l−t−bt+b−n−fn+f1⎦⎤=⎣⎡Aspect⋅Size10000Size10000n−f20−r−lr+l−t−bt+b−n−fn+f1⎦⎤注意:这里的
n 和
f 是
−z 轴上的,代表近裁面和远裁面的
z 值,值为负数。
完成推导透视投影矩阵
前面已经得出:
Mpersp→ortho=⎝⎛n0000n0000n+f100−nf0⎠⎞Mpersp=MorthoMpersp→ortho=⎣⎡Aspect⋅Size10000Size10000n−f20−r−lr+l−t−bt+b−n−fn+f1⎦⎤⎣⎡n0000n0000n+f100−nf0⎦⎤=⎣⎡Aspect⋅Sizen0000Sizen00−2⋅Aspect⋅Sizer+l0n−fn+f100n−f−2nf0⎦⎤注意:这里的
n 和
f 是
−z 轴上的,代表近裁面和远裁面的
z 值,值为负数。
通常我们透视投影的参数除了近裁面远裁面的距离外,还会有
fov 和
Aspect,且
r+l=0,因此整理公式可得:
Mpersp=⎣⎡Aspect⋅Sizen0000Sizen0000n−fn+f100n−f−2nf0⎦⎤ =⎣⎡Aspect1⋅t−∣n∣0000t−∣n∣0000n−fn+f100n−f−2nf0⎦⎤ =⎣⎡Aspect−cot2fovY0000−cot2fovY0000n−fn+f100n−f−2nf0⎦⎤
对投影变换的两种变换的推导,大家可以用 n, f 代表近远平面的距离来再推一遍。