Banner

「PBR 系列」第四篇 · 布料与 Sheen 渲染:从 Charlie 到丝绸天鹅绒之辨

核心结论:布料是 GGX 微表面框架的第一个真正”破例”——纤维的环形反射和绒毛感无法用传统 NDF 表达。Charlie 与 Ashikhmin Velvet 是两个工业候选 NDF,配合 V_Charlie / V_Ashikhmin / V_Neubelt 几何项,再加上 sheen color、subsurface color 两参数,就能用同一个模型架构覆盖从棉麻到丝绸到天鹅绒的全谱系。本篇详解布料 BRDF 的物理动机、数学选型、以及 UE4、Filament、Unity SRP 的工程实践。

一、布料为什么是个难题

打开 Filament 的 cloth 对照图就能看出问题:

Left: 标准 GGX  Right: Cloth BRDF

左图是用标准 GGX 渲染的牛仔布,右图是 cloth BRDF,差异显著。问题不在 GGX 本身——它对致密的硬表面(皮革、缎面、丝绸的纹理面)表现尚可——而在于布料从不是”致密表面”

  • 衣物和织物由松散的线连接,吸收并散射入射光;
  • 微纤维使高光极宽,不像金属那样锐利;
  • 在掠射角呈现强烈的环形反射(retroreflection)
  • 高光不由法线朝向控制,而由纤维的倾斜分布决定。

传统 microfacet BRDF 假设表面由镜面随机细槽组成(致密硬表面),与上述布料特性的根本不符。所以即使把 GGX 的粗糙度拉到 1.0,也得不到布料该有的”绒感”。

布料的两个核心特性

  • 较大的衰减 + 较柔和的镜面波瓣(specular lobe)
  • 绒毛感的照明效果(Fuzz Lighting)——前向 / 后向散射

天鹅绒(Velvet)这种织物由于前向和后向散射效应,表现出强烈的边缘照明。这种散射是由立在织物表面的纤维引起的:当入射光与观察方向相反时,纤维前向散射;当入射光与观察方向相同时,纤维后向散射

天鹅绒织物的前向/后向散射


二、两条技术路线:Sheen Lobe vs Fabric BRDF

在深入数学之前,必须先厘清一个容易混淆但极其关键的概念。”Sheen”和”Cloth BRDF”这两个词在工业语境下指代的是两条截然不同的技术路线,常被混用导致初学者困惑:到底 Sheen 是一种材质,还是一个参数?

2.1 路线一:Sheen Lobe 作为附加项(Add-on Lobe)

在标准 Disney/GGX 模型之上,叠加一个高粗糙度的边缘高光层,底层的 GGX 反射依然存在。

典型应用

  • 皮肤上的桃色绒毛(peach fuzz),用 sheen 表达毛发散射;
  • 沾灰尘的家具表面,硬表面 + 微绒毛附着;
  • 沙发布、皮革的”半绒毛”质感;
  • 现代汽车漆中织布感装饰漆。

实现特征:sheen 通常只是几个浮点参数(sheen, sheenTintsheenColor, sheenRoughness),美术调起来像调”边缘亮一下”。

2.2 路线二:Fabric BRDF 作为替换项(Dedicated Cloth Model)

彻底抛弃标准 GGX,使用专属的布料 NDF(Ashikhmin、Charlie)替换整个高光项:

典型应用

  • 棉布、麻布、毛呢等纯粹编织物;
  • 丝绸、缎子、天鹅绒;
  • UE4 The Order: 1886、Filament 的专用 cloth 模型。

实现特征:需要一整套专属 BRDF 框架——专属 NDF、专属 Visibility、专属 diffuse 衰减、可能还需要 sheen color、subsurface color 等额外参数。在引擎里通常作为独立的 shading model(MATERIAL_MODEL_CLOTHMATERIAL_TYPE_FABRIC)。

2.3 决策树

你的目标 选哪条路
给已有 PBR 球加点”绒毛感” Sheen Lobe
渲染纯粹的衣服、毯子 Fabric BRDF
桃毛、灰尘、半磨损硬表面 Sheen Lobe
丝绸、天鹅绒、需要 retroreflection Fabric BRDF
资产数量极少,全场景仅一两件衣服 Sheen Lobe(图省事)
AAA 角色衣物系统 Fabric BRDF(投资框架)

