几种sss 总结
2025-11-10
| 2025-11-12
Words 1516Read Time 4 min
type
status
date
slug
summary
tags
category
icon
password
依次从消耗最低的到最高的

cheap sss

这个效果正面是没有的 只在背面有 呈现就是背面的 一种边缘光效果所以就是
pow( normalize( dot(v,一个反向的虚拟光源)) , 次方数 ) * scale
负的 N和L中间的向量 就是自己定义的一盏灯 要normalize,那么怎么表示呢?
  • (N*一个数字+L) 这样性能好 原理就是平行四边形法则
lerp(N,-L,插值数字) 这样直观
最后的公式就是:
反向虚拟光 = lerp(N,-L,插值数字);
sss = pow( normalize( dot(v,一个反向的虚拟光源)) , 次方数 ) * scale
notion image
这个消耗很小就是了 就是没有肯定效果差 也没有厚薄的差别

(背光扰动 + 扩散) * 厚度系数

背光 + 扰动

本质上就是要实现个一个假背光 在正面我们只要一个简单的兰伯特 dot(N,L)就可以了
但是在背面 我们要考虑 V,N,l 三个
dot(V,一个虚拟的背部光源)
找个背部光源一般用 -(L+N)
dot(v , -(L + 一个系数*N))
(注意这个L+N 可以不用normalize 当然从理论上是需要的 但是其实结果效果影响不大 加个normalize 影响性能)
这个系数主要是为了控制这个明暗的强度

加背光扩散

其实就是对上面结果再加一个pow
float 背光扰动 = saturate(dot(v , -(L + 一个系数*N)) );
float 背光加扩散 = pow(背光扰动, e) * s;

效果增强:局部厚度

物理正确方法

其实就是加一个 厚度图 的scale 就行了
但是光的传播是有指数衰减效应的 所以要exp
厚度scale = exp(-贴图读取的厚度 * attenuation材质的衰减系数)
问题一: 为什么是 贴图读取的厚度 要负号?
因为:遵循物理规则 厚度越大 衰减越大 系数要越小
比如 当exp(0) =1 exp(-2)=0.135
notion image

某管线用的正向散射 和 反向散射的融合

这算是移动端工业级了,但是把。怎么说呢,还是要考虑性能因素。
使用的是也是一个比较便宜的算法。
只模拟次表面散射最关键的两个视觉特征:
  1. InScatter:捕捉 “直射穿透的集中高亮”(比如光线正对视线时的亮斑);
  1. BackScatter:捕捉 “内部散射的柔和发光”(比如侧光下的均匀透光)。
通过厚度统一控制两者的强度,再用lerp让它们根据视角自动切换主导权。
我们来看具体算法:

数据准备:

half3 SubsurfaceColor = GBuffer.CustomData.rgb;
float Opacity = GBuffer.CustomData.a;
float3 H = normalize(V+L);

正向散射InScatter:

half InScatter = pow(saturate(dot(L, -V)), 12.0) * lerp(3.0, 0.1f, Opacity);
首先:
dot(L, -V):判断 “光线是否正对视线
notion image
之后再pow 缩小范围
再乘以厚度系数,这里是 lerp(3.0, 0.1f, Opacity); 这很明显是比较经验的
可能的改进?
如果把厚度系数 改成 = 基础值 * exp(-厚度 * 衰减系数)。也许是一种改进方向
但是重点是衰减系数了。还需要给不同的材质不一样的衰减系数。那么还需要一个专门的通道。。。。干。

反向散射BackScatter

模拟 “内部散射的柔和光”(核心是 “均匀性”)
公式链:
NormalContribution = saturate(Context.NoH * Opacity + 1.0 - Opacity)
BackScatter = NormalContribution * INV_PI / 2.0h * GBuffer.GBufferAO
先看NormalContribution:
NoH就是bphone高光用的,描述的都是 “法线是否朝向光源与视线的中间方向”,方向越一致,值越大;
当Opacity为1的时候,即比较厚的时候,呈现的视线随动效果应该和bphone是一样的
当Opacity趋近0的时候,即非常薄的时候,NormalContribution趋近为数值1。那么光照不依赖法线
和高光的不同:
高光会对Noh进行高次pow。而这里接下会✖ INV_PI / 2.0h 乘以 GBuffer.GBufferAO
表现的会非常柔和。
为什么要✖ INV_PI / 2.0h ?
因为:
INV_PI是 1/π,用于漫反射能量守恒(次表面散射本质是 “体积漫反射”,需遵循能量守恒);
除以 2.0 是经验性衰减,避免 BackScatter 强度过高,与 InScatter 失衡。

融合

最终颜色 =
lerp(BackScatter, 1.0h, InScatter) * SubsurfaceColor * PI
  1. lerp(BackScatter, 1.0h, InScatter):让两种散射 “无缝过渡”
lerp(a, b, t)的逻辑:当t(InScatter)小时,结果接近a(BackScatter 的柔和光);当t大时,结果接近b(1.0,最大强度)。
为什么用 1.0h?
当 InScatter 很强(直射穿透明显),说明此时是 “强光穿透” 场景(如手电筒直射),需要忽略柔和的 BackScatter,让颜色直接 “满强度输出”(模拟 “过曝” 的透光感),避免两种效果叠加导致过亮。能量控制:lerp确保结果始终≤1.0,避免能量溢出(符合 “能量守恒” 的简化)。
  1. 乘以SubsurfaceColor * PI:赋予颜色并提升亮度
SubsurfaceColor:给散射光染上材质特有的颜色(如皮肤的红色、玉石的绿色);
乘以PI:抵消之前INV_PI的衰减(INV_PI * PI = 1),让最终亮度回到视觉舒适的范围(经验性调整,避免颜色过暗)
notion image

待补充-pc高性能 屏幕空间模糊 sss

  • 图形学
  • 渲染
  • 丝绸各向异性 - 十字高光双 Lobe 算法实际项目中的 次表面BXDF
    Loading...