光線追蹤(RayTracing)演算法理論與實踐(三)光照

pamxy發表於2013-05-17

轉自:http://blog.csdn.net/qp120291570/article/details/8257240

提要

經過之前的學習,我們已經可以在利用光線追蹤實現一些簡單的場景。今天我們要探討的是圖形學裡面的三種基本光源:方向光源,點光源,聚光燈。

不同於利用現成的Api,這次會從理論到實際一步步用C++實現。


前提工作

在老師的建議下,我將圖形引擎換成了SDL,最終的渲染效果比之前的好了很多,原來的GLFW雖然能夠很好的相容OpenGL,但並沒提供對畫素的控制,而SDL有Surface。

對與GLFW,本人覺得其終究只能算是glut的替代品,而SDL應當是一個完善的遊戲引擎,而且文件和教程都非常地豐富。

有關SDL的文章,請猛擊這裡


方向光源

方向光源是一組平行光。所以方向光源類只有方向和顏色兩個屬性。用一個向量物件來表示方向,顏色物件表示光的顏色。


陰影

回憶一下入門文章的第一幅圖片,在有光的情況下,判斷某一點是否是陰影,即判斷是否能夠從那一點看到光。

那麼光線追蹤的過程就是:

從攝像機產生光線->投射場景->若與物體相交,從該點產生光線,方向為光源方向的飯方向->投射場景->若與場景中的物體相交,則屬於陰影區域。


方向光源的實現:

  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:k283228391@126.com 
  4. File name: directlight.h 
  5. Description:directlight's h doc. 
  6. Author:Silang Quan 
  7. Version: 1.0 
  8. Date: 2012.12.04 
  9. *****************************************************************************/  
  10. #ifndef DIRECTLIGHT_H  
  11. #define DIRECTLIGHT_H  
  12. #include "color.h"  
  13. #include "gvector3.h"  
  14. #include "union.h"  
  15. class DirectLight  
  16. {  
  17.     public:  
  18.         DirectLight();  
  19.         DirectLight(Color _color,GVector3 _direction,bool _isShadow);  
  20.         virtual ~DirectLight();  
  21.         Color intersect(Union &scence,IntersectResult &result);  
  22.     protected:  
  23.     private:  
  24.         bool isShadow;  
  25.         Color color;  
  26.         GVector3 direction;  
  27. };  
  28. #endif // DIRECTLIGHT_H  

  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:k283228391@126.com 
  4. File name: directlight.cpp 
  5. Description:directlight's cpp doc. 
  6. Author:Silang Quan 
  7. Version: 1.0 
  8. Date: 2012.12.04 
  9. *****************************************************************************/  
  10. #include "directlight.h"  
  11.   
  12. DirectLight::DirectLight()  
  13. {  
  14.     //ctor  
  15. }  
  16. DirectLight::DirectLight(Color _color,GVector3 _direction,bool _isShadow)  
  17. {  
  18.     color=_color;  
  19.     direction=_direction;  
  20.     isShadow=_isShadow;  
  21. }  
  22. DirectLight::~DirectLight()  
  23. {  
  24.     //dtor  
  25. }  
  26. //通過光線與場景的相交結果計算光照結果  
  27. Color DirectLight::intersect(Union &scence,IntersectResult &rayResult)  
  28. {  
  29.     //生產shadowRay的修正值  
  30.     const float k=1e-4;  
  31.     //生成與光照相反方向的shadowRay  
  32.     GVector3 shadowDir=direction.normalize().negate();  
  33.     CRay shadowRay=CRay(rayResult.position+rayResult.normal*k,shadowDir);  
  34.     //計算shadowRay是否與場景相交  
  35.     IntersectResult lightResult = scence.isIntersected(shadowRay);  
  36.     Color resultColor = Color::black();  
  37.     if(isShadow)  
  38.     {  
  39.         if(lightResult.object)  
  40.         {  
  41.         return resultColor;  
  42.         }  
  43.     }  
  44.   
  45.     //計算光強  
  46.     float NdotL=rayResult.normal.dotMul(shadowDir);  
  47.     if (NdotL >= 0)  
  48.     resultColor=resultColor.add(this->color.multiply(NdotL));  
  49.     //return this->color;  
  50.   
  51.     return resultColor;  
  52. }  


