光线追踪(RayTracing)算法理论与实践(三)光照
提要
经过之前的学习,我们已经可以在利用光线追踪实现一些简单的场景。今天我们要探讨的是图形学里面的三种基本光源:方向光源,点光源,聚光灯。
不同于利用现成的Api,这次会从理论到实际一步步用C++实现。
前提工作
在老师的建议下,我将图形引擎换成了SDL,最终的渲染效果比之前的好了很多,原来的GLFW虽然能够很好的兼容OpenGL,但并没提供对像素的控制,而SDL有Surface。
对与GLFW,本人觉得其终究只能算是glut的替代品,而SDL应当是一个完善的游戏引擎,而且文档和教程都非常地丰富。
有关SDL的文章,请猛击这里。
方向光源
方向光源是一组平行光。所以方向光源类只有方向和颜色两个属性。用一个向量对象来表示方向,颜色对象表示光的颜色。
阴影
回忆一下入门文章的第一幅图片,在有光的情况下,判断某一点是否是阴影,即判断是否能够从那一点看到光。
那么光线追踪的过程就是:
从摄像机产生光线->投射场景->若与物体相交,从该点产生光线,方向为光源方向的饭方向->投射场景->若与场景中的物体相交,则属于阴影区域。
方向光源的实现:
- /*****************************************************************************
- Copyright: 2012, ustc All rights reserved.
- contact:[email protected]
- File name: directlight.h
- Description:directlight's h doc.
- Author:Silang Quan
- Version: 1.0
- Date: 2012.12.04
- *****************************************************************************/
- #ifndef DIRECTLIGHT_H
- #define DIRECTLIGHT_H
- #include "color.h"
- #include "gvector3.h"
- #include "union.h"
- class DirectLight
- {
- public:
- DirectLight();
- DirectLight(Color _color,GVector3 _direction,bool _isShadow);
- virtual ~DirectLight();
- Color intersect(Union &scence,IntersectResult &result);
- protected:
- private:
- bool isShadow;
- Color color;
- GVector3 direction;
- };
- #endif // DIRECTLIGHT_H
- /*****************************************************************************
- Copyright: 2012, ustc All rights reserved.
- contact:[email protected]
- File name: directlight.cpp
- Description:directlight's cpp doc.
- Author:Silang Quan
- Version: 1.0
- Date: 2012.12.04
- *****************************************************************************/
- #include "directlight.h"
- DirectLight::DirectLight()
- {
- //ctor
- }
- DirectLight::DirectLight(Color _color,GVector3 _direction,bool _isShadow)
- {
- color=_color;
- direction=_direction;
- isShadow=_isShadow;
- }
- DirectLight::~DirectLight()
- {
- //dtor
- }
- //通过光线与场景的相交结果计算光照结果
- Color DirectLight::intersect(Union &scence,IntersectResult &rayResult)
- {
- //生产shadowRay的修正值
- const float k=1e-4;
- //生成与光照相反方向的shadowRay
- GVector3 shadowDir=direction.normalize().negate();
- CRay shadowRay=CRay(rayResult.position+rayResult.normal*k,shadowDir);
- //计算shadowRay是否与场景相交
- IntersectResult lightResult = scence.isIntersected(shadowRay);
- Color resultColor = Color::black();
- if(isShadow)
- {
- if(lightResult.object)
- {
- return resultColor;
- }
- }
- //计算光强
- float NdotL=rayResult.normal.dotMul(shadowDir);
- if (NdotL >= 0)
- resultColor=resultColor.add(this->color.multiply(NdotL));
- //return this->color;
- return resultColor;
- }
需要注意的是intersect函数,输入的参数是场景的引用和光线和场景相交结果的引用,返回一个Color。
若shadowRay没有与场景相交,那么就要对那一点接收到的光强进行计算。
与之有关的就是平面法向量与光的方向的夹角,当这个夹角约大,接受的光强就越小,想想看,中午太阳光是不是最强,傍晚是不是比较弱一些:0).
计算夹角利用的是向量的点乘。
渲染一下:
- void renderLight()
- {
- Uint32 pixelColor;
- Union scene;
- PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, 0, -1),GVector3(0, 1, 0), 90);
- Plane* plane1=new Plane(GVector3(0, 1, 0),0.0);
- Plane* plane2=new Plane(GVector3(0, 0, 1),-50);
- Plane* plane3=new Plane(GVector3(1, 0, 0),-20);
- CSphere* sphere1=new CSphere(GVector3(0, 10, -10), 10.0);
- DirectLight light1(Color::white().multiply(10), GVector3(-1.75, -2, -1.5),true);
- scene.push(plane1);
- scene.push(plane2);
- scene.push(plane3);
- scene.push(sphere1);
- long maxDepth=20;
- float dx=1.0f/WINDOW_WIDTH;
- float dy=1.0f/WINDOW_HEIGHT;
- float dD=255.0f/maxDepth;
- for (long y = 0; y < WINDOW_HEIGHT; ++y)
- {
- float sy = 1 - dy*y;
- for (long x = 0; x < WINDOW_WIDTH; ++x)
- {
- float sx =dx*x;
- CRay ray(camera.generateRay(sx, sy));
- IntersectResult result = scene.isIntersected(ray);
- if (result.isHit)
- {
- Color color=light1.intersect(scene,result);
- 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));
- drawPixel(screen, x, y,pixelColor);
- }
- }
- }
- }
点光源
点光源/点光灯(point light),又称全向光源/泛光源/泛光灯(omnidirectional light/omni light),是指一个无限小的点,向所有光向平均地散射光。最常见的点光源就是电灯泡了,需要确定光源的位置,还有就是光的颜色。
在计算光强的时候,需要乘以一个衰减系数,接收到的能量和距离的关系,是成平方反比定律的:
点光源的实现:
- /*****************************************************************************
- Copyright: 2012, ustc All rights reserved.
- contact:[email protected]
- File name: pointlight.h
- Description:pointlight's h doc.
- Author:Silang Quan
- Version: 1.0
- Date: 2012.12.04
- *****************************************************************************/
- #ifndef POINTLIGHT_H
- #define POINTLIGHT_H
- #include "color.h"
- #include "gvector3.h"
- #include "union.h"
- class PointLight
- {
- public:
- PointLight();
- PointLight(Color _color,GVector3 _position,bool _isShadow);
- virtual ~PointLight();
- Color intersect(Union &scence,IntersectResult &result);
- protected:
- private:
- bool isShadow;
- Color color;
- GVector3 position;
- };
- #endif // POINTLIGHT_H
- /*****************************************************************************
- Copyright: 2012, ustc All rights reserved.
- contact:[email protected]
- File name: pointlight.cpp
- Description:pointlight's cpp doc.
- Author:Silang Quan
- Version: 1.0
- Date: 2012.12.04
- *****************************************************************************/
- #include "pointlight.h"
- PointLight::PointLight()
- {
- //ctor
- }
- PointLight::~PointLight()
- {
- //dtor
- }
- PointLight::PointLight(Color _color,GVector3 _position,bool _isShadow)
- {
- color=_color;
- position=_position;
- isShadow=_isShadow;
- }
- //通过光线与场景的相交结果计算光照结果
- Color PointLight::intersect(Union &scence,IntersectResult &rayResult)
- {
- //生产shadowRay的修正值
- const float k=1e-4;
- GVector3 delta=this->position-rayResult.position;
- float distance=delta.getLength();
- //生成与光照相反方向的shadowRay
- CRay shadowRay=CRay(rayResult.position,delta.normalize());
- GVector3 shadowDir=delta.normalize();
- //计算shadowRay是否与场景相交
- IntersectResult lightResult = scence.isIntersected(shadowRay);
- Color resultColor = Color::black();
- Color returnColor=Color::black();
- //如果shadowRay与场景中的物体相交
- if(lightResult.object&&(lightResult.distance<=distance))
- {
- return resultColor;;
- }
- else
- {
- resultColor=this->color.divide(distance*distance);
- float NdotL=rayResult.normal.dotMul(shadowDir);
- if (NdotL >= 0)
- returnColor=returnColor.add(resultColor.multiply(NdotL));
- return returnColor;
- }
- }
渲染一下:
在rendeLight函数中初始化点光源:
- PointLight light2(Color::white().multiply(200), GVector3(10,20,10),true);
聚光灯
聚光灯点光源的基础上,加入圆锥形的范围,最常见的聚光灯就是手电了,或者舞台的投射灯。聚光灯可以有不同的模型,以下采用Direct3D固定功能管道(fixed-function pipeline)用的模型做示范。
聚光灯有一个主要方向s,再设置两个圆锥范围,称为内圆锥和外圆锥,两圆锥之间的范围称为半影(penumbra)。内外圆锥的内角分别为和。聚光灯可计算一个聚光灯系数,范围为[0,1],代表某方向的放射比率。内圆锥中系数为1(最亮),内圆锥和外圆锥之间系数由1逐渐变成0。另外,可用另一参数p代表衰减(falloff),决定内圆锥和外圆锥之间系数变化。方程式如下:
聚光灯的实现
- /*****************************************************************************
- Copyright: 2012, ustc All rights reserved.
- contact:[email protected]
- File name: spotlight.h
- Description:spotlight's h doc.
- Author:Silang Quan
- Version: 1.0
- Date: 2012.12.04
- *****************************************************************************/
- #ifndef SPOTLIGHT_H
- #define SPOTLIGHT_H
- #include "color.h"
- #include "gvector3.h"
- #include "union.h"
- #include <math.h>
- class SpotLight
- {
- public:
- SpotLight();
- SpotLight(Color _color,GVector3 _position,GVector3 _direction,float _theta,float _phi,float _fallOff,bool _isShadow);
- virtual ~SpotLight();
- Color intersect(Union &scence,IntersectResult &result);
- protected:
- private:
- Color color;
- GVector3 position;
- GVector3 direction;
- bool isShadow;
- float theta;
- float phi;
- float fallOff;
- //negate the Direction
- GVector3 directionN;
- float cosTheta;
- float cosPhi;
- float baseMultiplier;
- };
- #endif // SPOTLIGHT_H
- /*****************************************************************************
- Copyright: 2012, ustc All rights reserved.
- contact:[email protected]
- File name: pointlight.cpp
- Description:pointlight's cpp doc.
- Author:Silang Quan
- Version: 1.0
- Date: 2012.12.04
- *****************************************************************************/
- #include "pointlight.h"
- PointLight::PointLight()
- {
- //ctor
- }
- PointLight::~PointLight()
- {
- //dtor
- }
- PointLight::PointLight(Color _color,GVector3 _position,bool _isShadow)
- {
- color=_color;
- position=_position;
- isShadow=_isShadow;
- }
- //通过光线与场景的相交结果计算光照结果
- Color PointLight::intersect(Union &scence,IntersectResult &rayResult)
- {
- //生产shadowRay的修正值
- const float k=1e-4;
- GVector3 delta=this->position-rayResult.position;
- float distance=delta.getLength();
- //生成与光照相反方向的shadowRay
- CRay shadowRay=CRay(rayResult.position,delta.normalize());
- GVector3 shadowDir=delta.normalize();
- //计算shadowRay是否与场景相交
- IntersectResult lightResult = scence.isIntersected(shadowRay);
- Color resultColor = Color::black();
- Color returnColor=Color::black();
- //如果shadowRay与场景中的物体相交
- if(lightResult.object&&(lightResult.distance<=distance))
- {
- return resultColor;;
- }
- else
- {
- resultColor=this->color.divide(distance*distance);
- float NdotL=rayResult.normal.dotMul(shadowDir);
- if (NdotL >= 0)
- returnColor=returnColor.add(resultColor.multiply(NdotL));
- return returnColor;
- }
- }
渲染一下:
在场景中初始化一个聚光灯:
- SpotLight light3(Color::white().multiply(1350),GVector3(30, 30, 20),GVector3(-1, -0.7, -1), 20, 30, 0.5,true);
渲染多个灯
这里用到了vector容器。场景中布置了很多个点光源,渲染耗时将近半分钟。
- void renderLights()
- {
- Uint32 pixelColor;
- Union scene;
- PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, 0, -1),GVector3(0, 1, 0), 90);
- Plane* plane1=new Plane(GVector3(0, 1, 0),0.0);
- Plane* plane2=new Plane(GVector3(0, 0, 1),-50);
- Plane* plane3=new Plane(GVector3(1, 0, 0),-20);
- CSphere* sphere1=new CSphere(GVector3(0, 10, -10), 10.0);
- CSphere* sphere2=new CSphere(GVector3(5, 5, -7), 3.0);
- PointLight *light2;
- vector<PointLight> lights;
- for (int x = 10; x <= 30; x += 4)
- for (int z = 20; z <= 40; z += 4)
- {
- light2=new PointLight(Color::white().multiply(80),GVector3(x, 50, z),true);
- lights.push_back(*light2);
- }
- scene.push(plane1);
- scene.push(plane2);
- scene.push(plane3);
- scene.push(sphere1);
- //scene.push(sphere2);
- long maxDepth=20;
- float dx=1.0f/WINDOW_WIDTH;
- float dy=1.0f/WINDOW_HEIGHT;
- float dD=255.0f/maxDepth;
- for (long y = 0; y < WINDOW_HEIGHT; ++y)
- {
- float sy = 1 - dy*y;
- for (long x = 0; x < WINDOW_WIDTH; ++x)
- {
- float sx =dx*x;
- CRay ray(camera.generateRay(sx, sy));
- IntersectResult result = scene.isIntersected(ray);
- if (result.isHit)
- {
- Color color=Color::black();
- for(vector<PointLight>::iterator iter=lights.begin();iter!=lights.end();++iter)
- {
- color=color.add(iter->intersect(scene,result));
- }
- 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));
- drawPixel(screen, x, y,pixelColor);
- }
- }
- }
- }
渲染结果:
渲染三原色
把原先场景中的球体去掉,布置3盏聚光动,发射红绿蓝,可以很清晰地看见它们融合之后的颜色。
- void renderTriColor()
- {
- Uint32 pixelColor;
- Union scene;
- PerspectiveCamera camera( GVector3(0, 40, 15),GVector3(0, -1.25, -1),GVector3(0, 1, 0), 60);
- Plane* plane1=new Plane(GVector3(0, 1, 0),0.0);
- Plane* plane2=new Plane(GVector3(0, 0, 1),-50);
- Plane* plane3=new Plane(GVector3(1, 0, 0),-20);
- PointLight light0(Color::white().multiply(1000), GVector3(30,40,20),true);
- SpotLight light1(Color::red().multiply(2000),GVector3(0, 30, 10),GVector3(0, -1, -1), 20, 30, 1,true);
- SpotLight light2(Color::green().multiply(2000),GVector3(6, 30, 20),GVector3(0, -1, -1), 20, 30, 1,true);
- SpotLight light3(Color::blue().multiply(2000),GVector3(-6, 30, 20),GVector3(0, -1, -1), 20, 30, 1,true);
- scene.push(plane1);
- scene.push(plane2);
- scene.push(plane3);
- long maxDepth=20;
- float dx=1.0f/WINDOW_WIDTH;
- float dy=1.0f/WINDOW_HEIGHT;
- float dD=255.0f/maxDepth;
- for (long y = 0; y < WINDOW_HEIGHT; ++y)
- {
- float sy = 1 - dy*y;
- for (long x = 0; x < WINDOW_WIDTH; ++x)
- {
- float sx =dx*x;
- CRay ray(camera.generateRay(sx, sy));
- IntersectResult result = scene.isIntersected(ray);
- if (result.isHit)
- {
- Color color=light0.intersect(scene,result);
- color=color.add(light1.intersect(scene,result));
- color=color.add(light2.intersect(scene,result));
- color=color.add(light3.intersect(scene,result));
- 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));
- drawPixel(screen, x, y,pixelColor);
- }
- }
- }
- }
渲染结果
结语
花了大概一周的时间来实现这个光照效果,虽然网上有相关文章,但亲自动手来实现又是另外一回事了。
当然,这都没有结束,期待后续。