T.TAO
Back to Works
2024/graphics / rendering

双向路径追踪渲染器

一个支持多重重要性采样(MIS)的双向路径追踪器,构建在完整的 CPU 渲染器之上:微表面 BSDF、体积介质、MIS 路径追踪与 BVH 加速。

ComputerGraphicsRenderingPathTracing
双向路径追踪渲染器

这是一个基于 Dartmouth Introductory Ray Tracer(DIRT)框架、使用 C++ 编写的基于物理的离线渲染器,是 CMU《基于物理的渲染》课程的项目。在一个学期里,我实现了完整的光传输技术栈——从 Whitted 风格的光线追踪,到带多重重要性采样(MIS)的蒙特卡洛路径追踪、体积渲染,并最终以双向路径追踪(BDPT)配合 balance heuristic 的 MIS 权重方案作为期末项目。

关于源码访问的说明: 由于这是受学术诚信政策约束的课程作业,仓库为私有,无法公开完整源码。核心算法、代码摘录和设计决策都记录在本页中,完整报告附在页面末尾。

BDPT with MIS

为什么需要双向路径追踪

单向路径追踪器的每条路径都从相机出发。当光照容易被"碰巧"采样到时,这种方式表现良好;但在间接、难以到达的光照占主导的场景里——小面积光源、透过狭窄开口进入的光线、或 specular-diffuse-specular 的焦散链——大多数相机路径在触及光源之前就终止了,几乎所有采样都被浪费,图像始终充满噪点。

BDPT 从两端同时进攻这个问题:

  1. 从视点出发追踪一条相机子路径
  2. 从光源上采样一个点,追踪一条光源子路径
  3. 将一条子路径的每个前缀与另一条子路径的每个前缀连接,得到一整族估计器——每个 (s, t) 组合都是对同一路径长度的不同采样策略
  4. MIS 权重组合所有策略,让每个策略只在自己方差低的区域贡献。

MIS 这一步不是可选项。朴素地把所有连接策略求和会对同一光传输重复计数,产生严重的 firefly 噪点。下面的对比最直观地说明了 MIS 的价值:

朴素连接(无 MIS)加入 MIS 权重路径追踪参考图
No MISWith MISReference

渲染器架构

渲染器是一个由 JSON 场景描述驱动的模块化 C++ 系统,每个子系统都通过工厂模式的解析器可插拔:

  • 积分器 —— 法线 / AO 调试视图、纯 BSDF 路径追踪、下一事件估计(NEE)、MIS 路径追踪(power heuristic)、体积路径追踪(uniform 与 NEE+MIS),以及 BDPT(balance heuristic)
  • BSDF —— Lambertian、金属(粗糙反射)、带 Fresnel 的电介质、Phong / Blinn-Phong 波瓣、带法线重要性采样的 Beckmann 微表面、Oren-Nayar 粗糙漫反射,以及纹理驱动的混合材质。所有 BSDF 都暴露统一的 sample / eval / pdf 接口和 specular 标志——正是这个设计让同一套材质库能同时服务于 PT、MIS-PT 和 BDPT。
  • 光源 —— 支持 sampleOn / pdfOn 立体角采样的四边形与球面面光源,以及为期末场景添加的 delta 点光源和聚光灯(带锥角衰减)。
  • 加速结构 —— 递归中点分割的包围盒层次结构(BVH)。
  • 介质 —— 均匀介质与 Perlin 噪声异构介质,配合 Henyey-Greenstein 相位函数,通过 free-flight 距离采样和 ratio tracking 求解。
  • 采样器 —— 独立采样、分层采样、Halton 低差异序列。
  • 相机 —— 薄透镜模型,可配置光圈与焦距实现景深。

单向路径追踪中的 MIS

在 BDPT 之前,MIS 路径追踪器在每次弹射时用 power heuristic 组合 BSDF 采样下一事件估计(光源采样)

wlight(x)=plight(x)βplight(x)β+pbsdf(x)β,β=2w_{\text{light}}(x) = \frac{p_{\text{light}}(x)^{\beta}}{p_{\text{light}}(x)^{\beta} + p_{\text{bsdf}}(x)^{\beta}}, \qquad \beta = 2
C++float powerHeuristic(float pdf1, float pdf2, float power) {
    float pow1 = pow(pdf1, power);
    float pow2 = pow(pdf2, power);
    return pow1 / (pow1 + pow2);
}

