PBRT笔记(11)——光源
自發光燈光
至今為止,人們發明了很多光源,現在被廣泛使用的有:
發光效率是衡量光源將能量轉化為可見光的效率。對人眼來說,非可見光波長的輻射幾乎沒有價值,有趣的是,它是光度量(發出的光通量)與輻射量(它所使用的總功率或它發出的總波長的總功率(以光通量計算)之比:
\(\frac{\int \Phi_e(\lambda)V(\lambda)d\lambda}{\int \Phi_i(\lambda)d\lambda}\)
其中V(λ)為光譜響應曲線。
 發光效率的單位是流明/瓦特,如果i是光源消耗的功率(而不是發射的功率),那么發光效率也包含了光源將功率轉換成電磁輻射的有效程度的度量。發光效率也可以定義為表面某一特定方向上的某一點上的發光強度與輻照度的比值(輻射強度的光度當量),也可以定義為表面某一特定方向上的某一點上的發光強度與輻照度的比值。
黑體發射器
黑體,是一個理想化了的物體,它能有效地將能量轉化為電磁輻射。雖然真正的黑體在物理上是不可實現的,但一些發射器表現出接近黑體的行為。黑體也有一個有用的封閉形式的表達式,作為溫度和波長的函數,這是對于黑體發射器來說非常有用的建模。
黑體之所以如此命名,是因為它們吸收了所有的入射能量,并且沒有反射。因此,一個真正的黑體看起來是完全黑色的。它能夠吸收外來的全部電磁輻射,并且不會有任何的反射與透射。換句話說,黑體對于任何波長的電磁波的吸收系數為1,透射系數為0。
普朗克定律給發出一個用于測量黑體發出的輻射的函數,其形參為波長λ和溫度T(單位:開爾文)
\(L_e(\lambda,T)=\frac{2hc^2}{\lambda^5 (e^{hc/\lambda k_b T}-1)}\)
其中c為光速,h為普朗克常量,kb為波爾茲曼常數,k為開爾文溫度
這里我們需要將波長單位從納米轉化成米。
void Blackbody(const Float *lambda, int n, Float T, Float *Le) {if (T <= 0) {for (int i = 0; i < n; ++i) Le[i] = 0.f;return;}const Float c = 299792458;const Float h = 6.62606957e-34;const Float kb = 1.3806488e-23;for (int i = 0; i < n; ++i) {// Compute emitted radiance for blackbody at wavelength _lambda[i]_Float l = lambda[i] * 1e-9;Float lambda5 = (l * l) * (l * l) * l;Le[i] = (2 * h * c * c) /(lambda5 * (std::exp((h * c) / (l * kb * T)) - 1));CHECK(!std::isnan(Le[i]));} }斯蒂芬-玻爾茲曼定律給出了黑體發射體在點p處的發射輻射度。
\(M(p)=\sigma T^4\) 其中σ是斯蒂芬玻爾茲曼常數,5.67032 × 10^?8 Wm^?2 K^?4 。請注意,所有頻率上的總發射以t^4的速度快速增長。因此,如何將黑體發射器的溫度加倍,那么總能量就會增加16倍。
標準光源
另一種有用的燈光發射分布類型是一個
 由CIE(國際照明委員會)制定的“標準光源”標準。
燈光類接口
類名為Light,位于core/light.h與core/light.cpp中。
所有的燈光類型都有4個公共參數:
燈光是否用delta分布來描述。(這類燈的例子包括點光源,它從一個點發出照明,以及方向燈,所有的光都來自相同的方向。)蒙特卡羅算法對來自光源的光照進行采樣,需要知道哪些光線是由delta分布描述的。
LightFlags
類型flags枚舉與對應方法
enum class LightFlags : int { DeltaPosition = 1, DeltaDirection = 2, Area = 4, Infinite = 8 };inline bool IsDeltaLight(int flags) { return flags & (int)LightFlags::DeltaPosition || flags & (int)LightFlags::DeltaDirection; }Sample_Li
Light類必須實現一個關鍵方法Sample_Li()。調用者通過一個Interaction對象提供在場景中與時間關聯的世界空間的參考點以及此時燈光往該位置發射的輻射度。(在假設中間沒有阻擋物的情況下)
virtual Spectrum Sample_Li(const Interaction &ref, const Point2f &u,Vector3f *wi, Float *pdf, VisibilityTester *vis) const = 0;Light類同時還負責初始化的入射方向光源ωi與VisibilityTester對象初始化。VisibilityTester保存著必須通過追蹤得到的Shadow Ray的信息,以驗證光線和參考點之間沒有遮擋對象。
對于某些類型的燈光,光線可以從許多方向到達參考點。不像點光源那樣只有一個方向。對于這些類型的光源,就需要使用Sample_Li()方法對光源表面進行采樣。之后再用蒙特卡洛方法求積分來得到對應位置的分布。
所有類型的Light類都必須能夠返回總發射功率。這個變量對光線傳輸算法很有用,因為光線傳輸算法想要盡可能得將計算資源用在對場景貢獻最大的燈光上。
virtual Spectrum Power() const = 0;Preprocess
Light類在被渲染前會調用接口Preprocess()。它將場景作為參數,使得光源在開始渲染前就能確定場景特征。
可見性測試
VisibilityTester是一個封閉的對象,它封裝了少量的數據和一些尚未完成的計算。它允許光在參考點和光源相互可見的情況下下返回一個輻射值。
積分器可以在追蹤陰影射線之前判斷來自入射方向的照明是否相關。例如,在非半透明表面背面入射的光對于來自另一側的反射沒有任何貢獻。
VisibilityTesters由兩個Interaction對象來創建。為用于光線追蹤的陰影射線的兩個端點。因為一個Interaction對象已經被使用,因此不需要特殊情況來計算表面參考點與參與介質中的參考點之間的可見性。
Light提供了兩種方法來確定這兩個點之間的可見性。一種是Unoccluded():它跟蹤兩者之間的陰影射線并返回布爾值。
 因為它只返回一個布爾值,Unoccluded()也會忽略光線通過的任何散射介質對其所攜帶的亮度的影響。
因為它只返回一個布爾值,Unoccluded()也會忽略光線通過的任何散射介質對其所攜帶的亮度的影響。積分器需要考慮到這種情況,所以他們使用Tr()方法。
Spectrum VisibilityTester::Tr(const Scene &scene, Sampler &sampler) const {Ray ray(p0.SpawnRayTo(p1));Spectrum Tr(1.f);while (true) {SurfaceInteraction isect;bool hitSurface = scene.Intersect(ray, &isect);// Handle opaque surface along ray's pathif (hitSurface && isect.primitive->GetMaterial() != nullptr)return Spectrum(0.0f);// Update transmittance for current ray segmentif (ray.medium) Tr *= ray.medium->Tr(ray, sampler);// Generate next ray segment or return final transmittanceif (!hitSurface) break;ray = isect.SpawnRayTo(p1);}return Tr; }如果在射線段上發現一個交點,且入射面是不透明的,則射線被阻擋,透射率為零。
點光源
點光源是各向同性的,各個方向發射出的能量都是一樣的。類名為PointLight,位于lights/point.h與lights/point.cpp中。
在構造函數中,PointLight存儲了世界空間與本地空間相互轉換的矩陣,以及光源的強度,單位是功率/立面角。又因為是各項同性光源,所以點光源的光源強度是個常數。因為點光源表示只從一個位置發出光的奇點,所以light::flags字段被初始化為LightFlags::DeltaPosition。
嚴格地來說,用亮度單位來描述點光源是不正確,輻射強度才是更為合適的描述點發射源的單位。所以在這里,我們將使用Sample_Li這個通用接口來計算到達某點的輻射照度,用輻射強度除以距離的平方獲得。
Spectrum PointLight::Sample_Li(const Interaction &ref, const Point2f &u,Vector3f *wi, Float *pdf,VisibilityTester *vis) const {ProfilePhase _(Prof::LightSample);*wi = Normalize(pLight - ref.p);*pdf = 1.f;*vis =VisibilityTester(ref, Interaction(pLight, ref.time, mediumInterface));return I / DistanceSquared(pLight, ref.p); }功率計算參考輻射照度公式
Spectrum PointLight::Power() const { return 4 * Pi * I; }聚光燈
類名為SpotLight,位于lights/spot.h與lights/spot.cpp中。聚光燈是從點光源衍生過來的一種光源類型,它在所在的位置發出一個方向錐狀的光。其構造函數接受錐體角寬度與衰減開始距離兩個值,之后分別計算并儲存兩者的余弦值。兩個變量的定義具體詳見圖12.8。
SpotLight::Sample_Li()幾乎與PointLight::Sample_Li()相同。不同之處在于SpotLight調用了Falloff()方法,該方法計算聚光燈錐的光分布。
為了計算點p接受到的聚光燈輻射強度,第一步就是計算從聚光燈原點到p的向量與沿聚光燈圓錐中心向量之間夾角余弦值。然后將這個余弦值和衰減開始角度余弦值與錐體角寬度余弦值進行比較,以確定該點在聚光燈錐體坐標系的相對位置。我們可以很容易地確定,余弦值大于衰減開始角度余弦值的點在錐內,接收到完整的光照,而余弦值小于錐體角寬度余弦值的點則完全在錐外。
Float SpotLight::Falloff(const Vector3f &w) const {Vector3f wl = Normalize(WorldToLight(w));Float cosTheta = wl.z;if (cosTheta < cosTotalWidth) return 0;if (cosTheta >= cosFalloffStart) return 1;// Compute falloff inside spotlight coneFloat delta =(cosTheta - cosTotalWidth) / (cosFalloffStart - cosTotalWidth);return (delta * delta) * (delta * delta); }Spectrum SpotLight::Power() const {return I * 2 * Pi * (1 - .5f * (cosFalloffStart + cosTotalWidth)); }貼圖透射燈
類名為ProjectionLight,定義在lights/projection.cpp與lights/projection.h。它將一個貼圖映射并投影到場景中。ProjectionLight類使用投影轉換,根據構造函數的fov參數,將場景中的點投影到光線的投影平面上。
然而,擁有精確表現的投影函數,就好比使用具有MIPMap的圖片一樣,能夠使用蒙特卡羅技術進行采樣對精確表現投影分布非常有用。
ProjectionLight::ProjectionLight(const Transform &LightToWorld,const MediumInterface &mediumInterface,const Spectrum &I, const std::string &texname,Float fov): Light((int)LightFlags::DeltaPosition, LightToWorld, mediumInterface),pLight(LightToWorld(Point3f(0, 0, 0))),I(I) {// Create _ProjectionLight_ MIP mapPoint2i resolution;std::unique_ptr<RGBSpectrum[]> texels = ReadImage(texname, &resolution);if (texels)projectionMap.reset(new MIPMap<RGBSpectrum>(resolution, texels.get()));// Initialize _ProjectionLight_ projection matrixFloat aspect =projectionMap ? (Float(resolution.x) / Float(resolution.y)) : 1;if (aspect > 1)screenBounds = Bounds2f(Point2f(-aspect, -1), Point2f(aspect, 1));elsescreenBounds =Bounds2f(Point2f(-1, -1 / aspect), Point2f(1, 1 / aspect));hither = 1e-3f;yon = 1e30f;lightProjection = Perspective(fov, hither, yon);Transform screenToLight = Inverse(lightProjection);Point3f pCorner(screenBounds.pMax.x, screenBounds.pMax.y, 0);Vector3f wCorner = Normalize(Vector3f(screenToLight(pCorner)));cosTotalWidth = wCorner.z; }與Spotlight類似,ProjectionLight::Sample_Li()調用了Projection(),用以計算對應方向的投影結果。因為投影變換具有將投影中心后面的點投影到它前面的性質。所以去掉z值為負的點是很重要的。如果不進行這種檢查,就不可能知道投影點原來是在燈后面(因為沒有被照亮)還是在燈前面。
Spectrum ProjectionLight::Sample_Li(const Interaction &ref, const Point2f &u,Vector3f *wi, Float *pdf,VisibilityTester *vis) const {ProfilePhase _(Prof::LightSample);*wi = Normalize(pLight - ref.p);*pdf = 1;*vis =VisibilityTester(ref, Interaction(pLight, ref.time, mediumInterface));return I * Projection(-*wi) / DistanceSquared(pLight, ref.p); }Spectrum ProjectionLight::Projection(const Vector3f &w) const {Vector3f wl = WorldToLight(w);// Discard directions behind projection lightif (wl.z < hither) return 0;// Project point onto projection plane and compute lightPoint3f p = lightProjection(Point3f(wl.x, wl.y, wl.z));//投影到投影平面后,丟棄屏幕窗口外坐標值的點。if (!Inside(Point2f(p.x, p.y), screenBounds)) return 0.f;if (!projectionMap) return 1;//取得轉換完的坐標Point2f st = Point2f(screenBounds.Offset(Point2f(p.x, p.y)));return Spectrum(projectionMap->Lookup(st), SpectrumType::Illuminant); }這種燈的功率與聚光燈近似。
Spectrum ProjectionLight::Power() const { return (projectionMap ? Spectrum(projectionMap->Lookup(Point2f(.5f, .5f), .5f), SpectrumType::Illuminant) : Spectrum(1.f)) * I * 2 * Pi * (1.f - cosTotalWidth); }方向光
類名為DistantLight,定義于lights/distant.cpp與lights/distant.cpp中。注意,DistantLight構造函數不接受mediluminterface參數;對于遙遠的光來說,唯一合理的介質是真空。如果它本身在介質中,那么它所有的輻射都會被介質吸收,因為它是無限遠的。
一些距離光的方法需要知道場景的邊界。因為燈光是在場景幾何體之前創建的,所以當DistantLight構造函數運行時,這些邊界是不可用的。因此,DistantLight實現了可選的Preprocess()方法來獲取界限。(該方法在場景構造函數的末尾調用。)
DistantLight::Sample_Li()的實現都較為簡單,唯一有趣的是:VisibilityTester的初始化:陰影光線的第二個點設置在沿著遠光源入射方向距離為場景邊界球半徑的兩倍的位置,這樣就保證了第二個點在場景邊界之外。
Spectrum DistantLight::Sample_Le(const Point2f &u1, const Point2f &u2,Float time, Ray *ray, Normal3f *nLight,Float *pdfPos, Float *pdfDir) const {ProfilePhase _(Prof::LightSample);// Choose point on disk oriented toward infinite light directionVector3f v1, v2;CoordinateSystem(wLight, &v1, &v2);Point2f cd = ConcentricSampleDisk(u1);Point3f pDisk = worldCenter + worldRadius * (cd.x * v1 + cd.y * v2);// Set ray origin and direction for infinite light ray*ray = Ray(pDisk + worldRadius * wLight, -wLight, Infinity, time);*nLight = (Normal3f)ray->d;*pdfPos = 1 / (Pi * worldRadius * worldRadius);*pdfDir = 1;return L; }方向光與別的類型燈光不同,它的發射功率與場景空間范圍有關。事實上,它與接收光線的場景面積成正比。為了理解為什么會這樣,考慮一個面積為A的圓盤,被輻射亮度為L的方向光以圓盤法線方向照亮。到達圓盤的總功率為\(\Phi=AL\),與接收面的面積成正比。
但是為了計算表面積而去計算對光源可見物體總的表面積是不切實際的,所以我們將用一個圓盤來進行近似計算。
Spectrum DistantLight::Power() const {return L * Pi * worldRadius * worldRadius; }面光源
面光源是由一個或多個形狀的光源組成的,這些形狀從其表面發射光輻射,在表面的每個點上有一定的方向分布的輻射。一般來說,計算與面積光有關的輻射量需要計算光表面的積分,而這些積分通常不能以閉合形式計算。類名為AreaLight,定義于core/light.h與core/light.cpp中。
AreaLight添加了新的方法L(),實現了對給定與表面相交的位置,估算在給定方向上光源發射輻射量L。為了方便起見,在SurfaceInteraction類中有Le(),它可以輕松計算射線相交的表面點的輻射亮度。
Spectrum SurfaceInteraction::Le(const Vector3f &w) const {const AreaLight *area = primitive->GetAreaLight();return area ? area->L(*this, w) : Spectrum(0.f); }DiffuseAreaLight類實現了一個基礎面光源,具有均勻的空間和方向輻射分布。它發射輻射由形狀決定,默認情況下,只有法線正方向的那個面發光,當然可以設置Shape::reverseOrientation為true,通過反轉法線的方式來切換發光方向。定于與light/diffuse.h與light/diffuse.cpp中。
因為這中面光源只從形狀表面的一側發出光,所以它的L()方法只是確保出射的方向與法線位于同一個半球。
Spectrum L(const Interaction &intr, const Vector3f &w) const {return Dot(intr.n, w) > 0.f ? Lemit : Spectrum(0.f); }DiffuseAreaLight::Sample_Li()方法不像之前描述的光源那樣簡單了。具體地說,在場景中的每個點,區域光的輻射可以從多個方向入射,而不像其他光那樣只從一個方向入射。這就需要使用到蒙特羅卡方法了,在之后的章節會解釋。因為表面發出的輻射是均勻的,所以輻射功率可以這么計算:
Spectrum DiffuseAreaLight::Power() const {return Lemit * area * Pi; }無窮大的面光源(環境球)
無窮大的面光源是另一種有用的光源??梢园堰@種光源當成一個向各個方向投射光線的巨大球體(它包圍了整個場景)。它的主要用途場景照明。因為是無窮大的,所以其介質指針為空。類名為:InfiniteAreaLight,定義于lights/infinite.h與lights/infinite.cpp中。
InfiniteAreaLight類具有一個變換矩陣成員變量,用于定位圖像映射。先使用球坐標方程講球體映射到(θ,φ)方向,之后再轉換到(u,v)坐標。
因為InfiniteAreaLight的光線會向各個方向射出,所以它必須使用蒙特卡洛方法進行采樣。
與定向光一樣,InfiniteAreaLight的總功率與場景的表面積有關。這里依然使用近似計算。
Spectrum InfiniteAreaLight::Power() const {return Pi * worldRadius * worldRadius * Spectrum(Lmap->Lookup(Point2f(.5f, .5f), .5f), SpectrumType::Illuminant); }pbrt為這個類實現了Le()接口,為這個場景提供環境光照,而其他光源中在這個接口中返回0。
轉載于:https://www.cnblogs.com/blueroses/p/10563274.html
總結
以上是生活随笔為你收集整理的PBRT笔记(11)——光源的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: matlab作图函数的总结与分析.pdf
- 下一篇: matlab 矩阵线性规划,matlab