理清这两条路线,本篇接下来的内容才能各得其位:第三、四节讨论的 Charlie/Ashikhmin/Neubelt 都是Fabric BRDF 路线的部件;而 glTF 的 KHR_materials_sheen 扩展、Disney 2012 原始 sheen 项是Sheen Lobe 路线的实现。


三、NDF 的选型

布料 BRDF 的核心争议在于 NDF(D 项)的形状选择。当前工业有两个主流:

3.1 Disney Sheen 的物理缺陷

Disney 2012 原始 sheen 仅仅是一个基于 Schlick Fresnel 的经验性 Color Tint:

它在物理上没有微表面支撑。它只能让边缘”亮一下”,无法表现真实绒毛随着视线/光源角度变化的复杂前向/后向散射。具体局限:

  • 形状失真:仅是 Schlick pow5 形状,不会表现绒毛的环带高光;
  • 能量不守恒:直接叠加在 specular 之上,没有任何衰减约束;
  • 粗糙度无关:sheen 项与 roughness 没有耦合,无法表达”柔软”对应”宽散射”的物理直觉;
  • 无各向异性支撑:丝绸沿纤维方向的拉长高光,原始 Disney sheen 完全表达不出。

正是这些缺陷推动了 Imageworks 在 2017 提出 Charlie 分布——把 sheen 真正拉回微表面物理框架。Charlie 不是粗暴的颜色叠加,而是一个有正弦指数规律的真实 NDF。

3.2 Ashikhmin Velvet NDF(2007)

Ashikhmin 与 Premoze 在 2007 年的论文 “Distribution-based BRDFs” 中提出基于倒置高斯(inverted Gaussian)的 NDF。这个分布两端高、中间低,刚好对应布料”两端有高光,中间柔和”的特性。

Filament 使用的是 Neubelt & Pettineo 在 Crafting a Next-Gen Material Pipeline for The Order: 1886 中提出的标准化版本:

1
2
3
4
5
6
7
8
9
10
float D_Ashikhmin(float roughness, float NoH)
{
// Ashikhmin 2007, "Distribution-based BRDFs"
float a2 = roughness * roughness;
float cos2h = NoH * NoH;
float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-7), 避免 fp16 下溢
float sin4h = sin2h * sin2h;
float cot2 = -cos2h / (a2 * sin2h);
return 1.0 / (PI * (4.0 * a2 + 1.0) * sin4h) * (4.0 * exp(cot2) + sin4h);
}

3.3 Charlie 分布(2017)

Estevez & Kulla 在 SIGGRAPH 2017 “Production Friendly Microfacet Sheen BRDF”(Sony Pictures Imageworks)中提出了基于指数正弦曲线(而非倒置高斯)的 Charlie 分布:

其中

1
2
3
4
5
6
7
8
float D_Charlie(float roughness, float NoH)
{
// Estevez and Kulla 2017
float invAlpha = 1.0 / roughness;
float cos2h = NoH * NoH;
float sin2h = max(1.0 - cos2h, 0.0078125);
return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);
}

Charlie NDF 相对 Ashikhmin Velvet 的三大优势:

  1. 参数化更直观roughness 单调控制柔和度;
  2. 外观更柔和:避免了倒置高斯在 时的奇异点;
  3. 实现更简单:只有一次 pow,无 exp。

下图直观对比两者:

左 Ashikhmin Velvet, 右 Charlie

工业上,新项目优先选 Charlie,老项目(如 Filament 早期版本、HDRP)保留 Ashikhmin 兼容。


四、Visibility(V/G)项的选型

布料的 visibility 项也有专门的设计。三个候选:

4.1 V_Charlie(精确但昂贵)

Estevez & Kulla 提出的精确 visibility 项基于 Conty-Kulla 的拟合曲线,包含 exp 和 pow,开销较大:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
real CharlieL(real x, real r)
{
r = saturate(r);
r = 1.0 - (1.0 - r) * (1.0 - r);

float a = lerp(25.3245, 21.5473, r);
float b = lerp( 3.32435, 3.82987, r);
float c = lerp( 0.16801, 0.19823, r);
float d = lerp(-1.27393, -1.97760, r);
float e = lerp(-4.85967, -4.32054, r);

return a / (1.0 + b * pow(x, c)) + d * x + e;
}

