write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie
說明
因為大學時在高等數學課程中學習過線性代數相關的內容,所以學習3D編程的時候這一段事實上是跳過去了,學習到某些內容的時候覺得很郁悶,(4,5年沒有用了,難免忘掉)最后常常依靠高級API完成,但是事實上這些高級API的算法具體實現啥的基本看不懂,于是還是決定回來好好的將基礎部分弄明白,當然,首先是數學部分。為了更好的達到直觀的效果,還有在復雜矩陣運算的時候驗證運算結果,將引入freemat或者scilab(5.1.1)或者GNU Octave(3.2.3)的使用,將此三個軟件作為matlab的替代品來使用。不能用龐大的matlab也是種解脫,默認使用freemat,不行的時候考慮其他替代。具體牽涉到計算的時盡量實現DirectX與Irrlicht兩個版本,也會參考部分源代碼。(主要用于看看公式用C/C++的實現)基本上,我希望能以概念的講解為主,最好是直觀的講解。
向量
只用大小就能表示的量叫數量,比如溫度,質量等。既需要用大小表示,同時還要指明方向的量叫向量,比如位移,速度等。幾何學中,我們用有向線段來表示向量。有兩個變量可以確定一個向量,即向量的長度和向量的方向。量與位置無關,有相同長度和方向的兩個向量是相等的。在irrlicht中有專門的類vector2d,vector3d分別來表示2維的,3維的向量。在DirectX中用于表示向量的是結構D3DXVECTOR2,D3DXVECTOR3,D3DXVECTOR4。
左右手坐標系
一圖勝前言,不懂怎么用手扭曲的去比劃的看看圖,就明白啥是左手,啥是右手坐標系了。在OpenGL中使用的是右手坐標系,DirectX,Irrlicht中使用的是左手坐標系。(圖片來自于網絡)
向量的模
向量的大小(或長度)稱為向量的模,向量a的模記為||a||。下面以3維的向量(3D中用的最多)為例:
在irrlicht中獲取向量模的函數是vector3d的成員函數
//! Get length of the vector. T getLength () const { return core :: squareroot ( X * X + Y * Y + Z * Z ); } //! Get squared length of the vector. /** This is useful because it is much faster than getLength(). /return Squared length of the vector. */ T getLengthSQ () const { return X * X + Y * Y + Z * Z ; }
可以看出公式的實現,其中getLengthSQ用于某些時候使用不開根號,直接使用平方值的方法來優化代碼。
DirectX中的實現差不多一樣,只是使用的是C風格的接口沒有使用C++的類而已。
D3DXINLINE FLOAT D3DXVec3Length ( CONST D3DXVECTOR3 * pV ) { #ifdef D3DX_DEBUG if(!pV) return 0.0f; #endif #ifdef __cplusplus return sqrtf ( pV -> x * pV -> x + pV -> y * pV -> y + pV -> z * pV -> z ); #else return (FLOAT) sqrt(pV->x * pV->x + pV->y * pV->y + pV->z * pV->z); #endif } D3DXINLINE FLOAT D3DXVec3LengthSq ( CONST D3DXVECTOR3 * pV ) { #ifdef D3DX_DEBUG if(!pV) return 0.0f; #endif return pV -> x * pV -> x + pV -> y * pV -> y + pV -> z * pV -> z ; }
FreeMat:
--> a = [1, 1, 1] a = 1 1 1 --> b = norm(a) b = 1.7321 --> |
三維空間中兩點的距離
Irrlicht的實現:
//! Get distance from another point. /** Here, the vector is interpreted as point in 3 dimensional space. */ T getDistanceFrom ( const vector3d < T >& other ) const { return vector3d < T >( X - other . X , Y - other . Y , Z - other . Z ). getLength (); } //! Returns squared distance from another point. /** Here, the vector is interpreted as point in 3 dimensional space. */ T getDistanceFromSQ ( const vector3d < T >& other ) const { return vector3d < T >( X - other . X , Y - other . Y , Z - other . Z ). getLengthSQ (); }
向量的規范化
向量的規范化也稱(歸一化)就是使向量的模變為1,即變為單位向量。可以通過將向量都除以該向量的模來實現向量的規范化。規范化后的向量相當于與向量同方向的單位向量,可以用它表示向量的方向。由于方向的概念在3D編程中非常重要,所以此概念也很重要,單位向量有很多重要的性質,在表示物體表面的法線向量時用的更是頻繁。
在irrlicht中的調用函數及實現:
//! Normalizes the vector. /** In case of the 0 vector the result is still 0, otherwise the length of the vector will be 1. /return Reference to this vector after normalization. */ vector3d < T >& normalize () { f64 length = ( f32 )( X * X + Y * Y + Z * Z ); if ( core :: equals ( length , 0.0)) // this check isn't an optimization but prevents getting NAN in the sqrt. return * this ; length = core :: reciprocal_squareroot ( ( f64 ) ( X * X + Y * Y + Z * Z ) ); X = ( T )( X * length ); Y = ( T )( Y * length ); Z = ( T )( Z * length ); return * this ; }
上述代碼中首先計算length以防其為0,然后直接計算1/||u||,(這樣做的目的從代碼實現上來看是因為SSE,Nviadia都有可以直接計算此值的能力) 然后再分別與各坐標值進行乘法運算。
DirectX中的調用函數:(無實現可看)
D3DXVECTOR3 * WINAPI D3DXVec3Normalize ( D3DXVECTOR3 * pOut , CONST D3DXVECTOR3 * pV );
向量的加減法,數乘
太簡單,不多描述,無非就是對應的加,減,乘罷了,幾何意義講一下,加法可以看做是兩個向量綜合后的方向,減法可以看做兩個向量的差異方向(甚至可以用于追蹤算法),數乘用于對向量進行縮放。
為了完整,這里從 百度百科 拷貝一段資料過來:(以下都是2維的,放到3維也差不多)
設 a =(x,y), b =(x',y')。
1、向量的加法
向量的加法滿足平行四邊形法則和三角形法則。
AB + BC = AC 。
a + b =(x+x',y+y')。
a + 0 = 0 + a = a。
向量加法的運算律:
交換律: a + b = b + a;
結合律:( a + b )+ c = a +( b + c )。
2、向量的減法
如果 a 、 b 是互為相反的向量,那么 a =- b , b =- a , a + b = 0. 0 的反向量為 0
AB - AC = CB. 即“共同起點,指向被減”
a =(x,y) b =(x',y') 則 a - b =(x-x',y-y').
3、數乘向量
實數λ和向量 a 的乘積是一個向量,記作λ a ,且∣λa∣=∣λ∣ · ∣a∣。
當λ>0時,λ a 與 a 同方向;
當λ<0時,λ a 與 a 反方向;
當λ=0時,λ a = 0 ,方向任意。
當 a = 0 時,對于任意實數λ,都有λ a = 0 。
注:按定義知,如果λ a = 0 ,那么λ=0或 a = 0 。
實數λ叫做向量 a 的系數,乘數向量λ a 的幾何意義就是將表示向量 a 的有向線段伸長或壓縮。
當∣λ∣>1時,表示向量a的有向線段在原方向(λ>0)或反方向(λ<0)上伸長為原來的∣λ∣倍;
當∣λ∣<1時,表示向量a的有向線段在原方向(λ>0)或反方向(λ<0)上縮短為原來的∣λ∣倍。
數與向量的乘法滿足下面的運算律
結合律:(λ a ) ·b =λ( a · b) =( a ·λ b )。
向量對于數的分配律(第一分配律):(λ+μ) a =λ a +μ a.
數對于向量的分配律(第二分配律):λ( a + b )=λ a +λ b.
數乘向量的消去律:① 如果實數λ≠0且λ a= λ b ,那么 a=b 。② 如果 a ≠ 0 且λ a= μ a ,那么λ = μ。
點積(dot product)又稱數量積或內積
v0 . v1 = v0.x*v1.x+v0.y*v1.y+v0.z*v1.z;
所以向量的點積結果是一個數,而非向量。
點積等于向量v0的長度乘以v1的長度,再乘以它們之間夾角的余弦,即|v0|*|v1|*cos(θ).
通過點積,可以計算兩個向量之間的夾角。
cos(θ)=v0.v1/|v0||v1|;
θ=Math.acos(v0.v1/|v0||v1|);
如果兩個向量都是單位向量,上面的公式可以簡化為
θ=Math.acos(v0.v1);
V0.v1=0 =》兩個向量互相垂直
V0.v1>0 =》兩個向量的夾角小于90度
V0.v1<0 =》兩個向量的夾角大于90度
Irrlicht中的實現:(很簡單的公式,很直白的實現)
//! Get the dot product with another vector. T dotProduct ( const vector3d < T >& other ) const { return X * other . X + Y * other . Y + Z * other . Z ; }
DirectX中的實現:(很簡單的公式,也是很直白的實現)
D3DXINLINE FLOAT D3DXVec3Dot ( CONST D3DXVECTOR3 * pV1 , CONST D3DXVECTOR3 * pV2 ) { #ifdef D3DX_DEBUG if(!pV1 || !pV2) return 0.0f; #endif return pV1 -> x * pV2 -> x + pV1 -> y * pV2 -> y + pV1 -> z * pV2 -> z ; }
叉積(cross product):也稱向量積
叉積的結果是一個向量,該向量垂直于相乘的兩個向量。
注意:叉積不滿足交換律,反過來相乘得到的向量與原向量方向相反。
左手坐標系可以通過左手法則來確定叉積返回的向量的方向,從第一個向量向第二個向量彎曲左手,這是拇指所指的方向就是求得的向量的方向。右手坐標系同樣的,可以通過右手法則來確定叉積返回的向量的方向,從第一個向量向第二個向量彎曲右手,這是拇指所指的方向就是求得的向量的方向。因此,事實上叉積獲得的向量總是垂直于原來兩個向量所在的平面。
如果兩個向量方向相同或相反,叉積結果將是一個零向量。(即a//b)
叉乘的一個重要應用就是求三角形的法向量。
Irrlicht的實現:
//! Calculates the cross product with another vector. /** /param p Vector to multiply with. /return Crossproduct of this vector with p. */ vector3d < T > crossProduct ( const vector3d < T >& p ) const { return vector3d < T >( Y * p . Z - Z * p . Y , Z * p . X - X * p . Z , X * p . Y - Y * p . X ); }
DirectX的實現:
D3DXINLINE D3DXVECTOR3 * D3DXVec3Cross ( D3DXVECTOR3 * pOut , CONST D3DXVECTOR3 * pV1 , CONST D3DXVECTOR3 * pV2 ) { D3DXVECTOR3 v ; #ifdef D3DX_DEBUG if(!pOut || !pV1 || !pV2) return NULL; #endif v . x = pV1 -> y * pV2 -> z - pV1 -> z * pV2 -> y ; v . y = pV1 -> z * pV2 -> x - pV1 -> x * pV2 -> z ; v . z = pV1 -> x * pV2 -> y - pV1 -> y * pV2 -> x ; * pOut = v ; return pOut ; }基本上也就是按公式來了。
作為最后一個概念,這里用代碼實踐一下。
求a=(2,2,1)和b=(4,5,3)的叉積。
freemat:
--> a = [2,2,1] a = 2 2 1 --> b = [4,5,3] b = 4 5 3 --> c = cross(a,b) c = 1 -2 2 --> |
Irrlicht:
#include <stdio.h> #include <irrlicht.h> using namespace irr :: core ; int _tmain ( int argc , _TCHAR * argv []) { vector3df a (2.0f, 2.0f, 1.0f); vector3df b (4.0f, 5.0f, 3.0f); vector3df c = a . crossProduct ( b ); printf ( "c = (%f, %f, %f)" , c . X , c . Y , c . Z ); return 0; }
輸出:
c = (1.000000, -2.000000, 2.000000)
DirectX:
#include <stdio.h> #include <d3dx9.h> int _tmain ( int argc , _TCHAR * argv []) { D3DXVECTOR3 a (2.0f, 2.0f, 1.0f); D3DXVECTOR3 b (4.0f, 5.0f, 3.0f); D3DXVECTOR3 c ; D3DXVec3Cross (& c , & a , & b ); printf ( "c = (%f, %f, %f)" , c . x , c . y , c . z ); return 0; }
輸出:
c = (1.000000, -2.000000, 2.000000)
這里給出個較為完整的例子是希望大家了解一下Irrlicht這種C++風格的接口及DirectX的C風格接口使用上的不同,這里就不對兩種風格的接口提出更多評論了,以防引起口水戰。
下一篇預計講矩陣的計算
參考資料:
1.《DirectX 9.0 3D游戲開發編程基礎》 ,(美)Frank D.Luna著,段菲譯,清華大學出版社
2.《大學數學》湖南大學數學與計量經濟學院組編,高等教育出版社
3.百度百科及wikipedia
原創文章作者保留版權 轉載請注明原作者 并給出鏈接
<script type="text/javascript">var sitebro_tracker_atc_kw = {u:'http://www.sitebot.com.cn/754892/',w:'NzU0ODky',bt:'#804000',bg:'#EEEEDD',fs:1,ca:'#770000',bh:'#f4f4c6',cp:'',l:10,s:1,lang:'zh_CN'};</script><script type="text/javascript" src="http://www.sitebot.com.cn/js/widget_track2/tracker_atc_kw.js"></script><script type="text/javascript"><!-- var sitebro_tracker_atc={u:'http://www.sitebot.com.cn/754892/',w:'NzU0ODky',bt:'#804000',bg:'#EEEEDD',cf:'#ffffff',ca:'#770000',bh:'#DDDDCC',cp:'%E6%9C%AC%E7%AB%99%E7%83%AD%E9%97%A8%E6%96%87%E7%AB%A0',l:10,s:0,lang:'zh_CN'}; // --></script><script src="http://www.sitebot.com.cn/js/widget_track2/tracker_atc.js" type="text/javascript"></script>
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
