GPU Gems 1 · Chapter 16. Real-Time Approximations to Subsurface Scattering

用 wrap lighting、光线穿透厚度估计与纹理空间 diffusion 近似皮肤、大理石等半透明材质的次表面散射外观。

学习目标

  • 能从 entry、内部散射、exit 三个阶段解释皮肤和大理石的柔化、邻域 bleed 与偏红阴影过渡
  • 能实现 wrap lighting 和 LUT 近似,并说明它没有显式模拟材料内部的真实传播
  • 能用 light depth map 的 d_o − d_i 估计穿透距离,并识别凸物体、深度精度和投影边缘限制
  • 能把模型展开到唯一 UV 的纹理空间,用低分辨率 light map 和 separable blur 近似 diffusion

大多数实时 shading 只在表面交点处理光:法线、光向量和材质颜色决定当前像素。但皮肤、蜡、大理石等材料会让光从一个表面点进入,在内部散射和吸收,再从另一个点离开。GPU Gems 1 第 16 章不追求完整的物理求解,而是把这种外观拆成几种可编程硬件能够承受的近似。

为什么次表面散射看起来不一样

的三个外观线索是:整体光照对比被柔化,局部光会 bleed 到相邻区域,细薄区域和明暗交界会出现更明显的颜色变化。皮肤的血液和组织会让背光过渡略偏红;耳朵、鼻翼等薄处尤其明显。

light enters → scatters → exits elsewhereentrylight entersscatterbleed + absorbexitdifferent pointshadesoft + tintthin regions amplify the effect: ears, nostrils, shadow transitions
与只在表面计算的 diffuse 不同,次表面散射让 entry 与 exit 不必是同一点;光传播距离越长,扩散和吸收越明显。

光传播越远,能量越被 attenuation 和 diffusion;这意味着“厚度”既影响亮度,也影响颜色和边缘宽度。实时近似的关键不是制造一张泛白的图,而是让传播距离、方向和材质颜色至少有一个可解释的对应关系。

1. 低成本的 wrap lighting

修改 dot(L, N) 的映射:标准 Lambert 在点积为负时直接归零,wrap 则使用一个 0 到 1 的 wrap 参数把曲线向背光侧延伸。

float diffuse = max(0, dot(L, N));
float wrapDiffuse = max(0, (dot(L, N) + wrap) / (1 + wrap));

wrap lighting 不能告诉我们光从哪个点进入、在内部走了多远,也不能处理真实的多次散射;它只是降低 diffuse 对比的 cheap approximation。章节还建议把点积映射编码进 1D 或 2D LUT:纹理可以同时加入过渡区的红色 tint、diffuse color 和 specular power。

wrap lighting:让 diffuse 在背光侧渐进Lambert max(0, dot(L,N))wrap (dot + w) / (1 + w)litbacklitw controls wrap width; LUT can add red transition tint
wrap lighting 是便宜的视觉近似:它不模拟真实体积传播,却能减少 diffuse 的硬截止,并把颜色 tint 放到光暗过渡处。

LUT 的优势是把多个经验曲线合成一次 texture lookup;代价是需要预先定义参数范围,并注意 dot(N,L)dot(N,H) 的归一化。适合先做视觉风格探索,再决定是否需要更高成本的厚度或纹理空间方案。

2. 用 depth map 估计穿透距离

是比 wrap 更具方向性的近似。第一遍从光源视点渲染,把进入表面的距离存入 light depth texture;正常 shading 时,把当前点投影回这张纹理得到 d_i,再计算当前点到光源的距离 d_o。

s=dodis = d_o - d_i

可以把 s 直接查到 artist-created 1D scatter texture,也可以使用指数吸收:

float s = trace(point, lightTexMatrix, lightMatrix, lightDepthTex);
float transmission = exp(-s * sigma_t);
return transmission * lightColor;
penetration depth: s = d_o − d_ientry d_iexit d_orender + projects = d_o − d_iabsorption ∼ exp(−s · sigma_t)thicker → dimmer / more diffuseddepth texture follows incoming light direction
depth-map 方法把体积厚度转成两个表面距离之差;它依赖凸物体近似,孔洞和复杂内部结构需要 depth peeling 或更多层。

这种方法把入射方向保留在 depth map 里,比静态 painted thickness 更适合动画模型;但单层深度只记录一个可见表面,孔洞和复杂内部结构会被错误解释。解决方向包括 depth peeling、额外的 front/back layers,或接受它作为 convex approximation。

深度精度同样是质量预算:旧硬件的 depth texture 读取可能只提供很少的有效位,需要 floating-point texture 或把 32-bit float pack 到 RGBA8。浮点纹理又可能不支持 filtering,放大投影时会出现 block artifacts;投影边缘还可能把背景采样到物体边缘,因此 depth pass 常会沿顶点法线做轻微 grow。

如果 shader 还需要入射点的 normal 或 surface color,可以把它们作为额外 render target 写出;支持 multiple render targets 的路径可以把 depth、normal 和其他信息合并到同一 pass。

3. 从单次散射到 diffusion

用 phase function 描述方向分布,并可在入射、离开处考虑 Fresnel。它能表达更具体的路径,但步进成本和参数复杂度会快速上升。

更适合皮肤这类高度散射介质;本章把它作为更复杂的模型方向,并把可实时实现的重点放在 wrap、depth map 和 texture-space diffusion 上。