real V_Charlie(real NoL, real NoV, real roughness)
{
real lambdaV = NoV < 0.5 ? exp(CharlieL(NoV, roughness))
: exp(2.0 * CharlieL(0.5, roughness) - CharlieL(1.0 - NoV, roughness));
real lambdaL = NoL < 0.5 ? exp(CharlieL(NoL, roughness))
: exp(2.0 * CharlieL(0.5, roughness) - CharlieL(1.0 - NoL, roughness));
return 1.0 / ((1.0 + lambdaV + lambdaL) * (4.0 * NoV * NoL));
}

电影渲染或离线方案首选,移动端不可用。

4.2 V_Ashikhmin(中等近似)

Ashikhmin-Shirley 模型衍生的简化形式:

1
2
3
4
real V_Ashikhmin(real NoL, real NoV)
{
return 1.0 / (4.0 * (NoL + NoV - NoL * NoV));
}

成本只有几次乘加,是当前 HDRP / UE4 / Filament 移动端的主流选择。

4.3 V_Neubelt(Filament 默认)

Neubelt 在 The Order: 1886 中使用的版本,与 Ashikhmin 等价但更稳定:

1
2
3
4
float V_Neubelt(float NoV, float NoL)
{
return saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));
}

加 saturate 防 overflow,移动端 fp16 友好。


五、Filament Cloth 模型

Filament 是当前文档最完整的 cloth BRDF 开源参考,下面拆解它的设计。

5.1 完整 Cloth Specular BRDF

注意 Filament 这里把传统 Cook-Torrance 分母中的 简化掉了——背后有深层物理原因,值得展开。

布料材质的能量危机:为什么丢弃分母?

传统 Cook-Torrance 分母 的推导,严格依赖一个假设:”表面由致密、不透明的 V 形凹槽微表面构成”。这套假设对硬表面(金属、塑料、瓷器)是合适的,但对布料完全失效:

  • 布料是由立体的、半透明的、圆柱形纤维相互交织而成;
  • 光线在纤维之间发生极度复杂的多重散射(一根光线要穿过 N 根纤维,每根纤维都既反射又透射);
  • 传统的可见性(Visibility)几何遮蔽假设——“被一根遮住就完全损失”——在这里完全失效,应该是”被部分散射、部分穿透”的连续过程。

如果不加修改地保留 Cook-Torrance 分母,会有两个失败模式:

  1. 掠射角过曝:当 NoL 或 NoV 极小时,分母趋近 0,BRDF 输出爆炸式增大,边缘出现 NaN 或亮斑;
  2. 正面过暗:相反方向上,由于布料的 NDF 设计本身就在两端偏高,如果再加上 Cook-Torrance 的 余弦分母,会双重抑制中间区域。

Filament 选择直接丢弃 Cook-Torrance 分母的余弦项,转而用 V_Neubelt 的 软化分母——这个分母在掠射角不会归零(保证 NaN-free),同时能量分布更接近真实纤维材质的观测。

Charlie 的能量补偿:与第五篇的呼应

更严谨地,Estevez & Kulla 在 Charlie 论文中也指出:如果把 Charlie sheen 作为附加项强行加在漫反射之上,会凭空创造能量——这就是第二篇 12.1 节讨论的能量过冲问题,也是第五篇 Kulla-Conty 章节的主题之一。

Imageworks 的解决方案:为 Charlie sheen 引入一个方向反射率(Directional Albedo)的缩放项 ,用以压暗底层的 Diffuse,确保总能量守恒:

其中 是 Charlie sheen 的方向反射率 LUT(与第五篇 Kulla-Conty 的 是同一个东西)。这样:

  • sheen 越强,底层 diffuse 被压得越暗;
  • sheen 在两端高的能量被精确”借”自 diffuse;
  • 总能量守恒。

工程实现常以 1D LUT 提供 ,运行时一次纹理采样即可。这也是为什么现代 cloth 实现往往伴随一个 sheen LUT。

5.2 Sheen Color:双色调镜面

Filament 把传统 BRDF 的 替换为可独立控制的 sheen color 参数。它本质上是菲涅尔的颜色控制:

与 Disney sheen 的差异:Filament 在 sheen 项中显式加了 (1 - NoV)^power 控制反射强度沿掠射角的衰减形状。

经验值:

  • 天鹅绒:power ≈ 6–8(边缘亮带极强)
  • 丝绸:power ≈ 2–4(柔和反射)

下图是 sheen 对比:

左:无 sheen   右:有 sheen

5.3 Subsurface Color:廉价次表面

