“从微面法线分布 D(h) 的世界,怎么转换到最终分母里的 4(n⋅v)”

也就是先不管 NoL,只看 出射侧到底怎么整理出 4 NoV

先从目标看。

我们最后想要的是宏观 BRDF:

fr(l,v)

它描述的是:

“单位宏观表面,在入射方向 l 来光时,往观察方向 v 送出多少辐亮度。”

而 D 一开始描述的不是这个。
D 描述的是:

“单位宏观表面上,有多少微面法线朝向 h。”

所以中间一定要做一次翻译:

从“按微面法线 h 计”
变成
“按观察方向 v 计”

这一步就是你要的核心。

对于固定的 lll,一个微面法线 h 唯一决定一个反射方向 v。

所以本来你是在数“有多少微面朝向 h”
现在你要改成问“有多少反射落到 v 方向附近”

这就要把方向小块从 dωhd\omega_hdωh​ 换成 dωvd\omega_vdωv​。

一个很小的视方向区域d\omega_vdωv​,对应到 half-vector 空间里的区域更小了,比例是1/[4(v⋅h)]。

真正把式子继续往最终 BRDF 形式推进的,不只是第一和第二类,第三类里也有实打实参与约分和重写的项。

第三类:微面实际反射贡献写成宏观辐亮度时的项
这里不是只有“镜面条件
lh=vhl\cdot h=v\cdot hl⋅h=v⋅h”这种口头条件,而是会真的出现一些乘法因子

验证为第三步是乱写

“镜面收缩”指的是:理想镜面反射不是把能量散到一片方向上,而是只允许能量出现在唯一满足反射定律的那个方向。

数学上,这种“只在一个点有贡献”的东西,用 Dirac delta 来写。

我是真倒了,这家伙在说什么

你这张图如果要改成“不容易错”的版本,最值得补上的一句就是:

“δ(h−n)\delta(h-n)δ(h−n) 是相对于 dωhd\omega_hdωh​ 的分布;变到 dωod\omega_odωo​ 时必须乘上 4(v⋅h)4(v\cdot h)4(v⋅h)。”

dωhd\omega_hdωh​ 不是位置面积元 dAdAdA,而是“方向空间面积元”。因为所有方向都落在单位球面上,一个方向附近的一小片方向集合,就对应单位球面上的一小块面积,这块面积的数值就是立体角。

brdf这个概念真的感觉很飘,在渲染方程里,本应该只给一个入射和出射,两个方向,就得到一个专属这个材质的brdf值,但是这些地方看起来好像还要再深一层去看,什么dfg都扯到法线, h了,这些动态的东西也被叫brdf,真的莫名其妙

只是直觉上可以认同了,但具体的公式,可能不太好确认;

“1/4 在有限粗糙度 GGX 中确实是显式因子;但在镜面 delta 极限中,它被 half-vector 到 outgoing-direction 的分布 Jacobian 吸收了,不会作为最终镜面 BRDF 的独立系数留下来。”

..............

因为 GGX 不是“理想镜面 delta BRDF”,而是“粗糙镜面微表面 BRDF”。两者的物理对象不同,所以那个 1/41/41/4 在 GGX 里是应该存在的;只有当你把它真正收缩成理想镜面分布时,连同 delta 的变量变换一起处理,最后才不会单独留下这个 1/41/41/4。核心区别不在“是不是反射”,而在“法线分布是不是普通密度函数,还是已经退化成 delta 分布”。

因为 GGX 不是“理想镜面 delta 已经彻底收缩完”的情况,而是“有坡度分布的粗糙镜面”。两者的物理约束不同,所以 N ⁣⋅ ⁣VN\!\cdot\!VN⋅V 在 GGX 里会保留下来。

更直接地说:

理想镜面时,出射方向 VVV 不是一个自由变量。给定入射 LLL 和法线 NNN,只有唯一那个镜面反射方向能成立。也就是:

V=R(L)V = R(L)V=R(L)

因此 BRDF 不是一个普通函数,而是带 delta 的分布。既然 VVV 已经被 delta 约束死了,很多本来依赖 VVV 的角度项,最后会在“分布变量替换”里被吸收掉,只剩标准镜面形式里的那个分母。

