PBRT 阅读 第一章
分享一下我老師大神的人工智能教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
參見http://www.opengpu.org/forum.php?mod=forumdisplay&fid=8
【題外話:這里只是記錄我學習該書的體會,既不是直譯,也不是意譯,而是順著作者的思路,把自己理解的東西再講述出來,對于可能出現的誤解會用原文說明。】
第一章介紹
關于渲染,有很多方式。大致有三類:
基于物理學的渲染(Physically based):著力于模擬現實。就是說,用物理學的原理搭建關于光和物質交互的模型,追求真實感是該類方法的首要任務。
非真實感的渲染(Nonphotorealistc)。這是為藝術的自由表達而作的渲染。
該書所描述的pbrt是基于光線追蹤算法的物理學渲染系統。其它相關的書籍只是介紹原理,算法,或許還夾雜些少許源代碼。該書則不同,因為它帶了一個完全能工作的完備的渲染系統。(正是這個原因,有很多人用這個系統為藍本作研究,甚至有LexRender這樣相當高級的系統出現)。
1.1?文學編程(Literate Programming)
【該書開篇講了文學編程,這是本書的組織方法,其理念和用法貫穿全書,故不得不學。文學編程是軟件老泰Donald Knuth(老泰:老泰斗之縮寫,他是誰就不用說了吧)的創造。該書作者Matt和Greg想必是他老人家的忠實信徒,也來一把“文學編程”.】
Knuth老泰寫Tex系統的時候,闡述了一個簡單而具革命性的思想:程序更應該寫給人讀的,而不僅僅是給計算機的,名之為文學編程。該書就被作者號稱為一部長長的文學程序(Literate program)。文學程序是用一種元語言(metalanguage)寫成,該元語言把一種文檔格式化語言(document?formatting language,?例如TeX, HTML)和一種編程語言(例如C++)混合使用。它提供兩種功能:1)把文章跟源程序混在一起,使得對程序的描述跟實際的源代碼一樣重要,這樣可鼓勵仔細的設計和文檔編寫。2)跟提交給編譯器的方式相比,它提供給程序讀者全然不同的展現方式,這樣使得程序的描述邏緝性很強。每段代碼都加以名字,稱為片斷(fragment).每個片斷可以用名字引用其它片斷。
舉例說明:有下面一段程序:
void InitGlobals(void) {
? ?? ???num_marbles = 25.7;
? ?? ???shoe_size = 13;
? ?? ???dialectric = true;
? ?? ???my_senator = REPUBLICAN;
}
如果沒有上下文的話,它很是費解。你得搜索整個程序來查看每個變量的定義和它們的目的。這種結構對編譯器沒有任何問題,而對讀者而言,讀者更希望看到每個變量的初始化代碼能在靠近聲明和使用它的地方單獨表達出來。(有點繞口,原文:a human reader would much rather see the initialization code for each variable presented separately,?near the code that actually declares and uses the variable).
在文學程序中,可以這樣寫:
<Function Definitions>=
? ?? ???void InitGlobals() {
? ?? ?? ?? ? <Initialize Global Variables??3>
}
(“3”是書上的頁碼)
這就是一個片斷,?名字是<Function Definitions>,?它定義了InitGlobals()函數,并引用在第3頁的另一個片斷<Initialize?Global Variables〉。
當我們引入全局變量shoe_size時,我們可以寫:
<Initialize Global Variables〉=
? ?? ???shoe_size = 13;
當我們再引入全局變量directric時,我們可以寫:
<Initialize Global Variables〉?+=
? ?? ???dialectric = true;符號?
+=(包括上面的“=”,原書是三條橫線,因無法輸入,用“=”代替)表示我們要對片斷添加新的聲明。
可以看出,我們可以把很復雜的函數化解成不同的邏輯部分,每一部分都很容易理解。(整部書都是按照這個步調有條不紊地,由簡入繁地解釋書中個個要點)。
| 1.3 pbrt:?系統概述 pbrt用的是插件式架構。pbrt執行文件包含了系統主要控制流程的核心代碼,但并不包括象球體,聚光燈這樣的具體元素的代碼。核心渲染器是用抽象類寫成的,這些抽象類定義了插件類型的接口。在系統運行時,用于場景渲染的子類模塊被加載進來。這種組織方法很適合于系統的擴展:僅僅寫個新插件就可以擴展出新的功能。當然,?我們無法預知開發者擴展系統的方式,有時修改核心渲染器還是很有必要的。 1.3.1?程序執行的各個階段 pbrt分三個執行階段: 1.?分析用戶提供的場景描述文件。該文件是一個文本文件,說明了場景中所有幾何形體及其材質,?光源,?相機,所有算法的參數。分析的結果是創建出一個Scene類的實例。 2.?渲染主循環 下一步是進入主要的渲染流程。這個階段是最耗時的了,實現代碼在Scene::Render(),我們將在1.3.3節介紹它。 3.?圖像后處理并存盤,信息統計,內存釋放等善后工作。 1.3.2?場景的表達 pbrt的main()函數非常簡單:首先調用pbrtInit()來做系統初始化,然后分析所傳入的每個場景文件,并對它們逐個進行渲染,最后調用pbrtCleanup()做最后的清理工作: <main program> = } 如果沒有命令行變量傳入pbrt,?則從標準輸入中讀取場景信息;否則,對每個命令行變量所指定的文件進行分析: < Processscene description>= ? ? } 場景文件的分析器是用lex和yacc寫成,lex和yacc文件分別在core/pbrtlex.l?和?core/pbrtparse.y。經過對場景文件的分析,表示相機、光源、幾何體素(geometric?primitives)的對象就被創建出來,再加上管理渲染過程的其它對象,就組合成了Scene對象,這個對象是由RenderOptions::MakeScene()來創建的。Scene類的定義在文件core/scene.h和core/scene.cpp。注意其中的類定義中用了COREDLL宏定義,這用來表明這些類是核心渲染庫所導出(export)的類,這在Windows平臺上是必須的。 <SceneDeclarations>= 每個幾何物體由一個Primitive對象來表示, Primitive包括兩個對象:一個Shape對象(用來說明幾何形狀)和一個Material對象(用來說明材質)。所有的這些幾何體素加在一起用一個單獨的Primitive來表示,被稱為Scene::aggregate。?它是一種特殊的Primitive,因為它只保持對其它許多Primitive的引用。 <SceneData> = 每一個光源由一個Light對象表示,?它說明光源的形狀和光能量分布。Scene類把所有的光源放在一個C++標準庫的Vector對象中。有些渲染程序讓每個體素帶有一個光源列表,這樣可以允許一個光源只能照到有限的幾個物體。這個方法不太適合pbrt的基于物理的渲染,所以我們只支持用于全場景的光源。 <SceneData> += Camera對象用來控制觀察和鏡頭參數,比如相機位置、朝向、焦點、視野等。Camera類中有一個Film成員,它被用來存儲圖像。 <SceneData> += 除了幾何體素,pbrt還支持參與介質(participating media)或體積體素(volumetric primitives),pbrt通過VolumeRegion接口對這類體素提供支持。跟原始幾何體一樣,?所有的VolumeRegions被放在一個單一的成員中: ? ?<SceneData> += 積分器模擬光在場景中的傳播并計算有多少光到達膠片版的圖像采樣位置。之所以稱之為積分器,是因為它用數值方法對表面上和體積上的光傳輸方程求積分。表面積分器計算從幾何表面上的反射光,?而體積積分器計算從體積體中散射出來的光。 <SceneData> += ? ?? ???VolumeIntegrator?* volumeIntegrator; 每個Scene還包括一個Sampler類的對象。采樣器的功能很微妙,?因為它的實現極大地影響圖像的質量。首先,?采樣器負責選擇圖像平面上的點,用來生成被追蹤的光線。其次,?它負責提供采樣位置給積分器,用于光傳輸計算。比如,有些采樣器隨機地在面光源上選擇點,來計算面光源的照明。 <SceneData> += 1.3.3?渲染主循環 當我們創建并初始化好Scene對象后,就可以調用Scene::Render(),開始pbrt的第二階段的執行: 渲染主循環。對圖像平面上的每一個位置,用Camera和Sampler生成射入場景中的光線,然后用SurfaceIntegrator和VolumeIntegrator決定有多少光沿著光線路徑到達圖像平面。這個值被傳給Film而被記錄下來。 (圖1.15) <SceneMethods> = } 在渲染開始之前,Render()構造一個Sample對象,?在主循環過程中,采樣器將把采樣結果放在里面。因為采樣的數量和類型多半取決于積分器,所以Sample構造器要用到積分器的指針: <Allocate and Initialize sample> = 渲染開始之前的另一項工作是調用積分器的Preprocess()方法,?進行跟場景相關的初始化。比如說,16.5節的PhotonIntegrator會創建關于光照分布的數據結構。 <Allowintegrators to do pre-processing for the scene> = 光線追蹤是很緩慢的過程,?特別是對有復雜光照和場景的情況更是如此。?ProgressReporter對象為用戶提供一個直觀的關于pbrt進程的反饋。 渲染主循環終于開場了:?在每次循環中,?我們調用Sampler::GetNextSample(),用下一個圖像采樣值初始化sample,?直到沒有采樣返回為止。在循環體中的片斷(fragments)找到相對應的相機光線,并將它傳給積分器,計算沿光線路徑到達膠片平面上的光輻射亮度(radiance)。最后,把結果放到圖像中,?釋放內存資源,?更新進程報告(ProgressReporter)。 <Trace rays: The main loop> = ? ?? ?} Camera類中的Camera::GenerateRay()生成給定圖像采樣位置的光線。它根據采樣的內容初始化ray的每個成員。所生成的光線的方向向量是規格化的(單位長度為1)。 相機還把一個浮點數權值賦給光線。對于簡單的相機模型而言,所有光線的權值是相同的。但對于更復雜的模型,相機可以生成更有貢獻性的光線。比如,?對于真實相機,到達膠片平面邊緣的光要少于中間的位置的光,即所謂的漸暈效應(vignetting)。?Camera::GenerateRay()返回這個權值,用于控制光線對圖像的貢獻值。 <Find camera rayfor sample> = 為了得到某些紋理函數的更佳效果(第11章),?有必要生成在圖像平面x和y方向上相距一個象素遠的額外光線。這些額外的光線可以用來計算紋理關于象素間距的變化,這是紋理反走樣的關鍵一環。類Ray只記錄光線的原點和方向,?RayDifferential繼承了Ray,并加上兩條額外的Ray成員rx,ry來記錄它的相鄰光線。 <Generate ray differentials for camera ray> = 現在我們有了一條光線,?下一個任務是確定有多少光(單位是光輻射亮度Radiance)沿這這條光線到達圖像平面,Scene:: Li()就是用來計算該值的。光輻射亮度值由Spectrum類來表示,這是pbrt對關于波長的能量分布的抽象—換句話說,就是顏色。 除了返回radiance值,Scene:: Li()還設置alpha值,即光線的透明度。如果光線碰到不透明的物體,則alpha值設為1;如果光線穿過象霧這樣的半透明體,且沒有碰上任何不透明體,則alpha在0和1之間。如果光線沒有碰到任何東西,?則alpha為0。Alpha值可用于很多的后處理效果。比如,?把一個被渲染的物體合成到一幅照片上。 <Evaluate radiance along camera ray> = 
 得到光線的貢獻值后,就可用Film::AddSample()更新圖像了(見7.6,8.1,8.2節)。 <Add sample contribution to image> = pbrt用BSDF類來描述表面上點的材質。在渲染過程中,?有必要為每個采樣存儲BSDF值。為了避免對系統內存申請函數的重復調用,我們用MemoryArena類管理BSDF內存池。一旦對一個采樣的貢獻值計算完畢,要通知BSDF類不再需要其相關的內存了。 <Free BSDF memory from computing image sample value> = 最后,調用ProgressReporter::Update(),讓ProgressReporter知道完成了一條光線的追蹤。 <Report renderingprogress> = 在主循環的最后, Scene::Render()釋放Sample的內存,?讓ProgressReporter報告任務完成,?并寫盤: <clean up after rendering and store final image> = 1.3.4?場景的成員函數 除了Render()以外, Scene類還有其它幾個很有用的函數。?Scene::Intersect用來測試光線是否和場景中的物體相交。如果相交,?則在Intersection結構中添入沿著光線的最近交點。 <Scene PublicMethods> = ? ?? ?? ? bool Intersect(const Ray &ray, Intersection *isect)const { ? ?? ? } 另一個相似的函數是Scene::IntersectP(),它只判定是否有交點存在,?并不計算出所有的交點并返回最近一個,?故效率要快得多,?它被用在陰影光線(shadow?rays)上。 <Scene PublicMethods> += ? ?? ? } Scene::WorldBound()返回包含場景中所有幾何體的包圍盒,實際上它是Scene::aggregate的包圍盒。 <Scene Data>+=? ? ?? ? BBox bound; <SceneConstructor Implementation> = <SceneMethods> += Scene:: Li()函數返回給定光線的輻射亮度。它首先調用SurfaceIntegrator:: Li()計算光線跟第一個相交的表面所產生的出射輻射亮度Lo。然后,調用VolumeIntegrator::Transmittance()計算光源T因參與介質而產生的光的消弱程度。最后,調用VolumeIntegrator::?Li()來計算因參與介質而使輻射亮度得到加強的那部分Lv。最終結果應該是TLo?+ Lv。 <SceneMethods> += ? ?? ?? ?? ???Spectrum Scene :: Li(const RayDifferential &ray, const Sample *sample, float*alpha) const { ? ?? ?? ?? ???Spectrum Lo = surfaceIntegrator->Li(this, ray, sample, alpha); ? ?? ?? ? } 另外, Scene::Transmittance()定義為對其中的體積積分器的調用: <Scene Methods>+= ? ?? ?? ?} 1.3.5一個Whitted風格的光線追蹤積分器 第16和17章介紹了很多表面和體積積分器的實現,?它們所基于的算法的精確度不同.?這里介紹一個基于Whitted光線追蹤算法的表面積分器.?這個積分器精確地計算從平滑表面(玻璃,鏡子,水面等)發出的反射光和透射光,并不考慮間接照明效果.更復雜的積分器也是建筑在這個積分器的基本思想上的. <WhittedIntegrator Declarations> = ? ?? ?? ?? ?? ?Class WhittedInteger : public SurfaceIntegrator { ? ?? ?? ?? ?? ?Public: ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? <WhittedIntegrator Public Methods> ? ?? ?? ?? ?? ?Private: ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? <WhittedIntegrator Private Data> ? ?? ?? ?? ?? ?}; 積分器的核心部分是Integrator::Li(),?它返回沿著光線的光輻射亮度.?下圖總結了在表面積分的過程中主要類之間的數據流程: (圖1.16) <WhittedIntegrator Mothod Definitions> = ? ?? ?? ?? ?? ?Spectrum WittedIntegrator::Li(const Scene *scene, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? const Raydifferential &ray, const Sample *sample, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? Float *alpha) const ? ?? ?? ?? ?? ?{ ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? Intersection isect; ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? Spectrum L(0.); ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? bool??hitSomething; ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? hitSomething = scene->Intersect(ray, &isect); ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? if(!hitSomething) ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???<Handle ray with nointersection>? ?? ?? ??? ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? else ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???{ ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?<Initialize alpha for ray hit> ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?<Compute emitted and reflected light at ray intersection point> ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???} ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? return L; ? ?? ?? ?? ?? ? } 積分器首先要用Scene::Intersect()求交點.?如果沒有找到交點,?我們把光線的alpha值設置為0.然而,?有些類型的光源沒有幾何信息但仍對那些沒有交點的光線產生貢獻值.?比如,天空會對地球表面產生藍色光照作用,?而天空沒有什么幾何信息.?所以,我們仍需調用Light::Le()來支持這種情形(雖然大多數光源并不會對這條光線有貢獻值).?第13.5節會介紹一種光源,它直接照到膠片平面上,這時,我們要把alpha值設置為1(不透明). <Handle ray with no intersection> = ? ?? ?? ?? ?? ?if (alpha)??*alpha = 0; ? ?? ?? ?? ?? ?for ( u_int i = 0;??i < scene->lights.size();??++i) ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? L??+=??scene->lights[ i ]->Le(ray); ? ?? ?? ?? ?? ?if(alpha && !L.Black()) *alpha = 1.; ? ?? ?? ?? ?? ?return L; Whitted積分器沿著光線反射和折射的方向遞歸地求值,所以要記錄遞歸深度,并當其到達預先設定的最大深度值時,停止遞歸過程,?以防止過程無限地進行下去(充滿鏡子的房間就會產生這種現象). <WhittedIntegrator Private Data> = ? ?? ?? ?? ?? ?int maxDepth; ? ?? ?? ?? ?? ?mutable int rayDepth; 如果我們找到了交點,首先要做的是將輸出變量初始化為1: <Initialize alpha for ray hit> = ? ?? ?? ?? ?? ?if (alpha) *alpha = 1.; 現在我們到達了Whitted積分器的核心:?累加每個光源的貢獻值并模擬全反射和折射: <Compute emitted and reflected light at rayintersection point> = ? ?? ?? ?? ?? ?<Evaluate BSDF at hit point> ? ?? ?? ?? ?? ?<Initialize common variables for Whitted integrator> ? ?? ?? ?? ?? ?<Compute emitted light if ray hit an area light source> ? ?? ?? ?? ?? ?<Add contribution of each light source> ? ?? ?? ?? ?? ?if (rayDepth++ <??maxDepth) { ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? <Trace rays for specular reflection and refraction> ? ?? ?? ?? ?? ?} ? ?? ?? ?? ?? ?--rayDepth; 在pbrt中,雙向散射分布函數由BSDF類表示.pbrt有幾種標準散射函數的實現,?包括Lambert反射,Torrance-Sparrow微表面模型(第9章).BSDF接口能用來對一個給定的表面上的點著色,但是表面上每個點的BSDF屬性可能不盡相同.?比如木頭和大理石,?即使木頭被模型化為全漫反射,但其表面上每個點上的顏色仍取決于木頭的紋理.這種著色參數的空間變化有Textures類表示,?Textures既可以是過程型的,也可以存儲在用圖像里. 我們用Intersection::GetBSDF()來取得交點處的BSDF值: <Evaluate BSDF at hit point> = ? ?? ?? ?? ?? ?BSDF *bsdf = isect.GetBSDF(ray); 下面一段代碼初始化交點位置p,表面法向量n,?從交點到光線原點的規則化向量ωo: <Initialize common variables for Whittedintegrator> = ? ?? ?? ?? ?? ?const Point &p = bsdf->dgShading.p; ? ?? ?? ?? ?? ?const Normal &n = bsdf->dgShading.nn; ? ?? ?? ?? ?? ?Vector wo = -ray.d; 如果交點所在的幾何體是發光的(如面光源),?積分器用Intersection::Le()返回所發出光的輻射亮度: <Compute emiited light if ray hit an area lightsource> = ? ?? ?? ?? ?? ?L += isect.Le(wo); 對于每個光源,積分器調用里Light::Sample_L()來計算其對著色點的貢獻值,?同時返回從點到光源的方向向量,存在變量wi中.?這個函數并不考慮光源被其他物體遮擋的情況,而是返回一個VisibilityTester對象,而這個對象可以探測出是否有物體當在光源和著色點之間.?正如前面將過的,是用陰影光線(shadow?ray)的方法解決這個問題. 如果到達該點的光輻射亮度非零,則BSDF給出關于方向對(ωo, ωi)的貢獻值,?積分器把光輻射亮度值Li乘以BSDF,cosine項,和光線與交點之間的透射比(Transmittance)T: <Add contribution of each light source> ? ?? ?? ?? ?? ?Vector wi; ? ?? ?? ?? ???for(u_int i = 0; i < scene->lights.size(); ++i) { ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? VisibilityTester visibility; ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? Spectrum Li = scene->lights[ i ]->Sample_L(p, &wi, &visibility); ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?if(Li.Black()) continue; ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? Specturm f = bsdf->f(wo, wi); ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? if(!f.Black() && visibility.Unoccluded(scene)) ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???L += f * Li * AbsDot(wi, n) * visibility.Transmittance(scene); ? ?? ?? ?? ?? ?} 積分器還處理全反射的表面(鏡子,玻璃等).?根據鏡像原理,積分器很容易地求得反射光的方向,并遞歸地對之追蹤. BSDF::Sample_f()對給定的散射模式和出射方向返回一個入射光的方向,這是蒙特卡羅光傳輸算法的基礎之一(本書最后幾章有詳細介紹)。這里,我們只用它得到相對于全反射和折射的出射方向,并且用一個標志來指示BSDF::Sample_f()要忽略其它類型的反射。雖然BSDF::Sample_f()采樣離開表面的隨機方向(用于概率積分算法),其隨機性要受BSDF的散射性質的限制。在全反射情況下,只有一個方向是可能的,所以就根本沒有隨機性了。 下面的片斷有兩個對BSDF::Sample_f()的調用,把wi初始化為選定的方向,并給出關于方向對(ωo, ωi)的BSDF值。如果BSDF值非零,積分器就用Scene::Li()來得到沿著ωi方向的入射輻射亮度,最后WhittedIntegrator::Li()再將被調用。為了計算反射積分的cosine項,積分器調用AbsDot(),由于向量wi和n都是正規化的,其返回的值正是cosine值。 還有用光線微分作紋理反走樣的內容,見第11.1.3?節。 <Trace rays for specular reflection and refraction> = 
 到這里,第一章內容大致地過了一遍。還有剩兩個短短的小節沒有涉及: 1.4?如何讀這本書 1.5?如何用本書的代碼 這里就略掉不提了。 | 
給我老師的人工智能教程打call!http://blog.csdn.net/jiangjunshow
總結
以上是生活随笔為你收集整理的PBRT 阅读 第一章的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 思科与华为设备OSPF配置命令对比
- 下一篇: 商城系统演示站 -多用户商城系统
