MVP Transformation(模型,观察,投影变换)
MVP变换是一种在三维图形渲染中常用的数学变换,全称为Model-View-Projection变换。它将三维模型从模型空间(Model Space)经过视图空间(View Space)转换到投影空间(Projection Space),MVP变换是三维图形渲染中的核心概念之一,它连接了三维模型的几何数据和最终的二维图像输出,是实现高质量渲染、相机控制和交互性的基础。
Model Transformation(模型变换)
模型变换是为了让模型的坐标从局部空间到世界空间进行转换。在渲染器中,如果把所有的顶点都记录在缓存中,则一移动模型,顶点坐标又要重新进行计算,重新计算顶点坐标这个过程不是最可怕的,最可怕的是顶点坐标会随着模型的运动而变换,这样一来存储顶点数据的缓冲就要在每一次变换时重新向 GPU 发送数据,性能开销极大。因此若只记录当模型根坐标在(0,0,0)时所有其他点的坐标(局部坐标),再使用矩阵变换来将所有的顶点移动到相应的位置,就不需要改变缓存中的顶点坐标了,而用于将模型顶点坐标转换到世界空间中的变换就被称为模型变换。
模型的 Transform 属性
在游戏引擎等软件中,通常都会看到一个模型具有一个 Transform 属性,此属性用于记录模型在世界空间中的 translate(位置),rotation(旋转)和 scale(放缩)。使用模型的这些属性,我们即可对模型进行模型变换。
模型变换矩阵推导
平移
进行平移只需要使用齐次坐标即可:
M T = [ 1 0 0 t x 0 1 0 t y 0 0 1 t z 0 0 0 1 ] M_T=\begin{bmatrix} 1&0&0&t_x\\0&1&0&t_y\\0&0&1&t_z\\0&0&0&1 \end{bmatrix} MT= 100001000010txtytz1
放缩
缩放不改变方向,只改变大小:
M S = [ s x 0 0 0 0 s y 0 0 0 0 s z 0 0 0 0 1 ] M_S=\begin{bmatrix} s_x&0&0&0\\0&s_y&0&0\\0&0&s_z&0\\0&0&0&1 \end{bmatrix} MS= sx0000sy0000sz00001
旋转
旋转可以用欧拉角或四元数进行表示,表示需要的参数如下:
- 欧拉角: [ r o l l , p i t c h , y a w ] [\mathrm{roll, pitch, yaw}] [roll,pitch,yaw]
- 四元数: a , b , c , d a,b,c,d a,b,c,d
欧拉角的表示更为直观,但会产生万向锁问题,四元数在计算机动画的插值中表现更好。
模型变换
将上述三种类型的矩阵作用在一起,即可得到模型变换矩阵,要注意矩阵的顺序是从右到左作用到局部空间中的顶点上的。即先进行缩放、旋转后,再进行平移。
M m o d e l = M T ⋅ M R ⋅ M S \begin{equation} M_{model}=M_T\cdot{}M_R\cdot{}M_S \end{equation} Mmodel=MT⋅MR⋅MS
View Transformation(观察变换)
在过渡到世界空间后,我们还希望能在某些观察者的角度上,以自己的坐标为原点来观察世界,不同观察者眼中都有不同的坐标。而这两个人眼中模型不同的坐标,就称作两个观察坐标,观察坐标所在的空间,就称作观察空间。观察变换,就是为了让模型坐标从世界空间过渡到观察空间。
观察变换矩阵推导
对于一个观察者(一般是一个相机,后文默认观察者为一个相机),我们会先定义相机位置和方向,通过相机的位置和方向可以推导出观察变换矩阵。
PS:用于观察变化的矩阵,就是常说的 LookUp 矩阵。
以左手系坐标轴为例,如图所示,我们首先定义相机的位置与方向:
- 相机位置(Position) e \mathbf{e} e
- 相机面对的方向(Look-at / forward direction) f \mathbf{f} f
- 相机向上方向(Up direction) u \mathbf{u} u
- 相机向右方向(Right direction)
r
\mathbf{r}
r
有了这些信息后,我们需要先进行平移变换,再进行旋转变换。
平移
平移只需要使用到相机位置 e ⃗ \vec{e} e,由于是要将相机位置看做 0 点,所以是减去相机位置。
T v i e w = [ 1 0 0 − e x 0 1 0 − e y 0 0 1 − e z 0 0 0 1 ] T_{view}=\begin{bmatrix} 1&0&0&-\mathbf{e}_x\\0&1&0&-\mathbf{e}_y\\0&0&1&-\mathbf{e}_z\\0&0&0&1 \end{bmatrix} Tview= 100001000010−ex−ey−ez1
旋转
旋转矩阵的推导可借由向量基底的概念,我们旋转的本质是将 xyz 轴上的坐标用基底为 [ r u f ] \begin{bmatrix} \mathbf{r}&\mathbf{u}&\mathbf{f} \end{bmatrix} [ruf] 的坐标表示。
反过来思考,若我们想将 [ r u f ] \begin{bmatrix} \mathbf{r}&\mathbf{u}&\mathbf{f} \end{bmatrix} [ruf]基底的坐标用xyz上的坐标来表示,只需要计算 [ r u f ] \begin{bmatrix} \mathbf{r}&\mathbf{u}&\mathbf{f} \end{bmatrix} [ruf]基底与坐标的点乘即可。由此可以想到, [ r u f ] \begin{bmatrix} \mathbf{r}&\mathbf{u}&\mathbf{f} \end{bmatrix} [ruf]所构成的矩阵,即是view旋转矩阵的逆矩阵,因此有 R v i e w − 1 = [ r x u x f x 0 r y u y f y 0 r z u z f z 0 0 0 0 1 ] R_{view}^{-1}=\begin{bmatrix} \mathbf{r}_x&\mathbf{u}_x&\mathbf{f}_x&0 \\\mathbf{r}_y&\mathbf{u}_y&\mathbf{f}_y&0 \\\mathbf{r}_z&\mathbf{u}_z&\mathbf{f}_z&0 \\0&0&0&1 \end{bmatrix} Rview−1= rxryrz0uxuyuz0fxfyfz00001 。此外,由于 [ r u f ] \begin{bmatrix} \mathbf{r}&\mathbf{u}&\mathbf{f} \end{bmatrix} [ruf] 基底是正交基底,此变换的矩阵是正交矩阵,只需将逆矩阵转置即可得到原矩阵,可得:
R v i e w = [ r x r y r z 0 u x u y u z 0 f x f y f z 0 0 0 0 1 ] R_{view}=\begin{bmatrix} \mathbf{r}_x&\mathbf{r}_y&\mathbf{r}_z&0 \\\mathbf{u}_x&\mathbf{u}_y&\mathbf{u}_z&0 \\\mathbf{f}_x&\mathbf{f}_y&\mathbf{f}_z&0 \\0&0&0&1 \end{bmatrix} Rview= rxuxfx0ryuyfy0rzuzfz00001
组合
最终将两个平移旋转两个矩阵合并,我们需要先平移,再旋转。
M v i e w = R v i e w T v i e w M_{view}=R_{view}T_{view} Mview=RviewTview
观察变换
在现代图形学 API 中,要获取 lookat 矩阵,一般需要的输入有:
- 相机位置(Position) e \mathbf{e} e
- 相机面对的物体的位置(Object center) c \mathbf{c} c
- 永远指向上的方向(t direction) t \mathbf{t} t(一般为 [ 0 1 0 ] T \begin{bmatrix} 0&1&0 \end{bmatrix}^T [010]T)
通过这三个输入,我们可以通过简单计算推导出所需的 f \mathbf{f} f(normalize( c − e \mathbf{c}-\mathbf{e} c−e)), r \mathbf{r} r( f \mathbf{f} f与 t \mathbf{t} t叉乘), u \mathbf{u} u( f \mathbf{f} f与 r \mathbf{r} r叉乘)。
注意:叉乘在左手坐标系下使用左手螺旋定则,右手坐标系下使用右手螺旋定则。
为了符合 x 轴向右,y 轴向上的习惯,左右手坐标系下的 lookat 矩阵并不相同。
左手坐标系
左手坐标系下的 lookat 矩阵就如上文推导:
M v i e w = R v i e w T v i e w = [ r x r y r z 0 u x u y u z 0 f x f y f z 0 0 0 0 1 ] [ 1 0 0 − e x 0 1 0 − e y 0 0 1 − e z 0 0 0 1 ] = [ r x r y r z − r ⋅ e u x u y u z − u ⋅ e f x f y f z − f ⋅ e 0 0 0 1 ] \begin{equation} M_{view}=R_{view}T_{view}= \begin{bmatrix} \mathbf{r}_x&\mathbf{r}_y&\mathbf{r}_z&0 \\\mathbf{u}_x&\mathbf{u}_y&\mathbf{u}_z&0 \\\mathbf{f}_x&\mathbf{f}_y&\mathbf{f}_z&0 \\0&0&0&1 \end{bmatrix} \begin{bmatrix} 1&0&0&-\mathbf{e}_x\\0&1&0&-\mathbf{e}_y\\0&0&1&-\mathbf{e}_z\\0&0&0&1 \end{bmatrix}= \begin{bmatrix} \mathbf{r}_x&\mathbf{r}_y&\mathbf{r}_z&-\mathbf{r}\cdot\mathbf{e} \\\mathbf{u}_x&\mathbf{u}_y&\mathbf{u}_z&-\mathbf{u}\cdot\mathbf{e} \\\mathbf{f}_x&\mathbf{f}_y&\mathbf{f}_z&-\mathbf{f}\cdot\mathbf{e} \\0&0&0&1 \end{bmatrix} \end{equation} Mview=RviewTview= rxuxfx0ryuyfy0rzuzfz00001 100001000010−ex−ey−ez1 = rxuxfx0ryuyfy0rzuzfz0−r⋅e−u⋅e−f⋅e1
右手坐标系
在右手坐标系下,当设定 x 轴向右,y 轴向上时,z 轴会向外,此时,若我们仍旧想将
r
\mathbf{r}
r对应 x 轴,
u
\mathbf{u}
u对应 y 轴的话,z 轴对应的将不再是
f
\mathbf{f}
f而是
−
f
-\mathbf{f}
−f,由此
f
\mathbf{f}
f自然就指向了-z 方向。这也就是为什么人们常说右手坐标系下相机变换相机朝向-z 方向。
此时,我们再想通过基底的概念来求观察矩阵的旋转部分时就会发现,xyz 轴对应的基底是 [ r ⃗ u ⃗ − f ⃗ ] \begin{bmatrix} \vec{r}&\vec{u}&-\vec{f} \end{bmatrix} [ru−f],因此,右手坐标系下的 lookat 矩阵就可以表示为:
M v i e w = R v i e w T v i e w = [ r x r y r z 0 u x u y u z 0 − f x − f y − f z 0 0 0 0 1 ] [ 1 0 0 − e x 0 1 0 − e y 0 0 1 − e z 0 0 0 1 ] = [ r x r y r z − r ⋅ e u x u y u z − u ⋅ e − f x − f y − f z f ⋅ e 0 0 0 1 ] \begin{equation} M_{view}=R_{view}T_{view}= \begin{bmatrix} \mathbf{r}_x&\mathbf{r}_y&\mathbf{r}_z&0 \\\mathbf{u}_x&\mathbf{u}_y&\mathbf{u}_z&0 \\ -\mathbf{f}_x &-\mathbf{f}_y &-\mathbf{f}_z &0 \\ 0&0&0&1 \end{bmatrix} \begin{bmatrix} 1&0&0&-\mathbf{e}_x\\0&1&0&-\mathbf{e}_y\\0&0&1&-\mathbf{e}_z\\0&0&0&1 \end{bmatrix}= \begin{bmatrix} \mathbf{r}_x&\mathbf{r}_y&\mathbf{r}_z&-\mathbf{r}\cdot\mathbf{e} \\\mathbf{u}_x&\mathbf{u}_y&\mathbf{u}_z&-\mathbf{u}\cdot\mathbf{e} \\-\mathbf{f}_x&-\mathbf{f}_y&-\mathbf{f}_z&\mathbf{f}\cdot\mathbf{e} \\0&0&0&1 \end{bmatrix} \end{equation} Mview=RviewTview= rxux−fx0ryuy−fy0rzuz−fz00001 100001000010−ex−ey−ez1 = rxux−fx0ryuy−fy0rzuz−fz0−r⋅e−u⋅ef⋅e1
——————————————————————
有时在其他资料或文献中会看到将观察变换写作
M
v
i
e
w
=
[
r
x
u
x
f
x
0
r
y
u
y
f
y
0
r
z
u
z
f
z
0
−
r
⋅
e
−
u
⋅
e
−
f
⋅
e
1
]
M_{view}=\begin{bmatrix} \mathbf{r}_x&\mathbf{u}_x&\mathbf{f}_x&0 \\\mathbf{r}_y&\mathbf{u}_y&\mathbf{f}_y&0 \\\mathbf{r}_z&\mathbf{u}_z&\mathbf{f}_z&0 \\-\mathbf{r}\cdot\mathbf{e}&-\mathbf{u}\cdot\mathbf{e}&-\mathbf{f}\cdot\mathbf{e}&1 \end{bmatrix}
Mview=
rxryrz−r⋅euxuyuz−u⋅efxfyfz−f⋅e0001
,或是赋值时M[0][0]=r.x; M[1][0]=r.y; M[2][0]=r.z
;,这有两种可能:
第一种是在这个库或资料中,把向量看做为行向量,在进行点乘时会进行
v
⋅
M
v
i
e
w
\mathbf{v} \cdot M_{view}
v⋅Mview。在这种情况下,矩阵与向量的相乘会从前向后进行,因此上文提到的多个矩阵相乘也需要将矩阵调整顺序。
第二种是在这个库或资料中,使用的是列主序的矩阵,即矩阵优先以列排布,M[1][0]不表示第二排第一列而是第一排第二列。这种情况下,矩阵与向量相乘依旧是
M
v
i
e
w
⋅
v
M_{view}\cdot \mathbf{v}
Mview⋅v,矩阵相乘顺序也无需改变,但是需要注意给矩阵赋值时不要找错位置。(比如glm)
——————————————————————
Projection Transformation(投影变换)
Clip Space(剪辑空间)
为方便图形学中几何体的渲染,在进行光栅化之前我们一般会把原始模型数据移动到一个称为剪辑空间的特殊坐标空间中,模型在这个空间中的坐标被称为 NDC(归一化设备坐标)。这是一个 2 个单位宽的立方体,以(0,0,0)为中心,角的范围从(-1,-1,-1)到(1,1,1)。
为保证向右x轴,向上y轴,向前z轴,剪辑空间是一个左手系空间。
投影变换方法
投影变换负责把观察空间中的坐标转换到剪辑空间中,将模型顶点的坐标变为 NDC 标准设备坐标。在三维空间中,相机为一个点,以一个视锥的形式,因此看到的视角是发散出去的,具有景深。透视变换的思想为先图像映射为正交图像(本来就以平行方向获取图像的情况下不需要这一步,例如 Directional Light),在正交图像的空间中,光线以平行方向获取图像,有利于后续的操作。然后将正交图像通过正交变换得到最终结果。
如下:左图为观察空间的视锥,右图为正交图像空间。
投影变换矩阵推导
在GAME101课程中,对投影变换矩阵的推导基于的假设是near和far是在右手坐标系下真实的z轴数值,因此导致near和far的数值都是负值,且near大于far(eg. near=-0.1; far=-1000),然而,在大部分图形学API中,会把near和far作为计算projection矩阵的参数,表示相机离近平面,远平面的距离。本文在进行推导时,直接将near far和相对于的Z值设定为不同的变量,以防搞混。
以左手系坐标轴为例,如图所示,我们需要的数值有:
- 相机离近平面的距离 n n n
- 相机离远平面的距离 f f f
- 相机近平面最左端的 x 值 l l l
- 相机近平面最右端的 x 值 r r r
- 相机近平面最上端的 y 值 t t t
- 相机近平面最下端的 y 值 b b b
- 相机近平面的 z 值 n e a r Z nearZ nearZ
- 相机远平面的 z 值 f a r Z farZ farZ
nearZ 和 farZ 分别代表近平面的 Z 值和远平面的 Z 值,在左手坐标系下,nearZ=n, farZ=f
,在右手坐标系下,nearZ=-n, farZ=-f
。
透视变换(Perspective Transformation)
透视变换是从观察空间的视锥到正交图像空间的变换,将观察空间锥形体中的点全部变换到一个长方形空间中。如图所示:
在透视变换公式的推导可通过待定系数法求解,
对于在视锥中的任意点 v = [ x y z 1 ] T \mathbf{v}=\begin{bmatrix} x&y&z&1 \end{bmatrix}^T v=[xyz1]T,都有 M p e r s p → o r t h o ⋅ v = [ n e a r Z ⋅ x n e a r Z ⋅ y k z ] T M_{persp\rightarrow{ortho}}\cdot \mathbf{v}=\begin{bmatrix}nearZ\cdot{}x&nearZ\cdot{}y &k&z \end{bmatrix}^T Mpersp→ortho⋅v=[nearZ⋅xnearZ⋅ykz]T(k 为未知数);
对于在近平面上的任意点 p = [ x y n e a r Z 1 ] T \mathbf{p}=\begin{bmatrix} x&y&nearZ&1 \end{bmatrix}^T p=[xynearZ1]T,都有 M p e r s p → o r t h o ⋅ p = [ x y n e a r Z 1 ] T M_{persp\rightarrow{ortho}}\cdot \mathbf{p}=\begin{bmatrix}x&y&nearZ&1 \end{bmatrix}^T Mpersp→ortho⋅p=[xynearZ1]T;
对于在远平面上的中心点 q = [ 0 0 f a r Z 1 ] T \mathbf{q}=\begin{bmatrix} 0&0&farZ&1 \end{bmatrix}^T q=[00farZ1]T,有 M p e r s p → o r t h o ⋅ q = [ 0 0 f a r Z 1 ] T M_{persp\rightarrow{ortho}}\cdot \mathbf{q}=\begin{bmatrix}0&0&farZ&1\end{bmatrix}^T Mpersp→ortho⋅q=[00farZ1]T。
由此可以算得:
M p e r s p → o r t h o = [ n e a r Z 0 0 0 0 n e a r Z 0 0 0 0 n e a r Z + f a r Z − n e a r Z ⋅ f a r Z 0 0 1 0 ] M_{persp\rightarrow{ortho}}=\begin{bmatrix} nearZ&0&0&0\\0&nearZ&0&0\\ 0&0&nearZ+farZ&-nearZ \cdot farZ\\0&0&1&0 \end{bmatrix} Mpersp→ortho= nearZ0000nearZ0000nearZ+farZ100−nearZ⋅farZ0
PS: 详细证明参见 GAMES101 Lecture 04
正交变换(Ortho Transformation)
正交变换试图将三维空间内任意一个立方体映射为一个标准立方体(canonical cube),标准立方体中心位于坐标原点,边长为 2,分别平行于 x, y, z 轴。
使用先平移,再缩放的方法变换
M
o
r
t
h
o
=
[
2
r
−
l
0
0
0
0
2
t
−
b
0
0
0
0
2
f
−
n
0
0
0
0
1
]
[
1
0
0
−
r
+
l
2
0
1
0
−
t
+
b
2
0
0
1
−
n
e
a
r
Z
+
f
a
r
Z
2
0
0
0
1
]
M_{ortho}=\begin{bmatrix} \frac{2}{r-l}&0&0&0\\0&\frac{2}{t-b}&0&0\\ 0&0&\frac{2}{f-n}&0\\0&0&0&1 \end{bmatrix} \begin{bmatrix} 1&0&0&-\frac{r+l}{2}\\0&1&0&-\frac{t+b}{2} \\0&0&1&-\frac{nearZ+farZ}{2}\\0&0&0&1 \end{bmatrix}
Mortho=
r−l20000t−b20000f−n200001
100001000010−2r+l−2t+b−2nearZ+farZ1
由于剪辑空间是一个左手系空间,因此右手系下的正交变换还需要反转z轴
平行获取图像的观察者或相机(无限远处的相机,Directional Light 等)的投影变换只需进行正交变换即可: M p r o j = M o r t h o M_{proj}=M_{ortho} Mproj=Mortho
从一个点发散获取图像的观察者(普通相机,点光源等)的投影变换需要先进行透视变换,再进行正交变换: M p r o j = M o r t h o M p e r s p → o r t h o M_{proj}=M_{ortho}M_{persp\rightarrow{ortho}} Mproj=MorthoMpersp→ortho
投影变换
在现代图形学 API 中,要获取 Projection 矩阵,一般需要的输入有:
- 相机离近平面的距离 n n n(或相机近平面的 z 值 n e a r Z nearZ nearZ)
- 相机离远平面的距离 f f f(或相机远平面的 z 值 f a r Z farZ farZ)
- 相机视锥的宽长比 a s p e c t R a t i o aspectRatio aspectRatio (不同于屏幕的宽长比,是相机的属性)
- Field of view f o v fov fov(一般是垂直可视角度)
通过 fov 和 nearZ,我们可以简单算得 t = n ⋅ tan ( f o v 2 ) t=n\cdot\tan(\frac{fov}{2}) t=n⋅tan(2fov), r = t ⋅ a s p e c t R a t i o r=t\cdot{aspectRatio} r=t⋅aspectRatio, b = − t b=-t b=−t, l = − r l=-r l=−r。再通过上文推导得出的矩阵求解即可。
有些API中会同时保留两个经过投影变换的坐标,ClipPosition和NDCPosition,它们的区别在于ClipPosition没有进行齐次除法,而NDCPosition经过了齐次除法,w=1。
假设我们现在已经得到 n , f , l , r , t , b n,f,l,r,t,b n,f,l,r,t,b
左手坐标系
正交投影矩阵:
M o r t h − p r o j = [ 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 ] \begin{equation} M_{orth-proj}=\begin{bmatrix} \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{bmatrix} \end{equation} Morth−proj= r−l20000t−b20000f−n20−r−lr+l−t−bt+b−f−nf+n1
透视投影矩阵:
M p e r p − p r o j = [ 2 n r − l 0 − r + l r − l 0 0 2 n t − b − t + b t − b 0 0 0 f + n f − n − 2 f n f − n 0 0 1 0 ] \begin{equation} M_{perp-proj}=\begin{bmatrix} \frac{2n}{r-l}&0&-\frac{r+l}{r-l}&0 \\ 0&\frac{2n}{t-b}&-\frac{t+b}{t-b}&0 \\ 0&0&\frac{f+n}{f-n}&-\frac{2fn}{f-n} \\ 0&0&1&0 \end{bmatrix} \end{equation} Mperp−proj= r−l2n0000t−b2n00−r−lr+l−t−bt+bf−nf+n100−f−n2fn0
右手坐标系
正交投影矩阵:
M o r t h − p r o j = [ 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 ] \begin{equation} M_{orth-proj}=\begin{bmatrix} \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{bmatrix} \end{equation} Morth−proj= r−l20000t−b20000−f−n20−r−lr+l−t−bt+b−f−nf+n1
透视投影矩阵:
M p e r p − p r o j = [ − 2 n r − l 0 − r + l r − l 0 0 − 2 n t − b − t + b t − b 0 0 0 f + n f − n 2 f n f − n 0 0 1 0 ] \begin{equation} M_{perp-proj}=\begin{bmatrix} -\frac{2n}{r-l}&0&-\frac{r+l}{r-l}&0 \\ 0&-\frac{2n}{t-b}&-\frac{t+b}{t-b}&0 \\ 0&0&\frac{f+n}{f-n}&\frac{2fn}{f-n} \\ 0&0&1&0 \end{bmatrix} \end{equation} Mperp−proj= −r−l2n0000−t−b2n00−r−lr+l−t−bt+bf−nf+n100f−n2fn0
PS:由于投影变换仅变换空间点位置,因此 M p r o j M_{proj} Mproj作用与 − M p r o j -M_{proj} −Mproj相同,有些地方可能写做 − M p r o j -M_{proj} −Mproj
MVP 变换
图形学中所谓的 MVP 变换矩阵,就是 Model,View,Projection 三个变换的矩阵相乘,需要注意的是,MVP 变换的顺序是先模型变换,随后观察变化,最后投影变换,而矩阵的乘法从右向左,因此 MVP 矩阵的求解公式为:
M M V P = M p r o j M v i e w M m o d e l M_{MVP}=M_{proj}M_{view}M_{model} MMVP=MprojMviewMmodel
视口变换
视口变换是将进行完 MVP 变换后在剪辑空间内的坐标转换到屏幕坐标的变换,这个变换较为简单,只需将剪辑空间拉成(width,height)的形状即可(有时还会将 z 轴从[-1,1]转换到[0,1])。可以直接通过 Lerp 实现。
用矩阵表示为:
M v i e w p o r t = [ w i d t h 2 0 0 w i d t h 2 0 h e i g h t 2 0 h e i g h t 2 0 0 1 0 0 0 0 1 ] M_{viewport}=\begin{bmatrix} \frac{width}{2}&0&0&\frac{width}{2}\\0&\frac{height}{2}&0&\frac{height}{2}\\ 0&0&1&0\\0&0&0&1 \end{bmatrix} Mviewport= 2width00002height0000102width2height01
有时还会将y轴进行翻转,因为在剪辑空间中原点在左下角,向右为x轴,向上为y轴,而大部分图形UI API的原点在左上角,向右为x轴,向下为y轴
透视校正插值
光栅化的最后一步对像素进行渲染时,会使用屏幕空间或 NDC 空间下的三角形坐标插值得到像素坐标的各项值(深度,法线,颜色等等)。但是用于计算的重心坐标是经过了 Projection Transformation 的,它与 view space 下的插值结果是不同的,如下图所示,因此需要进行透视校正。
校正的具体计算参考如下笔记:
https://zhuanlan.zhihu.com/p/144331875
计算用变量如下图所示,最终得到的结果是,深度值
Z t = 1 α Z A + β Z B + γ Z C \begin{equation}Z_t=\frac{1}{\frac{\alpha}{Z_A}+\frac{\beta}{Z_B}+\frac{\gamma}{Z_C}}\end{equation} Zt=ZAα+ZBβ+ZCγ1
其中 alpha,beta,gamma 为屏幕空间插值得到的系数。
其他值的计算公式为: I t = ( α I A Z A + β I B Z B + γ I C Z C ) / 1 Z t \begin{equation}I_t=(\alpha\frac{I_A}{Z_A}+\beta\frac{I_B}{Z_B}+\gamma\frac{I_C}{Z_C})/\frac{1}{Z_t}\end{equation} It=(αZAIA+βZBIB+γZCIC)/Zt1
Reference
https://zhuanlan.zhihu.com/p/551648397(图形学:MVP 变换概述)
https://www.bilibili.com/video/BV1X7411F744?p=4(GAMES101 Lecture 4)