但 GGX 里不是这样。GGX 的前提是表面由一族微平面组成,每个微平面法线 mmm 服从一个连续分布 D(m)D(m)D(m)。这意味着:

对同一个 (L,V)(L,V)(L,V),不是只有一个“整体表面镜面反射”事件,而是“存在某些微面法线 m=hm=hm=h,它们恰好把 LLL 反射到 VVV”。这个事件的概率密度要靠 NDF、遮蔽项和投影测度共同给出。

所以 GGX 的 BRDF 是普通密度函数,不是 delta 分布。既然没有收缩成 delta,就不会发生你前面说的那种“把 hhh-空间 delta 严格推到 VVV-空间并把 Jacobian 全吸掉”的事情。于是 N⋅V 作为投影测度的一部分,自然还在:

fr(L,V)=D(h) F(V⋅h) G(L,V)4(N⋅L)(N⋅V)f_r(L,V)=\frac{D(h)\,F(V\cdot h)\,G(L,V)}{4(N\cdot L)(N\cdot V)}

N⋅V 不是凭空来的,它反映的是一个很具体的物理条件:你在定义 BRDF 时用的是 radiance measure,而 radiance 本身就是相对投影面积 (N⋅V) dωo dA(N\cdot V)\,d\omega_o\,dA(N⋅V)dωo​dA 定义的。只要你的散射分布还是普通连续密度,而不是 delta 奇异分布,这个投影因子就不会自动消失。

GGX 里的
44 和 N ⁣⋅ ⁣VN\!\cdot\!V 不是一回事,它们来源不同,物理含义也不同。

N⋅V 为什么存在

这个项本质上来自 BRDF 的定义:BRDF 是“出射 radiance 相对于入射 irradiance”的比例。
而 irradiance 的测度里天然带一个余弦项:


dE_i = L_i(L)\,(N\!\cdot\!L)\,d\omega_LdEi​=Li​(L)(N⋅L)dωL​

另一方面,微表面模型最开始更像是在算“有多少朝向为 HH 的微面,把来自 L 的光反射到 VV”。这个过程天然是围绕微面自己的投影测度展开的,不是直接围绕宏观表面的 BRDF 测度展开的。把它最终改写成宏观表面上的 BRDF 时,就会除回宏观表面的入/出射 foreshortening,最后出现

这个
44 不是某种额外经验项,也不是 Fresnel 或 masking 的近似产物。它来自反射几何本身的变量变换:从微面法线
HH 到出射方向 V 的映射 Jacobian。


  • 44:
    H \leftrightarrow VH↔V 的镜面反射 Jacobian;

  • N⋅L,N⋅V:宏观表面的投影测度 / BRDF 定义。

那个 4,本质上不是 “GGX 特有的魔法常数”,而是微表面 BRDF 从“半角向量分布”换算到“入射/出射方向反射概率”时,自然出现的几何归一化因子。

那个 4 是通用微表面镜面 BRDF 结构的一部分,不是 GGX 独有。

Beckmann、Phong NDF、GGX/Trowbridge-Reitz 放进同一个 microfacet 形式里,都会有这个 4(n·l)(n·v) 分母。

https://www.cnblogs.com/timlly/p/11098212.html

双镜叶高光(Dual Lobe Specular)

两个独立的高光镜叶提供粗糙度值,二者组合后形成最终结果。当二者组合后,会为皮肤提供非常出色的亚像素微频效果,呈现出一种自然面貌。

UE默认的混合公式是:

Lobe1⋅0.85 + Lobe2⋅0.15

红色Bleed Color的AO,使得皮肤渲染更加贴切,

红色光由于穿透力更强,更容易在皮肤组织穿透,形成红色光。

而 BSSRDF 描述的是:

从表面某个点 pip_ipi​ 射入物体,再从另一个点 pop_opo​ 射出。不仅要对入射方向积分,对“所有可能的入射位置”积分:Bidirectional Surface Scattering Reflectance Distribution Function

为什么需要对面积积分

因为对于表面某个观察点 pop_opo​,它看到的出射光,不只来自“这个点正上方打进来的光”,而是来自物体表面很多别的点 pip_ipi​ 注入进去的能量。

比如蜡、皮肤、牛奶、大理石这类材料:

  • 光从鼻尖附近打进去

  • 在内部走了一段

  • 可能从鼻翼、耳朵边缘、脸颊别的位置出来