布料常常带有”穿透感”——光从布料另一侧透过来时呈现温暖色调。Filament 用 wrap diffuse 实现近似 SSS:

其中 ,Filament 选

效果对比:

左:白布   右:带棕色 subsurface 的白布

5.4 完整 GLSL 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// === Cloth specular BRDF ===
float D = distribution(roughness, NoH); // D_Charlie 或 D_Ashikhmin
float V = visibilityCloth(NoV, NoL); // V_Neubelt
vec3 F = sheenColor;
vec3 Fr = (D * V) * F;

// === Cloth diffuse BRDF ===
float diffuse = diffuse(roughness, NoV, NoL, LoH); // FabricLambert
#if defined(MATERIAL_HAS_SUBSURFACE_COLOR)
// Wrapped diffuse for cheap subsurface
diffuse *= saturate((dot(n, light.l) + 0.5) / 2.25);
#endif
vec3 Fd = diffuse * pixel.diffuseColor;

#if defined(MATERIAL_HAS_SUBSURFACE_COLOR)
Fd *= saturate(subsurfaceColor + NoL);
vec3 color = Fd + Fr * NoL;
color *= (lightIntensity * lightAttenuation) * lightColor;
#else
vec3 color = Fd + Fr;
color *= (lightIntensity * lightAttenuation * NoL) * lightColor;
#endif

5.5 美术参数对应表

参数 含义
sheenColor 镜面颜色,创造双色镜面布料(默认 0.04)
subsurfaceColor 材料散射和吸收后的漫反射颜色

美术指南

  • 天鹅绒:basecolor 设为黑色或深色,chromaticity(色调)设在 sheenColor 上;
  • 常见面料(牛仔、棉布):用 basecolor 表达 chromaticity,sheenColor 取默认或设为 basecolor 的亮度(HSL/HSV 中的 L 或 V);
  • 丝绸:basecolor 偏白,sheenColor 偏低饱和但高亮度。

六、UE4 The Order: 1886 方案

Neubelt & Pettineo 在 SIGGRAPH 2013 提出的 cloth pipeline 是另一个工业里程碑。它的核心 insight 是:不同布料的物理本质不同,应分类设计

6.1 两类布料

Two types of Fabric: Non-Metal(棉、牛仔、亚麻等)

  • 每根纤维上有微小绒毛(tiny furs on each fibers);
  • roughness 永远是 1.0(除非湿润);
  • 反射光向各方向随机散射;
  • 既有前向也有后向散射 → 边缘绒毛(fuzz on rim part);
  • specular 沿视线方向呈广泛分布;
  • specular color 是白色,但因前向散射看起来像 baseColor 的去饱和版本。

Cotton, Denim, Flax

Two types of Fabric: Metal(丝绸、缎子、天鹅绒、尼龙、涤纶)

  • 丝绸(silk)有近似圆三角形截面 + 光滑表面 → 类金属反射;
  • roughness 范围 0.3-0.7(远低于 cotton);
  • 单根纤维厚度 5-10 μm(足够细以呈现金属般的光滑表面);
  • 各种 specular 颜色(不同色线编织);
  • 视角依赖的 specular color;
  • 各向异性镜面形状。

Silk, Satin, Velvet

Velvet 的特殊性

  • 表面贴附极细的纤维(fine chain stuffer filling);
  • roughness 必须为 1.0;
  • 当光从背面照来,纤维前向散射 → 边缘 rim light;
  • diffuse shading 类似金属;
  • 表面色比纤维原色更暗。

6.2 最终公式

经过分类设计后,Neubelt & Pettineo 的 cloth shading 公式:

各项含义:

  • :粗糙漫反射;
  • :布料专用 NDF(Ashikhmin Velvet);
  • :wrap lighting 模拟纤维的前向散射;
  • :决定纤维散射的方向衰减;
  • :基础色和纤维色。

6.3 Fabric Scatter Amount

The Order: 1886 引入 fabricScatterAmount 参数控制纤维散射区域:

  • 0:仅边缘是 fabric scatter color;
  • 0.25 / 0.5 / 0.75:散射区域逐渐扩大;
  • 1.0:几乎整个表面是 fabric scatter color,仅边缘是 baseColor。

6.4 推荐参数表

布料类型 metallic roughness fabricScatterAmount
Cotton 或普通织物 0 1.0 0.5
Velvet 1 1.0 0
Silk / Satin 1 0.35–0.7 0.8–0.9

