← ALL ARTICLES

皮肤次表面散射

全库目录221
本文目录19

标准 BRDF 假设光在表面某一点进入、同一点离开。皮肤、玉石、蜡、树叶和牛奶不满足这个假设:光穿入表层,在内部多次散射后从邻近位置射出。用纯 BRDF 渲染皮肤,结果会像塑料——明暗交界线过硬,耳廓和鼻翼缺少透光的红色。

描述这类传输的是 BSSRDF,它显式区分入射与出射的表面位置,表达光在两个位置之间的传输。实时方案的核心工作,就是在不做真正体积路径追踪的前提下近似这个空间扩散。

扩散剖面

把一束无限细的光垂直打在半无限介质上,单位入射通量在表面产生的出射通量面密度,随离入射点距离变化的函数称为扩散剖面(Diffusion Profile)。它的单位为长度的负二次方;求某个出射方向的辐射亮度还需角分布与表面透射约定。按单位面积定义的径向剖面还要乘环面积 2πr dr 才能积分能量,代码采样时必须保持这个约定。

皮肤的剖面有强烈的波长依赖:常用皮肤参数中,红色通道的有效扩散尺度更宽。光谱吸收、散射和组织厚度共同影响明暗交界的色偏与薄部位的透光颜色。因此剖面必须按 RGB 分别存储,用单通道灰度做模糊无法得到皮肤的正确色偏。

常用的解析近似是多个高斯的加权和,或 Burley 提出的归一化 Diffusion:

R(r)=A8πdr(e−r/d+e−r/(3d))R(r)=\frac{A}{8\pi d r}\left(e^{-r/d}+e^{-r/(3d)}\right)

rr 与 dd 使用相同长度单位,AA 是总漫反射反照率。dd 与平均自由程的换算还依赖反照率及所选参数化;若直接把 dd 暴露为美术参数,应明确称为剖面尺度。dd 越大,能量扩散的范围越广。

点值 R(0)R(0) 发散,但中心圆盘的能量有限。对 [r0,r1][r_0,r_1] 的圆环积分得到

W(r0,r1)=A[e−r0/d−e−r1/d4+3(e−r0/(3d)−e−r1/(3d))4].W(r_0,r_1)=A\left[\frac{e^{-r_0/d}-e^{-r_1/d}}4+ \frac{3(e^{-r_0/(3d)}-e^{-r_1/(3d)})}4\right].

取 r0=0,r1→∞r_0=0,r_1\to\infty 得 W=AW=A。生成离散核时可用采样区间的积分权重处理中心,避免直接读取奇点。RGB 三通道分别采用自己的 AA 与 dd,核权重按约定保存总反照率或归一化到 1,后续材质乘法要与之匹配。

引擎可能分别暴露反照率、散射距离与剖面编号,也可能把 RGB 距离封装为散射颜色。参数名称不能替代定义:需确认它改变的是总反射能量 AA、扩散尺度 dd,还是两者的拟合关系。

三类实时方案

方案求解域主要成本适用
预积分查表逐像素、无邻域一次纹理采样移动端、大量角色
屏幕空间卷积屏幕邻域邻域卷积通道,常分多趟主机与 PC 主角
纹理空间扩散UV 空间每角色一张 RT影视级近景

三者不互斥。移动端项目常只用预积分;高端项目常以预积分打底、屏幕空间卷积补足邻域扩散。

预积分次表面散射

预积分用局部曲率近似形状,把固定剖面在该形状上的光照扩散预先积分为查找表。运行时只需局部查询,成本主要来自纹理读取;屏幕卷积的成本还随采样数和角色覆盖率变化。

表的两个维度

查找表以 N⋅LN\cdot L 为横轴、曲率为纵轴。

横轴用 N⋅LN\cdot L 而非 saturate(N·L),必须保留负值区间——次表面散射最显著的视觉特征恰恰是光越过几何明暗界线、渗入本应全黑的暗面。把负值裁掉,这个效果就消失了。实际映射常写成 u=N⋅L⋅0.5+0.5u=N\cdot L\cdot 0.5+0.5,把 [−1,1][-1,1] 铺满 [0,1][0,1]。

