Skip to content

Commit 2b63e23

Browse files
committed
upd
1 parent 21d41b1 commit 2b63e23

5 files changed

Lines changed: 80 additions & 10 deletions

File tree

content/posts/foundation/pt-6-path-tracing-adventures.md

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
author: mos9527
3-
lastmod: 2025-12-24T22:14:57.506750
3+
lastmod: 2025-12-25T10:29:55.237810
44
title: Foundation 施工笔记 【6】- 路径追踪
55
tags: ["CG","Vulkan","Foundation"]
66
categories: ["CG","Vulkan"]
@@ -768,7 +768,7 @@ public float SchlickFresnel(float F0, float F90, float cosTheta)
768768
769769
接下来就Heitz方法简要介绍,并对这里的Kulla and Conty方法进行复现。
770770
771-
#### Heitz 2016 Random Walk
771+
#### Random Walk (Heitz 2016)
772772
773773
记得$G1$ Masking/Shadowing 函数表达的量:宏观面内沿某视角$\mathbf{v}$可见的微面比例。
774774
@@ -788,9 +788,12 @@ public float SchlickFresnel(float F0, float F90, float cosTheta)
788788
789789
未来有机会再尝试复现这里的RW手段。在此之前,实现上更为简单且出图方差更低(不需逼近)的手段即为以下查表方法。
790790
791-
#### Kulla and Conty 2017 LUT
791+
#### 预积分查表(Kulla, Conty & Turquin)
792792
793-
[Blender 4.0 以后](https://projects.blender.org/blender/blender/src/commit/fc680f0287cdf84261a50e1be5bd74b8bd73c65b/intern/cycles/kernel/closure/bsdf_microfacet.h#L359) 采用了[该方法](https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf) (注意cycles做的$E_{avg}$等查表)。首先引入几个概念:
793+
[Blender 4.0 以后](https://projects.blender.org/blender/blender/src/commit/fc680f0287cdf84261a50e1be5bd74b8bd73c65b/intern/cycles/kernel/closure/bsdf_microfacet.h#L359) 采用了查表方法(注意cycles做的$E_{avg}$等查表)。形式上是后者Turquin的公式,不过鉴于其推导离不开Kulla 2017的工作,这里一并复现。方便读者参考,以下为二者链接:
794+
795+
- [Revisiting Physically Based Shading at Imageworks, Kulla, Conty 2017](https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf)
796+
- [Practical multiple scattering compensation for microfacet models, Turquin 2019](https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf)
794797
795798
##### 方向反照率 Albedo $E(w)$
796799
@@ -879,7 +882,7 @@ $$
879882
$$
880883
E = 2\pi \int_{0}^{1}{E_{ss}(\mu)\mu d\mu}
881884
$$
882-
入射能量再次是$\pi$ - 这里和之前一样。得到反射率为
885+
入射能量再次是$\pi$ - 这里和之前一样。得到反照率为
883886
$$
884887
E_{avg} = 2\int_{0}^{1}{E_{ss}(\mu) \mu d\mu}
885888
$$
@@ -914,12 +917,12 @@ float sampleGGX_E(float rough, float cosTheta, float uc, float2 u){
914917
float alpha = Sqr(rough);
915918
TrowbridgeReitzDistribution mfDistrib = TrowbridgeReitzDistribution(alpha, alpha);
916919
if (mfDistrib.EffectivelySmooth())
917-
return 1.0f; // Dirac delta - though we're conuting energy, so return 1
920+
return 1.0f; // Dirac delta - though we're counting energy, so return 1
918921
float3 wo = float3(sqrt(1.0 - cosTheta * cosTheta), 0.0, cosTheta);
919922
float3 wm = mfDistrib.Sample_wm(wo, u);
920923
float3 wi = Reflect(wo, wm);
921924
if (!SameHemisphere(wo, wi))
922-
return 0.0f; // Absorbtion
925+
return 0.0f; // Absorption
923926
float pdf = mfDistrib.PDF(wo, wm) / (4 * AbsDot(wo, wm));
924927
float f = mfDistrib.D(wm) * mfDistrib.G(wo, wi) / (4 * AbsCosTheta(wi) * AbsCosTheta(wo));
925928
return f * AbsCosTheta(wi) / pdf;
@@ -1004,12 +1007,13 @@ integrateGGX_Eavg.dispatch(thread_count=[1,1,1],vars={"output": ggx_Eavg})
10041007

10051008
就此,我们给出的几个积分计算完毕。
10061009

1007-
##### $f_{ms}$ 补偿 Lobe
1010+
##### $f_{ms}$ 推导
10081011

1009-
先不考虑反射率,[Revisiting Physically Based Shading at Imageworks - Kulla, Conty 2017](https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf) 给出了以下$E_{ms}$补偿BRDF。
1012+
先不考虑反射率,[Revisiting Physically Based Shading at Imageworks - Kulla, Conty 2017](https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf) 给出了以下$f_{ms}$补偿BRDF中的Fresnel(ss:single scattering, ms: multiple scattering):
10101013

10111014
$$
1012-
f_{ms}(w_o,w_i) = \frac{(1-E_{ss}(w_o))(1-E_{ss}(w_i))}{\pi(1-E_{avg})}
1015+
f_{ms}(w_o,w_i) = \frac{(1-E_{ss}(w_o))(1-E_{ss}(w_i))}{\pi(1-E_{avg})} \newline
1016+
f\prime = f_{ss} + f_{ms}
10131017
$$
10141018

10151019
这个式子由 [A Microfacet Based Coupled Specular-Matte BRDF Model with Importance Sampling - Kelemen 2001](https://www.researchgate.net/publication/2378872_A_Microfacet_Based_Coupled_Specular-Matte_BRDF_Model_with_Importance_Sampling) 找到(下图)
@@ -1018,7 +1022,73 @@ $$
10181022

10191023
代入$E_{ms}$计算积分满足$E_{ss} + E_{ms} = 1$关系。如果$E_{avg}$已知的话,$f_{ms}$则能被轻易算出。我们之前已经完成了这部分的工作!
10201024

1025+
不过注意,目前为止的公式都做了一个简化假设:$F=1$——表现实际的反照率则需打破因此带来的$E(\mu)=1$ 假设。
1026+
1027+
幸运的时,目前为止我们算的都是「平均量」。$F\ne1$的「能量」损失也是易得的。回顾之前的反射率定义,「一次**平均**」反射返回的能量为:
1028+
$$
1029+
F_{avg}E_{avg}
1030+
$$
1031+
继续:「之后」面介面内的反射呢?我们知道在(c)情况,不记$F$,内部反射丢失的平均能量是$1-E_{avg}$:结合之前的$F_{avg}$,则有次反射「丢失」的能量为
1032+
$$
1033+
l = F_{avg}(1-E_{avg}) \newline
1034+
\Delta{E} = F_{avg}E_{avg}l
1035+
$$
1036+
然后,未丢失部分能量继续反射,反反复复...直到被完全吸收。那中途「丢失」的能量和,是不是有头绪计算了?
1037+
1038+
是的——这里有一个等比数列!丢失的能量总和即为:
1039+
$$
1040+
\sum_{k=1}^{\infty}{\Delta{E}} = F_{avg}E_{avg}\sum_{k=1}^{\infty}{l^k} = F_{avg}E_{avg}\frac{l}{1 - l} = \frac{F^2_{avg}E_{avg}(1 - E_{avg})}{1 - F_{avg}(1 - E_{avg})}
1041+
$$
1042+
最后,$F_{avg}=1$时这个式子也恒等于$1 - E_{avg}$:这也是成立的。
1043+
1044+
反映到之前的$f_{ms}$,Kulla把这个和直接作比例乘回$f_{ms}$了——这是不对的(读者请思考$F_{avg}=1$时情况)。在原Talk Appendix中这也被修正:最后对应所有$F$的$f_{ms}$即为:
1045+
$$
1046+
f_{ms}\prime =\frac{(1-E_{ss}(w_o))(1-E_{ss}(w_i))F^2_{avg}E_{avg}}{\pi(1-E_{avg})(1 - F_{avg}(1 - E_{avg}))}
1047+
$$
1048+
幸运的是,这正是 Filament [4.7.2 Energy loss in specular reflectance](https://google.github.io/filament/Filament.md.html#mjx-eqn%3AenergyCompensationLobe) 用的式子!不过有点长,假如可以简化...
1049+
1050+
##### $f_{ms}$ 简化
1051+
1052+
![image-20251225095845997](/image-foundation/image-20251225095845997.png)
1053+
1054+
[The road toward unified rendering with Unity’s high definition rendering pipeline, Lagrade 2018](https://advances.realtimerendering.com/s2018/Siggraph%202018%20HDRP%20talk_with%20notes.pdf) 中就有提及,[Turquin在后来自己在2019的技术报告中也给出了一样的形式](https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf)
10211055

1056+
![image-20251225100427612](/image-foundation/image-20251225100427612.png)
1057+
1058+
核心观察是:正如我们之前所见,$F_{avg}$是个很小的值(记得之前推导的Shlick 模型,$n=5, s= 1/21$),$F_{ms}$不妨直接不算...
1059+
1060+
此外,另外的好处是,因为这样的计算相当于是对原Lobe进行“缩放”...
1061+
1062+
![image-20251225100324323](/image-foundation/image-20251225100324323.png)
1063+
1064+
这和Heitz跑出来的实际情况是很像的:多次反射/multiscatter的lobe很像第一次单次折射的lobe!
1065+
1066+
之前没有提及,但是Kulla的方法(记得不记$\phi$)的补偿lobe是一个简单的diffuse/半球lobe;参考上图,可见Turquin的方法会更接近物理事实。
1067+
1068+
最后,非常简单的补偿量$f_{ms}$即为
1069+
$$
1070+
f_{ms} = f_0 \frac{1-E}{E}f_{ss} = f_0 (\frac{1}{E} - 1)f_{ss}
1071+
$$
1072+
1073+
这里加到我们的glossy lobe即可。对`glTFBSDF``Sample_f`部分改进:
1074+
```c++
1075+
...
1076+
float mu = AbsCosTheta(wo);
1077+
float rough = sqrt(max(mfDistrib.alpha_x, mfDistrib.alpha_y));
1078+
float E = ggxLutE.SampleLevel(lutSampler, float2(mu, rough), 0);
1079+
float3 Fss = SchlickFresnel(F0, 1.0f, ClampedDot(wo, wm));
1080+
float3 Fms = F0 * (1/E - 1) * Fss;
1081+
float3 f = (Fss + Fms) * mfDistrib.D(wm) * mfDistrib.G(wo, wi) / (4 * AbsCosTheta(wi) * AbsCosTheta(wo));
1082+
return BSDFSample(f, wi, pdf, BxDFFlags::GlossyReflection);
1083+
```
1084+
1085+
#### 效果
1086+
1087+
一样的白炉测试效果如下,前后对比:
1088+
1089+
| ![image-20251223183648108](/image-foundation/image-20251223183648108.png) | ![image-20251225102842279](/image-foundation/image-20251225102842279.png) |
1090+
| ------------------------------------------------------------ | ------------------------------------------------------------ |
10221091
1092+
TBD 解决高roughness奇怪的异常 - -||
10231093
10241094
<h1 style="color:red">--- 施工中 ---</h1>
156 KB
Loading
298 KB
Loading
79.1 KB
Loading
153 KB
Loading

0 commit comments

Comments
 (0)