所以你在计算 pop_opo​ 的出射亮度时,必须把整个表面上所有可能的入射点都加起来:

∫A(⋯ ) dA\int_A (\cdots)\,dA∫A​(⋯)dA

这就是比 BRDF 多出来的那个面积积分。

Lo​(po​,ωo​)=∫A​∫Ω​S(po​,ωo​,pi​,ωi​)Li​(pi​,ωi​)(n⋅ωi​)dωi​dA

可以逐项拆开:

Li(pi,ωi)L_i(p_i,\omega_i)Li​(pi​,ωi​)

入射到表面点 pip_ipi​ 的辐亮度。

(n⋅ωi) dωi(\mathbf n\cdot \omega_i)\,d\omega_i(n⋅ωi​)dωi​

把方向域里的辐亮度转成“入射到该表面的能量贡献”。这和 BRDF 里一样,是投影项。

dAdAdA

表面上的一个微小面积元,表示在整个物体表面上累加所有入射点。

S(po,ωo,pi,ωi)S(p_o,\omega_o,p_i,\omega_i)S(po​,ωo​,pi​,ωi​)

这是关键项,表示:

从 pi,ωip_i,\omega_ipi​,ωi​ 注入的能量,经过介质内部传输后,对 po,ωop_o,\omega_opo​,ωo​ 的出射贡献有多大。


4. 为什么常写成分离形式

你图里下面给了一个常见分解:

S(po,ωo,pi,ωi)=1πFt(po,ωo) Rd(∥pi−po∥) Ft(pi,ωi)S(p_o,\omega_o,p_i,\omega_i) = \frac{1}{\pi} F_t(p_o,\omega_o)\, R_d(\|p_i-p_o\|)\, F_t(p_i,\omega_i)S(po​,ωo​,pi​,ωi​)=π1​Ft​(po​,ωo​)Rd​(∥pi​−po​∥)Ft​(pi​,ωi​)

这是经典的 dipole / diffusion 近似 风格写法。它把复杂的 8 维函数近似成几个更容易处理的部分。

Ft(pi,ωi)F_t(p_i,\omega_i)Ft​(pi​,ωi​)

入射点的 透射 Fresnel 项

表示光从空气进入介质时,有多少能量真正进去了,而不是被表面直接反射掉。

Rd(∥pi−po∥)R_d(\|p_i-p_o\|)Rd​(∥pi​−po​∥)

diffuse reflectance profile,也叫 diffusion profile。

它只看入射点和出射点之间的表面距离

r=∥pi−po∥r=\|p_i-p_o\|r=∥pi​−po​∥

表示:进入介质的能量,在内部散射后,有多大概率在离入射点距离为 rrr 的地方重新出来。

这是 subsurface scattering 最核心的“空间扩散核”。

Ft(po,ωo)F_t(p_o,\omega_o)Ft​(po​,ωo​)

出射点的透射 Fresnel 项。

表示内部传播到边界后的光,有多少能从介质射出来。

1π\frac{1}{\pi}π1​

通常对应“近似为 Lambertian 型角分布”的归一化项。扩散近似里经常默认出射方向分布接近漫反射。

BRDF 是 BSSRDF 的特例

如果一个材料根本没有内部扩散,所有光都在同一点局部出射,那么:

pi=pop_i = p_opi​=po​

BSSRDF 就退化成只在同一点上有贡献的形式,本质上接近 BRDF。

所以你可以认为:

  • BRDF:局部表面散射

  • BSSRDF:非局部表面-体-表面散射

游戏里一般不会真的做完整的 BSSRDF 面积积分,因为太贵。

常见近似有几类:

屏幕空间 SSS

把光照结果在屏幕空间按 profile 做模糊。
优点是便宜,适合皮肤。
缺点是视角依赖强,跨物体/厚度处理差。

纹理空间 diffusion

把 irradiance 烘到 texture space,再按 diffusion profile 卷积。
离线和半实时里常见。

预积分 profile / separable SSS

把 Rd(r)R_d(r)Rd​(r) 近似成若干高斯核,分离卷积。
这是很多皮肤渲染方案的基础。

Burley / Disney normalized diffusion

