天天看點

平移矩陣

轉自:https://blog.csdn.net/vagrxie/article/details/5016143

三維平移變換、比例變換可看成是二維情況的直接推廣。與二維變換相似,我們也采用齊次坐标技術來描述空間的各點坐标及其變換,這時,描述空間三維變換的變換矩陣是4×4的形式。由此,一系列變換可以用單個矩陣來表示。

平移矩陣

要想将向量(x, y, z, 1)沿x軸平移

平移矩陣

個機關,沿y軸平移

平移矩陣

,沿z軸平移

平移矩陣

個機關,我們隻需要将該向量與如下矩陣相乘。注意:行向量乘以該矩陣

平移矩陣
平移矩陣
平移矩陣

N(p) =

平移矩陣

從中可以看出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</pre>
  </td>
</tr></tbody></table><p>結果正确。其實看了實作的源代碼後也會發現這種公式還是沒事找事,事實上直接乘多省事啊。</p>
           

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軸順時針旋轉θ弧度。

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      
<pre>&gt; 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 );      

原創文章作者保留版權 轉載請注明原作者 并給對外連結接

write by 九天雁翎(JTianLing) – blog.csdn.net/vagrxie

繼續閱讀