光線追蹤(RayTracing)演算法理論與實踐(三)光照
轉自:http://blog.csdn.net/qp120291570/article/details/8257240
提要
經過之前的學習,我們已經可以在利用光線追蹤實現一些簡單的場景。今天我們要探討的是圖形學裡面的三種基本光源:方向光源,點光源,聚光燈。
不同於利用現成的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;
- //生成與光照相反方向的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: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();
- //生成與光照相反方向的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: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 Direction
- GVector3 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();
- //生成與光照相反方向的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);
- }
- }
- }
- }
渲染結果
結語
花了大概一週的時間來實現這個光照效果,雖然網上有相關文章,但親自動手來實現又是另外一回事了。
當然,這都沒有結束,期待後續。
相關文章
- [MetalKit]44-Raytracing with Metal射線追蹤
- RTX顯示卡實時光線追蹤技術解析 英偉達RTX顯示卡的光線追蹤技術是什麼?
- 用 Rust 實現簡單的光線追蹤Rust
- 如何在 HDRP 中實現光線追蹤?
- 在WebGL中使用GLSL實現光線追蹤Web
- 實時光線追蹤技術:發展近況與未來挑戰
- WebGPU+光線追蹤Ray Tracing 開發三個月總結WebGPU
- 光線追蹤往事:十年技術輪迴
- 從零開始的簡單光線追蹤示例
- TGDC | 向陽而生 —— 光線追蹤的專案應用
- 日誌與追蹤的完美融合:OpenTelemetry MDC 實踐指南
- 虛幻引擎中的實時光線追蹤(二):建築視覺化視覺化
- 把遊戲變成現實,光線追蹤技術到底有多神?遊戲
- 視覺技術的聖盃:光線追蹤如何再現真實世界?視覺
- NVIDIA RTX On:Gamescom海量新遊戲大作演示光線追蹤GAM遊戲
- 蟻群演算法理論介紹演算法
- 中國DXR Spotlight光線追蹤開發者大賽優勝者揭曉
- 三層架構--理論與實踐架構
- 鏈路追蹤技術的應用及實踐
- 分散式追蹤系統,最佳核心設計實踐分散式
- 追蹤演算法KCF體驗演算法
- SQL追蹤和事件追蹤SQL事件
- 從《使命召喚》到《我的世界》,光線追蹤技術如何改變遊戲?遊戲
- 追蹤將伺服器CPU耗光的兇手伺服器
- Unity GDC 2019 Keynote精彩要點:次時代圖形、實時光線追蹤、DOTSUnity
- games101 作業4及作業5 詳解光線追蹤框架GAM框架
- HMM-維特比演算法理解與實現(python)HMM維特比演算法Python
- 《我的世界》win10版將更新測試版本,首次支援光線追蹤技術Win10
- 八領域邊界追蹤演算法演算法
- Java實現Lagan法射線追蹤(lagan.java)Java
- 談理論與實踐
- GDC | 下一代的移動端圖形:《暗區突圍》手遊中的光線追蹤實現
- 資料結構系列之LRU演算法理論篇資料結構演算法
- 區塊鏈共識之Paxos演算法理解與實戰區塊鏈演算法
- 三星Gear VR最大亮點:將實現手勢追蹤VR
- 搭載「光線追蹤」技術的次時代主機會有怎樣的畫面表現?
- 8K 還是光線追蹤?業界頂尖開發者最關心的次世代技術
- https理論與實踐HTTP