原文轉(zhuǎn)自:
http://www.gesoftfactory.com/developer/Transform.htm
(提示:原文有圖片。)
三維變換
在使用三維圖形的應(yīng)用程序中,可以用變換做以下事情:
- 描述一個(gè)物體相對(duì)于另一個(gè)物體的位置。
- 旋轉(zhuǎn)并改變物體的大小。
- 改變觀察的位置、方向和視角。
可以用一個(gè)4 x 4矩陣將任意點(diǎn)(x,y,z)變換為另一個(gè)點(diǎn)(x',y',z')。
對(duì)(x,y,z)和矩陣執(zhí)行以下操作產(chǎn)生點(diǎn)(x',y',z')。
最常見的變換是平移、旋轉(zhuǎn)和縮放。可以將產(chǎn)生這些效果的矩陣合并成單個(gè)矩陣,這樣就可以一次計(jì)算多種變換。例如,可以構(gòu)造單個(gè)矩陣,對(duì)一系列的點(diǎn)進(jìn)行平移和旋轉(zhuǎn)。更多信息,請(qǐng)參閱矩陣串接。
矩陣以行列順序書寫。一個(gè)沿每根軸均勻縮放頂點(diǎn)的矩陣,也稱為統(tǒng)一縮放,用如下數(shù)學(xué)符號(hào)表示。
在C++應(yīng)用程序中,Microsoft® Direct3D®使用D3DMATRIX結(jié)構(gòu),將矩陣聲明為一個(gè)二維數(shù)組。以下示例代碼顯示了如何初始化一個(gè)D3DMATRIX結(jié)構(gòu),使之成為一個(gè)統(tǒng)一縮放矩陣。
// 本例中,s為浮點(diǎn)類型的變量
D3DMATRIX scale = {
s, 0.0f, 0.0f, 0.0f,
0.0f, s, 0.0f, 0.0f,
0.0f, 0.0f, s, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
平移
以下變換將點(diǎn)(x,y,z)平移到一個(gè)新的點(diǎn)(x',y',z')。
可以在C++應(yīng)用程序中手工創(chuàng)建一個(gè)平移矩陣。以下示例代碼顯示了的一個(gè)函數(shù)的源碼,該函數(shù)創(chuàng)建一個(gè)矩陣用于平移頂點(diǎn)。
D3DXMATRIX Translate(const float dx, const float dy, const float dz) {
D3DXMATRIX ret;
D3DXMatrixIdentity(&ret); // 由Direct3DX實(shí)現(xiàn)
ret(3, 0) = dx;
ret(3, 1) = dy;
ret(3, 2) = dz;
return ret;
} // 平移結(jié)束
為了方便,Direct3DX工具庫(kù)提供了D3DXMatrixTranslation函數(shù)。
縮放
以下變換用指定值縮放點(diǎn)(x,y,z)的x-,y-和z-方向,產(chǎn)生新的點(diǎn)(x',y',z')。
旋轉(zhuǎn)
這里描述的變換是基于左手坐標(biāo)系的,也許和別處見到的變換矩陣不同。更多信息,請(qǐng)參閱三維坐標(biāo)系。
以下變換將點(diǎn)(x,y,z)圍繞x軸旋轉(zhuǎn),產(chǎn)生新的點(diǎn)(x',y',z')。
以下變換將點(diǎn)圍繞y軸旋轉(zhuǎn)。
以下變換將點(diǎn)圍繞z軸旋轉(zhuǎn)。
在這些示例矩陣中,希臘字母(θ)表示旋轉(zhuǎn)的角度,以弧度為單位。當(dāng)沿著旋轉(zhuǎn)軸朝原點(diǎn)看時(shí),角度以順時(shí)針方向計(jì)量。
C++應(yīng)用程序可以用Direct3D擴(kuò)展(D3DX)工具庫(kù)提供的D3DXMatrixRotationX,D3DXMatrixRotationX和D3DXMatrixRotationX函數(shù)創(chuàng)建旋轉(zhuǎn)矩陣。以下示例是D3DXMatrixRotationX函數(shù)的源碼。
D3DXMATRIX* WINAPI D3DXMatrixRotationX
( D3DXMATRIX *pOut, float angle )
{
#if DBG
if(!pOut)
return NULL;
#endif
float sin, cos;
sincosf(angle, &sin, &cos); // 計(jì)算角度的正弦和余弦值。
pOut->_11 = 1.0f; pOut->_12 = 0.0f; pOut->_13 = 0.0f; pOut->_14 = 0.0f;
pOut->_21 = 0.0f; pOut->_22 = cos; pOut->_23 = sin; pOut->_24 = 0.0f;
pOut->_31 = 0.0f; pOut->_32 = -sin; pOut->_33 = cos; pOut->_34 = 0.0f;
pOut->_41 = 0.0f; pOut->_42 = 0.0f; pOut->_43 = 0.0f; pOut->_44 = 1.0f;
return pOut;
}
( D3DXMATRIX *pOut, float angle )
if(!pOut)
return NULL;
float sin, cos;
sincosf(angle, &sin, &cos); // Determine sin and cos of angle.
pOut->_11 = 1.0f; pOut->_12 = 0.0f; pOut->_13 = 0.0f; pOut->_14 = 0.0f;
pOut->_21 = 0.0f; pOut->_22 = cos; pOut->_23 = sin; pOut->_24 = 0.0f;
pOut->_31 = 0.0f; pOut->_32 = -sin; pOut->_33 = cos; pOut->_34 = 0.0f;
pOut->_41 = 0.0f; pOut->_42 = 0.0f; pOut->_43 = 0.0f; pOut->_44 = 1.0f;
return pOut;
矩陣串接
使用矩陣的一個(gè)優(yōu)勢(shì)就是可以通過把兩個(gè)以上的矩陣相乘,將它們的效果合并在一起。這意味著,要先旋轉(zhuǎn)一個(gè)建模然后把它平移到某個(gè)位置,無需使用兩個(gè)矩陣,只要把旋轉(zhuǎn)矩陣和平移矩陣相乘,產(chǎn)生一個(gè)包含了所有效果的合成矩陣。這個(gè)過程被稱為矩陣串接,可以寫成以下公式。
在這個(gè)公式中,C是將被創(chuàng)建的合成矩陣,M1到Mn是矩陣C包含的單個(gè)矩陣。雖然大多數(shù)情況下,只需要串接兩三個(gè)矩陣,但實(shí)際上并沒有數(shù)量上的限制。
可以使用D3DXMatrixMultiply函數(shù)進(jìn)行矩陣乘法。
進(jìn)行矩陣乘法時(shí)先后次序是至關(guān)重要的。前面的公式反映了矩陣串接從左到右的規(guī)則。也就是說,用來創(chuàng)建合成矩陣的每個(gè)矩陣產(chǎn)生的直觀效果會(huì)按從左到右的次序出現(xiàn)。下面顯示了一個(gè)典型的世界變換矩陣。想象一下給一個(gè)旋轉(zhuǎn)飛行的碟子創(chuàng)建世界變換矩陣。應(yīng)用程序也許想讓飛行的碟子繞它的中心——建模空間中的y軸——旋轉(zhuǎn),然后把它平移到場(chǎng)景中的另一個(gè)位置。要實(shí)現(xiàn)這樣的效果,首先創(chuàng)建一個(gè)旋轉(zhuǎn)矩陣,然后將它與平移矩陣相乘,如以下公式所示。
在這個(gè)公式中,Ry是繞y軸的旋轉(zhuǎn)矩陣,Tw是平移到世界坐標(biāo)中某個(gè)位置的矩陣。
矩陣相乘的順序很重要,因?yàn)榫仃嚦朔ㄊ遣豢山粨Q的,這和兩個(gè)標(biāo)量相乘不同。將矩陣以相反的順序相乘會(huì)產(chǎn)生這樣的直觀效果:先把飛行中的碟子平移到世界空間中的某個(gè)位置,然后將它圍繞世界坐標(biāo)的原點(diǎn)旋轉(zhuǎn)。
無論創(chuàng)建何種類型的矩陣,都要記住從左到右的規(guī)則,這樣才能保證得到想要的效果。
世界變換
對(duì)世界變換的討論介紹了基本概念,并提供了如何在Microsoft® Direct3D®應(yīng)用程序中設(shè)置世界變換矩陣的細(xì)節(jié)。
什么是世界變換
世界變換將坐標(biāo)從建模空間,在這個(gè)空間中的頂點(diǎn)相對(duì)于建模的局部原點(diǎn)定義,轉(zhuǎn)變到世界空間,在這個(gè)空間中的頂點(diǎn)相對(duì)于場(chǎng)景中所有物體共有的原點(diǎn)定義。本質(zhì)上,世界變換將一個(gè)建模放到世界中,并由此而得名。下圖描繪了世界坐標(biāo)系統(tǒng)和建模的局部坐標(biāo)系統(tǒng)間的關(guān)系。
世界變換可以包含任意數(shù)量的平移、旋轉(zhuǎn)和縮放的合并。有關(guān)對(duì)變換的數(shù)學(xué)討論,請(qǐng)參閱三維變換。
設(shè)置世界矩陣
同任何其它變換一樣,通過將一系列變換矩陣串接成單個(gè)矩陣,應(yīng)用程序可以創(chuàng)建包含這些矩陣全部效果的世界矩陣。最簡(jiǎn)單的情況下,建模在世界的原點(diǎn)并且它的局部坐標(biāo)軸與世界空間的方向相同,這時(shí)世界矩陣就是單位矩陣。更通常的情況下,世界矩陣是一系列矩陣的合成,包含一個(gè)平移矩陣,并且根據(jù)需要可能有一個(gè)以上的旋轉(zhuǎn)矩陣。
以下示例,來自一個(gè)用C++編寫的假想三維建模類,使用Direct3D擴(kuò)展(D3DX)工具庫(kù)提供的幫助函數(shù)創(chuàng)建了一個(gè)世界矩陣,這個(gè)世界矩陣包含了三個(gè)旋轉(zhuǎn)矩陣,用于調(diào)整三維建模的方向,以及一個(gè)平移矩陣,用來根據(jù)建模在世界空間中的相對(duì)坐標(biāo)重新確定它的位置。
/*
* 根據(jù)本示例的目的,假設(shè)以下變量都是有效的并經(jīng)過初始化。
*
* 變量m_xPos,m_yPos,m_zPos包含了建模在世界坐標(biāo)中的位置。
*
* 變量m_fPitch,m_fYaw和m_fRoll為浮點(diǎn)數(shù),包含了建模的方向,
* 用pitch,yaw和roll旋轉(zhuǎn)角表示,以弧度為單位。
*/
void C3DModel::MakeWorldMatrix( D3DXMATRIX* pMatWorld )
{
D3DXMATRIX MatTemp; // 用于旋轉(zhuǎn)的臨時(shí)矩陣
D3DXMATRIX MatRot; // 最終的旋轉(zhuǎn)矩陣,應(yīng)用于pMatWorld.
// 使用從左到右的矩陣串接順序,在旋轉(zhuǎn)之前對(duì)物體在世界空間中
// 的位置進(jìn)行平移。
D3DXMatrixTranslation(pMatWorld, m_xPos, m_yPos, m_zPos);
D3DXMatrixIdentity(&MatRot);
// 現(xiàn)在將方向變量應(yīng)用于世界矩陣
if(m_fPitch || m_fYaw || m_fRoll) {
// 產(chǎn)生并合成旋轉(zhuǎn)矩陣。
D3DXMatrixRotationX(&MatTemp, m_fPitch); // Pitch
D3DXMatrixMultiply(&MatRot, &MatRot, &MatTemp);
D3DXMatrixRotationY(&MatTemp, m_fYaw); // Yaw
D3DXMatrixMultiply(&MatRot, &MatRot, &MatTemp);
D3DXMatrixRotationZ(&MatTemp, m_fRoll); // Roll
D3DXMatrixMultiply(&MatRot, &MatRot, &MatTemp);
// 應(yīng)用旋轉(zhuǎn)矩陣,得到最后的世界矩陣。
D3DXMatrixMultiply(pMatWorld, &MatRot, pMatWorld);
}
}
當(dāng)準(zhǔn)備好世界變換矩陣后,應(yīng)該調(diào)用IDirect3DDevice9::SetTransform方法設(shè)置它,并把第一個(gè)參數(shù)指定D3DTS_WORLD宏。
注意 Direct3D使用應(yīng)用程序設(shè)置的世界和觀察矩陣配置許多內(nèi)部數(shù)據(jù)結(jié)構(gòu)。應(yīng)用程序每次設(shè)置新的世界或觀察矩陣時(shí),系統(tǒng)都要重新計(jì)算相關(guān)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。頻繁地設(shè)置這些矩陣——例如,每幀上千次——是計(jì)算量很大的。通過將世界矩陣和觀察矩陣串接成一個(gè)世界/觀察矩陣,并將該矩陣設(shè)置為世界矩陣,然后將觀察矩陣設(shè)置為單位矩陣,應(yīng)用程序可以將所需的計(jì)算次數(shù)減到最少。最好保存一份單獨(dú)的世界矩陣和觀察矩陣的副本在高速緩存中,這樣就可以根據(jù)需要修改、串接及重置世界矩陣。為清晰起見,本文檔中的Direct3D示例很少使用這項(xiàng)優(yōu)化。
觀察變換
本節(jié)介紹觀察變換的基本概念,并提供有關(guān)如何在Microsoft® Direct3D®應(yīng)用程序中設(shè)置觀察矩陣的細(xì)節(jié)。信息被分為以下主題。
什么是觀察變換?
觀察變換根據(jù)觀察者在世界空間中的位置,把頂點(diǎn)變換到攝像機(jī)空間。在攝像機(jī)空間中,攝像機(jī),或觀察者,位于原點(diǎn),朝sz軸的正向看去。再次提醒一下,因?yàn)镈irect3D使用左手坐標(biāo)系,所以z軸的正向是朝著場(chǎng)景的。觀察矩陣根據(jù)攝像機(jī)的位置——攝像機(jī)空間的原點(diǎn)——和方向重定位世界中的所有物體。
有兩方法可以創(chuàng)建觀察矩陣。所有情況下,攝像機(jī)在世界空間中的邏輯位置和方向會(huì)被用作起始點(diǎn)來創(chuàng)建觀察矩陣,得到的觀察矩陣會(huì)被應(yīng)用于場(chǎng)景中的三維建模。觀察矩陣平移并旋轉(zhuǎn)物體,將它們放入攝像機(jī)空間中,攝像機(jī)位于原點(diǎn)。創(chuàng)建觀察矩陣的一種方法是把平移矩陣和圍繞每根坐標(biāo)軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣合并。這種方法使用了以下通用矩陣公式。
在這個(gè)公式中,V是要?jiǎng)?chuàng)建的觀察矩陣,T是在世界中重定位物體的平移矩陣,Rx到Rz分別是繞x軸,y軸和z軸旋轉(zhuǎn)物體的旋轉(zhuǎn)矩陣。平移和旋轉(zhuǎn)矩陣基于攝像機(jī)在世界空間中邏輯位置和方向。因此,如果攝像機(jī)在世界中的邏輯位置是<10,20,100>,那么平移矩陣的目的是沿x軸移動(dòng)物體-10單位,沿y軸移動(dòng)-20單位,沿z軸移動(dòng)-100單位。公式中的旋轉(zhuǎn)矩陣基于攝像機(jī)的方向,根據(jù)攝像機(jī)空間的坐標(biāo)軸與世界空間的坐標(biāo)軸間的夾角決定。例如,如果前面提到的攝像機(jī)是垂直向下放的,那么它的z軸與世界空間的z軸有90度夾角,如下圖所示。
旋轉(zhuǎn)矩陣將角度相同但方向相反的旋轉(zhuǎn)量應(yīng)用于場(chǎng)景中的建模。這個(gè)攝像機(jī)的觀察矩陣包含了一個(gè)繞x軸-90度的旋轉(zhuǎn)。旋轉(zhuǎn)矩陣與平移矩陣合并生成觀察矩陣,觀察矩陣調(diào)整物體在場(chǎng)景中的位置和方向,使它們的頂部朝著攝像機(jī),看起來就好像攝像機(jī)在建模的上方一樣。
設(shè)置觀察矩陣
D3DXMatrixLookAtLH和D3DXMatrixLookAtRH輔助函數(shù)根據(jù)攝像機(jī)的位置和被觀察點(diǎn)創(chuàng)建一個(gè)觀察矩陣。
以下示例代碼創(chuàng)建了一個(gè)用于右手系的觀察矩陣。
D3DXMATRIX out;
D3DXVECTOR3 eye(2,3,3);
D3DXVECTOR3 at(0,0,0);
D3DXVECTOR3 up(0,1,0);
D3DXMatrixLookAtRH(&out, &eye, &at, &up);
Direct3D使用應(yīng)用程序設(shè)置的世界矩陣和觀察矩陣配置許多內(nèi)部數(shù)據(jù)結(jié)構(gòu)。每次應(yīng)用程序設(shè)置一個(gè)新的世界矩陣或觀察矩陣,系統(tǒng)都要重新計(jì)算相關(guān)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。頻繁地設(shè)置這些矩陣——例如,每幀20,000次——計(jì)算量非常大。通過將世界矩陣和觀察矩陣串接成一個(gè)世界/觀察矩陣,并將之設(shè)置為世界矩陣,然后將觀察矩陣設(shè)為單位矩陣,應(yīng)用程序可以將所需的計(jì)算量減到最小。最好保存一份單獨(dú)的世界矩陣和觀察矩陣的副本在高速緩存中,這樣就可以根據(jù)需要修改、串接及重置世界矩陣。為清晰起見,Direct3D示例很少使用這項(xiàng)優(yōu)化。
投影變換
可以認(rèn)為投影變換是控制攝像機(jī)的內(nèi)部參數(shù),這選擇攝像機(jī)的鏡頭有些相似。這是三種類型的變換中最為復(fù)雜的。對(duì)投影變換的討論被分為以下主題。
什么是投影變換?
典型的投影變換就是一個(gè)縮放和透視投影。投影變換將視棱錐轉(zhuǎn)變?yōu)橐粋€(gè)立方體。因?yàn)橐暲忮F的近端比遠(yuǎn)端小,所以這就產(chǎn)生了離攝像機(jī)近的物體被放大的效果,這就是透視如何被應(yīng)用于場(chǎng)景的。
在視棱錐中,攝像機(jī)與觀察變換空間的原點(diǎn)之間的距離被定義為D,因此投影矩陣看起來是這樣:
通過在z方向平移-D,觀察矩陣將攝像機(jī)平移到原點(diǎn)。平移矩陣如下所示:
將平移矩陣與投影矩陣相乘(T*P),得到合成的投影矩陣。如下:
下圖描繪了透視投影如何將視棱錐轉(zhuǎn)變到新的坐標(biāo)空間。注意棱錐變成了立方體,同時(shí)原點(diǎn)從場(chǎng)景的右上角移到了中心。(譯注:應(yīng)該是從前裁剪平面的中心移到了原點(diǎn))
在透視變換中,x和y方向的邊界值是-1和1。Z方向的邊界值分別是,0對(duì)應(yīng)于前平面,1對(duì)應(yīng)于后平面。
這個(gè)矩陣根據(jù)指定的從攝像機(jī)到近裁剪平面的距離,平移并縮放物體,但沒有考慮視角(fov),并且用它為遠(yuǎn)處物體產(chǎn)生的z值可能幾乎相同,這使深度比較變得困難。以下矩陣解決了這些問題,并根據(jù)視區(qū)的縱橫比調(diào)整頂點(diǎn),這使它成為透視投影的一個(gè)很好的選擇。
在這個(gè)矩陣中,Zn是近裁剪平面的z值。變量w,h和Q有以下含義。注意fovw和fovh表示視區(qū)在水平和垂直方向上的視角,以弧度為單位。
對(duì)應(yīng)用程序而言,使用視角的角度定義x和y的比例系數(shù)可能不如使用視區(qū)在水平和垂直方向上的大小(在攝像機(jī)空間中)方便。可以用數(shù)學(xué)推導(dǎo),得出下面兩個(gè)使用視區(qū)大小計(jì)算w和h的公式,它們與前面的公式是等價(jià)的。
在這兩個(gè)公式中,Zn表示近裁剪平面的位置,Vw和Vh變量表示視區(qū)在攝像機(jī)空間的寬和高。
對(duì)于C++應(yīng)用程序而言,這兩個(gè)大小直接對(duì)應(yīng)于D3DVIEWPORT9結(jié)構(gòu)的Width和Height成員。(譯注:雖然直接對(duì)應(yīng),但是不等價(jià)的,因?yàn)?span>Vw和Vh位于攝像機(jī)空間,而Width和Height位于屏幕空間)
無論決定使用什么公式,非常重要的一點(diǎn)是要盡可能將Zn設(shè)得大,因?yàn)榻咏鼣z像機(jī)的z值變化不大。這使得用16位z緩存的深度比較變得有點(diǎn)復(fù)雜。
同世界變換和觀察變換一樣,應(yīng)用程序調(diào)用IDirect3DDevice9::SetTransform方法設(shè)置投影矩陣。
設(shè)置投影矩陣
以下ProjectionMatrix示例函數(shù)設(shè)置了前后裁剪平面,以及在水平和垂直方向上視角的角度。此處的代碼與什么是投影矩陣?主題中討論的方法相似。視角應(yīng)該小于弧度π。
D3DXMATRIX
ProjectionMatrix(const float near_plane, // 到近裁剪平面的距離
const float far_plane, // 到遠(yuǎn)裁剪平面的距離
const float fov_horiz, // 水平視角,用弧度表示
const float fov_vert) // 垂直視角,用弧度表示
{
float h, w, Q;
w = (float)1/tan(fov_horiz*0.5); // 1/tan(x) == cot(x)
h = (float)1/tan(fov_vert*0.5); // 1/tan(x) == cot(x)
Q = far_plane/(far_plane - near_plane);
D3DXMATRIX ret;
ZeroMemory(&ret, sizeof(ret));
ret(0, 0) = w;
ret(1, 1) = h;
ret(2, 2) = Q;
ret(3, 2) = -Q*near_plane;
ret(2, 3) = 1;
return ret;
} // End of ProjectionMatrix
在創(chuàng)建矩陣之后,調(diào)用IDirect3DDevice9::SetTransform方法設(shè)置投影矩陣,要將第一個(gè)參數(shù)設(shè)為D3DTS_PROJECTION。
Direct3D擴(kuò)展(D3DX)工具庫(kù)提供了以下函數(shù),幫助應(yīng)用程序設(shè)置投影矩陣。
- D3DXMatrixPerspectiveLH
- D3DXMatrixPerspectiveRH
- D3DXMatrixPerspectiveFovLH
- D3DXMatrixPerspectiveFovRH
- D3DXMatrixPerspectiveOffCenterLH
- D3DXMatrixPerspectiveOffCenterRH
W友好投影矩陣
對(duì)于已經(jīng)用世界、觀察和投影矩陣變換過的頂點(diǎn),Microsoft® Direct3D®使用頂點(diǎn)的W成員進(jìn)行深度緩存中基于深度的計(jì)算或計(jì)算霧效果。類似這樣的計(jì)算要求應(yīng)用程序歸一化投影矩陣,使生成的W與世界空間中的Z相同。簡(jiǎn)而言之,如果應(yīng)用程序的投影矩陣包含的(3,4)系數(shù)不為1,那么為了生成適用的矩陣,應(yīng)用程序必須用(3,4)系數(shù)的倒數(shù)縮放所有的系數(shù)。如果應(yīng)用程序不提供符合這樣要求的矩陣,那么會(huì)造成霧效果和深度緩存不正確。什么是投影矩陣?中推薦的矩陣符合基于w的計(jì)算的要求。
下圖顯示了不符合要求的投影矩陣,以及對(duì)同一個(gè)矩陣進(jìn)行縮放,這樣就可以啟用基于視點(diǎn)的霧。
我們假設(shè)在前面的矩陣中,所有變量都不為零。更多有關(guān)相對(duì)于視點(diǎn)的霧的信息,請(qǐng)參閱基于視點(diǎn)的深度與基于Z的深度的比較。更多有關(guān)基于w的深度緩存,請(qǐng)參閱深度緩存。
注意 Direct3D在基于w的深度計(jì)算中使用當(dāng)前設(shè)置的投影矩陣。因此,即使應(yīng)用程序不使用Direct3D變換流水線,但是為了使用基于w的特性,應(yīng)用程序必須設(shè)置一個(gè)符合要求的投影矩陣。