refer:
雷火知乎
毛星云总结
参考1
参考2
参考3
(有一个想法,厚度图,通过厚度图来控制SSSSS的范围和强度)
-
严格来讲并不是基于物理的渲染,但是其出发点是对BSSRDF的近似,因此我归到PBR这个专栏了,其实本来就不好分类
-
如果希望理解原理,有更深入的理解的话,欢迎来看我的BSSRDF
-
主要解释都在代码注释里
SSSSS
流程:
- 分别获取Object的 遮罩、 漫反射颜色_A、深度 各自存入RenderTex中
- 对遮罩内的漫反射进行blur
两个要点:1、根据Object到Camera的距离确定一个总体的blur范围
2、根据当前片元的深度值,进行微调 - 再获取一张 漫反射颜色_B
- A和B 相加
- 再加上遮罩内的单独的高光
- 得到最后结果
关于blur的详细介绍可以看本人的另一篇文章
注意:后处理中无法使用模板测试
- 多次尝试后,尚未发现能在后处理中使用模板缓冲的办法,SSSSS的案例考虑采用蒙版
后续发现解决办法再来更新
后处理中是不能使用模板测试的,如果使用则会失效,这是因为后处理是在虚拟的渲染纹理中进行的,而这个渲染纹理并没有继承屏幕空间中的各个缓冲(不确定,但应该是这样)
官方API文档中有说 RenderTexture.GetTemporary(rtW, rtH,24);
第三个参数改成24会保留深度模板缓冲,经测试无效
一篇文章介绍说再添加一个语句
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH,24);
Graphics.SetRenderTarget(buffer.colorBuffer, src.depthBuffer);
这样会保留缓冲, 但经测试依然无效
代码实现
- 本人学艺不精,现在还没有搞懂遮罩的脚本部分,因此无法完整再现上述流程
但本文的目的是为了理解,因此下面的代码部分,使用了本人自己写的较为基础的版本:没有遮罩,没有深度图和摄像机距离,仅介绍SSSSS中核心的部分
如果有需要完整代码的,Unity的AssetStore中有前辈写好的免费资源
Camera Script
using UnityEngine;
public class SSS_Camera : PostEffectsBase
{
public Shader SSS_Shader;
private Material SSS_Mat = null;
public Material material{
get{
SSS_Mat = CheckShader_CreateMat(SSS_Shader, SSS_Mat);
return SSS_Mat;
}
}
//模糊所需参数
[Range(0,4)]
public int iterations = 3;
[Range(0.2f, 10.0f)]
public float blurSpread = 0.6f;
[Range(1,8)]
public int downSample = 2;
//bloom截取强度
[Range(0.0f, 4f)]
public float lumi = 0.6f; //不止到1,是为了HDR
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null){
material.SetFloat("_Lumi", lumi);
int rtW = src.width/downSample;
int rtH = src.height/downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH,24);
//Graphics.SetRenderTarget(buffer0.colorBuffer, src.depthBuffer);
buffer0.filterMode = FilterMode.Bilinear;
//先走第一个pass提取需要bloom的部分
Graphics.Blit(src, buffer0,material,0);
//注意循环内部创建的RenderTex在循环外是访问不到的,因此需要左手倒右手
for (int i = 0; i < iterations; i++) {
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 24);
Graphics.Blit(buffer0, buffer1, material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 24);
Graphics.Blit(buffer0, buffer1, material, 2);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
//这一步很重要啊,把bloom好的图像传递给Shader
material.SetTexture("_Bloom", buffer0);
Graphics.Blit(src, dest, material, 3);
RenderTexture.ReleaseTemporary(buffer0);
}else{
Graphics.Blit(src, dest);
}
}
}
Camera Shader
Shader "Unlit/SSSSS_Camera"
{
Properties
{
_MainTex ("Base", 2D) = "white" {
}
_Bloom ("Bloom", 2D) = "black"{
} //用于存储亮度图
_BlurSize ("Blur Size", float) = 1.0
_Lumi ("lumi", float) = 0.6 //这个值是用来控制bloom的阈值
}
SubShader
{
ZTest Always Cull Off ZWrite Off
Pass//提取亮度Pass
{
Stencil
{
Ref 1
Comp always
Pass Keep
}
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _Bloom;
half4 _MainTex_TexelSize; //内置方法,可以获取纹素大小
float _BlurSize;
float _Lumi;
struct v2f{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vertBloom(appdata_img v){
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed Luminance(fixed4 color){
//这个是明度的计算,可以去看本人的文章“色彩知识总结”
return 0.2125*color.r + 0.7154*color.g + 0.0721*color.b;
}
fixed4 fragBloom(v2f i): SV_Target {
fixed3 c = tex2D(_MainTex, i.uv);
fixed l = clamp(Luminance(c) - _Lumi, 0, 1); //clamp函数是一个截取函数,后面两个参数是范围
return fixed4 (c*l,1);//为什么这里要乘?
//这里要乘而不是单纯的取0/1,是因为:
//(1)GPU不擅长分支运算
//(2)数值越大的fragment在模糊时,影响的范围更大,符合真实逻辑,乘法保留了这种特性
}
ENDCG
}
//模糊Pass
//注意这里是直接引用了同工程下的另外一个shader中的Pass,
//可以在本人的其他文章中找到,或直接使用《入门精要》附带的文件
//UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_HORIZONTAL"
UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_HORIZONTAL"
UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL"
//UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL"
Pass//混合Pass
{
Stencil
{
Ref 1
Comp equal
Pass Keep
}
CGPROGRAM
#pragma vertex vertMerge
#pragma fragment fragMerge
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _Bloom;
half4 _MainTex_TexelSize; //内置方法,可以获取纹素大小
float _BlurSize;
float _Lumi;
struct v2fMerg{
float4 vertex : SV_POSITION;
float4 uv : TEXCOORD0;
};
v2fMerg vertMerge(appdata_img v)
{
v2fMerg o;
o.vertex = UnityObjectToClipPos(v.vertex);
//将两张纹理的坐标分开是为了处理平台差异化
o.uv.xy = v.texcoord;
o.uv.zw = v.texcoord;
//平台差异化处理,详见“Unity的一些机制”和《精要》5.6.1
//#if UNITY_UV_STARTS_AT_TOP
//if (_MainTex_TexelSize.y < 0.0)
// o.uv.w = 1.0 - o.uv.w;
//#endif
return o;
}
fixed4 fragMerge(v2fMerg i): SV_Target{
//直接加?
//当然直接相加,不然不就是单纯的模糊了,bloom的亮度就是要超额,超额也更适合HDR
return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.xy);
}
ENDCG
}
}
Fallback Off
}
Mask 遮罩
既然不能用模板,那么就来学习遮罩吧,但是呢,对于本人而言有那么一点复杂,我不是很会写脚本,这部分先跳过,以后脚本能力够了回来补