光线追踪(RayTracing)算法理论与实践(三)光照

提要

经过之前的学习,我们已经可以在利用光线追踪实现一些简单的场景。今天我们要探讨的是图形学里面的三种基本光源:方向光源,点光源,聚光灯。

不同于利用现成的Api,这次会从理论到实际一步步用C++实现。


前提工作

在老师的建议下,我将图形引擎换成了SDL,最终的渲染效果比之前的好了很多,原来的GLFW虽然能够很好的兼容OpenGL,但并没提供对像素的控制,而SDL有Surface。

对与GLFW,本人觉得其终究只能算是glut的替代品,而SDL应当是一个完善的游戏引擎,而且文档和教程都非常地丰富。

有关SDL的文章,请猛击这里


方向光源

方向光源是一组平行光。所以方向光源类只有方向和颜色两个属性。用一个向量对象来表示方向,颜色对象表示光的颜色。


阴影

回忆一下入门文章的第一幅图片,在有光的情况下,判断某一点是否是阴影,即判断是否能够从那一点看到光。

那么光线追踪的过程就是:

从摄像机产生光线->投射场景->若与物体相交,从该点产生光线,方向为光源方向的饭方向->投射场景->若与场景中的物体相交,则属于阴影区域。


方向光源的实现:

[cpp]   view plain  copy
  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:[email protected] 
  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  

[cpp]   view plain  copy
  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:[email protected] 
  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).

计算夹角利用的是向量的点乘。

渲染一下:

[cpp]   view plain  copy
  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),是指一个无限小的点,向所有光向平均地散射光。最常见的点光源就是电灯泡了,需要确定光源的位置,还有就是光的颜色。


在计算光强的时候,需要乘以一个衰减系数,接收到的能量和距离的关系,是成平方反比定律的:


点光源的实现:

[cpp]   view plain  copy
  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:[email protected] 
  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  

[cpp]   view plain  copy
  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:[email protected] 
  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函数中初始化点光源:

[cpp]   view plain  copy
  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),决定内圆锥和外圆锥之间系数变化。方程式如下:


聚光灯的实现

[cpp]   view plain  copy
  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:[email protected] 
  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  

[cpp]   view plain  copy
  1. /***************************************************************************** 
  2. Copyright: 2012, ustc All rights reserved. 
  3. contact:[email protected] 
  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. }  

渲染一下:

在场景中初始化一个聚光灯:

[cpp]   view plain  copy
  1. SpotLight light3(Color::white().multiply(1350),GVector3(30, 30, 20),GVector3(-1, -0.7, -1), 20, 30, 0.5,true);  





渲染多个灯

这里用到了vector容器。场景中布置了很多个点光源,渲染耗时将近半分钟。

[cpp]   view plain  copy
  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盏聚光动,发射红绿蓝,可以很清晰地看见它们融合之后的颜色。

[cpp]   view plain  copy
  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. }  

渲染结果



结语

花了大概一周的时间来实现这个光照效果,虽然网上有相关文章,但亲自动手来实现又是另外一回事了。

当然,这都没有结束,期待后续。


猜你喜欢

转载自blog.csdn.net/szt292069892/article/details/80720351