光線(xiàn)追蹤技術(shù)的理論和實(shí)踐(面向對象)
CGObject類(lèi)成員變量有五個(gè),分別表示物體表面環(huán)境光反射系數(m_Ka),漫反射系數(m_Kd),鏡面反射系數(m_Ks),鏡面反射強度(m_Shininess)和環(huán)境反射強度(m_Reflectivity)。前四個(gè)變量是計算光照所需要的最基本量,而環(huán)境反射強度表示該物體能反射環(huán)境的能力。這些成員變量都的類(lèi)型都是protected,因為我們要把CGObject最為物體的基類(lèi),這些protected成員變量可以被該類(lèi)的子類(lèi)所繼承。該類(lèi)的所有g(shù)et方法和set方法都能被子類(lèi)繼承,而且所有繼承了該類(lèi)的子類(lèi)的方法都相同。該類(lèi)還有兩個(gè)虛成員函數,分別是getNormal()和isIntersected()。getNormal()函數的作用是獲取物體表面一點(diǎn)的法線(xiàn),它接受一個(gè)GVector3類(lèi)型的參數_Point,并返回物體表面點(diǎn)_Point處的法線(xiàn)。當然不同物體表面獲得法線(xiàn)的方法是不一樣的。比如,對于平面來(lái)說(shuō),平面上所有點(diǎn)的法線(xiàn)都是一樣的。而對于球來(lái)說(shuō),球面上每一個(gè)的法線(xiàn)是球面上的該交點(diǎn)p和球心的c的差向量。
本文引用地址:http://dyxdggzs.com/article/164673.htmNSphere = p - c
所以將getNormal()設置為虛成員函數就可以實(shí)現類(lèi)的多態(tài)性,凡是繼承了該方法的子類(lèi),都可以實(shí)現自己的getNormal()方法。同樣的道理,函數isInserted也是虛成員函數,該方法接受參數射線(xiàn)CRay
和距離Distance,CRay是輸入參數,用于判斷射線(xiàn)和該物體的交點(diǎn),Distance是輸出參數,如果物體和射線(xiàn)相交,則返回相機到該交點(diǎn)的距離。Distance還應該有個(gè)很大初始值,表示在無(wú)限遠處物體和射線(xiàn)相交,這種情況用于判斷物體和射線(xiàn)沒(méi)有交點(diǎn)。函數isIntersected()還返回一個(gè)枚舉類(lèi)型INTERSECTION_TYPE,定義如下:
enum INTERSECTION_TYPE {INTERSECTED_IN = -1, MISS = 0, INTERSECTED = 1};
其中INTERSECTED_IN表示射線(xiàn)從物體內部出發(fā)并和物體有交點(diǎn),MISS射線(xiàn)和物體沒(méi)有交點(diǎn),INTERSECTED表示射線(xiàn)從物體外部出發(fā)并且和物體有交點(diǎn)。射線(xiàn)和不同物體交點(diǎn)的計算方法不同,于是該函數為虛函數,繼承該函數的子類(lèi)可以實(shí)現自己的isIntersected()方法。下面的代碼就可以判斷一條射線(xiàn)和場(chǎng)景中所有物體的是否有交點(diǎn),并且返回離相機最近的一個(gè)。
double distance = 1000000; // 初始化無(wú)限大距離
GVector3 Intersection; // 交點(diǎn)
for(int i = 0; i
{
CGObject *obj = objects_list[i];
if( obj->isIntersected(ray, distance) != MISS) // 判斷是否有交點(diǎn)
{
Intersection = ray.getPoint(distance); //如果相交,求出交點(diǎn)保存到Intersection
}
}
為了計算方便,這里就以球為例,創(chuàng )建一個(gè)CSphere的類(lèi),該類(lèi)繼承于CGObject。

作為球,只需要提供球心Center和半徑Radius就可以決定它的幾何性質(zhì)。所以CSphere類(lèi)只有兩個(gè)私有成員變量。在所有成員函數中,我們重點(diǎn)來(lái)看看isIntersected()方法。
INTERSECTION_TYPE CSphere::isIntersected(CRay _ray, double _dist)
{
GVector3 v = _ray.getOrigin() - m_Center;
double b = -(v * _ray.getDirection());
double det = (b * b) - v*v + m_Radius;
INTERSECTION_TYPE retval = MISS;
if (det > 0){
det = sqrt(det);
double t1 = b - det;
double t2 = b + det;
if (t2 > 0){
if (t1 0) {
if (t2 _dist) {
_dist = t2;
retval = INTERSECTED_IN;
}
}
else{
if (t1 _dist){
_dist = t1;
retval = INTERSECTED;
}
}
}
}
return retval;
}
如果射線(xiàn)和球有交點(diǎn),那么交點(diǎn)肯定在球面上。球面上的點(diǎn)P都滿(mǎn)足下面的關(guān)系,
| P – C | = R
很明顯球面上的點(diǎn)和球心的差向量的大小等于球的半徑。然后將射線(xiàn)的參數方程帶入上面的公式,再利用求根公式判斷解的情況。具體的方法這里就不詳述了,有興趣的同學(xué)可以參考另一篇文章“利用OpenGL實(shí)現RayPicking”,這篇文章詳細講解了射線(xiàn)和球交點(diǎn)的計算過(guò)程。
現在我們實(shí)現了射線(xiàn)CRay,球體CSphere,還差一個(gè)重要的角色——光源。光源也是物體的一種,完全可以從我們的基類(lèi)CGObject類(lèi)繼承。這里做一點(diǎn)區別,我們單獨創(chuàng )建一個(gè)所有光源的基類(lèi)CLightSource,然后從它在派生出不同的光源種類(lèi),比如平行光源DirectionalLight,點(diǎn)光源CPointLight和聚光源CSpotLight。本文中只詳細講解平行光源的情況,其他兩種光源有興趣的同學(xué)可以自己實(shí)現。

類(lèi)CLightSource的成員變量有四個(gè),分別表示光源的位置,光源的環(huán)境光成分,漫反射成分和鏡面反射成分。同樣地,所有的set和get方法都為該類(lèi)的子類(lèi)提供相同的功能。最后也有三個(gè)虛成員函數,EvalAmbient(),EvalDiffuse()和EvalSpecular(),它們名字分別說(shuō)明它們的功能,并且都返回GVector3類(lèi)型的值——顏色。由于對于不同種類(lèi)的光源,計算方法可能不同,于是將它們設置為虛函數為以后的擴展做準備。筆者這里將光照計算放在了光源類(lèi)里面,當然你也可以放在物體類(lèi)CGObject里,也可以單獨寫(xiě)一個(gè)方法,將光源和物體作為參數傳入,計算出顏色后最為返回值返回。具體使用哪一種好還是要根據具體情況具體分析。

上面的平行光源類(lèi)CDirectionalLight是CLightSource的子類(lèi),它繼承了父類(lèi)三個(gè)虛函數方法。下面來(lái)看看這三個(gè)函數的具體實(shí)現。
環(huán)境光的計算是最簡(jiǎn)單的,將物體材質(zhì)環(huán)境反射系數和光源的環(huán)境光成分相乘即可。
ambient = Ia•Ka
計算環(huán)境光的代碼如下
GVector3 CDirectionalLight::EvalAmbient(const GVector3 _material_Ka)
c++相關(guān)文章:c++教程
評論