关键 Hint

  • 用 metallic 的值表示丝纤维在织物中的比例(比例分割图);
  • 仅”金属类”布料的 roughness 可低于 1.0;
  • 不要用 normal map 描述丝绸表面的图案,应用 roughness 区分图案/非图案区域。

七、丝绸 vs 天鹅绒之辨

Charlie BRDF 是个”母体”模型,通过参数调整即可生成不同布料质感。同样的模型如何区分丝绸与天鹅绒?

7.1 物理特征对比

属性 丝绸(Silk) 天鹅绒(Velvet)
纤维结构 光滑、细长、有序排列 纤维竖立、粗糙、随机分布
反射特性 强方向性高光,略有镜面 强背向反射(retroreflection),正面较暗
高光形状 稍锐利、沿切线方向拉伸 宽而柔和,环带状
Fresnel 变化 较明显 极强(斜角夸张)
能量分布 Specular 占比高 Diffuse + Sheen 占比高

7.2 参数调控

Roughness(最直接分界)

材质 roughness 视觉效果
丝绸 0.2 ~ 0.4 亮而柔滑,高光清晰,略带光泽
天鹅绒 0.6 ~ 0.9 高光宽广、柔软、亮区分布于边缘

Fresnel / Sheen 强度

1
F_sheen = pow(1 - saturate(NdotV), sheenPower) * sheenColor;
材质 sheenPower sheenColor
丝绸 2 ~ 4 淡色反射(略带主色)
天鹅绒 6 ~ 8 高饱和、暖色反射(明显边缘亮带)

Visibility 项选择

  • 丝绸:V_Ashikhmin(成本低,效果集中)
  • 天鹅绒:V_Charlie(精确捕获 retroreflection 环带)

切线方向偏移(Tangent Warp)

为强化丝绸的”顺纹理方向反射”,可让半角向量沿切线偏移:

1
H = normalize(lerp(H, T, silkWarp));

天鹅绒不需要——它的反射本来就是随机分布的。

7.3 简化版区分实现

丝绸:必须各向异性

回顾 6.1 节,丝绸(silk)属于”金属类布料”——其根本视觉特征是沿纤维方向被拉长的高光。这意味着仅靠低粗糙度的各向同性 Charlie 是不够的。真正的丝绸质感必须使用各向异性 NDF(Anisotropic GGX 或变形的 Ashikhmin),并配合沿纤维方向的切线贴图(Tangent Map)。

HLSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 丝绸风(各向异性版本,参考第二篇 5.4 节的 DV_SmithJointGGXAniso)
float3 BRDF_Silk(float3 N, float3 V, float3 L,
float3 T, float3 B, // 沿纤维方向的切线 / 副切线
float rough, float anisotropy)
{
float NdotL = saturate(dot(N, L));
float NdotV = saturate(abs(dot(N, V)) + 1e-5);
float3 H = SafeNormalize(L + V);
float NdotH = saturate(dot(N, H));

// 各向异性粗糙度分离:沿纤维方向(T)粗糙度高,垂直方向(B)粗糙度低
// 这就是丝绸高光被"拉长"的物理来源
float a = rough * rough;
float aT = max(a * (1.0 + anisotropy), 0.001);
float aB = max(a * (1.0 - anisotropy), 0.001);

// 用 Anisotropic GGX 替代各向同性 Charlie(关键差异)
float ToH = dot(T, H), BoH = dot(B, H);
float ToV = dot(T, V), BoV = dot(B, V);
float ToL = dot(T, L), BoL = dot(B, L);
float DV = DV_SmithJointGGXAniso(ToH, BoH, NdotH,
ToV, BoV, NdotV,
ToL, BoL, NdotL, aT, aB);

// F0 比布料高一些(丝绸属于"金属类布料",类金属反射)
float3 F = F_Schlick(0.08, saturate(dot(H, V)));

float3 spec = DV * F;
float3 diff = FabricLambert(rough);

// 沿掠射角的轻微 sheen(丝绸的"光泽感")
float sheen = pow(1.0 - NdotV, 4.0) * 0.3;

return diff + spec * 1.5 + sheen;
}

