环境
目的
思路
次表面散射原理
背光
背光强度扰动
背光扩散
Translucent shader 伪代码示例
- 添加采样厚度图 伪代码
- 考虑上 shadow map 距离的 伪代码
- 多光源下的 SSS
- BRP 多光源效果
- URP 多光源效果
- SP 中贴图导出设置
- 用途
- Project
- 其他方式 - RampTexture Or LUT
- References
环境
unity : 2018.2.11f1, 2020.3.18f1 pipeline : BRP, URP
目的
备份,备忘
思路
一般次散射效果都是使用 光线追踪 来渲染的话,还原度会很高(也不是100精准,都是模拟,毕竟物质内的如何让光子偏移方向散射出来,这不是现代计算机,现代渲染架构能支撑得起的)
所以这个 假 次散射,相对 实时渲染效果的价值还是有的
具体思路:
- 导出模型的厚度图
- 将片段(像素)中的 观察位置方向 和 光源入射方向 做 dot
- 然后控制 dot 后的 pow, scale 来控制边缘,和 整体亮度
- 叠加到之前的颜色着色上即可
次表面散射原理
次表面散射我就 简称为:次散射
- 次散射:SubSurface Scattering,简写:SSS
- 次散射不是透明(两者没有什么关系,次散射是透光的,只不过吸收,直射,物质内部漫反射比较特殊)
- 次散射不是透射(准确的说是,还是透射的,没透射光线,你怎么看到光照效果?只不过先经过了介质内部的 漫反射,吸收,折射后,光波 波长,波频都可能发生了变化)
- 次散射可以使用光线追踪实现,但是比较计算量比较大
- 实时游戏中更多的是对次散射“快速模拟”
快速的模拟当中,可以简单概括:SSS 包含:背光、扰动、扩散
背光
根据我们的 lambert 可以得到正面光照的 diffuse 效果 lambert = dot(L, N);,这是基于 表面(N)受正面光照的强度
那么要相对于我们的眼睛,透过物体的某个片段是,判断该片段是否在视角(V)、光源是背光的 back4L = dot(-L, V) 即可
背光强度扰动
背光强度 = dot(V, -normalize(L + a * N) = dot(V, -H),其中 0 <= a <= 1 可以控制强度
如果为了性能考虑,可以省去:normalize,反正都是快速模拟,不用太精准
所以我们可以将公式修改为:背光强度 = dot(V, -(L+a * N) = dot(V, -H)
背光扩散
在扰动公式基础上调整为:背光强度 = (saturate(dot(V, -(L + a * N))^p) * s
现将 背光强度 clamp 到 [0~1] 之间,所以使用 saturate,然后再用 pow(x, p) 的 p 来控制边缘大小
因此:其中 0 < p <= +R 是控制扩展边缘大小,0 <= s <= +R 是控制背光整体强度值
Translucent shader 伪代码示例
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include ....
...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
uniform float _TIIvAttenByDistance; // 1.0 / atten
...
fixed4 frag(...) : SV_Target
{
// jave.lin : 原始光照颜色
fixed4 albedo = tex2D(_MainTex, i.uv);
fixed3 diffuse = lamert;
fixed3 specular = blinn phong mode...;
fixed3 ambient = ...;
fixed4 finalColor;
finalColor.rgb = lambert + blinn phong + ambient + ...;
finalColor.a = 1;
// jave.lin : Shadow map
float shadowMapDepth = tex2D(_ShadowMap, i.shadowProjUV);
float3 shadowMapDepth_positionShadowWS = ...;
float3 shadowMapDepth_positionMainCamWS = ...;
float distance_with_shadow = distance(i.positionWS.xyz - shadowMapDepth_positionMainCamWS);
// jave.lin : 计算透光度
float3 L = normalize(_WorldSpaceLightPos.xyz);
float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
float3 N = s.Normal;
float3 TH = normalize(L + N * _TIDistortion);
float translucentAtten = distance_with_shadow * _TIIvAttenByDistance;
float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
translucentIntensity *= translucentAtten;
// jave.lin : 叠加透光颜色
finalColor.rgb += albedo.rgb * _LightColor.rgb * translucentIntensity;
return finalColor;
}
添加采样厚度图 伪代码
在上面的代码基础上,添加一个图
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include ....
...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
...
fixed4 frag(...) : SV_Target
{
// jave.lin : 原始光照颜色
fixed4 albedo = tex2D(_MainTex, i.uv);
fixed3 diffuse = lamert;
fixed3 specular = blinn phong mode...;
fixed3 ambient = ...;
fixed4 finalColor;
finalColor.rgb = lambert + blinn phong + ambient + ...;
finalColor.a = 1;
// jave.lin : 厚度采样
float thickness = tex2D(_TITicknessMap, i.uv).r; // 0~1 => TI弱~TI强
// jave.lin : 计算透光度
float3 L = normalize(_WorldSpaceLightPos.xyz);
float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
float3 N = s.Normal;
float3 TH = normalize(L + N * _TIDistortion);
float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
translucentIntensity *= thickness;
// jave.lin : 叠加透光颜色
finalColor.rgb += albedo.rgb * _LightColor.rgb * translucentIntensity;
return finalColor;
}
考虑上 shadow map 距离的 伪代码
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include ....
...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
uniform float _TIIvAttenByDistance; // 1.0 / atten
...
fixed4 frag(...) : SV_Target
{
// jave.lin : 原始光照颜色
fixed4 albedo = tex2D(_MainTex, i.uv);
fixed3 diffuse = lamert;
fixed3 specular = blinn phong mode...;
fixed3 ambient = ...;
fixed4 finalColor;
finalColor.rgb = lambert + blinn phong + ambient + ...;
finalColor.a = 1;
// jave.lin : Shadow map
float shadowMapDepth = tex2D(_ShadowMap, i.shadowProjUV);
float3 shadowMapDepth_positionShadowWS = ...;
float3 shadowMapDepth_positionMainCamWS = ...;
float distance_with_shadow = distance(i.positionWS.xyz - shadowMapDepth_positionMainCamWS);
// jave.lin : 厚度采样
float thickness = tex2D(_TITicknessMap, i.uv).r; // 0~1 => TI弱~TI强
// jave.lin : 计算透光度
float3 L = normalize(_WorldSpaceLightPos.xyz);
float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
float3 N = s.Normal;
float3 TH = normalize(L + N * _TIDistortion);
float translucentAtten = distance_with_shadow * _TIIvAttenByDistance;
float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
translucentIntensity *= translucentAtten * thickness;
// jave.lin : 叠加透光颜色
finalColor.rgb += albedo.rgb * _LightColor.rgb * translucentIntensity;
return finalColor;
}
多光源下的 SSS
下面是以类似 URP 中的多光源遍历的方式 (BRP 中是区分 Pass 来绘制多光源的)
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include ....
...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
uniform float _TIIvAttenByDistance; // 1.0 / atten
uniform float _TIAmbientScale;
...
fixed4 frag(...) : SV_Target
{
fixed4 finalColor = 0;
float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
float3 N = normalize(i.normalWS);
fixed3 ambient = ...;
fixed4 albedo = tex2D(_MainTex, i.uv);
// jave.lin : 厚度采样
float thickness = tex2D(_TITicknessMap, i.uv).r; // 0~1 => TI弱~TI强
for (int i = 0; i < _AvailidatedLightCount; ++i)
{
Light light = Lights[i];
// jave.lin : 光源方向
float3 L = light.dir;
// jave.lin : 原始光照颜色
fixed3 diffuse = lamert with N and L;
fixed3 specular = blinn phong mode with V, N, L;
finalColor.rgb = lambert + blinn phong + ...;
// jave.lin : Shadow map - 这部分多光源的话,不同类型的 shadow map 是不同的
// 点光源可能是:一个 cube map,或是 6 张 shadow map
// spot, directional light 一张
// 这里不光是
float3 shadowSampleLocation = GetShadowMapSampleLocation(light);
float shadowMapDepth = tex2D(GetShadowMap(light), i.shadowProjUV);
float3 shadowMapDepth_positionShadowWS = ...;
float3 shadowMapDepth_positionMainCamWS = ...;
float distance_with_shadow = distance(i.positionWS.xyz - shadowMapDepth_positionMainCamWS);
// jave.lin : 计算透光度
float3 TH = normalize(L + N * _TIDistortion);
float translucentAtten = distance_with_shadow * _TIIvAttenByDistance;
float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
translucentIntensity *= translucentAtten * thickness;
// jave.lin : 叠加透光颜色
finalColor.rgb += albedo.rgb * light.color.rgb * translucentIntensity;
}
// jave.lin : 最后添加上 ambient 的计算
finalColor.rgb += albedo.rgb * ambient * (_TIAmbientScale * thickness);
finalColor.a = 1;
return finalColor;
}
BRP 多光源效果
我们将 厚度值 放到 MRAT 贴图中的 a 通道
厚度值的显示
下面是 BRP 中的效果
URP 多光源效果
(URP下麻烦很,毕竟是个 package,需要修改其里面的 shader,具体可以参考另一篇: Unity - 如何修改一个 Package 或是如何将 Package Local化)
没开效果
开了效果
关了
开了
SP 中贴图导出设置
(后续找个 皮肤的 素材来看效果)
用途
可以用于一些 散射透光强 的材质:
- 玉石
- 皮肤
- 浓稠不透明的液体
- 橡胶
- 等等。。。
Project
- TestRecovery_MRA_PBR_SSS_BRP_Unity_2018_2_11f1 - BRP
- Test_PBR_SSS_URP_2020_3_18f1 - URP,beyond compare at packages same version
其他方式 - RampTexture Or LUT
后面重写编写的:Unity Shader - SSS皮肤
References
- Fast Subsurface scattering shader in Unity URP for mobile
- Subsurface Scattering for Foliage, in Unity (WITHOUT RAYTRACING!)
- GDC 2011 – Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look
- Approximating Translucency Revisited – With “Simplified” Spherical Gaussian Exponentiation
- Fast Subsurface Scattering in Unity (Part 1)
- Fast Subsurface Scattering in Unity (Part 2)
- 099 Mechanical Skull - Substance Painter Thickness & SSS and Photoshop Brushes
- How to Setup SSS in Substance Painter Tutorial - SP 中 SSS 皮肤效果的制作