本文為《基於物理的渲染:從理論到實現》第三版第一章的筆記.此書透過結合具體的軟體(pbrt)程式碼,講解光線追蹤的實現.
相關資源
本書的 官方網站.你可以在 這裡 免費閱讀第三版(英文),或者在 這裡 免費下載第三版(中文)的 PDF(目前翻譯進度到第八章).你也可以購買清華大學出版社翻譯的《物理渲染:從理論到實現(第二版)》紙質書(ISBN 9787302449812),不過價格不菲.
pbrt-v3 是原書作者的 C++ 實現,程式碼在 GitHub.
rs_pbrt 是一個 Rust 的 pbrt-v3 實現,不過目前還有多處待完成,缺少一些功能.不過 Rust 有統一的構建環境和更好的開發體驗.你可以看看其 GitHub 倉庫 和 網站.
本文末尾附有簡明的編譯和使用教程.
第一章:緒論
渲染(rendering)是由 3D 場景(scene)描述生成影象的過程.基於物理的(physically based)渲染運用物理學規律對光與物質的相互作用建模.
長期以來,實時(real-time)渲染主要採用柵格化(rasterization)或柵格化+光線追蹤(ray-tracing),而本書中完全採用光線追蹤.
1.1 文學程式設計
講解書中虛擬碼的表示方法文學程式設計(literate programming).
不認可這種表示方法,即便想法很好——作為教學用的程式碼不能過長,但是這樣程式碼支離破碎.為了便於理解,應該提供更小更快、能即時檢視到效果的教科書.官方的電子版很好,可以展開摺疊的程式碼,而不是在頁內跳來跳去.
Jupyter Notebook 應是一個更好的 literate programming.
1.2 逼真渲染和光線追蹤演算法
1.2.1 相機
針孔相機也可以看作是把膠片平面放置在針孔的前方但距離不變——出於模擬目的,可以將膠片放在視見體(viewing volume)位置.視見體、小孔、膠片構成一個雙錐體.
incident light 是「入射光」的意思.
1.2.2 光線-物體相交
交點資訊
將射線
其中
由隱函式
該點的特定屬性
除交點外,光線追蹤器還需知道如曲面法線
加速結構
加速結構(acceleration structure)可使時間複雜度降為
1.2.3 光的分佈
圍繞光源的單位球面在單位面積上的功率為
到達半徑為
光源若與法線有夾角,
綜上所述,單位面積上的輻射照度(differential irradiance)
1.2.4 可見性
若有陰影遮擋著色點,光源路徑不暢通時不會照亮該點.透過 陰影射線(shadow ray)可判斷是否可見.方法是 簡單構造一條新射線,其端點是表面上的點,方向指向光源,如下圖中虛線所示.
圖片來源為 閆令琪.GAMES101: 現代計算機圖形學入門.Lecture 13 光線追蹤(基本原理)[pdf]
1.2.5 表面散射
為著色我們還需確定入射光如何被散射(scattered).物體的材質由雙向反射分佈函式(bidirectional reflectance distribution function,BRDF)描述.該函式告訴我們從入射(incoming)方向
我們把
1.2.6 間接光傳輸
從物體上一點到達相機的光量由物體的發光量(如果它自己就是光源)與反射光量之和決定.它被形式化為光傳輸方程(light transport equation),表示從點
Whitted 演算法把積分變為少量方向上的求和,故可以擴充到實現鏡面和玻璃外的更多效果.
1.2.7 光線傳播
在非真空中存在如煙、霧、塵等介質(participating media).
熄滅(衰減)
介質可以透過吸收或沿不同方向散射來熄滅(extinguish)aka. 衰減(attenuate)光.
需要計算射線與交點之間的透射率(transmittance)
增強
介質也可以沿路線增強光.在介質發光(例如火焰)或從其他方向把光散射回該射線時可發生該現象.
可以透過數值計算體積光傳輸方程(volume light transport equation)來尋求該量,該方法還能計算光傳輸方程求得從表面反射回的光量.
1.3 pbrt:系統概述
1.3.1 執行階段
pbrt 在概念上可分為兩個執行階段.
首先解析場景描述檔案, 最終結果 是 Scene 和 Integrator 的例項,後者實現了渲染前者的演算法,被稱為積分器主要是計算 1.2.6 節中式的積分.
然後執行渲染主迴圈,由 Integrator::Render() 執行.
1.3.2 場景表示
程式首先解析命令列引數並 parse 場景描述檔案,rs_pbrt 的這部分程式碼在 rs_pbrt 的 parse_file 中.然後 就建立表示場景中光源和幾何圖元的物件.這兩者都儲存在 Scene 物件中.
pub struct Scene {
pub lights: Vec<Arc<Light>>,
pub infinite_lights: Vec<Arc<Light>>,
pub aggregate: Arc<Primitive>,
pub world_bound: Bounds3f,
}C++ 的 shared_ptr 對應的就是 Arc:
A thread-safe reference-counting pointer. 'Arc' stands for 'Atomically Reference Counted'.
光源
場景中每個光源都由Light物件表示,指定燈光的形狀和發射能量的分佈.
幾何物件
場景中每個幾何物件都由Primitive表示,由幾何結構 Shape 和外觀描述 Material 組成.他們都儲存在 Primitive 中:
pub enum Primitive {
Geometric(Box<GeometricPrimitive>),
Transformed(Box<TransformedPrimitive>),
BVH(Box<BVHAccel>),
KdTree(Box<KdTreeAccel>),
}這個聚合體是一種特殊的圖元,它自己持有許多對其他圖元的引用.聚合體的實現用加速的資料結構儲存了所有場景圖元,減少對遠離給定光線的圖元做不必要的光線相交測試量.
1.3.3 積分器介面與取樣積分器
積分器(integrator)用於計算場景照明的一組測量值.Integrator 提供 render() 方法,接收一個 &Scene.其中一種實現是SamplerIntegrator,它是由來自 Sampler 的樣本(sample)流驅動的,每個樣本標識了影象上的一點,用於計算到達該點以構成影象的光量.
取樣器的實現會極大影響系統生成影象的質量.它責選取光線要追蹤的影象平面上的點,並且提供 1.2.6 節公式中所需的取樣位置.
1.3.4 主渲染迴圈
為了並行化,影象會被分成圖塊,每個圖塊可並行獨立處理.pbrt 固定使用
(C++) Li() 需要為每次輻亮度計算臨時分配少量記憶體,所以我們會用一個 MemoryArena 例項管理記憶體池以啟用比標準庫例程更高效能的分配.
1.3.5 Whitted 光線追蹤積分器
Whitted 積分器工作時遞迴地計算沿反射和折射光線方向的輻亮度.還是剛才這張圖:
對於每個光源,積分器呼叫方法 Light::sample_li()計算從該光源落到表面上待著色點的輻亮度.
1.4 pbrt 的並行化
當執行多執行緒訪問共享可改資料時它們必須以某種方式 同步(synchronize)其訪問,即為互斥(mutual exclusion)和原子操作(atomic operation).
互斥
pbrt 採用 std::mutex 物件實現互斥.至於 C++ 中使用 mutex 的方法,需要宣告一個值和一個 mutex,修改值時建立一個 std::lock_guard<std::mutex>,該鎖會在 drop 時自動釋放.cppreference.com 給出了 簡明的例子,我將其貼在下方:
#include <thread>
#include <mutex>
#include <iostream>
int g_i = 0;
std::mutex g_i_mutex; // protects g_i
void safe_increment()
{
const std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
std::cout << "g_i: " << g_i << "; in thread #"
<< std::this_thread::get_id() << '\n';
// g_i_mutex is automatically released when lock
// goes out of scope
}
int main()
{
std::cout << "g_i: " << g_i << "; in main()\n";
std::thread t1(safe_increment);
std::thread t2(safe_increment);
t1.join();
t2.join();
std::cout << "g_i: " << g_i << "; in main()\n";
}原子操作
C++ 中使用 std::atomic 完成原子操作.簡單的例子如下:
std::atomic<int> x(0);
++x;1.5 如何繼續閱讀本書
前面的鋪墊也太長了,還是過於詳細了……
1.6 使用和理解程式碼
傳遞 mullptr 來表示引數不可用或不該用,此時總是使用指標.
在當下 CPU 架構上最慢的數運算是除法、平方根和三角函式.
1.7 基於物理的渲染簡史
基於物理的蒙特卡羅渲染方法成功用於製作的一大原因是它們最終提高了藝術家們的生產力.透過調整取樣次數來快速獲取縮圖;採用能量未必守恆的反射模型時反射引數可能需要在每個光照環境下都要調整; 光線追蹤計算的陰影質量比柵格化方法好得多.
1.8 擴充閱讀
無重要內容.
編譯和執行
書中為 pbrt-v3 提供的場景檔案在此 3.7 GB tar.gz 檔案 中.images 裡有渲染好的影象.
在 Windows 上安裝和使用 C++ pbrt-v3
scoop install cmake
git clone --recursive "https://github.com/mmp/pbrt-v3/" --depth=1
cd pbrt-v3
mkdir build
cd build
cmake ..若成功,則會有一 .sln 檔案。用 Visual Studio 開啟,構建 BUILD_ALL。若編譯成功,則會提示
30>spectrum.cpp
30>正在生成程式碼...
30>pbrt_test.vcxproj -> …\pbrt-v3\build\Release\pbrt_test.exe
30>已完成生成專案“pbrt_test.vcxproj”的操作.
34>------ 已啟動生成: 專案: ALL_BUILD, 配置: Release x64 ------
34>Building Custom Rule …/pbrt-v3/CMakeLists.txt
========== “生成”: 34 成功,0 失敗,0 更新,0 已跳過 ==========
可以在 build 目錄下的 Release(或 Debug)資料夾中找到 pbrt.exe.
使用 rs_pbrt 渲染檔案
我先 cargo build -r 進行 release build,然後透過
E:\code\rs_pbrt\target\release\rs_pbrt.exe "E:\downloads\pbrt-v3-scenes\killeroos\killeroo-simple.pbrt"絕對路徑使用會報錯.其原因是 .pbrt 檔案中有 Include 語句,在 Include "geometry/killeroo.pbrt" 時遇到相對路徑,拼接的 parent 目錄為 env::current_dir().cd 到 pbrt-v3-scenes/killeroos/,引數填寫絕對路徑依然會有路徑拼接錯誤.解決方法是填寫相對路徑.
❯ E:\downloads\pbrt-v3-scenes\killeroos> E:\code\rs_pbrt\target\release\rs_pbrt.exe "killeroo-simple.pbrt"
pbrt version 0.9.8 (unknown) [Detected 24 cores]
Copyright (c) 2016-2022 Jan Douglas Bert Walter.
Rust code based on C++ code by Matt Pharr, Greg Humphreys, and Wenzel Jakob.
opening file FILE = killeroo-simple.pbrt
Film "image"
"string filename" ["killeroo-simple.exr"]
"integer xresolution" [700]
"integer yresolution" [700]
Sampler "sobol"
"integer pixelsamples" [64]
Integrator "directlighting"
Include "E:\\downloads\\pbrt-v3-scenes\\killeroos\\geometry/killeroo.pbrt"
opening file FILE = E:\downloads\pbrt-v3-scenes\killeroos\geometry/killeroo.pbrt
Include "E:\\downloads\\pbrt-v3-scenes\\killeroos\\geometry/killeroo.pbrt"
opening file FILE = E:\downloads\pbrt-v3-scenes\killeroos\geometry/killeroo.pbrt
Integrator "directlighting"
Rendering with 24 thread(s) ...
1936 / 1936 [======================================================================================================================================] 100.00 % 14.81/s
Writing image "pbrt.png" with bounds Bounds2i { p_min: Point2i { x: 0, y: 0 }, p_max: Point2i { x: 700, y: 700 } } 26.76 % 36.11/s 39s渲染的圖片在當前工作目錄下.

版权许可
- 本作品 采用 知识共享 署名—非商业性使用 4.0 国际许可协议(CC BY-NC 4.0 International)许可,阁下可自由地共享(复制、发行) 和演绎(修改、转换或二次创作) 这一作品,唯须遵守许可协议条款。
评论