纵轴是表面曲率。曲率大(鼻尖、指节)时,相同的物理扩散距离覆盖更大的法线变化范围,散射看起来更强;曲率小(额头、脸颊)时散射表现平缓。曲率可以从世界空间位置与法线的屏幕导数估算,也可以离线烘焙成贴图——后者稳定得多,因为屏幕导数在边缘和 Mipmap 过渡处噪声明显。

局部圆弧上的查表生成

下面给出一份能直接复算的简化模型。假设表面沿光照变化方向可近似为曲率恒定的圆弧,以弧长 ss 表示邻近位置,法线相对中心旋转 κs\kappa s。扩散使用标准差为 σ\sigma 的一维高斯核,并忽略遮挡、表面反照率变化及垂直于这条截面的扩散。它用于展示预积分过程;前面的二维 Burley 剖面和完整 BSSRDF 需要各自的积分与拟合。

令 μ=N⋅L\mu=N\cdot L、u=s/σu=s/\sigma、q=κσq=\kappa\sigma。在法线与光线确定的截面内,邻近法线与光线的点积为

N(u)⋅L=μcos⁡(qu)+1−μ2sin⁡(qu).N(u)\cdot L=\mu\cos(qu)+\sqrt{1-\mu^2}\sin(qu).

把该处的余弦照明与高斯核积分,得到无量纲的查表值

S(μ,q)=∫−∞∞e−u2/22πmax⁡ ⁣(μcos⁡(qu)+1−μ2sin⁡(qu),0)du.S(\mu,q)=\int_{-\infty}^{\infty} \frac{e^{-u^2/2}}{\sqrt{2\pi}} \max\!\left(\mu\cos(qu)+\sqrt{1-\mu^2}\sin(qu),0\right)du.

SS 保存单位光照下的归一化漫反射形状。运行时另乘光照强度与约定的漫反射颜色,避免把反照率重复计入。实际生成表时,将高斯截断到 [−4,4][-4,4],对剩余权重归一化,并用中点法积分:

python
# mu ∈ [-1,1],q ≥ 0,samples 为正整数;完整输入检查见参考文件。
transverse = math.sqrt(max(0, 1 - mu * mu))
accumulated = weight_sum = 0.0
for i in range(samples):
    u = -4 + 8 * (i + 0.5) / samples
    weight = math.exp(-0.5 * u * u)
    cosine = mu * math.cos(q * u) + transverse * math.sin(q * u)
    accumulated += weight * max(cosine, 0)
    weight_sum += weight
value = accumulated / weight_sum

完整 Python 3 参考与输入边界见 preintegrated_diffuse。生成 RGB 表时,可固定三个通道的相对尺度 aca_c,以参考尺度 σref\sigma_{ref} 构造纵轴 q=κσrefq=\kappa\sigma_{ref},分别计算 S(μ,acq)S(\mu,a_cq)。下方采样片段的 _ProfileDistance 就对应这份表的 σref\sigma_{ref};它与 Burley 参数 dd 的关系需要另外拟合。将模型用在曲率变化很快、凹面自遮挡或薄片上时,圆弧和一维卷积假设都可能失效,应限制表的覆盖范围并与目标形状比较。

q=0q=0 时法线不随位置变化,结果恰好为 max⁡(μ,0)\max(\mu,0);μ=0\mu=0 且 q>0q>0 时,受光邻域会向明暗界线贡献正值。正面入射 μ=1\mu=1、q=0.2q=0.2 时,截断区间内余弦始终为正,积分接近完整高斯的解析值 e−q2/2≈0.98020e^{-q^2/2}\approx0.98020,截断误差低于 10−410^{-4}。这些边界能发现曲率单位、核归一化或余弦被重复乘入的问题。

采样与法线分层

另一个关键手法是对法线分层。在此近似中,高频法线继续驱动表面高光,而散射采用平滑法线,避免把已由扩散核滤掉的细节再次写入漫反射。做法是准备一条从几何法线到完整法线贴图法线的插值,用较平滑的那条算散射项:

hlsl
// curvatureTex 为离线烘焙的曲率,paramTex.x 为逐像素散射强度遮罩
float3 sssNormal = normalize(lerp(normalWS, geomNormalWS, _NormalSmoothFactor));
float  NoL       = dot(sssNormal, lightDirWS);

float2 lutUV;
float2 parameter = float2(NoL * 0.5 + 0.5,
    curvature * _ProfileDistance / _MaxDimensionlessCurvature);
lutUV = (saturate(parameter) * (_LutSize - 1.0) + 0.5) / _LutSize;

float3 sssDiffuse = SAMPLE_TEXTURE2D(_PreintegratedLUT, sampler_LUT, lutUV).rgb;

这是 Unity 风格 HLSL 采样片段,宿主提供纹理、采样器及宏。曲率单位为 1/m,_ProfileDistance 为 m,两者乘积为无量纲曲率;_MaxDimensionlessCurvature 为烘焙表覆盖的正上限。此处表的参数端点分别存于首尾纹素中心,因此使用按尺寸缩放和偏移的地址。烘焙表纵轴朝向或参数化不同,运行时也必须相应修改。查找表必须按线性数据导入,不能标记 sRGB,否则色偏会被二次编码。表本身分辨率不必高,可以先以 64×32 作为预算起点,再比较参考误差;压缩可能在梯度上产生色带,格式由误差与平台测试决定。

验证方法是让方向光绕头部水平旋转一周:明暗交界处应始终有一条柔和的红色过渡带,并随曲率变化宽窄。交界处出现硬边说明 N⋅LN\cdot L 被提前 saturate;红色过渡带宽度处处相同说明曲率维度没有真正参与采样。

边界与代价

预积分是逐像素的,不读取邻域,因此无法表达真正的空间扩散:

  • 阴影边缘不会变软。Shadow Map 投下的硬边仍然是硬边,因为散射发生在 N⋅LN\cdot L 项内部,而不在可见性项上;
  • 薄部位透光(耳朵背光时透出红色)需要单独的 Transmission 项,通常用厚度图配合背面 N⋅LN\cdot L 求解;
  • 纹理细节不会被散射模糊,毛孔和痘印仍然锐利。

查表只增加少量局部读取,适合受限预算。移动端也能按目标 GPU 和角色覆盖采用简化屏幕卷积,选择仍由画质和成本决定。

屏幕空间次表面散射

屏幕空间方案直接在图像域做扩散:先把皮肤区域的漫反射结果单独渲出,再按扩散剖面做一次加权模糊,最后叠加回主画面。它能解决预积分做不到的邻域问题——阴影边缘会真正变软,纹理细节会被恰当柔化。

可分离近似

二维卷积代价是 O(n2)O(n^2)。Separable SSS 的做法是把二维核近似成横向与纵向两次一维卷积,代价降到 O(n)O(n)。严格来说高斯和的二维核不完全可分离,但在皮肤这类内容上的误差可以接受。

每个高斯本身可分离,高斯和通常需要逐项执行两趟并相加;只用一对横纵核近似整个剖面会引入误差。核权重按 RGB 分别归一化,中心值由尺度、抽样位置和拟合决定。论文的采样方案、早期公开演示代码和引擎实现可能使用不同核,固定一组中心权重不能代表所有版本。

离散核的一次求值

先用三点、对称、各通道和为1的教学核观察颜色怎样传播:红通道权重(0.25,0.5,0.25),绿通道(0.15,0.7,0.15),蓝通道(0.05,0.9,0.05)。输入三点均为灰色,强度从左到右为(1,0,0),中心输出为(0.25,0.15,0.05),因此暗侧获得偏红贡献。这组数只展示不同通道扩散宽度,不代表拟合后的皮肤剖面。

常量输入(1,1,1)应保持不变,因为每通道核和为1。若左邻为衣物而被丢弃,各通道需要除以各自剩余权重0.75、0.85、0.95;若采用中心颜色替代无效邻居,则保持原核和。两种边界策略具有不同的截断行为,均不能读取衣物颜色后只靠输出模板消除污染。