// 天鹅绒风(保留各向同性 Charlie 即可)
float3 BRDF_Velvet(float3 N, float3 V, float3 L, float rough)
{
float NdotL = saturate(dot(N, L));
float NdotV = saturate(abs(dot(N, V)) + 1e-5);
float3 H = SafeNormalize(L + V);
float NdotH = saturate(dot(N, H));

float D = D_Charlie(NdotH, max(rough * 0.9, 0.05));
float Vt = V_Charlie(NdotL, NdotV, rough);
float3 F = pow(1.0 - NdotV, 6.0) * float3(1.0, 0.8, 0.7); // 强边缘暖色

float3 spec = D * Vt * F;
float3 diff = FabricLambert(rough);
return diff * 0.8 + spec * 1.2;
}

注意丝绸的实现核心差异:

  1. Anisotropic GGX 替代各向同性 Charlie——丝绸的高光是被”拉长”的,而不仅仅是”变亮”;
  2. 需要额外的 切线贴图 输入(对应纤维流向);
  3. F0 比普通布料稍高(≈0.08,对应丝绸的类金属表面)。

与 IllusionRP 的对应

第八节 8.3 的 AnisoFabricLighting 实现就是这个思路的工程化版本——它用 SheenData.Sheen 参数在”丝绸的方向性高光(Anisotropic GGX)”和”天鹅绒的绒毛感(Charlie sheen)”之间无缝插值。同一个 shader 覆盖整个布料谱系的关键,就在于两套 BRDF 项(各向异性高光 + sheen 高光)的可调混合

一句话总结:模型相同,参数不同。Roughness、Fresnel/Sheen 强度、Visibility 选择、视角依赖性、tangent warp、各向异性五个旋钮的不同组合,就能从同一个 Charlie + Anisotropic GGX 框架生成棉、麻、丝、绒、缎五种迥异质感。


八、Unity SRP / IllusionRP 完整实现

下面是基于 IllusionRP(开源 URP 扩展)的 fabric BRDF 完整实现,融合了上述各家方案:

8.1 Fabric BRDF 函数库

HLSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// === Diffuse: Oren-Nayar 简化版(来自 UE4 The Order)===
half3 Diffuse_OrenNayar(half NoV, half3 albedo, half roughness)
{
half lambda = -0.5 * NoV + 1;
half lambda2 = (1 - lambda);
half fakey = (1 - lambda2 * lambda2) * 0.62;
return lerp(1, fakey, roughness) * albedo;
}

// === D_Ashikhmin(Filament 风格)===
half D_AshikhminNoPI(half NoH, half roughness2)
{
half cos2h = NoH * NoH;
half sin2h = max(1.0 - cos2h, 0.0078125);
half sin4h = sin2h * sin2h;
half cot2 = -cos2h / (roughness2 * sin2h);
return (4.0 * roughness2 + 1.0) * sin4h * (4.0 * exp(cot2) + sin4h);
}

half D_Ashikhmin(half NoH, half roughness2)
{
return D_AshikhminNoPI(NoH, roughness2) * INV_PI;
}

// === D_Charlie(Imageworks 风格)===
real D_CharlieNoPI(real NoH, real roughness)
{
float invR = rcp(roughness);
float cos2h = NoH * NoH;
float sin2h = 1.0 - cos2h;
return (2.0 + invR) * PositivePow(sin2h, invR * 0.5) / 2.0;
}

real D_Charlie(real NoH, real roughness)
{
return INV_PI * D_CharlieNoPI(NoH, roughness);
}

// === V_Neubelt(Filament 默认)===
half V_Neubelt(half NoL, half NoV)
{
return rcp(4 * (NoL + NoV - NoL * NoV));
}

// === V_Ashikhmin(移动端友好)===
real V_Ashikhmin(real NoL, real NoV)
{
return 1.0 / (4.0 * (NoL + NoV - NoL * NoV));
}

// === FabricLambert: 经验性漫反射衰减 ===
real FabricLambertNoPI(real roughness)
{
return lerp(1.0, 0.5, roughness);
}

real FabricLambert(real roughness)
{
return INV_PI * FabricLambertNoPI(roughness);
}

8.2 Sheen 散射主流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void SheenScattering(BRDFData brdfData, half NoH, half NoV, half NoL, out half SheenTerm)
{
// URP 由于美术习惯不在 diffuse 上除 PI
// 因此 specular 项需要乘 PI 以维持能量平衡
#ifdef _SHEEN_VELET
half D_Sheen = D_AshikhminNoPI(NoH, brdfData.roughness2);
#else
half D_Sheen = D_CharlieNoPI(NoH, brdfData.roughness);
#endif

half V_Sheen = V_Neubelt(NoL, NoV);
SheenTerm = D_Sheen * V_Sheen;

#if REAL_IS_HALF
SheenTerm = SheenTerm - HALF_MIN;
SheenTerm = clamp(SheenTerm, 0.0, 100.0); // 移动端 FP16 防溢出
#endif
}