需要注意的是intersect函式,輸入的引數是場景的引用和光線和場景相交結果的引用,返回一個Color。

若shadowRay沒有與場景相交,那麼就要對那一點接收到的光強進行計算。

與之有關的就是平面法向量與光的方向的夾角,當這個夾角約大,接受的光強就越小,想想看,中午太陽光是不是最強,傍晚是不是比較弱一些:0).

計算夾角利用的是向量的點乘。

渲染一下:

  1. void renderLight()  
  2. {  
  3.     Uint32 pixelColor;  
  4.     Union scene;  
  5.     PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, 0, -1),GVector3(0, 1, 0), 90);  
  6.     Plane* plane1=new Plane(GVector3(0, 1, 0),0.0);  
  7.     Plane* plane2=new Plane(GVector3(0, 0, 1),-50);  
  8.     Plane* plane3=new Plane(GVector3(1, 0, 0),-20);  
  9.     CSphere* sphere1=new CSphere(GVector3(0, 10, -10), 10.0);  
  10.     DirectLight light1(Color::white().multiply(10), GVector3(-1.75, -2, -1.5),true);  
  11.     scene.push(plane1);  
  12.     scene.push(plane2);  
  13.     scene.push(plane3);  
  14.     scene.push(sphere1);  
  15.     long maxDepth=20;  
  16.     float dx=1.0f/WINDOW_WIDTH;  
  17.     float dy=1.0f/WINDOW_HEIGHT;  
  18.     float dD=255.0f/maxDepth;  
  19.     for (long y = 0; y < WINDOW_HEIGHT; ++y)  
  20.     {  
  21.         float sy = 1 - dy*y;  
  22.         for (long x = 0; x < WINDOW_WIDTH; ++x)  
  23.         {  
  24.             float sx =dx*x;  
  25.             CRay ray(camera.generateRay(sx, sy));  
  26.             IntersectResult result = scene.isIntersected(ray);  
  27.   
  28.             if (result.isHit)  
  29.             {  
  30.                 Color color=light1.intersect(scene,result);  
  31.                 pixelColor=SDL_MapRGB(screen->format,std::min(color.r*255,(float)255),std::min(color.g*255,(float)255.0),std::min(color.b*255,(float)255.0));  
  32.                 drawPixel(screen, x, y,pixelColor);  
  33.             }  
  34.         }  
  35.     }  
  36. }  


點光源

點光源/點光燈(point light),又稱全向光源/泛光源/泛光燈(omnidirectional light/omni light),是指一個無限小的點,向所有光向平均地散射光。最常見的點光源就是電燈泡了,需要確定光源的位置,還有就是光的顏色。


在計算光強的時候,需要乘以一個衰減係數,接收到的能量和距離的關係,是成平方反比定律的:


