【译】光线跟踪:理论与实现(一) 简介
生活随笔
收集整理的這篇文章主要介紹了
【译】光线跟踪:理论与实现(一) 简介
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
光線跟蹤的目的是為了模擬自然現象:你能見到各種顏色是因為太陽發射出來的光線,經過各種自然物體的反射或折射后,最終進入你的眼睛。若我們暫時不去計較其他因素,所有的這些光線都應該是直線。 2008040902.jpg 如圖所示,黃色的光直接從太陽射入照相機中;紅色的光線在跟場景發生發射后到達照相機,而藍色的光線被玻璃球折射后命中照相機。圖中沒有畫出的是那些無法到達觀察者的光線,這些光線也是我們不從光源往照相機進行跟蹤的原因,而是采用想反的路徑。上圖標識的是一種理想情形,因為光線的方向沒有影響。 從上面我們得到一個啟示:與其等待光源發射一條光線穿過一個目前顏色還是黑色的像素,不如我們自己從照相機發射光線去穿過平面的每個像素,去觀察這些光線能擊中幾何體上的哪些像素。 // ----------------------------------------------------------- // Ray class definition // ----------------------------------------------------------- class Ray { public: Ray() : m_Origin( vector3( 0, 0, 0 ) ), m_Direction( vector3( 0, 0, 0 ) ) {}; Ray( vector3& a_Origin, vector3& a_Dir ); void SetOrigin( vector3& a_Origin ) { m_Origin = a_Origin; } void SetDirection( vector3& a_Direction ) { m_Direction = a_Direction; } vector3& GetOrigin() { return m_Origin; } vector3& GetDirection() { return m_Direction; } private: vector3 m_Origin;//光線的起點 vector3 m_Direction;//光線的方向 };
一條光線有它的起點和方向。當從照相機發射光線時,起點一般是一個固定點,并且光線會穿過屏幕表面的像素。 2008040903.jpg // ----------------------------------------------------------- // Fires rays in the scene one scanline at a time, from left // to right // ----------------------------------------------------------- bool Engine::Render() { // render scene vector3 o( 0, 0, -5 ); // initialize timer int msecs = GetTickCount(); // reset last found primitive pointer Primitive* lastprim = 0; // render remaining lines for ( int y = m_CurrLine; y < (m_Height - 20); y++ ) { //逐條掃描線處理 m_SX = m_WX1; // render pixels for current line for ( int x = 0; x < m_Width; x++ ) {//對當前掃描線上的所有像素點處理 // fire primary ray Color acc( 0, 0, 0 ); vector3 dir = vector3( m_SX, m_SY, 0 ) - o; //發射出的光線的方向 NORMALIZE( dir ); Ray r( o, dir ); float dist; Primitive* prim = Raytrace( r, acc, 1, 1.0f, dist ); int red = (int)(acc.r * 256); int green = (int)(acc.g * 256); int blue = (int)(acc.b * 256); if (red > 255) red = 255; if (green > 255) green = 255; if (blue > 255) blue = 255; m_Dest[m_PPos++] = (red << 16) + (green << 8) + blue; m_SX += m_DX; } m_SY += m_DY; // see if we've been working to long already if ((GetTickCount() - msecs) > 100)? { // return control to windows so the screen gets updated m_CurrLine = y + 1; return false; } } // all done return true; }
注意這段代碼: vector3 o( 0, 0, -5 ); vector3 dir = vector3( m_SX, m_SY, 0 ) - o; NORMALIZE( dir ); Ray r( o, dir );
一條光線起始點在’o’,方向朝向屏幕平面上的一個位置,并且方向進行了單位化處理,從而建立了這條光線。 屏幕平面指的是一個漂浮在虛擬世界的一個矩形,用來表示屏幕。代碼中它以原點為中心,寬為8個單位,高為6個單位,這對于800*600的分辨率是合適的。你可以對這個平面做各種處理:若你將它移開照相機,則光線的寬度就變窄,從而物體會在屏幕上變大。若你旋轉這個平面(且照相機以它為中心),你會得到虛擬世界的另一種視圖。 接下來,我們需要一個場景來進行光線跟蹤。一個場景中包含各種元素:如球體和平面等幾何物體。你也可以使用三角面片,并且用這些三角面片來構造其他各種元素。 元素Sphere和PlanePrim是從Primitive繼承下來的,每個元素都有一個Material,并且都實現了方法Intersect和GetNormal. // ----------------------------------------------------------- // Scene class definition // -----------------------------------------------------------
class Scene { public: Scene() : m_Primitives( 0 ), m_Primitive( 0 ) {}; ~Scene(); void InitScene(); int GetNrPrimitives() { return m_Primitives; } Primitive* GetPrimitive( int a_Idx ) { return m_Primitive[a_Idx]; } private: int m_Primitives; Primitive** m_Primitive;//保存的是指向各種元素的指針 };
void Scene::InitScene() { m_Primitive = new Primitive*[100];//最多100個立體元素 // ground plane m_Primitive[0] = new PlanePrim( vector3( 0, 1, 0 ), 4.4f ); m_Primitive[0]->SetName( "plane" ); m_Primitive[0]->GetMaterial()->SetReflection( 0 ); m_Primitive[0]->GetMaterial()->SetDiffuse( 1.0f ); m_Primitive[0]->GetMaterial()->SetColor( Color( 0.4f, 0.3f, 0.3f ) ); // big sphere m_Primitive[1] = new Sphere( vector3( 1, -0.8f, 3 ), 2.5f ); m_Primitive[1]->SetName( "big sphere" ); m_Primitive[1]->GetMaterial()->SetReflection( 0.6f ); m_Primitive[1]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 0.7f ) ); // small sphere m_Primitive[2] = new Sphere( vector3( -5.5f, -0.5, 7 ), 2 ); m_Primitive[2]->SetName( "small sphere" ); m_Primitive[2]->GetMaterial()->SetReflection( 1.0f ); m_Primitive[2]->GetMaterial()->SetDiffuse( 0.1f ); m_Primitive[2]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 1.0f ) ); // light source 1 m_Primitive[3] = new Sphere( vector3( 0, 5, 5 ), 0.1f ); m_Primitive[3]->Light( true ); m_Primitive[3]->GetMaterial()->SetColor( Color( 0.6f, 0.6f, 0.6f ) ); // light source 2 m_Primitive[4] = new Sphere( vector3( 2, 5, 1 ), 0.1f ); m_Primitive[4]->Light( true ); m_Primitive[4]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 0.9f ) ); // set number of primitives m_Primitives = 5; }
這個方法中我們加入了一個地表平面,兩個球體以及2個光源。 現在就開始跟蹤光線了,首先來看下處理的偽代碼: For each pixel { Construct ray from camera through pixel Find first primitive hit by ray Determine color at intersection point Draw color }?
為了確定光線命中的最近的一個元素,我們必須對其所有可能的交點做測試。
// ----------------------------------------------------------- // Naive ray tracing: Intersects the ray with every primitive // in the scene to determine the closest intersection // ----------------------------------------------------------- Primitive* Engine::Raytrace( Ray& a_Ray, Color& a_Acc, int a_Depth, float a_RIndex, float& a_Dist ) { if (a_Depth > TRACEDEPTH) return 0; // trace primary ray a_Dist = 1000000.0f; vector3 pi; Primitive* prim = 0; int result; // find the nearest intersection for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ ) { Primitive* pr = m_Scene->GetPrimitive( s ); int res; if (res = pr->Intersect( a_Ray, a_Dist ))? { prim = pr; result = res; // 0 = miss, 1 = hit, -1 = hit from inside primitive } } // no hit, terminate ray if (!prim) return 0; // handle intersection if (prim->IsLight()) { // we hit a light, stop tracing a_Acc = Color( 1, 1, 1 ); } else { // determine color at point of intersection pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist; // trace lights for ( int l = 0; l < m_Scene->GetNrPrimitives(); l++ ) { Primitive* p = m_Scene->GetPrimitive( l ); if (p->IsLight())? { Primitive* light = p; // calculate diffuse shading vector3 L = ((Sphere*)light)->GetCentre() - pi; NORMALIZE( L ); vector3 N = prim->GetNormal( pi ); if (prim->GetMaterial()->GetDiffuse() > 0) { float dot = DOT( N, L ); if (dot > 0) { float diff = dot * prim->GetMaterial()->GetDiffuse(); // add diffuse component to ray color a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor(); } } } } } // return pointer to primitive hit by primary ray return prim; }
其中這段代碼:
// find the nearest intersection for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ ) {//對所有的元素做測試 Primitive* pr = m_Scene->GetPrimitive( s ); int res; if (res = pr->Intersect( a_Ray, a_Dist ))? {//找到第一個命中的元素 prim = pr; result = res; // 0 = miss, 1 = hit, -1 = hit from inside primitive } }
對場景中的所以元素做循環處理,為每個元素調用其Intersect方法,這個方法以一條光線為參數,返回一個整數表明是命中還是沒有命中,以及相交的距離是在體內還是體外。除此以外還會記錄下最近相交的記錄。 一旦我們知道光線命中的是那個元素,那么就可以來計算光線的顏色了。若只是簡單地使用元素的材質顏色就太簡單了,并且結果顏色也很枯燥。因此,我們使用兩個點光源來計算散射陰影。 // determine color at point of intersection pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist; // trace lights for ( int l = 0; l < m_Scene->GetNrPrimitives(); l++ ) { Primitive* p = m_Scene->GetPrimitive( l ); if (p->IsLight())? { Primitive* light = p; // calculate diffuse shading vector3 L = ((Sphere*)light)->GetCentre() - pi; NORMALIZE( L ); vector3 N = prim->GetNormal( pi ); if (prim->GetMaterial()->GetDiffuse() > 0) { float dot = DOT( N, L );//點積 if (dot > 0) { float diff = dot * prim->GetMaterial()->GetDiffuse(); // add diffuse component to ray color a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor(); } } }
這段代碼計算從相交點(pi)到光源(L)的向量,并用這個向量和相交點的單位向量的叉積來計算出光源的亮度。這個計算出的亮度是元素朝向光源的那一點被光源照亮,而其他點就是陰暗的了。叉積大于0為了防止面與光源反向。 2008040901.jpg 好了,這一篇就到這里了,沒有反射,沒有折射,更沒有加入陰影,這些東東在后續的文章中會慢慢加入的,而這只是最簡單的一個光線跟蹤而已
本文轉自Phinecos(洞庭散人)博客園博客,原文鏈接:http://www.cnblogs.com/phinecos/archive/2008/04/09/1145255.html,如需轉載請自行聯系原作者
一條光線有它的起點和方向。當從照相機發射光線時,起點一般是一個固定點,并且光線會穿過屏幕表面的像素。 2008040903.jpg // ----------------------------------------------------------- // Fires rays in the scene one scanline at a time, from left // to right // ----------------------------------------------------------- bool Engine::Render() { // render scene vector3 o( 0, 0, -5 ); // initialize timer int msecs = GetTickCount(); // reset last found primitive pointer Primitive* lastprim = 0; // render remaining lines for ( int y = m_CurrLine; y < (m_Height - 20); y++ ) { //逐條掃描線處理 m_SX = m_WX1; // render pixels for current line for ( int x = 0; x < m_Width; x++ ) {//對當前掃描線上的所有像素點處理 // fire primary ray Color acc( 0, 0, 0 ); vector3 dir = vector3( m_SX, m_SY, 0 ) - o; //發射出的光線的方向 NORMALIZE( dir ); Ray r( o, dir ); float dist; Primitive* prim = Raytrace( r, acc, 1, 1.0f, dist ); int red = (int)(acc.r * 256); int green = (int)(acc.g * 256); int blue = (int)(acc.b * 256); if (red > 255) red = 255; if (green > 255) green = 255; if (blue > 255) blue = 255; m_Dest[m_PPos++] = (red << 16) + (green << 8) + blue; m_SX += m_DX; } m_SY += m_DY; // see if we've been working to long already if ((GetTickCount() - msecs) > 100)? { // return control to windows so the screen gets updated m_CurrLine = y + 1; return false; } } // all done return true; }
注意這段代碼: vector3 o( 0, 0, -5 ); vector3 dir = vector3( m_SX, m_SY, 0 ) - o; NORMALIZE( dir ); Ray r( o, dir );
一條光線起始點在’o’,方向朝向屏幕平面上的一個位置,并且方向進行了單位化處理,從而建立了這條光線。 屏幕平面指的是一個漂浮在虛擬世界的一個矩形,用來表示屏幕。代碼中它以原點為中心,寬為8個單位,高為6個單位,這對于800*600的分辨率是合適的。你可以對這個平面做各種處理:若你將它移開照相機,則光線的寬度就變窄,從而物體會在屏幕上變大。若你旋轉這個平面(且照相機以它為中心),你會得到虛擬世界的另一種視圖。 接下來,我們需要一個場景來進行光線跟蹤。一個場景中包含各種元素:如球體和平面等幾何物體。你也可以使用三角面片,并且用這些三角面片來構造其他各種元素。 元素Sphere和PlanePrim是從Primitive繼承下來的,每個元素都有一個Material,并且都實現了方法Intersect和GetNormal. // ----------------------------------------------------------- // Scene class definition // -----------------------------------------------------------
class Scene { public: Scene() : m_Primitives( 0 ), m_Primitive( 0 ) {}; ~Scene(); void InitScene(); int GetNrPrimitives() { return m_Primitives; } Primitive* GetPrimitive( int a_Idx ) { return m_Primitive[a_Idx]; } private: int m_Primitives; Primitive** m_Primitive;//保存的是指向各種元素的指針 };
void Scene::InitScene() { m_Primitive = new Primitive*[100];//最多100個立體元素 // ground plane m_Primitive[0] = new PlanePrim( vector3( 0, 1, 0 ), 4.4f ); m_Primitive[0]->SetName( "plane" ); m_Primitive[0]->GetMaterial()->SetReflection( 0 ); m_Primitive[0]->GetMaterial()->SetDiffuse( 1.0f ); m_Primitive[0]->GetMaterial()->SetColor( Color( 0.4f, 0.3f, 0.3f ) ); // big sphere m_Primitive[1] = new Sphere( vector3( 1, -0.8f, 3 ), 2.5f ); m_Primitive[1]->SetName( "big sphere" ); m_Primitive[1]->GetMaterial()->SetReflection( 0.6f ); m_Primitive[1]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 0.7f ) ); // small sphere m_Primitive[2] = new Sphere( vector3( -5.5f, -0.5, 7 ), 2 ); m_Primitive[2]->SetName( "small sphere" ); m_Primitive[2]->GetMaterial()->SetReflection( 1.0f ); m_Primitive[2]->GetMaterial()->SetDiffuse( 0.1f ); m_Primitive[2]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 1.0f ) ); // light source 1 m_Primitive[3] = new Sphere( vector3( 0, 5, 5 ), 0.1f ); m_Primitive[3]->Light( true ); m_Primitive[3]->GetMaterial()->SetColor( Color( 0.6f, 0.6f, 0.6f ) ); // light source 2 m_Primitive[4] = new Sphere( vector3( 2, 5, 1 ), 0.1f ); m_Primitive[4]->Light( true ); m_Primitive[4]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 0.9f ) ); // set number of primitives m_Primitives = 5; }
這個方法中我們加入了一個地表平面,兩個球體以及2個光源。 現在就開始跟蹤光線了,首先來看下處理的偽代碼: For each pixel { Construct ray from camera through pixel Find first primitive hit by ray Determine color at intersection point Draw color }?
為了確定光線命中的最近的一個元素,我們必須對其所有可能的交點做測試。
// ----------------------------------------------------------- // Naive ray tracing: Intersects the ray with every primitive // in the scene to determine the closest intersection // ----------------------------------------------------------- Primitive* Engine::Raytrace( Ray& a_Ray, Color& a_Acc, int a_Depth, float a_RIndex, float& a_Dist ) { if (a_Depth > TRACEDEPTH) return 0; // trace primary ray a_Dist = 1000000.0f; vector3 pi; Primitive* prim = 0; int result; // find the nearest intersection for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ ) { Primitive* pr = m_Scene->GetPrimitive( s ); int res; if (res = pr->Intersect( a_Ray, a_Dist ))? { prim = pr; result = res; // 0 = miss, 1 = hit, -1 = hit from inside primitive } } // no hit, terminate ray if (!prim) return 0; // handle intersection if (prim->IsLight()) { // we hit a light, stop tracing a_Acc = Color( 1, 1, 1 ); } else { // determine color at point of intersection pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist; // trace lights for ( int l = 0; l < m_Scene->GetNrPrimitives(); l++ ) { Primitive* p = m_Scene->GetPrimitive( l ); if (p->IsLight())? { Primitive* light = p; // calculate diffuse shading vector3 L = ((Sphere*)light)->GetCentre() - pi; NORMALIZE( L ); vector3 N = prim->GetNormal( pi ); if (prim->GetMaterial()->GetDiffuse() > 0) { float dot = DOT( N, L ); if (dot > 0) { float diff = dot * prim->GetMaterial()->GetDiffuse(); // add diffuse component to ray color a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor(); } } } } } // return pointer to primitive hit by primary ray return prim; }
其中這段代碼:
// find the nearest intersection for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ ) {//對所有的元素做測試 Primitive* pr = m_Scene->GetPrimitive( s ); int res; if (res = pr->Intersect( a_Ray, a_Dist ))? {//找到第一個命中的元素 prim = pr; result = res; // 0 = miss, 1 = hit, -1 = hit from inside primitive } }
對場景中的所以元素做循環處理,為每個元素調用其Intersect方法,這個方法以一條光線為參數,返回一個整數表明是命中還是沒有命中,以及相交的距離是在體內還是體外。除此以外還會記錄下最近相交的記錄。 一旦我們知道光線命中的是那個元素,那么就可以來計算光線的顏色了。若只是簡單地使用元素的材質顏色就太簡單了,并且結果顏色也很枯燥。因此,我們使用兩個點光源來計算散射陰影。 // determine color at point of intersection pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist; // trace lights for ( int l = 0; l < m_Scene->GetNrPrimitives(); l++ ) { Primitive* p = m_Scene->GetPrimitive( l ); if (p->IsLight())? { Primitive* light = p; // calculate diffuse shading vector3 L = ((Sphere*)light)->GetCentre() - pi; NORMALIZE( L ); vector3 N = prim->GetNormal( pi ); if (prim->GetMaterial()->GetDiffuse() > 0) { float dot = DOT( N, L );//點積 if (dot > 0) { float diff = dot * prim->GetMaterial()->GetDiffuse(); // add diffuse component to ray color a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor(); } } }
這段代碼計算從相交點(pi)到光源(L)的向量,并用這個向量和相交點的單位向量的叉積來計算出光源的亮度。這個計算出的亮度是元素朝向光源的那一點被光源照亮,而其他點就是陰暗的了。叉積大于0為了防止面與光源反向。 2008040901.jpg 好了,這一篇就到這里了,沒有反射,沒有折射,更沒有加入陰影,這些東東在后續的文章中會慢慢加入的,而這只是最簡單的一個光線跟蹤而已
本文轉自Phinecos(洞庭散人)博客園博客,原文鏈接:http://www.cnblogs.com/phinecos/archive/2008/04/09/1145255.html,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的【译】光线跟踪:理论与实现(一) 简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端程序员的一些有学习借鉴作用的网站
- 下一篇: [20171227]表的FULL_HAS