每个策略都会对自己采样出的方向计算另一个策略的 PDF,据此加权自己的贡献;权重会沿递归传递,保证自发光辐射不被重复计数。Specular 弹射完全绕过加权(delta 分布无法被另一策略采样,权重为 1)。经典的 Veach 场景在整个粗糙度范围内都能正确渲染:

Veach MIS scene

MIS path tracing

BDPT 深入解析

子路径生成

两条子路径都是随机游走,在每个顶点记录 MIS 之后需要的数据:正向 PDF(该游走实际生成此顶点的概率)和反向 PDF相反方向的游走生成此顶点的概率),以及累积的 throughput。

C++struct Vertex {
    Vec3f wi, wo;            // 入射 / 出射方向
    HitInfo hit;             // 表面交互信息
    Color3f emitted;         // 沿游走累积的自发光
    Color3f throughput;      // 到达此顶点的路径 throughput
    Color3f nextThroughput;  // 在此顶点散射后的 throughput
    float pdfFwd;            // 累积正向采样 PDF
    float pdfRev;            // 累积反向采样 PDF
};

相机游走在每个表面上通过 BSDF 的重要性采样散射,并一路累积两个方向的 PDF 乘积。Specular 顶点只把 throughput 乘以 delta 衰减而不更新 PDF;漫反射顶点则同时追踪两个方向:

C++// traceCameraPath() 内部:逐弹射的 PDF 记账
float pdfMat = hit.mat->pdf(currentRay.d, normalize(srec.scattered), hit);

vertex.pdfFwd = pdfFwd;
pdfFwd *= pdfMat;                                              // 正向:视点 -> 场景
pdfRev *= hit.mat->pdf(-srec.scattered, normalize(-currentRay.d), hit); // 反向游走
vertex.pdfRev = pdfRev;

Color3f evalMat = hit.mat->eval(currentRay.d, normalize(srec.scattered), hit);
throughput *= evalMat / pdfMat;

光源游走首先在光源上同时采样一个位置一个出射方向(initVertex.pdfFwd = pdfDir * lightPDF),之后的扩展方式与相机游走相同。两条游走都以 throughput 亮度为概率执行俄罗斯轮盘终止,在限制路径长度的同时保持估计器无偏。

连接子路径

主循环枚举弹射预算内所有合法的 (s, t) 策略:

C++Color3f BDPTIntegrator::Li(const Scene &scene, Sampler &sampler, const Ray3f &ray) const {
    Color3f L(0.f);
    vector<Vertex> lightPath  = traceLightPath(scene, sampler);
    vector<Vertex> cameraPath = traceCameraPath(scene, sampler, ray);

    for (int s = 1; s <= cameraPath.size(); ++s) {
        for (int t = 1; t <= lightPath.size(); ++t) {
            int depth = t + s - 2;
            if ((s == 1 && t == 1) || depth < 0 || depth > m_maxBounces) continue;

            Color3f contribution = connect(scene, cameraPath, lightPath, s - 1, t - 1);
            float misWeight = calculateMIS(cameraPath, lightPath, s, t);
            L += contribution * misWeight;
        }
    }
    return L;
}

一次连接会在两个端点顶点之间投射 shadow ray 做可见性测试,然后把两条子路径的 throughput、两个端点处朝连接方向求值的 BSDF、光源项,以及在立体角与面积测度之间转换的几何项相乘:

G(xy)=cosθxcosθyxy2G(x \leftrightarrow y) = \frac{|\cos\theta_x| \, |\cos\theta_y|}{\lVert x - y \rVert^{2}}
C++float geometryTerm(const Vec3f &nx, const Vec3f &x, const Vec3f &ny, const Vec3f &y) {
    Vec3f d = y - x;
    float cosThetaI = abs(dot(normalize(nx), normalize(d)));
    float cosThetaO = abs(dot(normalize(ny), normalize(-d)));
    return cosThetaI * cosThetaO / length2(d);
}

// connect():一般 (s, t) 策略
Color3f bsdfCam   = cameraVertex.hit.mat->eval(cameraVertex.wi, normalize(dir), cameraVertex.hit);
Color3f bsdfLight = lightVertex.hit.mat->eval(lightVertex.wo,  normalize(dir), lightVertex.hit);