點光源的實現:

  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:k283228391@126.com 
  4. File name: pointlight.h 
  5. Description:pointlight's h doc. 
  6. Author:Silang Quan 
  7. Version: 1.0 
  8. Date: 2012.12.04 
  9. *****************************************************************************/  
  10. #ifndef POINTLIGHT_H  
  11. #define POINTLIGHT_H  
  12. #include "color.h"  
  13. #include "gvector3.h"  
  14. #include "union.h"  
  15.   
  16. class PointLight  
  17. {  
  18.     public:  
  19.         PointLight();  
  20.         PointLight(Color _color,GVector3 _position,bool _isShadow);  
  21.         virtual ~PointLight();  
  22.         Color intersect(Union &scence,IntersectResult &result);  
  23.     protected:  
  24.     private:  
  25.         bool isShadow;  
  26.         Color color;  
  27.         GVector3 position;  
  28. };  
  29.   
  30. #endif // POINTLIGHT_H  

  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:k283228391@126.com 
  4. File name: pointlight.cpp 
  5. Description:pointlight's cpp doc. 
  6. Author:Silang Quan 
  7. Version: 1.0 
  8. Date: 2012.12.04 
  9. *****************************************************************************/  
  10. #include "pointlight.h"  
  11.   
  12. PointLight::PointLight()  
  13. {  
  14.     //ctor  
  15. }  
  16.   
  17. PointLight::~PointLight()  
  18. {  
  19.     //dtor  
  20. }  
  21. PointLight::PointLight(Color _color,GVector3 _position,bool _isShadow)  
  22. {  
  23.     color=_color;  
  24.     position=_position;  
  25.     isShadow=_isShadow;  
  26. }  
  27. //通過光線與場景的相交結果計算光照結果  
  28. Color PointLight::intersect(Union &scence,IntersectResult &rayResult)  
  29. {  
  30.     //生產shadowRay的修正值  
  31.     const float k=1e-4;  
  32.     GVector3 delta=this->position-rayResult.position;  
  33.     float distance=delta.getLength();  
  34.     //生成與光照相反方向的shadowRay  
  35.     CRay shadowRay=CRay(rayResult.position,delta.normalize());  
  36.     GVector3 shadowDir=delta.normalize();  
  37.     //計算shadowRay是否與場景相交  
  38.     IntersectResult lightResult = scence.isIntersected(shadowRay);  
  39.     Color resultColor = Color::black();  
  40.     Color returnColor=Color::black();  
  41.     //如果shadowRay與場景中的物體相交  
  42.     if(lightResult.object&&(lightResult.distance<=distance))  
  43.     {  
  44.         return resultColor;;  
  45.     }  
  46.     else  
  47.     {  
  48.         resultColor=this->color.divide(distance*distance);  
  49.         float NdotL=rayResult.normal.dotMul(shadowDir);  
  50.         if (NdotL >= 0)  
  51.         returnColor=returnColor.add(resultColor.multiply(NdotL));  
  52.          return returnColor;  
  53.     }  
  54.   
  55. }  

渲染一下:

在rendeLight函式中初始化點光源:

  1. PointLight light2(Color::white().multiply(200), GVector3(10,20,10),true);  



聚光燈

聚光燈點光源的基礎上,加入圓錐形的範圍,最常見的聚光燈就是手電了,或者舞臺的投射燈。聚光燈可以有不同的模型,以下采用Direct3D固定功能管道(fixed-function pipeline)用的模型做示範。


聚光燈有一個主要方向s,再設定兩個圓錐範圍,稱為內圓錐和外圓錐,兩圓錐之間的範圍稱為半影(penumbra)。內外圓錐的內角分別為和。聚光燈可計算一個聚光燈係數,範圍為[0,1],代表某方向的放射比率。內圓錐中係數為1(最亮),內圓錐和外圓錐之間係數由1逐漸變成0。另外,可用另一引數p代表衰減(falloff),決定內圓錐和外圓錐之間係數變化。方程式如下:


