3D 圖形程式設計的數學基礎(3) 矩陣基本變換
這裡開始,是真正的與3D圖形程式設計相關的知識了,前兩節只能算是純數學。
平移矩陣
要想將向量(x, y, z, 1)沿x軸平移個單位,沿y軸平移,沿z軸平移個單位,我們只需要將該向量與如下矩陣相乘。
從中可以看出4*4矩陣N中的N41,N42,N43分別控制其在x軸y軸z軸上的平移單位.
是單位矩陣,我們已經知道,乘以其他矩陣相當於沒有乘的傢伙。這個矩陣就是從單位矩陣稍微變下型,多了第4行的幾個值。我們先來看為最後結果做出的貢獻,向量M(x,y,z,1)與矩陣N(p)相乘後,最後X座標的值(也就是矩陣M11的值)為x*1 + y*0 + z*0 + 1*px = x + px。(套一下矩形相乘的公式)
y,z的公式一樣,就不多說了。這裡可以看到,對於實施矩陣平移計算來說,需要將原向量(3維)擴充的一維(一般用w表示)設為1,不然的話,上述x座標=x*1 + y*0 + z*0 + 0*px=x,也就是說,完全不會改變原矩陣了。
GNU Octave(matlab) 驗證一下:
> p = [1,0,0,0;0,1,0,0;0,0,1,0;2,3,4,1] p = 1 0 0 0 0 1 0 0 0 0 1 0 2 3 4 1 octave-3.2.3.exe:6:d:/Octave/3.2.3_gcc- > x x = 1 2 3 1 octave-3.2.3.exe:7:d:/Octave/3.2.3_gcc- > x * p ans = 3 5 7 1 octave-3.2.3.exe:8:d:/Octave/3.2.3_gcc- |
x = 2 + 1 = 3,依次類推,結果正確。
在irrlicht中,平移矩陣的程式碼直接偷懶。。。。。利用了上述公式的推導結果,轉換後的x值為x+px。。。。汗-_-!,理論和實際果然還是有差距的。不過想想,說來也是,一個加法就可以完成的平移,為啥非要整個矩陣乘法去完成?此公式的存在就讓人鬱悶。。。難道僅僅是因為需要的是用矩陣進行的計算。。。。。。
template <class T> inline void CMatrix4<T>::translateVect( vector3df& vect ) const { vect.X = vect.X+M[12]; vect.Y = vect.Y+M[13]; vect.Z = vect.Z+M[14]; }
D3D中利用函式:
// Build a matrix which translates by (x, y, z) D3DXMATRIX* WINAPI D3DXMatrixTranslation ( D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z );
實現矩陣的平移,具體方式不明。
縮放矩陣
我們將一單位矩陣沿X軸縮放X倍,Y軸縮放Y倍,Z軸縮放Z倍,可令該向量與下列矩陣相乘。
按公式推導:X(M11)座標值為X*x+y*0+z*0+0*0=X*x
y,z的推導類似。
GNU Octave(matlab):
> x = [1,2,3,0] x = 1 2 3 0 octave-3.2.3.exe:6:f: > p p = 2 0 0 0 0 3 0 0 0 0 4 0 0 0 0 1 octave-3.2.3.exe:7:f: > x * p ans = 2 6 12 0 |
結果正確。其實看了實現的原始碼後也會發現這種公式還是沒事找事,事實上直接乘多省事啊。
irrlicht中利用下面的實現來構造一個縮放矩陣:
template <class T> inline CMatrix4<T>& CMatrix4<T>::setScale( const vector3d<T>& scale ) { M[0] = scale.X; M[5] = scale.Y; M[10] = scale.Z; #if defined ( USE_MATRIX_TEST ) definitelyIdentityMatrix=false; #endif return *this; }
D3D中利用下面的實現完成縮放運算,直接乘就好了。。。。。。
D3DXINLINE D3DXVECTOR3* D3DXVec3Scale ( D3DXVECTOR3 *pOut, CONST D3DXVECTOR3 *pV, FLOAT s) { #ifdef D3DX_DEBUG if(!pOut || !pV) return NULL; #endif pOut->x = pV->x * s; pOut->y = pV->y * s; pOut->z = pV->z * s; return pOut; }
旋轉矩陣:
旋轉矩陣是在乘以一個向量的時候有改變向量的方向但不改變大小的效果的矩陣。旋轉矩陣不包括反演,它可以把右手座標系改變成左手座標系或反之。所有旋轉加上反演形成了正交矩陣的集合。需要注意的是,進行旋轉變換時,擴充3維向量的辦法是令w=0;
我們可用如下3個矩陣將一個分量分別繞著x,y,z軸順時針旋轉θ弧度。
還是先看第一個公式,向量M(x,y,z,0)與矩陣X(θ)相乘後,最後X(M11)座標值為x*1+y*0+z*0+0*0=x,Y(M12)座標值為x*0+y*cosθ+z*(-sinθ)+0*0 = y*cosθ + z * (-sinθ),Z(M13)座標值為x*0+y*sinθ+z*cosθ+0*0 = y*sinθ + z*cosθ,w(m14)座標為x*0+y*0+z*0+0*1 = 0。
這個就複雜了。。。。。不太好直觀的看到驗證的結果,我們將其收到2維去看結果。
我們利用GNU Octave(matlab) 的compass命令在2維空間中直觀的顯示出向量x = [ 1, tan(pi/3), 0, 0](實際顯示在x,y平面中)
我們用其圍繞Z軸順時針旋轉30度時,方式是乘以θ為30的如上矩陣,結果如下:
> a = pi / 6 > p = [cos(a),sin(a),0,0;-sin(a),cos(a),0,0;0,0,1,0;0,0,0,1] p = 0.86603 0.50000 0.00000 0.00000 -0.50000 0.86603 0.00000 0.00000 0.00000 0.00000 1.00000 0.00000 0.00000 0.00000 0.00000 1.00000 octave-3.2.3.exe:26:f:/Octave/3.2.3_gcc-4.4.0/bin > x = [1, tan(pi/3), 0, 0] x = 1.00000 1.73205 0.00000 0.00000 octave-3.2.3.exe:27:f:/Octave/3.2.3_gcc-4.4.0/bin > x2 = x * p x2 = 0.00000 2.00000 0.00000 0.00000 octave-3.2.3.exe:28:f:/Octave/3.2.3_gcc-4.4.0/bin > |
精確的30度。
irrlicht中設定旋轉矩陣就有學問了:
template <class T> inline CMatrix4<T>& CMatrix4<T>::setRotationRadians( const vector3d<T>& rotation ) { const f64 cr = cos( rotation.X ); const f64 sr = sin( rotation.X ); const f64 cp = cos( rotation.Y ); const f64 sp = sin( rotation.Y ); const f64 cy = cos( rotation.Z ); const f64 sy = sin( rotation.Z ); M[0] = (T)( cp*cy ); M[1] = (T)( cp*sy ); M[2] = (T)( -sp ); const f64 srsp = sr*sp; const f64 crsp = cr*sp; M[4] = (T)( srsp*cy-cr*sy ); M[5] = (T)( srsp*sy+cr*cy ); M[6] = (T)( sr*cp ); M[8] = (T)( crsp*cy+sr*sy ); M[9] = (T)( crsp*sy-sr*cy ); M[10] = (T)( cr*cp ); #if defined ( USE_MATRIX_TEST ) definitelyIdentityMatrix=false; #endif return *this; }
為了解釋這個函式的作用,看看下列程式:
#include "irrlicht.h" #include <math.h> #pragma comment(lib, "Irrlicht.lib") using namespace irr; using namespace irr::core; int _tmain(int argc, _TCHAR* argv[]) { f32 a = 30; f32 M[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; matrix4 mt; mt.setM(M); vector3df vec(0.0, 0.0, PI / 6); mt.setRotationRadians(vec); for(int i = 0; i < 4; ++i) { for(int j = 0; j < 4; ++j) { printf("%.6f/t", mt(i, j)); } printf("/n"); } return 0; }
執行結果為:
0.866025 0.500000 -0.000000 0.000000 -0.500000 0.866025 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 |
看到是啥了嗎?沒錯,就是GNU Octave(matlab) 那個例子中的矩陣:
p = [cos(a),sin(a),0,0;-sin(a),cos(a),0,0;0,0,1,0;0,0,0,1]
事實上,上面程式中的vec表示不繞x,y軸旋轉,繞Z軸旋轉PI/6,實際的作用就是構造了上述的矩陣P。
上述矩陣通過以下成員函式應用以使用生成的矩陣,其實就是乘法-_-!
template <class T> inline void CMatrix4<T>::rotateVect( vector3df& vect ) const { vector3df tmp = vect; vect.X = tmp.X*M[0] + tmp.Y*M[4] + tmp.Z*M[8]; vect.Y = tmp.X*M[1] + tmp.Y*M[5] + tmp.Z*M[9]; vect.Z = tmp.X*M[2] + tmp.Y*M[6] + tmp.Z*M[10]; } //! An alternate transform vector method, writing into a second vector template <class T> inline void CMatrix4<T>::rotateVect(core::vector3df& out, const core::vector3df& in) const { out.X = in.X*M[0] + in.Y*M[4] + in.Z*M[8]; out.Y = in.X*M[1] + in.Y*M[5] + in.Z*M[9]; out.Z = in.X*M[2] + in.Y*M[6] + in.Z*M[10]; } //! An alternate transform vector method, writing into an array of 3 floats template <class T> inline void CMatrix4<T>::rotateVect(T *out, const core::vector3df& in) const { out[0] = in.X*M[0] + in.Y*M[4] + in.Z*M[8]; out[1] = in.X*M[1] + in.Y*M[5] + in.Z*M[9]; out[2] = in.X*M[2] + in.Y*M[6] + in.Z*M[10]; }
上面程式後加上如下幾句,使用上面剛生成的矩陣:
vector3df x(1.0, tan(PI/3), 0.0);
mt.rotateVect(x);
printf("x = [%f, %f, %f]/n", x.X, x.Y, x.Z);
輸出結果:
x = [-0.000000, 2.000000, 0.000000] |
與通過GNU Octave(matlab) 的一樣,精確的30度旋轉。
與旋轉有關的還有vector的幾個函式:
//! Rotates the vector by a specified number of degrees around the Y axis and the specified center. /** /param degrees Number of degrees to rotate around the Y axis. /param center The center of the rotation. */ void rotateXZBy(f64 degrees, const vector3d<T>& center=vector3d<T>()) { degrees *= DEGTORAD64; f64 cs = cos(degrees); f64 sn = sin(degrees); X -= center.X; Z -= center.Z; set((T)(X*cs - Z*sn), Y, (T)(X*sn + Z*cs)); X += center.X; Z += center.Z; } //! Rotates the vector by a specified number of degrees around the Z axis and the specified center. /** /param degrees: Number of degrees to rotate around the Z axis. /param center: The center of the rotation. */ void rotateXYBy(f64 degrees, const vector3d<T>& center=vector3d<T>()) { degrees *= DEGTORAD64; f64 cs = cos(degrees); f64 sn = sin(degrees); X -= center.X; Y -= center.Y; set((T)(X*cs - Y*sn), (T)(X*sn + Y*cs), Z); X += center.X; Y += center.Y; } //! Rotates the vector by a specified number of degrees around the X axis and the specified center. /** /param degrees: Number of degrees to rotate around the X axis. /param center: The center of the rotation. */ void rotateYZBy(f64 degrees, const vector3d<T>& center=vector3d<T>()) { degrees *= DEGTORAD64; f64 cs = cos(degrees); f64 sn = sin(degrees); Z -= center.Z; Y -= center.Y; set(X, (T)(Y*cs - Z*sn), (T)(Y*sn + Z*cs)); Z += center.Z; Y += center.Y; }
事實上這些函式就是前面兩步的一步實現,實際就是利用了上述公式推導最後的結果,可以去對比一下。
比如下列程式碼:
vector3df x(1.0, tan(PI/3), 0.0);
x.rotateXYBy(30);
printf("x = [%f, %f, %f]/n", x.X, x.Y, x.Z);
輸出:
x = [-0.000000, 2.000000, 0.000000] |
就是前面通過兩步得出的結果。上面irrlicht程式碼需要注意的是,引數是degree是表示單位是度數,其他時候都預設為弧度。
D3D中使用下列函式實現旋轉,沒有實現原始碼,沒有太多好說的。
// Build a matrix which rotates around the X axis D3DXMATRIX* WINAPI D3DXMatrixRotationX ( D3DXMATRIX *pOut, FLOAT Angle ); // Build a matrix which rotates around the Y axis D3DXMATRIX* WINAPI D3DXMatrixRotationY ( D3DXMATRIX *pOut, FLOAT Angle ); // Build a matrix which rotates around the Z axis D3DXMATRIX* WINAPI D3DXMatrixRotationZ ( D3DXMATRIX *pOut, FLOAT Angle );
原創文章作者保留版權 轉載請註明原作者 並給出連結