More On Ray Tracing

最近又看了看光线追踪..

Theory

上回提到, Path Tracing 就是在试图解 Kajiya 86 年提出的渲染方程:

Monte Carlo 法来解它的话, 就是从每个像素都发一定数量的光线用于采样, 按 BRDF 随机反射 / 折射, 最后所有样本取个平均.

有足够多的采样, Monte Carlo 是能够收敛到真实解的, 但收敛的实在太慢, 我的程序里往往要每个像素三千多个样本才能保证输出图片有好质量. 这三千多里是包括了每个像素 2x2 的抗锯齿的, 所以应该算是比局部光照模型慢了几百倍.

于是接下来大家的工作就是, 怎样改进 Sampling 和 Tracing 的过程, 使得程序可以快速收敛到一个较好, 甚至是无偏的结果上.

smallpt一个版本里就包含 explicit lighting 优化. Implicit lighting 是说, 光线打到了光源上的时候, 再计算光源强度对这条光路的贡献; 而 explicit lighting 有点像局部模型那种方式, 就是光线打到一个表面时, 只要这一点到光源的连线没被挡住, 就按某种辐射模型给这一点增亮, 相当于强行采样了一条到光源的光路.

这种优化确实有极大提速, smallpt 多了 23 行代码就能够做 explicit lighting, 之后他说, 渲染下面这张图只需要不到 10s:

Explicit lighting 的问题在于, 它处理不了 caustic. 要渲染 caustic 必须要有 "真正连接了光源" 的光路. 在各种论文的光路图里, 一般把 explicit lighting 里这种强行连上的光路用虚线连, 称为 "shadow ray". 由于它是直接通过是否被挡住来判断光路存在性的, 因此对于透明物体的 caustic 无能为力.

为了更好的结合 implicit 与 explicit lighting, 改进 tracing 过程, 出现了 Bidirectional Path Tracing 方法. 它是从视点和光源都发出光线, 将两条光路上的点一对对进行 shadow ray 测试, 再强行连上一条 explicit 的光路 (如下图), 再为每条连上的光路赋予一定权值, 以确保结果无偏. 相比较而言, explicit lighting 相当于只与光源强行相连, 而 bidir tracing 把两条路上的点对相连, 增加了光路的重用次数, 也由于光源光路的存在, 使得 caustic 等效果真实了.

Metropolis Light Transport 是在 sampling 上做的改进, 它来自于 Metropolis-Hastings 采样方法.

Metropolis Sampling 方法解决的是这样一个问题: 我们有一个 pdf 叫做 , 我们想采一堆符合这个分布的样本. 现在这个函数不知道怎么求值什么的, 更不能积分再求逆然后利用均匀分布做变换, 但是我们有一个正比于 的, 可求单点值的函数, 甚至仅仅是 "统计意义可求值" 的, 这时候我们如何采样 .

Path Tracing 里, 想要的图像就是一个不可直接求值的函数, 但在每个像素处都是 "统计意义可求值" 的, 更进一步, 在每条特定的完整光路上的亮度, 是可直接求值的. Metropolis Sampling 的方法就是, 在可求值的采样空间上构造一个 Markov 过程, 使他在采样空间上存在稳态, 且常返非周期 (从而稳态唯一), 并且这个稳态分布 hopefully 就是我们的目标.

(这一部分数学细节还没看..

用在 Path Tracing 中, 就是将光路用于采样, 对其做扰动 (mutation), 以一定转移概率换到另一个样本点. 这个扰动可以是各种形式的, 其作者提出的形式就包括添、删、改一个节点, 改变光线角度等, 对每种形式的扰动都要算出特定形式的转移概率.

Implementation

然后就纠结了.. 要有光路, 还要有扰动, 扰动转移概率要根据各种 BRDF 和几何关系来算, 复杂程度太大, 抽象不出来.. 对于怎么实现, 没什么想法. 就找了一下现有的实现:

Mitsuba 是一个 "research-oriented rendering system". 目测大部分都是 Wenzel Jakob 一个人写的..

代码有 20W + 行, 风格, 架构, 注释都极好, 而且有不少值得称道的地方:

  • 由 Doxygen 生成的很详细的 api 文档.
  • 由于架构特别好, 代码中大部分内容, 包括不同的形状, BRDF, 介质散射, 甚至渲染模型 (如 MLT), 都是用插件实现的. 多数都只有一个单独的 cpp 文件, 被编译成动态链接库之后调用...
  • 自动生成的算法文档.. 作者写了一个 parser 从代码中找出用 latex 写的注释, 生成了一份精美的 pdf. cross-reference 良好.
  • 优化强劲. 有 SSE, 各种 caching, 自己手动管理了一些内存分配, 还能 grep 到 __asm__
  • 跨平台. Linux/Win/OSX. 装了必要的库和工具之后一遍编译通过, 包括文档也是.. 很靠谱
  • 自带 python api..
  • 用了 GPU..
  • 有个开发博客, 作者没事上去贴张图什么的.
  • 很多不错的封装, 对流读写, 线程, 内存分配的手动管理, 智能指针, Cache, SSE 都有专门的类负责管理, 有不少值得研究和收藏的部分.
  • 构架奇葩的地方: 有一个大基类 Object, 大家都有 toString (), getType () 方法. 另外还有一个神奇的类叫 Class, 写了一些宏来继承这个类实现各种奇葩操作. Stroustrup 在 The Design and Evolution of C++ 中批评过 Object 和 getType 的设计方式, 不过项目太大了这样设计可能也有一定道理.

稍微看了一下 mitsuba 的代码和文档之后, 大概明白了渲染部分的架构, 发现自己以前那个架构与它实在差的太远, 想扩展出一个 MLT 出来几乎不可能. 架构果然是件神奇的事..

Mitsuba 说它是基于 pbrt 设计的, 于是之后又去看了 pbrt.

pbrt 既是一本书的名字, 也指这本书配的代码. 代码依然很靠谱, 渲染相关的构架与 mitsuba 差不多, 也做了很多优化, 也跨平台. 代码量有 10W+, 看来应该算是 mitsuba 的原型.

神奇的是那本书, 网上可以找得到文字版精装 pdf, 就弄来看了一下. 全书 1200 页, 无比详细的解释了 pbrt 里的几乎所有代码! 包括它们的理论推导, 架构设计, 代码实现, 算是在手把手教你写渲染引擎了. 覆盖的内容也特别丰富, 有几何求交, SAH-Based KDTree, Sampling Theory, 相机模型, 光学, 代码用到的四元数和矩阵分解.. 我听说过的技巧全都覆盖了.

而且这本书是按照 Knuth 所提出的 Literate programming 方式写的.. 让我总算明白了 literate programming 是什么, 好像确实增加了可读性.

最后我去以 6 分钱一页的价格把这本书给打印了... 近期看一看.

作者貌似是 Pat Hanrahan 的学生, 也很有名的样子, 为 pixar 开发过 renderman.

References

Comments