聚光燈的實現

  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:k283228391@126.com 
  4. File name: spotlight.h 
  5. Description:spotlight's h doc. 
  6. Author:Silang Quan 
  7. Version: 1.0 
  8. Date: 2012.12.04 
  9. *****************************************************************************/  
  10. #ifndef SPOTLIGHT_H  
  11. #define SPOTLIGHT_H  
  12. #include "color.h"  
  13. #include "gvector3.h"  
  14. #include "union.h"  
  15. #include <math.h>  
  16.   
  17. class SpotLight  
  18. {  
  19.     public:  
  20.         SpotLight();  
  21.         SpotLight(Color _color,GVector3 _position,GVector3 _direction,float _theta,float _phi,float _fallOff,bool _isShadow);  
  22.         virtual ~SpotLight();  
  23.         Color intersect(Union &scence,IntersectResult &result);  
  24.     protected:  
  25.     private:  
  26.         Color color;  
  27.         GVector3 position;  
  28.         GVector3 direction;  
  29.         bool isShadow;  
  30.         float theta;  
  31.         float phi;  
  32.         float fallOff;  
  33.         //negate the Direction  
  34.         GVector3 directionN;  
  35.         float cosTheta;  
  36.         float cosPhi;  
  37.         float baseMultiplier;  
  38. };  
  39.   
  40. #endif // SPOTLIGHT_H  

  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:k283228391@126.com 
  4. File name: pointlight.cpp 
  5. Description:pointlight's cpp doc. 
  6. Author:Silang Quan 
  7. Version: 1.0 
  8. Date: 2012.12.04 
  9. *****************************************************************************/  
  10. #include "pointlight.h"  
  11.   
  12. PointLight::PointLight()  
  13. {  
  14.     //ctor  
  15. }  
  16.   
  17. PointLight::~PointLight()  
  18. {  
  19.     //dtor  
  20. }  
  21. PointLight::PointLight(Color _color,GVector3 _position,bool _isShadow)  
  22. {  
  23.     color=_color;  
  24.     position=_position;  
  25.     isShadow=_isShadow;  
  26. }  
  27. //通過光線與場景的相交結果計算光照結果  
  28. Color PointLight::intersect(Union &scence,IntersectResult &rayResult)  
  29. {  
  30.     //生產shadowRay的修正值  
  31.     const float k=1e-4;  
  32.     GVector3 delta=this->position-rayResult.position;  
  33.     float distance=delta.getLength();  
  34.     //生成與光照相反方向的shadowRay  
  35.     CRay shadowRay=CRay(rayResult.position,delta.normalize());  
  36.     GVector3 shadowDir=delta.normalize();  
  37.     //計算shadowRay是否與場景相交  
  38.     IntersectResult lightResult = scence.isIntersected(shadowRay);  
  39.     Color resultColor = Color::black();  
  40.     Color returnColor=Color::black();  
  41.     //如果shadowRay與場景中的物體相交  
  42.     if(lightResult.object&&(lightResult.distance<=distance))  
  43.     {  
  44.         return resultColor;;  
  45.     }  
  46.     else  
  47.     {  
  48.         resultColor=this->color.divide(distance*distance);  
  49.         float NdotL=rayResult.normal.dotMul(shadowDir);  
  50.         if (NdotL >= 0)  
  51.         returnColor=returnColor.add(resultColor.multiply(NdotL));  
  52.          return returnColor;  
  53.     }  
  54.   
  55. }  

渲染一下:

在場景中初始化一個聚光燈:

  1. SpotLight light3(Color::white().multiply(1350),GVector3(30, 30, 20),GVector3(-1, -0.7, -1), 20, 30, 0.5,true);  





渲染多個燈

這裡用到了vector容器。場景中佈置了很多個點光源,渲染耗時將近半分鐘。

  1. void renderLights()  
  2. {  
  3.     Uint32 pixelColor;  
  4.     Union scene;  
  5.     PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, 0, -1),GVector3(0, 1, 0), 90);  
  6.     Plane* plane1=new Plane(GVector3(0, 1, 0),0.0);  
  7.     Plane* plane2=new Plane(GVector3(0, 0, 1),-50);  
  8.     Plane* plane3=new Plane(GVector3(1, 0, 0),-20);  
  9.     CSphere* sphere1=new CSphere(GVector3(0, 10, -10), 10.0);  
  10.     CSphere* sphere2=new CSphere(GVector3(5, 5, -7), 3.0);  
  11.   
  12.     PointLight *light2;  
  13.     vector<PointLight> lights;  
  14.     for (int x = 10; x <= 30; x += 4)  
  15.         for (int z = 20; z <= 40; z += 4)  
  16.         {  
  17.             light2=new PointLight(Color::white().multiply(80),GVector3(x, 50, z),true);  
  18.             lights.push_back(*light2);  
  19.         }  
  20.   
  21.     scene.push(plane1);  
  22.     scene.push(plane2);  
  23.     scene.push(plane3);  
  24.     scene.push(sphere1);  
  25.     //scene.push(sphere2);  
  26.     long maxDepth=20;  
  27.     float dx=1.0f/WINDOW_WIDTH;  
  28.     float dy=1.0f/WINDOW_HEIGHT;  
  29.     float dD=255.0f/maxDepth;  
  30.     for (long y = 0; y < WINDOW_HEIGHT; ++y)  
  31.     {  
  32.         float sy = 1 - dy*y;  
  33.         for (long x = 0; x < WINDOW_WIDTH; ++x)  
  34.         {  
  35.             float sx =dx*x;  
  36.             CRay ray(camera.generateRay(sx, sy));  
  37.             IntersectResult result = scene.isIntersected(ray);  
  38.   
  39.             if (result.isHit)  
  40.             {  
  41.                 Color color=Color::black();  
  42.   
  43.                 for(vector<PointLight>::iterator iter=lights.begin();iter!=lights.end();++iter)  
  44.                 {  
  45.                     color=color.add(iter->intersect(scene,result));  
  46.                 }  
  47.                 pixelColor=SDL_MapRGB(screen->format,std::min(color.r*255,(float)255),std::min(color.g*255,(float)255.0),std::min(color.b*255,(float)255.0));  
  48.                 drawPixel(screen, x, y,pixelColor);  
  49.             }  
  50.         }  
  51.     }  
  52. }  