核在二维的构造仍需匹配模型。单个高斯可先横向后纵向;多个不同宽度高斯的加权和应分别卷积再相加,或使用论文拟合的两趟近似,直接把所有一维核相加后做两趟会产生额外的交叉项。作者说明区分了高斯和与两趟近似的成本。

深度感知的采样半径

扩散半径由物理距离决定,但卷积在屏幕空间进行,因此像素半径必须随深度缩放:同一块皮肤在近景占很多像素、远景只占几个,用固定像素半径会让远处的脸糊成一团。

hlsl
// viewDepthMeters 为正视空间深度,radiusMeters 为剖面采样半径。
float focalPixels = targetHeight / (2.0 * tan(verticalFovRadians * 0.5));
float radiusPixels = radiusMeters * focalPixels / viewDepthMeters;
float2 stepUV = radiusPixels / float2(targetWidth, targetHeight)
              * blurDirection;

blurDirection 在横向 Pass 取 (1,0)(1,0)、纵向取 (0,1)(0,1)。这是透视投影、方形像素、正深度且局部平行于像平面的近似;正交相机使用固定世界到像素比例。90° 垂直视场、1080 像素高度下,1 m 深度的 1 cm 半径对应 5.4 像素,深度翻倍后变为 2.7 像素。

边界泄漏

屏幕空间方法最大的问题是采样会越过物体边界,把背景或衣物的颜色吸进皮肤。两道防线:

其一,用模板或分类遮罩标记皮肤区域,限制哪些输出像素执行卷积。邻域采样仍可能读到非皮肤数据,因此每个输入样本也要检查分类与深度。限制输出范围可以减少无关区域的卷积工作。

其二,逐抽样比较深度,深度差超过阈值就降低该抽样的权重,退回中心颜色:

hlsl
float depthDelta = abs(sampleLinearDepth - centerLinearDepth);
float weight = saturate(depthDelta * _DepthFalloff);
sampleColor = lerp(sampleColor, centerColor, weight);

若把无效样本替换成中心颜色,归一化权重总和保持不变;若直接丢弃样本,则需重新归一化有效权重。颜色差异过滤会改变亮暗交界的真实扩散,应作为单独近似评估。

管线集成

一类 HDRP 实现先保存次表面材质的漫反射照明,并用模板标记区域。后续计算通道过滤这部分照明,再将结果与主颜色中的其他着色项合成。具体过滤是否可分离、资源格式和合成混合状态,应核对目标 HDRP 包源码。

如果主颜色预先排除了待散射的漫反射,可以把过滤结果加回去;如果主颜色已有这部分,则先替换或减去原项再合成,避免双算。高光等非散射项单独保留。光栅全屏通道可以用模板 Equal 限定输出;计算着色器没有光栅模板测试,需显式读取分类数据或由有效工作列表调度。项目还需统一模板位分配,避免与描边和其他遮罩冲突。

纹理空间扩散

屏幕卷积只能处理当前可见表面,UV空间则给角色表面分配一张照明纹理。先把网格按UV渲染到这张纹理,保存待散射的线性漫反射照明、有效覆盖及必要的尺度数据,再在UV域扩散,最后在正常相机着色时用同一UV读取结果。高光单独求值,避免将表面镜面也模糊。每个角色的照明纹理需要更新与内存预算;共享UV的重叠面不能保存不同表面的照明。

UV距离不是物理距离。假设水平一单位UV对应0.2米、纹理宽1024,1厘米的扩散半径约为51.2纹素;另一块图表一单位UV对应1米,同样半径只有10.24纹素。使用固定51纹素核会让第二块表面的物理扩散约增大五倍。一般情况下,表面位置P(u,v)的两个偏导决定局部度量:沿偏移(du,dv)的物理长度平方为|Pu*du+Pv*dv|²。UV拉伸、剪切与密度变化因此需要烘焙尺度/度量、限制图表变形,或接受明确近似。

UV缝又把相邻表面分成图上不相邻的区域。普通Padding只能延伸边缘颜色,不能自动把另一侧真实邻域带入扩散。可为图表边界建立跨缝对应并复制足够宽的邻域,再按同一物理尺度过滤;覆盖宽度还须容纳最大核和Mip足迹。若没有跨缝数据,核被截断时重新归一化可保持常量颜色,但会减少跨缝传输,接缝附近与内部仍可能不同。相反,直接模糊整张图集会把无关岛的照明混进皮肤。