8.3 各向异性 + Sheen 复合

IllusionRP 还做了一个有趣的设计:在同一个 fabric shader 中同时支持 anisotropic(用于丝绸的拉丝高光)和 sheen(用于绒毛感):

HLSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
half3 AnisoFabricLighting(BRDFData brdfData, half3 lightColor, half3 lightDirectionWS, ...)
{
half Alpha = brdfData.roughness2;
half aT = max(Alpha * (1.0 + anisotropyData.Anisotropy), 0.001f);
half aB = max(Alpha * (1.0 - anisotropyData.Anisotropy), 0.001f);

// ... 基础 dot 产品计算 ...

half3 diffuse = Diffuse_OrenNayar(NoV, brdfData.diffuse, brdfData.roughness);

#ifdef FABRIC_SUBSURFACE_SCATTERING
half wrap = 0.5f;
half scatter = saturate(dot(normalWS, lightDirectionWS) + wrap) / (1 + wrap);
diffuse += scatterColor * scatter;
#endif

// Anisotropy Specular(用于丝绸的方向性高光)
half DV = DV_SmithJointGGXAniso(...);
half3 F = F_Schlick(brdfData.specular, VoH);
half3 AnisoSpecular = DV * F;

// Sheen Specular(用于绒毛感,使用 smooth normal 避免细节噪声)
NoH = saturate(dot(SheenData.N, H));
NoV = saturate(abs(dot(SheenData.N, viewDirectionWS)) + 1e-5);
NoL = saturate(dot(SheenData.N, lightDirectionWS));

half SheenTerm;
SheenScattering(brdfData, NoH, NoV, NoL, SheenTerm);
half3 SheenSpecular = SheenTerm * SheenData.Color;

// 在两种 specular 之间插值
half3 LerpSpecular = lerp(AnisoSpecular, SheenSpecular, SheenData.Sheen);

return (diffuse + LerpSpecular) * Radiance;
}

SheenData.Sheen 参数让美术能在”丝绸方向性高光”和”天鹅绒绒毛感”之间无缝插值——同一个 shader 覆盖整个布料谱系。注意 sheen 部分使用 SheenData.N(smooth normal,无细节扰动),这是为了避免布纹细节扰动 sheen 的环带形状,是 The Order 1886 的经典做法。


九、布料的纤维微结构与未来方向

9.1 微薄片理论(Microflake Theory)

布料属于多层物理结构,除了微表面理论外,也能用微薄片理论来描述:将分层 BSDF 渲染视为体积渲染,将体积介质抽象为微薄片(microflakes),基于体渲染中的消失系数、相位函数等工具构建渲染方程。

每个薄片是被称作微薄片的微小、双面平滑的完美镜面的体积分布。每个薄片有常数或方向变化的反射率(类似于镜子的 fresnel 项)。微薄片的法线方向遵循一个方向分布

微薄片相位函数:

其中 是投影面积。

9.2 SpongeCake 模型(2023)

王宁北等人提出的 SpongeCake 是基于微薄片理论的分层 BSDF 模型,每层为基于微薄片(如 SGGX 微薄片,Henyey-Greenstein 等相位函数)或其他相位函数的均匀体积散射介质

核心优势:

  1. 任意层数体积层的精确解析单散射解——避免蒙特卡洛噪声;
  2. 解析多重散射近似——通过添加”修改参数的单散射 lobe + 朗伯 lobe”实现;
  3. 轻量级神经网络预测参数——预训练的全连接网络在渲染时无需推理;
  4. 支持微薄片方向映射——避免传统法线映射 artifacts。

单散射 BRDF 的推导:

其中 G 项考虑了体积衰减:

NDF 使用 SGGX:

SpongeCake 能模拟塑料、木材、布料、植物叶片等多种材质外观,在渲染效率与效果上优于蒙特卡洛模拟和其他解析模型。

当前 SpongeCake 主要用于离线/影视渲染,但其”分层 + 微薄片”的思路正在被实时引擎吸收。Unity HDRP 的 fabric 多层模型已经走在这个方向上。


十、工程调试

Charlie NaN 噪点