这些模型的边界应写进产品决策:若目标只是让角色在实时光照中不显得像塑料,wrap + LUT 可能已足够;若需要方向性、厚度和薄处的透射,应加入 depth map;若需要稳定的邻域柔化且拥有良好 UV,则 texture space 更可控。

4. texture-space diffusion

将 3D 表面参数化为二维图像:顶点 shader 把 UV 的 0 到 1 映射到 NDC,先在纹理空间渲染 diffuse lighting,再对 light map 做 blur,最后像普通纹理一样应用回 3D 模型。

它的优势是 shading 复杂度由屏幕分辨率解耦到纹理 texel;很多 image-processing 操作在 2D 上更高效。若 UV 参数化相对均匀,纹理空间邻近可以近似世界空间邻近。低分辨率 light map 本身也会提供一些平滑效果。

texture-space diffusion3D meshnormal lightingUV unwraptexel = surfaceseparable blurx then yreapplydiffused lightlow-res texel lighting + blur ≈ soft transport
texture-space diffusion 把着色复杂度从屏幕像素解耦到纹理 texel;它需要良好且不重叠的 UV,并能利用 separable filter 与 bilinear hardware。

blur 应拆成 horizontal 与 vertical 两个 pass,利用 separable filter 和 bilinear filtering 减少采样。皮肤的颜色传播还可以对 RGB 使用不同权重:红色通道用更宽的 kernel,绿色和蓝色更窄,以模拟波长依赖的吸收和散射。

half4 diffusion(float2 uv, sampler2D lightTex) {
  half4 sum = 0;
  sum += tex2D(lightTex, uv + float2(-5.5, 0)) * weight0;
  sum += tex2D(lightTex, uv + float2(-1.5, 0)) * weight1;
  sum += tex2D(lightTex, uv) * weight2;
  sum += tex2D(lightTex, uv + float2(1.5, 0)) * weight3;
  sum += tex2D(lightTex, uv + float2(5.5, 0)) * weight4;
  return sum;
}

如果把 shadow 也写进 light map,blur 会自然地产生软阴影;但最终材质仍应把 diffused lighting 与原始高分辨率 color map 合成,避免把所有纹理细节都抹掉。对于内部密度变化明显的物体,表面 UV 方法仍然不如体积渲染准确。

5. 动手实验:分辨 wrap、厚度与纹理空间

先预测:把 material thickness 拉高时,transmission 会如何变化?把 scatter width 拉宽时,邻域 bleed 是否增强?把 mode 从 texture 切到 wrap,哪些 entry/exit 信息会消失?最后提高 red diffusion,观察阴影边缘的色相而非只有亮度。

Subsurface Scattering Lab

先猜:厚度增加时,亮度和红色 bleed 会怎样?

可交互
texture-space diffusionbleed 0.51entryexitabsorption 0.38 · red-channel shift 0.43mode changes the approximation, not the need for believable art

观察:厚度增加会增强吸收;较宽的散射会模糊邻域;红色通道更宽时,阴影过渡更偏红。wrap 便宜但不记录真实 entry/exit。

实验中的 entry 与 exit 用来提醒你:wrap 只改照明曲线,depth 估计 d_o - d_i,texture mode 则将 light map 在 UV 空间扩散。四个滑杆不是物理参数求解器,而是把“厚度、传播宽度、颜色通道差异和近似模式”拆开观察。

小结:用最小模型换取可信外观

  • 次表面散射让光从 entry 点进入、在材料内部传播后从另一个 exit 点离开,形成柔化、bleed、吸收和偏红过渡。
  • wrap lighting 便宜且适合 LUT 风格控制,但不表达真实穿透距离。
  • depth map 通过 s = d_o - d_i 估计光程,需面对凸物体、深度精度、过滤和投影边缘限制。
  • single scattering 和 diffusion approximation 是更复杂的路径模型;texture-space diffusion 用 UV light map 与 separable blur 近似邻域传播。
  • 唯一且均匀的 UV、合理的低分辨率、分通道 blur 权重和最终与原始 color map 合成,是可发布实现的质量护栏。

练习

问题 1|改 Demo 代码 将 wrap 参数从 0.2 提高到 0.8,同时保持原始 diffuse 和 LUT 的 specular alpha 不变。请说明你会观察哪些视觉变化,以及为什么不能把结果称为真实 subsurface scattering。

问题 2|推导题 当前点到光源的离开距离 d_o 为 3.4,light depth texture 中的进入距离 d_i 为 2.6,吸收系数 sigma_t 为 1.2。计算 penetration depth 和指数 transmission,并说明厚度增加的趋势。

问题 3|独立实现题 为一张带动画的人脸选择方案:只要求柔和光照时使用什么;需要薄耳朵透射时增加什么;要做稳定的邻域 diffusion 时还要验证什么?

名词解释

本章出现的专业名词,用大白话再讲一遍。

subsurface scattering
光在材料内部散射、吸收并从不同表面点离开的次表面光传输。
wrap lighting
将 diffuse 明暗曲线向背光侧延伸的低成本光照近似。
penetration depth
由离开距离减去进入距离得到的材料内光程估计。
single scattering
假设光只发生一次内部散射的近似模型。
diffusion approximation
用扩散过程近似强散射介质多次散射累积效果的模型。
texture-space diffusion
在唯一 UV 的二维 light map 上计算、模糊并重新应用光照的扩散方法。

资料与写作方式声明

本章以GPU Gems 1 · Chapter 16. Real-Time Approximations to Subsurface Scattering公开完整正文核定章节范围、事实坐标与时代语境,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…