SSSSS屏幕空间次表面散射


refer:
雷火知乎
毛星云总结
参考1
参考2
参考3

(有一个想法,厚度图,通过厚度图来控制SSSSS的范围和强度)

  • 严格来讲并不是基于物理的渲染,但是其出发点是对BSSRDF的近似,因此我归到PBR这个专栏了,其实本来就不好分类

  • 如果希望理解原理,有更深入的理解的话,欢迎来看我的BSSRDF

  • 主要解释都在代码注释里

SSSSS

流程:

  1. 分别获取Object的 遮罩漫反射颜色_A深度 各自存入RenderTex中
  2. 遮罩内的漫反射进行blur
    两个要点:1、根据Object到Camera的距离确定一个总体的blur范围
         2、根据当前片元的深度值,进行微调
  3. 再获取一张 漫反射颜色_B
  4. A和B 相加
  5. 再加上遮罩内的单独的高光
  6. 得到最后结果

流程示意

关于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 遮罩

既然不能用模板,那么就来学习遮罩吧,但是呢,对于本人而言有那么一点复杂,我不是很会写脚本,这部分先跳过,以后脚本能力够了回来补

猜你喜欢

转载自blog.csdn.net/dogman_/article/details/130079949