(论文)[1997-SIG] Metropolis Light Transport(3)
Metropolis Light Transport
mitsuba0.6 实现
- 总变异数:spp x resolution
- 每个样本变异数目:~20w
- 样本数目 seedCount
- 估计 luminance(10w 随机采样,求平均值)
- 同时记录采样得到的路径以及 luminance
- 在其中根据 luminance 重采样得到 seedCount
数目的初始路径
- 【重采样让更近似接近于 \(f(\bar{x})\),减轻 startup bias】
- 每条初始路径独立处理【进入
process()
函数】 process()
- 根据初始种子构建原始路径
current
,并计算relWeight
【\({\color{red}\dfrac{f}{p}G}\)】 - 进入变异循环
- 根据初始种子构建原始路径
- 变异循环
- 检测当前路径所有合适的变异方法,均匀采样一种变异方法(mutator)
- 根据选择的 mutator 采样新路径
- 计算
Qxy
【论文中的 \(f(\bar{x})T(\bar{y}|\bar{x})\)】、Qyx
- 根据规则累计平均 luminance
develop()
统计直方图- 写优化
- 如果变异没有被接受,那么先不累计【权重需要加】,等到最后被放弃的时候在累积到结果中
变异例子
LensPerturbation 为例【感觉整体来说挺复杂的】
前置
- path 光源开始编号 0,边的长度 \(len\)
- 如下是一个示例光路类型 \(len=5\)
1
2
3
4
5
6emitterSupernode
emitterSample
surfaceInteraction
surfaceInteraction
sensorSample
sensorSupernode- emitterSupernode 中记录了光源 radiance 信息;类似的 sensorSupernode 记录了相机 importance 信息
- 屏幕扰动范围:\([r_1,r_2]=[0.1,\sqrt{0.05\times\dfrac{\text{resolution}}{\pi}}]\)
- path 光源开始编号 0,边的长度 \(len\)
suitability()
:适用条件- \(len-1\) 开始【因为有
supernode】,第一个 connectable 的顶点记作 \(k\)
- 边的数目 \(len\),顶点数目 \(len+1\)
- \(k>0\) 而且 \(k-1\) 顶点也需要 conectable
- \(len-1\) 开始【因为有
supernode】,第一个 connectable 的顶点记作 \(k\)
sampleMutation()
:变异1
2
3edge | l m
| o --- o --- o' --- o' --- o --- o
vertex | l m k- 找到上述顶点 \(k\)【记 \(l=k-1,m=len-1\)】
'
表示 connectable
- 在屏幕空间采样 offset
- \(r=r_2\exp(-\ln\dfrac{r_2}{r_1}\mathcal{U}_1);\phi=2\pi\mathcal{U}_2\)
- 如果超出屏幕/相机采样不出来这个点,拒绝采样
- 构建 proposed path【要求顶点数和 current path
一样,顶点类型一样,顶点的 connectable 一样】
- 拷贝基础路径:除了点 \([l+1,m-1)\),边 \([l+1,m-1]\) 之外,都拷贝 current
path;这几个点先用空值,然后使用下面的方式更新
- 细节:不变的点用浅拷贝,会变化的点用深拷贝
vertex.perturbDirection()
:扰动相机顶点【\(m\)】(sensorSample),根据上面的采样edge.perturbDirection()
:更新边,同时计算下一个交点【\(m-1\)】
vertex.propagatePerturbation()
:顶点依次传播扰动,\((l+1,m-1]\)- 采样下一个方向的时候,使用固定随机数 \([0.5,0.5]\),【是因为反正是 specular 吗?至少是 unconnectable】
edge.perturbDirection()
- 连接顶点 \(l,l+1\)
- 记录结构体 MutationRecord
- \(ka = m - l\)
1
2
3
4
5
6
7
8
9
10struct MutationRecord {
Mutator::EMutationType type; ///< Type of executed mutation
int l; ///< Left vertex of the affected range
int m; ///< Right vertex of the affected range
int ka; ///< Size of the insertion
Spectrum weight; ///< Spectral weight of the unchanged portion
int extra[5];
};
struct MutationRecord muRec; - 拷贝基础路径:除了点 \([l+1,m-1)\),边 \([l+1,m-1]\) 之外,都拷贝 current
path;这几个点先用空值,然后使用下面的方式更新
- 找到上述顶点 \(k\)【记 \(l=k-1,m=len-1\)】
Q
:计算 \(f(\bar{x})T(\bar{y}|\bar{x})\)muRec.weight
:记录了 \([0,l),(m,k]\) 的边、\([0,l),[m,k-1)\) 的边的权重累乘- 乘上连接的边的权重、扰动的边的权重;\(T\) 的计算包含在这里面了,参考这个
- 这里的 \(f(\bar{x})\) 指的是在在原始采样框架下的无偏估计
- 【哎哟我的天呐,weight 这一块计算复杂了,感觉看不太懂】
累计结果:归一化的 path luminance【Why】
特殊
- twoStage
- 第一阶段渲染一个低分辨率的图片,上采样之后,作为 \(I(pixel_x)\)
- \(pixel_x\) 表示 \(x\) 所在的像素位置
- 第二阶段近似的时候,mlt 的近似目标变为 \(\dfrac{f(\bar{x})}{I(pixel_{\bar{x}})}\)
- 就是 \(\dfrac{f(\bar{x})}{I(pixel_{\bar{x}})}\) 替换 \(f(\bar{x})\)
- 效果上而言,让每个像素的计算资源分配更加均匀
- 第一阶段渲染一个低分辨率的图片,上采样之后,作为 \(I(pixel_x)\)
- separateDirect:直接光的渲染不使用 MLT,而是直接算
- 支持的变异种类
1 | enum EMutationType { |
mitsuba 相关
process 逻辑
- 构建一个 Process,绑定资源,然后交给 Scheduler
scheduler->schedule(process);
schedule()
逻辑- 将 process 加入到任务队列里面,然后通知所有线程(Worker)
- Worker 在函数
acquireWork()
中被唤醒【Worker 状态类似于while(acquireWork()) {}
】- 调用
process->createWorkProcessor()
构建 WorkProcessor - 调用
process->generateWork()
- 调用 WorkProcessor 的
process()
开始工作 - 工作完调用
releaseWork()
通知 Scheduler,进入休眠
- 调用
- 【非统一接口】所有并行程序运行完之后,后处理
process->develop();
- 默认的 integrator 行为就是和上面类似的:参考文件
mitsuba/src/librender/integrator.cpp
- 在
process()
中逐点调用Li()
- 在
一些疑问
bsdf
- sensor subpath 对应 Radiance【常规 PT】;emitter subpath 对应 Importance
- mitsuba 里面 \(w_i\)
指的是生成路径时前一个点指向当前点
- sensor:相机方向指向当前点
- emitter:光源方向指向当前点
- BSDF evaluate 的时候都是使用相同的形式,这个是正确的吗?
- 就都是
bsdf->eval(wi, wo)
【而不需要将 \(w_i\) 都用光源方向吗?】 mitsuba/src/libbidir/vertex.cpp::Line35::sampleNext()
- 不太懂,之后看 BDPT 再看吧
- 就都是