工业里很常用,比经典 dipole 更稳,更容易调参。

,BSSRDF 本质是在求一个表面 irradiance 到出射 radiance 的 空间卷积核,其中核函数就是由介质光学参数决定的 diffusion profile。

区别只在于其密度分布函数R(r)R(r) 的集中程度,

UE源码中,与SSSS相关的主要文件(笔者使用的是UE 4.22,不同版本可能有所差别):

  • \Engine\Shaders\Private\SeparableSSS.ush:

    SSSS的shader主要实现。

  • \Engine\Shaders\Private\PostProcessSubsurface.usf:

    后处理阶段为SeparableSSS.ush提供数据和工具接口的实现。

  • \Engine\Shaders\Private\SubsurfaceProfileCommon.ush:

    定义了SSSS的常量和配置。

  • \Engine\Source\Runtime\Engine\Private\Rendering\SeparableSSS.cpp:

    实现CPU版本的扩散剖面、高斯模糊及透射剖面等逻辑,可用于离线计算。

  • \Engine\Source\Runtime\Engine\Private\Rendering\SubsurfaceProfile.cpp:

    SSS Profile的管理,纹理的创建,及与SSSS交互的处理。

完整 BSSRDF:8D(Screen Space SubSurface Scattering,SSSSS)。

https://dai.fmph.uniba.sk/w/Introduction/en

子域名 theses.hal.science 指向的就是 HAL 的论文/学位论文入口。HAL 的文档说明里写得很清楚:论文可以通过 TEL 门户、HAL 主站或学校自己的门户提交,但无论从哪里提交,论文都会在论文门户里可见;另外,某篇 thesis 也可能同时出现在 HAL-thèses 这样的论文门户里。

HAL 是法国学术界的国家级开放档案体系,CNRS 官方直接把它定义为“被整个法国科研与高校共同体选用”的多学科开放仓储,并明确写进法国的 National Plan for Open Science;它同时覆盖机构仓储、专题仓储和论文/学位论文门户。这个层面上,它在法国的制度地位是很高的。

放到国际上看,HAL 也不是边缘站点。它被 OpenAIRE 这类欧洲开放科研基础设施纳入数据提供方体系,说明它是欧洲开放获取生态里的正规节点,而不是某个学校自建的小库。

HAL / theses.hal.science 的“地位高”,主要高在“基础设施地位”和“公开传播地位”,不是“学术评价地位”。
也就是说,把论文或 thesis 放到 HAL,说明它进入了一个可靠、正式、长期保存、可公开访问的仓储体系;但这不等于它本身经过了期刊那种同行评审,也不等于“发在 HAL 上”本身就是一种学术头衔。OpenAIRE 对 HAL 的描述里也明确提到,HAL 上的文档不是按期刊那样做同行评审的。

所以可以这么理解:

在法国,HAL/theses.hal.science 类似“官方主干道”;
在欧洲开放科学体系里,它属于“主流基础设施”;
但在学术评价语境里,它更接近 arXiv、机构仓储、学位论文库这一类“存放与传播平台”,而不是 Nature、SIGGRAPH、TOG 这种“发表 venue”。

一句话概括:
它的地位高,但高在 repository,不高在 venue。

inria.hal.science / inria-00084212 / document

第一段,inria.hal.science
表示这是 HAL 体系下给 INRIA 用的机构门户子域名。也就是“HAL 上属于 INRIA 机构视图里的记录”。

第二段,inria-00084212
这是这条记录的标识符。
通常可以理解成“INRIA 在 HAL 里的某篇条目 ID”。前缀 inria- 说明这条记录最初是按 INRIA 机构库这条线归档/编号的。

第三段,document
表示你访问的是这条记录对应的文档文件本体,一般就是 PDF 或正文文件,而不是只看摘要页。

所以整串:

inria.hal.science/inria-00084212/document

基本可以理解为:

“HAL 平台里,INRIA 机构库中的编号 00084212 这篇文档的直接文件地址”。

顺手说一下,这类链接通常还会有一个对应的“记录页/摘要页”,那里会放标题、作者、摘要、下载入口;而带 /document 的这种,更像是直接打到附件正文。

比如这一页里:

P, M, K, S 这种大写字母,通常优先猜“点”