最常见的 cloth shader bug:极高点光下出现 NaN 噪点,整个 mesh 局部出现彩色斑点。99% 是因为 Charlie 在 roughness → 0 时 pow(sin2h, invR * 0.5) 爆炸

1
2
3
// 当 roughness 接近 0 时,invR = 1/roughness 趋近无穷,pow 溢出
float invR = rcp(roughness); // ❌ 无防护
return (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * PI);

修复:强制最小粗糙度。

1
2
float roughness = max(perceptualRoughness, 0.045);   // ✅
float invR = rcp(roughness);

URP 的 MIN_PERCEPTUAL_ROUGHNESS = 0.045 就是用于这种场景。

sheen 与 specular 双计

如果你的 cloth shader 同时启用 sheen 和 GGX specular,会发现 metallic 值高的布料(如丝绸)边缘双重过亮——sheen 与 GGX 在掠射角同时贡献能量。修复有两种思路:

  1. 路线分离:金属布料(丝绸/缎子)禁用 sheen,仅用各向异性 GGX;非金属布料(棉麻天鹅绒)禁用 GGX,仅用 sheen。这是 The Order: 1886 的方案;
  2. 能量插值:在 sheen 强度参数上做 lerp(如 IllusionRP 的 SheenData.Sheen),保证两者总和守恒。

Wrap diffuse 的能量过冲

Filament 的 saturate((dot(n, l) + 0.5) / 2.25) 中的 2.25 = (1 + 0.5)^2 是归一化常数。如果你改了 wrap 因子(例如从 0.5 改成 0.7),必须同步改归一化常数,否则会能量过曝。

各向异性方向与切线贴图脱节

各向异性 silk 实现中,Tangent 必须与丝纤维方向对齐。常见 bug:

  • 美术给的 tangent map 是 RGB 编码(-1 → 0+1 → 1),需要 * 2 - 1 解码;
  • 切线空间转世界空间时忘记乘 TBN 矩阵;
  • 模型导出时切线方向错乱(特别是 FBX 经过多次软件转换)。

调试时可以把 tangentWS 直接输出为颜色,正常应当呈现”沿纤维方向流动的色斑”,而不是噪声。

V_Charlie 在移动端的崩溃

V_Charlie 包含 exp(CharlieL(x, r)),CharlieL 内部还有 pow(x, c)。在 Mali-G77 之前的移动 GPU,这套组合会因为 FP16 溢出而出现局部爆白。建议:

  • 移动端强制使用 V_Ashikhmin 或 V_Neubelt
  • 仅 PC / 主机用 V_Charlie;
  • 通过 #pragma multi_compile 编译期切换。

十一、本篇总结

布料 BRDF 的工业实践图谱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
布料的物理特性

├─→ Disney sheen (2012) - 简单经验项 [Sheen Lobe 路线]

├─→ Ashikhmin Velvet (2007) - 倒置高斯 NDF
│ ↓ 工程化
│ Filament Cloth (2018)
│ ↓
│ The Order: 1886 分类设计 (2013)

├─→ Charlie (2017) - 指数正弦 NDF [Fabric BRDF 路线]
│ ↓ 工程化
│ HDRP / IllusionRP / glTF-sheen

└─→ SpongeCake (2023) - 微薄片分层 BSDF(前沿)

下一篇也是最后一篇,我们关注另外两块拼图:高维次表面散射的近似(皮肤、玉石)与多次散射的能量补偿(Kulla-Conty)。


参考文献

  1. Ashikhmin, M. & Premoze, S. (2007). Distribution-based BRDFs.
  2. Neubelt, D. & Pettineo, M. (2013). Crafting a Next-Gen Material Pipeline for The Order: 1886. SIGGRAPH.
  3. Estevez, A. C. & Kulla, C. (2017). Production Friendly Microfacet Sheen BRDF. SIGGRAPH (Sony Pictures Imageworks).
  4. Knarkowicz, B. (2018). Cloth Shading. Blog.
  5. Custom fabric shader for Unreal Engine 4. SlideShare.
  6. Wang, B. et al. (2023). SpongeCake: A Layered Microflake Surface Appearance Model. SIGGRAPH.
  7. Filament 文档(Cloth Shading 章节). Filament.md.html.
  8. IllusionRP 项目. GitHub.
  9. Filament 探索(N1)—— Cloth specular BRDF 探索 | 知乎.
  10. Production Friendly Microfacet Sheen BRDF 中文翻译 | 知乎.