type
status
date
slug
summary
tags
category
icon
password
1.Gbuffer设计:

可以看到厚度存放在 CustomData.a
而厚度数据在base pass阶段是这样的:
GBuffer.CustomData.w = clamp(MaterialInputs.color_mask.y, 0.0h, 1.0h);
所有厚度的传递是:
MaterialInputs.color_mask.y 到 GBuffer.CustomData.w
核心问题(重要):
这个BXDF的问题挺多:
1.没有强度控制 SSScolor 在进入Gbuffer前会被clamp到0-1,这很显然无法进行强度控制了。其实明明可以多一个槽位给强度的,但是为啥没有呢?
2.对于 sss 最重要的。Lighting.Transmission的BXDF阶段, L显然都是太阳光,那用这个shadingmodel的材质,很显然就只能表现视角和太阳光L的 次表面散射 的效果。但是实际上,诸如物体内部的发光体造成的投射,就无法实现(你需要物体内部发光体的 向量L)
使用这个shading moderl 的材质只能表现和太阳光的sss关系 就很无语呢。 但是也能理解,通用。
材质输入阶段:
主要是需要一个厚度图,其他没啥
越白越厚。黑的话全透光。
2.平行光 光照计算
2.1相关代码:
.png?table=block&id=2a8a1847-3e71-80ee-b740-e34a62dbad16&t=2a8a1847-3e71-80ee-b740-e34a62dbad16)
2.2计算实现:
首先直接光的漫反射和高光用的都是 通用光照的BXDF
所以直接调用DefaultLitBxDF 就行了
那么重点就是Lighting.Transmission的计算了
2.2.1透射光计算:
首先复习一下:
NX使用的是也是一个比较便宜的算法。
只模拟次表面散射最关键的两个视觉特征:
- InScatter:捕捉 “直射穿透的集中高亮”(比如光线正对视线时的亮斑);
- 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):判断 “光线是否正对视线
.png?table=block&id=2a8a1847-3e71-8083-bdf4-d64f738dc013&t=2a8a1847-3e71-8083-bdf4-d64f738dc013)
之后再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
- lerp(BackScatter, 1.0h, InScatter):让两种散射 “无缝过渡”
lerp(a, b, t)的逻辑:当t(InScatter)小时,结果接近a(BackScatter 的柔和光);当t大时,结果接近b(1.0,最大强度)。
为什么用 1.0h?
当 InScatter 很强(直射穿透明显),说明此时是 “强光穿透” 场景(如手电筒直射),需要忽略柔和的 BackScatter,让颜色直接 “满强度输出”(模拟 “过曝” 的透光感),避免两种效果叠加导致过亮。能量控制:lerp确保结果始终≤1.0,避免能量溢出(符合 “能量守恒” 的简化)。
- 乘以SubsurfaceColor * PI:赋予颜色并提升亮度
SubsurfaceColor:给散射光染上材质特有的颜色(如皮肤的红色、玉石的绿色);
乘以PI:抵消之前INV_PI的衰减(INV_PI * PI = 1),让最终亮度回到视觉舒适的范围(经验性调整,避免颜色过暗)