光线追踪(RayTracing)算法理论与实践(三)光照
提要
經過之前的學習,我們已經可以在利用光線追蹤實現一些簡單的場景。今天我們要探討的是圖形學里面的三種基本光源:方向光源,點光源,聚光燈。
不同于利用現成的Api,這次會從理論到實際一步步用C++實現。
前提工作
在老師的建議下,我將圖形引擎換成了SDL,最終的渲染效果比之前的好了很多,原來的GLFW雖然能夠很好的兼容OpenGL,但并沒提供對像素的控制,而SDL有Surface。
對與GLFW,本人覺得其終究只能算是glut的替代品,而SDL應當是一個完善的游戲引擎,而且文檔和教程都非常地豐富。
有關SDL的文章,請猛擊這里。
方向光源
方向光源是一組平行光。所以方向光源類只有方向和顏色兩個屬性。用一個向量對象來表示方向,顏色對象表示光的顏色。
陰影
回憶一下入門文章的第一幅圖片,在有光的情況下,判斷某一點是否是陰影,即判斷是否能夠從那一點看到光。
那么光線追蹤的過程就是:
從攝像機產生光線->投射場景->若與物體相交,從該點產生光線,方向為光源方向的飯方向->投射場景->若與場景中的物體相交,則屬于陰影區域。
方向光源的實現:
/***************************************************************************** Copyright: 2012, ustc All rights reserved. contact:k283228391@126.com 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:k283228391@126.com 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;//生成與光照相反方向的shadowRayGVector3 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:k283228391@126.com 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:k283228391@126.com 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();//生成與光照相反方向的shadowRayCRay 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:k283228391@126.com 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 DirectionGVector3 directionN;float cosTheta;float cosPhi;float baseMultiplier; };#endif // SPOTLIGHT_H/***************************************************************************** Copyright: 2012, ustc All rights reserved. contact:k283228391@126.com 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();//生成與光照相反方向的shadowRayCRay 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);}}} }渲染結果
結語
花了大概一周的時間來實現這個光照效果,雖然網上有相關文章,但親自動手來實現又是另外一回事了。
當然,這都沒有結束,期待后續。
總結
以上是生活随笔為你收集整理的光线追踪(RayTracing)算法理论与实践(三)光照的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java如何快速标记条_【JAVA】如何
- 下一篇: Anaconda安装 + Anacond