Color3f result = lightVertex.throughput * cameraVertex.throughput;
result *= initLight.emitted / initLight.pdfFwd;   // 光源辐射 / 采样 PDF
result *= bsdfCam * bsdfLight;
result *= geometryTerm(cameraVertex.hit.sn, cameraVertex.hit.p,
                       lightVertex.hit.sn,  lightVertex.hit.p);

跨策略的 MIS 权重

对于给定长度的完整路径,每个 (s, t) 切分都是对同一条路径的不同采样方式。所选策略的 balance heuristic 权重是它的 PDF 除以所有策略 PDF 之和:

ws,t=ps,tkpkw_{s,t} = \frac{p_{s,t}}{\sum_{k} p_{k}}

实现上不需要显式构造每个假想策略的 PDF,而是采用 Veach 论文中(并由 PBRT 推广的)递推形式:沿每条子路径累积反向 PDF 与正向 PDF 的比值,表达"相反方向的游走生成这个顶点的可能性"相对于它实际被生成方式的比例:

C++float BDPTIntegrator::calculateMIS(const vector<Vertex> &camPath,
                                   const vector<Vertex> &lightPath,
                                   size_t s, size_t t) const {
    if (s == 1 && t == 1) return 1.f;

    float sumCamera = 1.f, sumLight = 1.f;
    for (int i = 1; i <= s; i++)
        sumCamera = (remap0(camPath[i].pdfRev) / remap0(camPath[i].pdfFwd)) * (sumCamera + 1.f);
    for (int i = 1; i <= t; i++)
        sumLight  = (remap0(lightPath[i].pdfRev) / remap0(lightPath[i].pdfFwd)) * (sumLight + 1.f);

    return 1.f / (sumLight + 1.f + sumCamera);
}

remap0() 把接近零的 PDF(delta 交互)钳制为 1,让它们从比值中消去而不是除以零——这是递推中处理 specular 顶点的标准做法。

Delta 光源

点光源和聚光灯(可配置锥角与衰减)是为期末场景添加的。Delta 光源无法被随机游走命中,它们的贡献只能通过显式连接策略流入——这是检验 MIS 记账是否正确处理"不可采样策略"的一个很好的压力测试。

点光源带衰减的聚光灯
Point lightSpot light

通往 BDPT 之路:功能演进

期末项目建立在一整个学期的渲染器开发之上:

基础 —— 光线与图元求交(球体、四边形、Möller-Trumbore 三角形、OBJ 网格)、实例化变换、递归 Whitted 光线追踪、程序化与图像纹理、BVH 构建。

Cornell box

蒙特卡洛与 BSDF —— cosine 加权、Phong 波瓣、Beckmann 法线重要性采样,每种都用直方图对比验证了解析 PDF 的正确性;分层与 Halton 采样器;薄透镜景深。

Beckmann 微表面景深
BeckmannDepth of field

直接光照与 MIS —— 立体角 PDF 的面光源采样、下一事件估计、power heuristic MIS、HDR 环境光照。

体积渲染 —— 解析反演采样的 Henyey-Greenstein 相位函数、指数 free-flight 采样的均匀介质、ratio tracking 的异构 Perlin 介质,以及介质交互处相位函数采样与光源采样之间的 MIS。

各向同性体积,NEE穿过参与介质的聚光灯
Volume Cornell boxVolume spotlight

这个项目展示了什么

  • 光传输理论的实践 —— 推导并实现了渲染方程的四种求解策略(Whitted、PT、MIS-PT、BDPT),并精确理解每种策略的方差来源。
  • 两套 MIS 体系 —— 两策略(BSDF × NEE)上的 power heuristic,以及通过正向/反向 PDF 递推覆盖整个策略族的 balance heuristic。
  • 采样功力 —— 每个 BSDF 波瓣的重要性采样、立体角光源采样、低差异序列、无偏俄罗斯轮盘,以及对照真值的 PDF 验证。
  • 渲染器工程 —— 让一套材质库服务所有积分器的统一 BSDF 接口、工厂驱动的场景解析,以及让期末场景可渲染的 BVH。

完整技术报告(含推导与更多对比):

BDPT 最终报告Open ↗