N_M, ω_in 这种,通常优先猜“向量/方向”

Π_M 这种大写希腊字母,通常优先猜“平面”

||MP|| 这种双竖线,通常优先猜“长度/范数”

A·B 通常优先猜“点积”

~N_M 上面带波浪/箭头/帽子之类,通常在表示“某种变体、近似、单位化、插值后的版本”

没有先区分“数学对象的类型”

快读时,第一眼不是读值,而是先给每个符号分类型。

第二,没有把“定义语句”和“计算语句”分开读。

比如这句:

Π_M : { ... }

它不是在“求值”,而是在定义M 相关的平面 Π_M 的组成。

也就是说,这里更接近“设一个对象,它有两个组成部分”,而不是“把左边算成右边”。

很多论文里,冒号、花括号、下标,都是在做对象定义和命名空间限定。

你如果把它也按普通代数运算读,就会非常堵。

第三,不知道哪些符号是通用语言,哪些是作者方言。

通用语言通常包括这些:

  • 下标:表示“跟谁相关”“在哪个点处”“第几个分量”

上标:有时表示幂,有时表示第 i 层/第 i 次,必须结合上下文

  • ||x||:向量长度/范数

  • |x|:标量绝对值;有时也会拿来写长度

  • a·b:点积

  • a/b:比例、归一化或长度换算

exp(...) / e^{-x}:指数衰减

希腊字母 α β θ σ:通常只是参数,不自带固定物理含义

把“式子当句子读”。

主语就是 αβ

α = ||MM_max|| and β = ||MP||

谓语是“等于”。
宾语是两段长度。

P, M, K ...
先猜点。

N, ω, v, l ...
先猜向量或方向。

Π, Γ, Ω ...
先猜几何对象、区域、域、平面、曲面、积分域。

下标 _M
先翻译成“在 M 处的”或“与 M 相关的”。

上标 ^i
先看是不是层号/编号;不一定是乘方。

||x||
先翻译成“x 的长度”。

A := B 或冒号定义
先翻译成“把 A 定义为 B”。

花括号
先翻译成“这个对象由以下几个分量组成”。

第一眼最有“触感”的信息。

必须先接受“先给名字

老广告里的 “condenser” 是老式英文里对 capacitor(电容器) 的叫法;电容器本身就是靠 dielectric(介电材料) 工作的。

  • Slide (幻灯片/PPT): 供学术演讲、汇报(Presentation)时使用,主要以可视化图表、要点(Bullet points)展示研究的核心观点和成果。
  • 学术论文 (Paper/Article/Thesis): 详尽的文档,包含完整的实验数据、方法、讨论和参考文献。在学术会议上,通常是先提交论文(Paper),被录用后在现场作学术演讲展示Slides。

“抵消了”指的是把具体的 Smith-GGX G 形式代回总 BRDF 之后,代数上可以约掉,不是说“整个最终表达式里一定完全看不到这些项”。

G 的分子中的 2NoL·2NoV 与总 BRDF 分母里的 4NoLNoV 约掉了

作者还在写原始结构:

那当然你仍然“看见”这个分母,因为作者还没把 G 展开
这时“抵消”只是说:如果你把 G 展开到 G_1 的具体表达式,就能约掉

第二种,作者把它改写成 visibility 形式,比如:

4(NoL)(NoV)

Cook-Torrance 微表面 BRDF 总体结构自带的
G 只是其中“遮蔽-阴影”那一项。只有当你选的 G 具体形式里,恰好含有分子 2 N o L 2NoL 2NoL 2 N o V 2NoV 2NoV 时,才会出现你前面看到的那种 cancellation。

G 不是唯一固定的。
你可以用不同的几何遮蔽模型,比如:

  • 原始 Cook-Torrance 的几何项

  • Smith

  • Smith + Beckmann

  • Smith + GGX

  • correlated / uncorrelated Smith

  • 各种实时近似 Schlick-GGX

这些通常都还属于 Cook-Torrance / microfacet 这一路,只是选了不同的 G

一句话归纳:

  • G:大多数情况下还是 Cook-Torrance,只是换了几何项

  • D:很多情况下也还是 microfacet / Cook-Torrance 家族,只是换 NDF

  • F:也常见,仍可能属于同一家族

  • H 的定义:往往就是在改模型的几何基础了,通常不再是标准 Cook-Torrance

