矩阵变换
矩阵变换
矩阵的调用顺序和作用可以参考之前的博客:
渲染流程概述
线性代数相关数学知识可以参考
https://www.bilibili.com/video/av6731067/?p=13
我们的模型矩阵,视图矩阵,投影矩阵大小都是4x4的。
为了让一个三维的顶点坐标可以4x4的矩阵进行运算,必须先让他变成四维的,及齐次化,增加维度w。一个齐次化坐标(x, y, z, w),和世界坐标(x,y,z)的关系是:
(
x
w
,
y
w
,
z
w
)
=
(
x
,
y
,
z
)
\begin{pmatrix} {x \over w}, {y \over w}, {z \over w} \\ \end{pmatrix} =\begin{pmatrix} x,y,z \\ \end{pmatrix}
(wx,wy,wz)=(x,y,z)
1.模型矩阵
这里所有矩阵的使用方法都是矩阵左乘一个向量
平移矩阵
在三维空间中,一个顶点(x, y, z)平移(a, b, c)的结果是(x + a, y + b, z + c)这一变换用矩阵表达为:
[
1
0
0
a
0
1
0
b
0
0
1
c
0
0
0
1
]
∗
[
x
y
z
1
]
=
[
x
+
a
y
+
b
z
+
c
1
]
\begin{bmatrix} 1 & 0 & 0 & a \\ 0 & 1 & 0 & b \\ 0 & 0 & 1 & c \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}}={\begin{bmatrix} x+a\\ y+b\\ z+c\\ 1 \end{bmatrix}}
⎣⎢⎢⎡100001000010abc1⎦⎥⎥⎤∗⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡x+ay+bz+c1⎦⎥⎥⎤
Matrix translate(double x, double y, double z) {
Matrix t;
t = Matrix::identity();
t[0][3] = x;
t[1][3] = y;
t[2][3] = z;
return t;
};
缩放矩阵
在三维空间中,一个顶点(x, y, z)以坐标原点(0,0,0)为中心,各个方向缩放(a, b, c)的结果是(x * a, y * b, z * c)这一变换用矩阵表达为:
[
a
0
0
0
0
b
0
0
0
0
c
0
0
0
0
1
]
∗
[
x
y
z
1
]
=
[
x
∗
a
y
∗
b
z
∗
c
1
]
\begin{bmatrix} a & 0 & 0 & 0 \\ 0 & b & 0 & 0 \\ 0 & 0 & c & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}}={\begin{bmatrix} x*a\\ y*b\\ z*c\\ 1 \end{bmatrix}}
⎣⎢⎢⎡a0000b0000c00001⎦⎥⎥⎤∗⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡x∗ay∗bz∗c1⎦⎥⎥⎤
Matrix scale(double x, double y, double z) {
Matrix t;
t= Matrix::identity();
t[0][0] = x;
t[1][1] = y;
t[2][2] = z;
return t;
};
旋转矩阵
Reference:
旋转变换(一)旋转矩阵
常用三角函数公式
二维情况
如图所示点v 绕 原点旋转θ 角,设v到原点距离为r,
则v的坐标为
(
r
∗
c
o
s
(
ϕ
)
,
r
∗
s
i
n
(
ϕ
)
)
(r*cos(ϕ),r*sin(ϕ))
(r∗cos(ϕ),r∗sin(ϕ))
旋转后v‘坐标为 ( r ∗ c o s ( θ + ϕ ) , r ∗ s i n ( θ + ϕ ) ) (r*cos(θ+ϕ),r*sin(θ+ϕ)) (r∗cos(θ+ϕ),r∗sin(θ+ϕ))
通过两角和公式展开得:v‘坐标为
( r ∗ c o s θ c o s ϕ − r ∗ s i n θ s i n ϕ , r ∗ s i n θ c o s ϕ + r ∗ c o s θ s i n ϕ ) (r*cosθcosϕ−r*sinθsinϕ ,r*sinθcosϕ+r*cosθsinϕ ) (r∗cosθcosϕ−r∗sinθsinϕ,r∗sinθcosϕ+r∗cosθsinϕ)
代入v点表达式得: ( x ∗ c o s ϕ − y ∗ s i n ϕ , y ∗ c o s ϕ + x ∗ s i n ϕ ) (x*cosϕ−y*sinϕ ,y*cosϕ+x*sinϕ ) (x∗cosϕ−y∗sinϕ,y∗cosϕ+x∗sinϕ)
即
x
′
=
x
c
o
s
θ
−
y
s
i
n
θ
x′=xcosθ−ysinθ
x′=xcosθ−ysinθ
y ′ = x s i n θ + y c o s θ y′=xsinθ+ycosθ y′=xsinθ+ycosθ
用矩阵表达为:
[
x
′
y
′
]
=
[
c
o
s
θ
−
s
i
n
θ
s
i
n
θ
c
o
s
θ
]
∗
[
x
y
]
\begin{bmatrix}x' \\y' \\\end{bmatrix} = \begin{bmatrix} cosθ&−sinθ \\ sinθ&cosθ \\ \end{bmatrix} * \begin{bmatrix}x \\y \\\end{bmatrix}
[x′y′]=[cosθsinθ−sinθcosθ]∗[xy]
绕任意点的二维旋转
我们可以三部把绕任意点的情况转换为绕原点旋转的情况:
- 首先将旋转点移动到原点处
- 执行绕原点的旋转
- 再将旋转点移回到原来的位置
平移矩阵我们前面已经介绍过了,现在设点绕t(x,y)旋转θ度,则复合旋转矩阵为:
[
1
0
t
.
x
0
1
t
.
y
0
0
1
]
∗
[
c
o
s
θ
−
s
i
n
θ
0
s
i
n
θ
c
o
s
θ
0
0
0
1
]
∗
[
1
0
−
t
.
x
0
1
−
t
.
y
0
0
1
]
\begin{bmatrix} 1&0&t.x \\ 0&1&t.y \\ 0&0&1 \end{bmatrix}* \begin{bmatrix} cosθ&−sinθ&0 \\ sinθ&cosθ&0 \\ 0&0&1\end{bmatrix} * \begin{bmatrix} 1&0&-t.x \\ 0&1&-t.y \\ 0&0&1 \end{bmatrix}
⎣⎡100010t.xt.y1⎦⎤∗⎣⎡cosθsinθ0−sinθcosθ0001⎦⎤∗⎣⎡100010−t.x−t.y1⎦⎤
= [ c o s θ − s i n θ t . x s i n θ c o s θ t . y 0 0 1 ] ∗ [ 1 0 − t . x 0 1 − t . y 0 0 1 ] = \begin{bmatrix} cosθ&−sinθ&t.x \\ sinθ&cosθ&t.y \\ 0&0&1\end{bmatrix} * \begin{bmatrix} 1&0&-t.x \\ 0&1&-t.y \\ 0&0&1 \end{bmatrix} =⎣⎡cosθsinθ0−sinθcosθ0t.xt.y1⎦⎤∗⎣⎡100010−t.x−t.y1⎦⎤
= [ c o s θ − s i n θ − t . x ∗ c o s θ + t . y ∗ s i n θ + t . x s i n θ c o s θ − t . x ∗ s i n θ − t . y ∗ c o s θ + t . y 0 0 1 ] = \begin{bmatrix} cosθ&−sinθ&-t.x*cosθ+t.y*sinθ+t.x \\ sinθ&cosθ&-t.x*sinθ-t.y*cosθ+t.y \\ 0&0&1\end{bmatrix} =⎣⎡cosθsinθ0−sinθcosθ0−t.x∗cosθ+t.y∗sinθ+t.x−t.x∗sinθ−t.y∗cosθ+t.y1⎦⎤
= [ c o s θ − s i n θ ( 1 − c o s θ ) ∗ t . x + t . y ∗ s i n θ s i n θ c o s θ ( 1 − s i n θ ) ∗ t . y − t . x ∗ s i n θ 0 0 1 ] = \begin{bmatrix} cosθ&−sinθ&(1-cosθ)*t.x+t.y*sinθ\\ sinθ&cosθ&(1-sinθ)*t.y-t.x*sinθ \\ 0&0&1\end{bmatrix} =⎣⎡cosθsinθ0−sinθcosθ0(1−cosθ)∗t.x+t.y∗sinθ(1−sinθ)∗t.y−t.x∗sinθ1⎦⎤
三维基本旋转
1.绕坐标轴旋转
这里我们旋转方向用右手坐标系定义
绕z轴旋转其实就是在xoy面上进行旋转,与我们的二维情况完全相同,可以直接套用我们刚刚推出的二维旋转公式。
[
x
′
y
′
z
′
1
]
=
[
c
o
s
θ
−
s
i
n
θ
0
0
s
i
n
θ
c
o
s
θ
0
0
0
0
1
0
0
0
0
1
]
∗
[
x
y
z
1
]
{\begin{bmatrix} x'\\ y'\\ z'\\ 1 \end{bmatrix}}= \begin{bmatrix} cosθ & −sinθ & 0 & 0\\ sinθ & cosθ & 0 & 0 \\ 0 &0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}}
⎣⎢⎢⎡x′y′z′1⎦⎥⎥⎤=⎣⎢⎢⎡cosθsinθ00−sinθcosθ0000100001⎦⎥⎥⎤∗⎣⎢⎢⎡xyz1⎦⎥⎥⎤
绕x轴旋转为在yoz面上旋转,y轴类似二维情况的x轴,z轴为二维情况的y轴,符合右手方向,同样可以套用我们之前推出的公式:
[
x
′
y
′
z
′
1
]
=
[
1
0
0
0
0
c
o
s
θ
−
s
i
n
θ
0
0
s
i
n
θ
c
o
s
θ
0
0
0
0
1
]
∗
[
x
y
z
1
]
{\begin{bmatrix} x'\\ y'\\ z'\\ 1 \end{bmatrix}}= \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & cosθ & −sinθ & 0 \\ 0 & sinθ & cosθ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}}
⎣⎢⎢⎡x′y′z′1⎦⎥⎥⎤=⎣⎢⎢⎡10000cosθsinθ00−sinθcosθ00001⎦⎥⎥⎤∗⎣⎢⎢⎡xyz1⎦⎥⎥⎤
绕y轴旋转为在zox面上旋转,z轴类似二维情况的x轴,x轴为二维情况的y轴,即:
x′=zsinθ+xcosθ
y′=y
z′=zcosθ−xsinθ
可以得到矩阵
[
x
′
y
′
z
′
1
]
=
[
c
o
s
θ
0
s
i
n
θ
0
0
1
0
0
−
s
i
n
θ
0
c
o
s
θ
0
0
0
0
1
]
∗
[
x
y
z
1
]
{\begin{bmatrix} x'\\ y'\\ z'\\ 1 \end{bmatrix}}= \begin{bmatrix} cosθ & 0& sinθ & 0\\ 0 & 1& 0& 0 \\ -sinθ & 0& cosθ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}}
⎣⎢⎢⎡x′y′z′1⎦⎥⎥⎤=⎣⎢⎢⎡cosθ0−sinθ00100sinθ0cosθ00001⎦⎥⎥⎤∗⎣⎢⎢⎡xyz1⎦⎥⎥⎤
2.绕任意轴旋转
推导方法一:
类似与二维时绕任意点旋转,我们同样可以把绕任意轴的旋转分解成绕三个坐标轴分别旋转。假设现在有一个点P要绕向量u旋转θ角度,得到点Q,求Q的步骤如下:
- 将旋转轴u绕x轴旋转至xoz平面
- 将旋转轴u绕y轴旋转至于z轴重合
- 绕z轴旋转θ角
- 执行步骤2的逆过程
- 执行步骤1的逆过程
过程:
u为旋转轴,作点P在yoz平面的投影点q,q的坐标是(0, b, c),原点o与q点的连线oq和z轴的夹角α就是u绕x轴旋转的角度。通过这次旋转使得u向量旋转到xoz平面(图中的or向量)【步骤1】
过r点作z轴的垂线,or与z轴的夹角为β, 这个角度就是绕Y轴旋转的角度,通过这次旋转使得u向量旋转到与z轴重合【步骤2】
步骤一绕x轴旋转α度的旋转矩阵为:
[
1
0
0
0
0
c
o
s
α
−
s
i
n
α
0
0
s
i
n
α
c
o
s
α
0
0
0
0
1
]
\begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & cosα & −sinα & 0 \\ 0 & sinα & cosα & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
⎣⎢⎢⎡10000cosαsinα00−sinαcosα00001⎦⎥⎥⎤
由图片可以得到,
c
o
s
α
=
c
b
2
+
c
2
cosα = {c \over \sqrt{b^2+c^2}}
cosα=b2+c2c
s i n α = b b 2 + c 2 sinα = {b \over \sqrt{b^2+c^2}} sinα=b2+c2b
记第一步旋转矩阵Rx(α) ,代入数据得:
[
1
0
0
0
0
c
(
b
2
+
c
2
)
−
b
(
b
2
+
c
2
)
0
0
b
(
b
2
+
c
2
)
c
(
b
2
+
c
2
)
0
0
0
0
1
]
\begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & {c \over \sqrt{(b^2+c^2)}} & −{b \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & {b \over \sqrt{(b^2+c^2)}} & {c \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
⎣⎢⎢⎢⎡10000(b2+c2)c(b2+c2)b00−(b2+c2)b(b2+c2)c00001⎦⎥⎥⎥⎤
逆变换Rx(-α)为:
[
1
0
0
0
0
c
o
s
α
s
i
n
α
0
0
−
s
i
n
α
c
o
s
α
0
0
0
0
1
]
=
[
1
0
0
0
0
c
(
b
2
+
c
2
)
b
(
b
2
+
c
2
)
0
0
−
b
(
b
2
+
c
2
)
c
(
b
2
+
c
2
)
0
0
0
0
1
]
\begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & cosα & sinα & 0 \\ 0 & -sinα & cosα & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & {c \over \sqrt{(b^2+c^2)}} & {b \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & -{b \over \sqrt{(b^2+c^2)}} & {c \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
⎣⎢⎢⎡10000cosα−sinα00sinαcosα00001⎦⎥⎥⎤=⎣⎢⎢⎢⎡10000(b2+c2)c−(b2+c2)b00(b2+c2)b(b2+c2)c00001⎦⎥⎥⎥⎤
在完成步骤1之后,向量u被变换到了r的位置,我们继续步骤2的操作,绕y轴旋转负的β角(注意:这里的β是负的,因为我们按照右手方向进行旋转),经过这次变换之后向量u与z轴完全重合,由于这一步也是执行的一次绕Y轴的基本旋转,旋转矩阵(记作 Ry(−β))为:
[
c
o
s
β
0
s
i
n
β
0
0
1
0
0
−
s
i
n
β
0
c
o
s
β
0
0
0
0
1
]
\begin{bmatrix} cosβ & 0& sinβ & 0\\ 0 & 1& 0& 0 \\ -sinβ & 0& cosβ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
⎣⎢⎢⎡cosβ0−sinβ00100sinβ0cosβ00001⎦⎥⎥⎤
其中 c o s β = ( b 2 + c 2 ) ( a 2 + b 2 + c 2 ) cosβ = {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} cosβ=(a2+b2+c2)(b2+c2)
s i n β = a ( a 2 + b 2 + c 2 ) sinβ = {a \over \sqrt{(a^2+b^2+c^2)}} sinβ=(a2+b2+c2)a
记第二步旋转矩阵Ry(-β) ,代入数据得:
[
c
o
s
(
−
β
)
0
s
i
n
(
−
β
)
0
0
1
0
0
−
s
i
n
(
−
β
)
0
c
o
s
(
−
β
)
0
0
0
0
1
]
=
[
c
o
s
β
0
−
s
i
n
β
0
0
1
0
0
s
i
n
β
0
c
o
s
β
0
0
0
0
1
]
\begin{bmatrix} cos(-β) & 0& sin(-β) & 0\\ 0 & 1& 0& 0 \\ -sin(-β) & 0& cos(-β) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}= \begin{bmatrix} cosβ & 0& -sinβ & 0\\ 0 & 1& 0& 0 \\ sinβ & 0& cosβ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
⎣⎢⎢⎡cos(−β)0−sin(−β)00100sin(−β)0cos(−β)00001⎦⎥⎥⎤=⎣⎢⎢⎡cosβ0sinβ00100−sinβ0cosβ00001⎦⎥⎥⎤
=
[
(
b
2
+
c
2
)
(
a
2
+
b
2
+
c
2
)
0
−
a
a
2
+
b
2
+
c
2
0
0
1
0
0
a
(
a
2
+
b
2
+
c
2
)
0
(
b
2
+
c
2
)
(
a
2
+
b
2
+
c
2
)
0
0
0
0
1
]
= \begin{bmatrix} {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0& - {a \over \sqrt{a^2+b^2+c^2}} & 0\\ 0 & 1& 0& 0 \\ {a \over \sqrt{(a^2+b^2+c^2)}} & 0& {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
=⎣⎢⎢⎢⎢⎢⎡(a2+b2+c2)(b2+c2)0(a2+b2+c2)a00100−a2+b2+c2a0(a2+b2+c2)(b2+c2)00001⎦⎥⎥⎥⎥⎥⎤
逆变换Ry(β)为:
[
c
o
s
β
0
s
i
n
β
0
0
1
0
0
−
s
i
n
β
0
c
o
s
β
0
0
0
0
1
]
=
[
(
b
2
+
c
2
)
(
a
2
+
b
2
+
c
2
)
0
a
(
a
2
+
b
2
+
c
2
)
0
0
1
0
0
−
a
(
a
2
+
b
2
+
c
2
)
0
(
b
2
+
c
2
)
(
a
2
+
b
2
+
c
2
)
0
0
0
0
1
]
\begin{bmatrix} cosβ & 0& sinβ & 0\\ 0 & 1& 0& 0 \\ -sinβ & 0& cosβ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}= \begin{bmatrix} {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0& {a \over \sqrt{(a^2+b^2+c^2)}} & 0\\ 0 & 1& 0& 0 \\ -{a \over \sqrt{(a^2+b^2+c^2)}} & 0& {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
⎣⎢⎢⎡cosβ0−sinβ00100sinβ0cosβ00001⎦⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎡(a2+b2+c2)(b2+c2)0−(a2+b2+c2)a00100(a2+b2+c2)a0(a2+b2+c2)(b2+c2)00001⎦⎥⎥⎥⎥⎥⎤
最终得到 绕任意轴u旋转的旋转矩阵是【因为使用的列向量,因此执行的是左乘(从右往左)】:
M
R
=
R
x
(
−
α
)
∗
R
y
(
β
)
∗
R
z
(
θ
)
∗
R
y
(
−
β
)
∗
R
x
(
α
)
=
MR=Rx(−α) * Ry(β) * Rz(θ) * Ry(−β) * Rx(α)=
MR=Rx(−α)∗Ry(β)∗Rz(θ)∗Ry(−β)∗Rx(α)=
其中(u, v, w)对应上文(a,b,c),如果旋转轴向量时单位化的,矩阵可以化简为:
推导方法二:
除了将旋转分解到三个方向,我们还能直接构建新的坐标系进行旋转,再将新坐标系的坐标转换到世界坐标系里。
代码:
Matrix rotate(Vec3f &axis,double theta) {
Matrix t;
axis.normalize();
theta = theta * PI / 180.0;
double cosTheta = std::cos(theta);
double sinTheta = std::sin(theta);
//std::cout << "sinAngle:" << sinTheta << std::endl;
//std::cout << "cosAngle:" << cosTheta << std::endl;
double u = axis[0];
double v = axis[1];
double w = axis[2];
t[0][0] = cosTheta + (u * u) * (1. - cosTheta);
t[0][1] = u * v * (1. - cosTheta) + w * sinTheta;
t[0][2] = u * w * (1.- cosTheta) - v * sinTheta;
t[0][3] = 0;
t[1][0] = u * v * (1. - cosTheta) - w * sinTheta;
t[1][1] = cosTheta + v * v * (1 - cosTheta);
t[1][2] = w * v * (1. - cosTheta) + u * sinTheta;
t[1][3] = 0;
t[2][0] = u * w * (1. - cosTheta) + v * sinTheta;
t[2][1] = v * w * (1. - cosTheta) - u * sinTheta;
t[2][2] = cosTheta + w * w * (1. - cosTheta);
t[2][3] = 0;
t[3][0] = 0;
t[3][1] = 0;
t[3][2] = 0;
t[3][3] = 1;
return t;
};
问题:
模型矩阵的第四行是(0, 0, 0, 1),它的运算结果是让新向量的w等于原来的w,那么为什么模型矩阵的大小不是3x4而是4x4呢?更近一步,为什么我们要用矩阵来表达这一变换,而不是直接让两个向量相加得到最终结果?
1.为什么使用4x4矩阵
假设我们的模型有n个顶点。在整个流程里,模型的每一个顶点都要乘以模型矩阵,视图矩阵,投影矩阵得到最终的坐标。这样的话我们就要进行3*n次矩阵乘法。但是同一个模型的这三个矩阵在这一帧画面里都是相同的,又因为矩阵乘法的符合结合律,所以我们可以先让模型矩阵,视图矩阵,投影矩阵相乘得到MVP复合矩阵,再让每一个顶点乘以复合矩阵,此时计算量就降低到n+2次。
故使用复合矩阵可以大大提高我们的计算效率,虽然模型矩阵和视图矩阵的第四维都不对w进行操作,但是透视矩阵会。所以我们要让矩阵的大小符合计算规则,即所有矩阵大小都是4x4。让他们可以互相相乘。
我们用4x4矩阵表示平移也是同样的目的。
2.视图矩阵
Reference:计算机图形学视图矩阵推导过程
视图矩阵的作用时将世界坐标系的点转换到摄像机坐标系,首先我们先来构建摄像机坐标系。
构建摄像机坐标系需要3个参数,摄像机位置,摄像机看向的目标,摄像机的上方向。摄像机位置,摄像机看向的目标做差可以得到摄像机的视线方向,加上我们指定的上方向,可以保证摄像机不会绕视线方向旋转。这三个参数就能唯一指定一个摄像机。
具体构建坐标系过程为:
假设视点或camera的局部坐标系为UVN,UVN分别指向右方、上方和后方从而构成右手坐标系,视点则局部坐标系处于坐标原点。
1、首先我们来求得视线方向 N = camPos – looka,并把N归一化。
2、up和N差积得到右向量U, U= up X N,归一化U。
3、然后N和U差积得到垂直于U,N平面的上向量V,V = U x N
此时我们已经构建好了摄像机坐标系的基向量。也就得到了摄像机坐标系到世界坐标系的过度矩阵R。
R
=
[
U
.
x
V
.
x
N
.
x
0
U
.
y
V
.
y
N
.
y
0
U
.
z
V
.
z
N
.
z
0
0
0
0
1
]
R= \begin{bmatrix} U.x & V.x & N.x & 0 \\ U.y & V.y & N.y & 0 \\ U.z & V.z &N.z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
R=⎣⎢⎢⎡U.xU.yU.z0V.xV.yV.z0N.xN.yN.z00001⎦⎥⎥⎤
我们的目标是计算世界坐标系中的物体在摄像机坐标系下的坐标,将世界坐标系平移至于相机坐标系重合,此时就可以利用过度矩阵进行坐标变换。
T
=
[
1
0
0
c
a
m
P
o
s
.
x
0
1
0
c
a
m
P
o
s
.
y
0
0
1
c
a
m
P
o
s
.
z
0
0
0
1
]
T= \begin{bmatrix} 1 & 0 & 0 & camPos.x \\ 0 & 1 & 0 & camPos.y \\ 0 & 0 &1 & camPos.z \\ 0 & 0 & 0 & 1 \end{bmatrix}
T=⎣⎢⎢⎡100001000010camPos.xcamPos.ycamPos.z1⎦⎥⎥⎤
这样这个旋转R和平移T矩阵的组合矩阵M=T∗R,就是将相机坐标系中坐 标变换到世界坐标系中坐标的变换矩阵,那么所求的视图变换矩阵(世界坐标系中坐标转换到相机坐标系中坐标的矩阵)即为这个矩阵的逆: v i e w = M − 1 = ( T ∗ R ) − 1 = R − 1 ∗ T − 1 view=M^{−1}=(T*R)^{−1}=R^{−1}*T^{−1} view=M−1=(T∗R)−1=R−1∗T−1
= [ U . x U . y U . z 0 V . x V . y V . z 0 N . x N . y N . z 0 0 0 0 1 ] ∗ [ 1 0 0 − c a m P o s . x 0 1 0 − c a m P o s . y 0 0 1 − c a m P o s . z 0 0 0 1 ] =\begin{bmatrix} U.x & U.y & U.z &0 \\ V.x &V.y & V.z & 0 \\ N.x & N.y &N.z& 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} *\begin{bmatrix} 1 & 0 & 0 & -camPos.x \\ 0 & 1 & 0 & -camPos.y \\ 0 & 0 &1 & -camPos.z \\ 0 & 0 & 0 & 1 \end{bmatrix} =⎣⎢⎢⎡U.xV.xN.x0U.yV.yN.y0U.zV.zN.z00001⎦⎥⎥⎤∗⎣⎢⎢⎡100001000010−camPos.x−camPos.y−camPos.z1⎦⎥⎥⎤
=
[
U
.
x
U
.
y
U
.
z
−
U
∗
c
a
m
P
o
s
V
.
x
V
.
y
V
.
z
−
V
∗
c
a
m
P
o
s
N
.
x
N
.
y
N
.
z
−
N
∗
c
a
m
P
o
s
0
0
0
1
]
=\begin{bmatrix} U.x & U.y & U.z &-U*camPos \\ V.x &V.y & V.z & -V*camPos \\ N.x & N.y &N.z& -N*camPos \\ 0 & 0 & 0 & 1 \end{bmatrix}
=⎣⎢⎢⎡U.xV.xN.x0U.yV.yN.y0U.zV.zN.z0−U∗camPos−V∗camPos−N∗camPos1⎦⎥⎥⎤
代码:
Matrix lookat(Vec3f &eye, Vec3f& center, Vec3f& up){
Vec3f z = (eye - center).normalize();
Vec3f x = cross(up, z).normalize();
Vec3f y = cross(z, x).normalize();
Matrix Minv;
Minv= Matrix::identity();
for (int i = 0; i < 3; i++) {
Minv[0][i] = x[i];
Minv[1][i] = y[i];
Minv[2][i] = z[i];
}
Minv[0][3] = -1*(x * eye);
Minv[1][3] = -1*(y * eye);
Minv[2][3] = -1*(z * eye);
return Minv;
}
3.投影矩阵
Reference:
OpenGL学习脚印: 投影矩阵和视口变换矩阵(math-projection and viewport matrix)
AirGuanZ:透视投影矩阵推导
YEMI:透视投影矩阵推导
醉里挑灯看剑:投影矩阵推导
投影矩阵主要分为两种,正交投影和透视投影。
两者都会将可见范围(对于透视投影是四棱台,正交投影是长方体)中的点映射到一个立方体中。
透视投影
指定透视投影的视锥需要6个参数,投影平面左边的位置(left),投影平面右边的位置(right),投影平面上边(top),投影平面下边(bottom),投影平面距摄像机的距离(near),最远平面距离摄像机的距离(far)。下面会用到这些参数的英文首字母简写。
我们的目标是最终将视图坐标(eye)通过投影矩阵变换为裁切坐标(clip),再通过透视除法将裁切坐标(clip)变成规范化设备坐标系(NDC)坐标,他们的转换关系如下
[
x
c
h
i
p
y
c
h
i
p
z
c
h
i
p
w
c
h
i
p
]
=
P
∗
[
x
e
y
e
y
e
y
e
z
e
y
e
w
e
y
e
]
\begin{bmatrix} x_{chip}\\ y_{chip}\\ z_{chip}\\ w_{chip}\\ \end{bmatrix} =P*\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix}
⎣⎢⎢⎡xchipychipzchipwchip⎦⎥⎥⎤=P∗⎣⎢⎢⎡xeyeyeyezeyeweye⎦⎥⎥⎤
[
x
n
d
c
y
n
d
c
z
n
d
c
]
=
[
x
c
h
i
p
/
w
c
h
i
p
y
c
h
i
p
/
w
c
h
i
p
z
c
h
i
p
/
w
c
h
i
p
]
(0)
\tag{0}\begin{bmatrix} x_{ndc}\\ y_{ndc}\\ z_{ndc}\\ \end{bmatrix} =\begin{bmatrix} x_{chip} / w_{chip}\\ y_{chip} / w_{chip}\\ z_{chip} / w_{chip}\\ \end{bmatrix}
⎣⎡xndcyndczndc⎦⎤=⎣⎡xchip/wchipychip/wchipzchip/wchip⎦⎤(0)
左边是裁切坐标系示意图,右边是ndc示意图
我们假设视锥内有一点
P
(
x
e
y
e
,
y
e
y
e
,
z
e
y
e
)
P(x_{eye}, y_{eye}, z_{eye})
P(xeye,yeye,zeye)
他投影到投影平面的点为
P
(
x
p
r
o
j
e
c
t
,
y
p
r
o
j
e
c
t
,
−
n
e
a
r
)
P(x_{project}, y_{project}, -near)
P(xproject,yproject,−near)
通过俯视图,我们通过相似三角西得到如下关系
x p r o j e c t x e y e = − n e a r z e y e {x_{project} \over {x_{eye}}} = {{-near} \over {z_{eye}}} xeyexproject=zeye−near
即:
x
p
r
o
j
e
c
t
=
−
n
e
a
r
∗
x
e
y
e
z
e
y
e
(1)
\tag{1} x_{project} = {{-near * x_{eye}} \over {z_{eye}}}
xproject=zeye−near∗xeye(1)
同理通过侧视图我们可以得到y的值:
y
p
r
o
j
e
c
t
=
−
n
e
a
r
∗
y
e
y
e
z
e
y
e
(2)
\tag{2} y_{project} = {{-near * y_{eye}} \over {z_{eye}}}
yproject=zeye−near∗yeye(2)
因为我们需要Z值保存深度信息后续使用,所以这里先设Z为f(z),那么我们当前的投影坐标就是:
P
′
=
(
−
n
e
a
r
∗
x
e
y
e
z
e
y
e
,
−
n
e
a
r
∗
y
e
y
e
z
e
y
e
,
f
(
z
)
)
P' = ({{-near * x_{eye}} \over {z_{eye}}},{{-near * y_{eye}} \over {z_{eye}}},f(z))
P′=(zeye−near∗xeye,zeye−near∗yeye,f(z))
P’x和P‘y都不是关于Px,Py的线性函数,不能通过矩阵变换得到,我们这里进行等价的齐次变化
P
′
=
(
n
e
a
r
∗
x
e
y
e
,
n
e
a
r
∗
y
e
y
e
,
−
z
e
y
e
∗
f
(
z
)
,
−
z
e
y
e
)
P' = (near * x_{eye}, near * y_{eye},-z_{eye}*f(z),-z_{eye})
P′=(near∗xeye,near∗yeye,−zeye∗f(z),−zeye)
现在我们的运算写成矩阵形式为:
[
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
]
∗
[
x
e
y
e
y
e
y
e
z
e
y
e
w
e
y
e
]
=
[
n
e
a
r
∗
x
e
y
e
n
e
a
r
∗
y
e
y
e
f
(
z
)
−
z
e
y
e
]
\begin{bmatrix} ?&?&?&?\\ ?&?&?&?\\ ?&?&?&?\\ ?&?&?&?\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} near * x_{eye}\\ near * y_{eye}\\ f(z)\\ -z_{eye}\\ \end{bmatrix}
⎣⎢⎢⎡????????????????⎦⎥⎥⎤∗⎣⎢⎢⎡xeyeyeyezeyeweye⎦⎥⎥⎤=⎣⎢⎢⎡near∗xeyenear∗yeyef(z)−zeye⎦⎥⎥⎤
因为 结果的w维已经确定为-Zeye,我们可以推出矩阵最后一行为:
[
?
?
?
?
?
?
?
?
?
?
?
?
0
0
−
1
0
]
∗
[
x
e
y
e
y
e
y
e
z
e
y
e
w
e
y
e
]
=
[
n
e
a
r
∗
x
e
y
e
n
e
a
r
∗
y
e
y
e
f
(
z
)
−
z
e
y
e
]
\begin{bmatrix} ?&?&?&?\\ ?&?&?&?\\ ?&?&?&?\\ 0&0&-1&0\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} near * x_{eye}\\ near * y_{eye}\\ f(z)\\ -z_{eye}\\ \end{bmatrix}
⎣⎢⎢⎡???0???0???−1???0⎦⎥⎥⎤∗⎣⎢⎢⎡xeyeyeyezeyeweye⎦⎥⎥⎤=⎣⎢⎢⎡near∗xeyenear∗yeyef(z)−zeye⎦⎥⎥⎤
接下来我们将投影平面上的坐标转线性映射到(-1,1)空间。
其中Xp的映射关系如下图所示:
设这条直线方程为
X
n
d
c
=
A
∗
X
p
r
o
j
e
c
t
+
B
X_{ndc} = A * X_{project} + B
Xndc=A∗Xproject+B
我们已知直线上两点(l,-1), (r,1);
带入直线得:
{
−
1
=
A
∗
l
+
B
1
=
A
∗
r
+
B
⇒
{
A
=
2
r
−
l
B
=
l
+
r
l
−
r
\begin{cases} -1 = A * l +B\\ 1 = A * r +B\\ \end{cases}⇒\begin{cases} A= {2 \over {r-l}}\\ B={ {l+r}\over{l-r}}\\ \end{cases}
{−1=A∗l+B1=A∗r+B⇒{A=r−l2B=l−rl+r
即
X
n
d
c
=
2
r
−
l
∗
X
p
r
o
j
e
c
t
+
l
+
r
l
−
r
(3)
\tag{3} X_{ndc} = {2 \over {r-l}} * X_{project} + { {l+r}\over{l-r}}
Xndc=r−l2∗Xproject+l−rl+r(3)
将(1)带入(3)得:
X
n
d
c
=
2
r
−
l
∗
−
n
e
a
r
∗
x
e
y
e
z
e
y
e
+
l
+
r
l
−
r
X_{ndc} = {2 \over {r-l}} * {{-near * x_{eye}} \over {z_{eye}}} + { {l+r}\over{l-r}}
Xndc=r−l2∗zeye−near∗xeye+l−rl+r
同理可得
Y
n
d
c
=
2
t
−
b
∗
−
n
e
a
r
∗
y
e
y
e
z
e
y
e
+
t
+
b
b
−
t
Y_{ndc} = {2 \over {t-b}} * {{-near * y_{eye}} \over {z_{eye}}} + { {t+b}\over{b-t}}
Yndc=t−b2∗zeye−near∗yeye+b−tt+b
根据ndc坐标和clip坐标的关系(0)得到
X
c
l
i
p
=
2
∗
n
r
−
l
∗
x
e
y
e
−
l
+
r
l
−
r
∗
z
e
y
e
X_{clip} = {2*n \over {r-l}} * { x_{eye}} - { {l+r}\over{l-r}}*z_{eye}
Xclip=r−l2∗n∗xeye−l−rl+r∗zeye
Y c l i p = 2 ∗ n t − b ∗ y e y e − t + b b − t ∗ z e y e Y_{clip} ={2*n \over {t-b}} * { y_{eye}} - { {t+b}\over{b-t}}*z_{eye} Yclip=t−b2∗n∗yeye−b−tt+b∗zeye
由此得到矩阵的前两行为:
[
2
∗
n
r
−
l
0
−
l
+
r
l
−
r
0
0
2
∗
n
t
−
b
−
t
+
b
b
−
t
0
?
?
?
?
0
0
−
1
0
]
∗
[
x
e
y
e
y
e
y
e
z
e
y
e
w
e
y
e
]
=
[
X
c
l
i
p
Y
c
l
i
p
f
(
z
)
−
z
e
y
e
]
\begin{bmatrix} {2*n \over {r-l}}&0&- { {l+r}\over{l-r}}&0\\ 0&{2*n \over {t-b}}&- { {t+b}\over{b-t}}&0\\ ?&?&?&?\\ 0&0&-1&0\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} X_{clip}\\ Y_{clip}\\ f(z)\\ -z_{eye}\\ \end{bmatrix}
⎣⎢⎢⎡r−l2∗n0?00t−b2∗n?0−l−rl+r−b−tt+b?−100?0⎦⎥⎥⎤∗⎣⎢⎢⎡xeyeyeyezeyeweye⎦⎥⎥⎤=⎣⎢⎢⎡XclipYclipf(z)−zeye⎦⎥⎥⎤
由于Ze投影到平面时结果都为−n,因此寻找zn与之前的x,y分量不太一样。我们知道zn与x,y分量无关,因此上述矩阵P可以书写为:
[
2
∗
n
r
−
l
0
−
l
+
r
l
−
r
0
0
2
∗
n
t
−
b
−
t
+
b
b
−
t
0
0
0
A
B
0
0
−
1
0
]
∗
[
x
e
y
e
y
e
y
e
z
e
y
e
w
e
y
e
]
=
[
X
c
l
i
p
Y
c
l
i
p
f
(
z
)
−
z
e
y
e
]
\begin{bmatrix} {2*n \over {r-l}}&0&- { {l+r}\over{l-r}}&0\\ 0&{2*n \over {t-b}}&- { {t+b}\over{b-t}}&0\\ 0&0&A&B\\ 0&0&-1&0\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} X_{clip}\\ Y_{clip}\\ f(z)\\ -z_{eye}\\ \end{bmatrix}
⎣⎢⎢⎡r−l2∗n0000t−b2∗n00−l−rl+r−b−tt+bA−100B0⎦⎥⎥⎤∗⎣⎢⎢⎡xeyeyeyezeyeweye⎦⎥⎥⎤=⎣⎢⎢⎡XclipYclipf(z)−zeye⎦⎥⎥⎤
则有
Z
n
d
c
=
A
∗
z
e
y
e
+
B
∗
w
e
y
e
−
z
e
y
e
Z_{ndc} = {{A*z_{eye} + B*w_{eye}}\over{-z_{eye}}}
Zndc=−zeyeA∗zeye+B∗weye
又因为w_{eye} = 1,故可以化简得:
Z
n
d
c
=
A
∗
z
e
y
e
+
B
−
z
e
y
e
(4)
\tag{4} Z_{ndc} = {{A*z_{eye} + B}\over{-z_{eye}}}
Zndc=−zeyeA∗zeye+B(4)
我们已知Zndc与Zeye之间的对应关系(-n,-1)(-f,1)带入(4)得:
{ − 1 = − n A + B n 1 = − f A + B f ⇒ { A = − f + n f − n B = − 2 n f f − n \begin{cases} -1 = {{-nA + B}\over{n}}\\ 1 = {{-fA + B}\over{f}}\\ \end{cases} ⇒\begin{cases} A= -{{f+n} \over {f-n}}\\ B={ {-2nf}\over{f-n}}\\ \end{cases} {−1=n−nA+B1=f−fA+B⇒{A=−f−nf+nB=f−n−2nf
将AB带入4得到
Z
n
d
c
=
−
(
f
+
n
)
f
−
n
∗
z
e
y
e
+
−
2
n
f
f
−
n
−
z
e
y
e
Z_{ndc} = {{{{-(f+n)} \over {f-n}}*z_{eye} + { {-2nf}\over{f-n}}}\over{-z_{eye}}}
Zndc=−zeyef−n−(f+n)∗zeye+f−n−2nf
我们的投影矩阵为:
[
2
∗
n
r
−
l
0
−
l
+
r
l
−
r
0
0
2
∗
n
t
−
b
−
t
+
b
b
−
t
0
0
0
−
(
f
+
n
)
f
−
n
−
2
n
f
f
−
n
0
0
−
1
0
]
(透视投影矩阵)
\tag{透视投影矩阵} \begin{bmatrix} {2*n \over {r-l}}&0&- { {l+r}\over{l-r}}&0\\ 0&{2*n \over {t-b}}&- { {t+b}\over{b-t}}&0\\ 0&0&{{-(f+n)} \over {f-n}}&{{-2nf}\over{f-n}}\\ 0&0&-1&0\\ \end{bmatrix}
⎣⎢⎢⎡r−l2∗n0000t−b2∗n00−l−rl+r−b−tt+bf−n−(f+n)−100f−n−2nf0⎦⎥⎥⎤(透视投影矩阵)
如果我们的视锥是对称的,满足r = -l,t = -b,则投影矩阵可以化简为
[
n
r
0
0
0
0
n
t
0
0
0
0
−
(
f
+
n
)
f
−
n
−
2
n
f
f
−
n
0
0
−
1
0
]
(简化的透视投影矩阵)
\tag{简化的透视投影矩阵} \begin{bmatrix} {n \over {r}}&0&0&0\\ 0&{n \over {t}}&0&0\\ 0&0&{{-(f+n)} \over {f-n}}&{ {-2nf}\over{f-n}}\\ 0&0&-1&0\\ \end{bmatrix}
⎣⎢⎢⎡rn0000tn0000f−n−(f+n)−100f−n−2nf0⎦⎥⎥⎤(简化的透视投影矩阵)
通过fov计算投影矩阵
另外一种经常使用 的方式是通过视角(Fov),宽高比(Aspect)来指定透视投影
我们需要给定fov,Aspect,near,far来指定视锥。
通过fov和near,我们可以求得平面高度为
h
2
=
n
e
a
r
∗
t
a
n
(
θ
2
)
{h \over 2} = near * tan({θ\over 2})
2h=near∗tan(2θ)
通过平面高度和宽高比,我们可以求得投影平面宽度
w
2
=
h
2
∗
a
s
p
e
c
t
=
n
e
a
r
∗
t
a
n
(
θ
2
)
∗
a
s
p
e
c
t
{w \over 2} = {h \over 2}*aspect = near * tan({θ\over 2})*aspect
2w=2h∗aspect=near∗tan(2θ)∗aspect
宽高与之前的 l, r, t, b 的对应关系为:
r
=
w
2
=
n
e
a
r
∗
t
a
n
(
θ
2
)
∗
a
s
p
e
c
t
l
=
−
r
t
=
h
2
=
n
e
a
r
∗
t
a
n
(
θ
2
)
b
=
−
t
r = {w \over 2} = near * tan({θ\over 2})*aspect\\ l = -r\\ t = {h \over 2} = near * tan({θ\over 2})\\ b = -t\\
r=2w=near∗tan(2θ)∗aspectl=−rt=2h=near∗tan(2θ)b=−t
带入之前的简化投影矩阵得到
[
1
t
a
n
(
θ
2
)
∗
a
s
p
e
c
t
0
0
0
0
1
t
a
n
(
θ
2
)
0
0
0
0
−
(
f
+
n
)
f
−
n
−
2
n
f
f
−
n
0
0
−
1
0
]
(fov透视投影矩阵)
\tag{fov透视投影矩阵} \begin{bmatrix} {1 \over {tan({θ\over 2})∗aspect}}&0&0&0\\ 0&{1 \over { tan({θ\over 2})}}&0&0\\ 0&0&{{-(f+n)} \over {f-n}}&{ {-2nf}\over{f-n}}\\ 0&0&-1&0\\ \end{bmatrix}
⎣⎢⎢⎢⎡tan(2θ)∗aspect10000tan(2θ)10000f−n−(f+n)−100f−n−2nf0⎦⎥⎥⎥⎤(fov透视投影矩阵)
Matrix setFrustum(double fovy, double aspect, double near, double far)
{
double z_range = far - near;
Matrix matrix;
matrix= Matrix::identity();
assert(fovy > 0 && aspect > 0);
assert(near > 0 && far > 0 && z_range > 0);
matrix[1][1] = 1 / (float)tan(fovy / 2);
matrix[0][0] = matrix[1][1] / aspect;
matrix[2][2] = -(near + far) / z_range;
matrix[2][3] = -2 * near * far / z_range;
matrix[3][2] = -1;
matrix[3][3] = 0;
return matrix;
}
Zfighting问题
回过头去看透视投影部分,zn与ze的关系式
Z
n
d
c
=
−
(
f
+
n
)
f
−
n
∗
z
e
y
e
+
−
2
n
f
f
−
n
−
z
e
y
e
Z_{ndc} = {{{{-(f+n)} \over {f-n}}*z_{eye} + { {-2nf}\over{f-n}}}\over{-z_{eye}}}
Zndc=−zeyef−n−(f+n)∗zeye+f−n−2nf
这是一个非线性关系函数,函数关系如图。
从图我们可以看到,在近裁剪平面附近Zn值变化比较大,精确度较好;而在远裁剪平面附近,有一段距离内,Zn近乎持平,精确度不好。当增大远近裁剪平面的范围[−n,−f]后,如右边图所示,我们看到在远裁剪平面附近,不同相机坐标Ze对应的Zn相同,精确度低的现象更为明显,这种深度的精确度引起的问题称之为zFighting。要尽量减小[-n,-f]的范围,以减轻zFighting问题。
正交投影
对于正交投影,有
x
p
=
x
e
,
y
p
=
y
e
x_p=x_e,y_p=y_e
xp=xe,yp=ye
因而可以直接利用Xe与Xn的映射关系:[l,−1],[r,1],
利用Ye和Yn的映射关系:[b,−1],[t,1],
以及Ze和Zn的映射关系:[−n,−1],[−f,1]。
与透视矩阵推导类似,我们直接将映射关系[l,−1],[r,1]带入直线。
x
n
d
c
=
A
∗
x
e
y
e
+
B
x_{ndc}=A*x_{eye}+B
xndc=A∗xeye+B
解得:
x
n
d
c
=
2
r
−
l
∗
x
e
y
e
+
r
+
l
r
−
l
x_{ndc}={2\over{r-l}}*x_{eye}+{{r+l}\over{r-l}}
xndc=r−l2∗xeye+r−lr+l
同理可以得出
y
n
d
c
=
2
t
−
b
∗
y
e
y
e
+
t
+
b
t
−
b
y_{ndc}={2\over{t-b}}*y_{eye}+{{t+b}\over{t-b}}
yndc=t−b2∗yeye+t−bt+b
z n d c = − 2 f − n ∗ z e y e + f + n f − n z_{ndc}={-2\over{f-n}}*z_{eye}+{{f+n}\over{f-n}} zndc=f−n−2∗zeye+f−nf+n
对于正交坐标,因为xy维度没有变量z参与,所以可以不进行齐次化。故ndc坐标和裁切坐标相同。我们通过上面三个式子得到正交投影矩阵
[
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
]
(正交透视投影矩阵)
\tag{正交透视投影矩阵} \begin{bmatrix} {2 \over {r-l}}&0&0&-{{r+l}\over{r-l}}\\ 0& {2 \over {t-b}} &0&-{{t+b}\over{t-b}}\\ 0&0&{{-2} \over {f-n}}&-{ {f+n}\over{f-n}}\\ 0&0&0&1\\ \end{bmatrix}
⎣⎢⎢⎡r−l20000t−b20000f−n−20−r−lr+l−t−bt+b−f−nf+n1⎦⎥⎥⎤(正交透视投影矩阵)
如果视锥对称(r = −l, t = −b),我们同样可以进行简化得到:
[
1
r
0
0
0
0
1
t
0
0
0
0
−
2
f
−
n
−
f
+
n
f
−
n
0
0
0
1
]
(简化正交透视投影矩阵)
\tag{简化正交透视投影矩阵} \begin{bmatrix} {1 \over r}&0&0&0\\ 0& {1 \over t} &0&0\\ 0&0&{{-2} \over {f-n}}&-{ {f+n}\over{f-n}}\\ 0&0&0&1\\ \end{bmatrix}
⎣⎢⎢⎡r10000t10000f−n−2000−f−nf+n1⎦⎥⎥⎤(简化正交透视投影矩阵)
4.视窗变换
视窗变换需要指定4个参数,窗口的左下角位置(sx,sy),窗口宽度width,窗口宽度height
在ndc坐标系中,我们的所有坐标范围都在[-1,1]中,视窗变换的目的是让x坐标变换到[sx,sx+width],y坐标变换到[sy,sy+height],z坐标变换到[0,1]即:
x
s
c
r
e
e
n
=
s
x
+
x
(
x
n
d
c
+
1
)
∗
0.5
∗
w
i
d
t
h
y
s
c
r
e
e
n
=
s
y
+
(
y
n
d
c
+
1
)
∗
0.5
∗
h
e
i
g
h
t
z
s
c
r
e
e
n
=
(
z
n
d
c
+
1
)
∗
0.5
x_{screen} = s_x+x(x_{ndc}+1)*0.5*width\\ y_{screen} = s_y+(y_{ndc}+1)*0.5*height\\ z_{screen} = (z_{ndc}+1)*0.5
xscreen=sx+x(xndc+1)∗0.5∗widthyscreen=sy+(yndc+1)∗0.5∗heightzscreen=(zndc+1)∗0.5
写为矩阵模式为:
[
w
i
d
t
h
2
0
0
s
x
+
w
i
d
t
h
2
0
h
e
i
g
h
t
2
0
s
y
+
h
e
i
g
h
t
2
0
0
1
2
1
2
0
0
0
1
]
(视口变换矩阵)
\tag{视口变换矩阵} \begin{bmatrix} {width \over 2}&0&0&{s_x +{width \over 2} }\\ 0& {height \over 2} &0&{s_y + {height \over 2}}\\ 0&0&{1 \over 2}&{ 1\over2}\\ 0&0&0&1\\ \end{bmatrix}
⎣⎢⎢⎡2width00002height0000210sx+2widthsy+2height211⎦⎥⎥⎤(视口变换矩阵)