渲染結果:


渲染三原色

把原先場景中的球體去掉,佈置3盞聚光動,發射紅綠藍,可以很清晰地看見它們融合之後的顏色。

  1. void renderTriColor()  
  2. {  
  3.       Uint32 pixelColor;  
  4.     Union scene;  
  5.     PerspectiveCamera camera( GVector3(0, 40, 15),GVector3(0, -1.25, -1),GVector3(0, 1, 0), 60);  
  6.     Plane* plane1=new Plane(GVector3(0, 1, 0),0.0);  
  7.     Plane* plane2=new Plane(GVector3(0, 0, 1),-50);  
  8.     Plane* plane3=new Plane(GVector3(1, 0, 0),-20);  
  9.   
  10.     PointLight light0(Color::white().multiply(1000), GVector3(30,40,20),true);  
  11.     SpotLight light1(Color::red().multiply(2000),GVector3(0, 30, 10),GVector3(0, -1, -1), 20, 30, 1,true);  
  12.     SpotLight light2(Color::green().multiply(2000),GVector3(6, 30, 20),GVector3(0, -1, -1), 20, 30, 1,true);  
  13.     SpotLight light3(Color::blue().multiply(2000),GVector3(-6, 30, 20),GVector3(0, -1, -1), 20, 30, 1,true);  
  14.     scene.push(plane1);  
  15.     scene.push(plane2);  
  16.     scene.push(plane3);  
  17.   
  18.     long maxDepth=20;  
  19.     float dx=1.0f/WINDOW_WIDTH;  
  20.     float dy=1.0f/WINDOW_HEIGHT;  
  21.     float dD=255.0f/maxDepth;  
  22.     for (long y = 0; y < WINDOW_HEIGHT; ++y)  
  23.     {  
  24.         float sy = 1 - dy*y;  
  25.         for (long x = 0; x < WINDOW_WIDTH; ++x)  
  26.         {  
  27.             float sx =dx*x;  
  28.             CRay ray(camera.generateRay(sx, sy));  
  29.             IntersectResult result = scene.isIntersected(ray);  
  30.             if (result.isHit)  
  31.             {  
  32.                 Color color=light0.intersect(scene,result);  
  33.                 color=color.add(light1.intersect(scene,result));  
  34.                 color=color.add(light2.intersect(scene,result));  
  35.                 color=color.add(light3.intersect(scene,result));  
  36.                 pixelColor=SDL_MapRGB(screen->format,std::min(color.r*255,(float)255),std::min(color.g*255,(float)255.0),std::min(color.b*255,(float)255.0));  
  37.                 drawPixel(screen, x, y,pixelColor);  
  38.             }  
  39.         }  
  40.     }  
  41. }  

渲染結果



結語

花了大概一週的時間來實現這個光照效果,雖然網上有相關文章,但親自動手來實現又是另外一回事了。

當然,這都沒有結束,期待後續。


相關文章