你这句话如果改成更准确的版本,可以说成:

“不是换 G 就不是 Cook-Torrance;真正不能随便换的是 H 的几何定义。G 本来就是 Cook-Torrance 结构中允许变化的一部分。”

更准确地说:

  • G1:单方向可见性,描述一个方向上的 masking/shadowing

  • G2:双方向联合可见性,描述 光线方向 L 和视线方向 V 同时可见 的概率

所以 G2 是完整的双向项,不是 “G1 之后再乘一个补丁项”。

(注:现代PBR常使用高度相关的Smith版本,公式会稍微复杂一点,但本质还是G1的组合)。

原因是 Cook-Torrance 里的 G 不是一个唯一固定公式,而是一整类“masking-shadowing / visibility”建模选择。微表面 BRDF 本身就是模块化的:FGD 都可以在不同候选之间替换;一些资料甚至直接点明这套公式的优点之一就是这种 modularity。

Smith 家族内部再分:

  • Beckmann-Smith

  • GGX-Smith

  • separable / uncorrelated

  • correlated / height-correlated

  • 各种实时近似,例如 Schlick-style fit

例如 correlated Smith GGX 很多代码直接写 visibility term,而不是先写一个完整 G
也就是说,这两者在工程上本来就经常作为一个整体实现。

第三,数值稳定和代码实现更方便。
实时渲染里你常会看到:


glsl
float Vis = V_SmithGGXCorrelated(NoV, NoL, roughness);
float3 Fr = D * F * Vis;

而不是:


glsl
float G = ...;
float3 Fr = D * F * G / (4 * NoL * NoV);

原因包括:

  • 避免重复除法

  • 更容易做 grazing-angle 稳定处理

  • 相关/非相关 Smith 版本都能统一包装成一个 visibility API

  • LUT、拟合、多散射补偿时更方便复用

所以不是“只能合并成 V”,而是“合并成 V 最合理”。

当然,理论上也可以合并别的:

但这样做的问题是语义会变脏:

  • F 不再只是 Fresnel

  • D 不再只是 NDF

  • 模块边界变差

  • 不同论文/代码之间更难对照

原始 Cook-Torrance 的几何项并不具备 Smith-GGX 那种“和外面 4 N o L N o V 4NoLNoV 4NoLNoV 整体约掉”的结构

Smith-GGX 的可约分,靠的是:

  • G 本身已经是“L方向一份、V方向一份”的乘积

  • 每份分子都带一个 2NoX

原始 Cook-Torrance 的 min 形式:

  • 不是 separable 乘积

  • 是在几个约束之间取最小值

  • 里面还混着 NoHVoHLoH

所以它更像是一个“几何夹紧上界”,不是一个天然能拆成 NoLNoV 对称乘积的函数。

原始的cooktorrance并不实用,所以Smith也是后面才替换掉里面的g项的

原始 Cook-Torrance 使用了一个早期的几何项;后来图形学界发现 Smith 这一套 masking-shadowing 模型更系统、更好用,于是现代 microfacet BRDF 大多改用 Smith family 的 G

但历史上不是一次简单的“官方升级替换”。

更准确地说:

  • Cook-Torrance 是一个微表面 specular 框架

  • G 在这个框架里本来就是一个可替换模块

  • 后来业界逐渐发现 Smith 的 G1 / G2 体系更有理论一致性,也更适合和 Beckmann、GGX 这类 NDF 配套

  • 所以现代 PBR 基本都写成 “Cook-Torrance 结构 + Smith-based geometry”

也就是今天大家口头说的 “Cook-Torrance GGX” 之类,往往其实不是“原始 1982 那个完整配方”,而是:

  • Cook-Torrance 的整体微表面 BRDF 结构

  • 加上现代 NDF(常见 GGX)

  • 加上现代 Fresnel 近似(常见 Schlick)

  • 加上 Smith 几何项及其各种近似/相关版本

所以你看到的很多“Cook-Torrance”其实已经不是“原始 CT 原版”,而是“CT 框架下的现代变体”。

https://www.youtube.com/watch?v=wbBtAFpOxg8

https://juejin.cn/post/6995333774623899684

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