验收用一条斜光明暗边跨越UV缝,再改变图表缩放。物理扩散宽度应保持一致,常量输入应保持常量;若过缝突然变窄,先查邻域缺失,若同一物理部位随UV缩放而变宽,先查度量换算。这条路径依赖表面参数化,不能凭UV卷积处理表面之间穿过内部的全部传输。

薄组织透射的简化模型

耳廓背光还包含穿过组织的传输,可在已声明的美术模型中单独近似。设有效厚度t以米计,RGB衰减系数sigma以每米计,使用exp(-sigma*t)衰减背光,再乘背面方向权重max(dot(-N,L),0)、遮罩与光源贡献。这里的N是朝外单位法线、L从表面指向光源;厚度应描述采用方向或固定测量方向下的有效路径,不能把任意距离图直接当作完整光路长度。

例如t=0.002米、sigma=(100,300,600)/米,透射比例约为(0.819,0.549,0.301),红通道衰减较少。厚度翻倍时,每通道比例变成原比例的平方;t=0得到1,这是模型边界,需要遮罩或最小有效厚度避免无组织区域发亮。该指数只描述给定有效路径的衰减,不包含内部多次散射、入射折射或表面Fresnel,因此参数不能直接冒充测得的组织散射系数。

透射项与正面扩散属于不同近似路径,但总能量仍需一起检查,不能把两个满能量结果无约束相加。用厚度阶梯、背面光与正面光分别显示透射贡献;只有背光变化时出现的红色不能证明完整BSSRDF已经实现。上述纹理空间与透射为明确的教学数据流,尚未在目标引擎执行。

皮肤的其他分层

完整的皮肤材质不只有散射。常见分层:

双层高光。皮肤表面有油脂层与角质层两重反射。以权重 ww 和 1−w1-w 混合两个粗糙度波瓣,可同时表达集中与宽阔的高光。若按两层介质处理,还需考虑上层透射对下层的衰减,不能把两个完整能量的波瓣直接相加。

偏红的 AO。皮肤内部的多次散射让凹陷处并非中性变暗,而是偏向红色。把 AO 颜色偏向红色是可用的美术近似;它不能代替对内部传输、光谱吸收与遮挡的求解,也不应被当作所有皮肤的物理规律。

薄部位的独立透射项按前面的“薄组织透射的简化模型”求值,与扩散和高光一起检查总贡献。

眼睛与嘴唇。它们的散射特性与皮肤不同,通常需要独立材质与独立参数,不应复用同一套剖面。

验证方法

  • 主光绕头部旋转一周,检查明暗交界的红色过渡带是否连续、是否随曲率变化。
  • 关闭散射项对比,交界处应从硬边变为柔和过渡;若无差别,说明散射结果没有真正参与合成。
  • 背光检查耳廓与鼻翼是否透光,本文由单独的 Transmission 近似表示这条穿过薄组织的传输路径。
  • 在皮肤与衣物、皮肤与背景的边界检查是否有颜色泄漏。
  • 把相机从近景拉到远景,确认屏幕空间卷积半径随深度收缩,远处的脸不应糊化。
  • 用帧捕获检查光栅卷积的模板测试,或计算卷积的分类读取与有效工作列表,确认输出与邻域样本都满足皮肤区域约束。
  • 单独输出查找表采样结果,确认 UV 两个维度都在有效范围内变化。

本机机制检查:光照模型测试 检查圆环能量可加性、中心区间、3 个尺度和投影半径随深度变化,以及预积分的平面极限、明暗界线扩散、正面入射解析值和采样收敛;既有检查对3个扩散尺度按环面积数值积分,归一化剖面总能量接近1。检查代码见 reference.py,运行 python 知识库/examples/mechanisms/reference.py。compile_lighting.py 为三个 HLSL 片段补齐最小宿主并完成离线编译。CPU 与编译输出见 核验记录。引擎画面、GPU 捕获与性能另外验证。

相关主题

参考资料