pc8888888 发表于 2022-2-13 10:56

【Unity Shader】后处理——SunShaft容积光

一、Sunshaft原理

也叫God Ray,类似丁达尔效应,用Unity还原UE4容积光的效果,参考了一些插件中的实现和算法。Sunshaft原理是在PostProcessing时提取区域,在特定方向进行uv偏移做模糊处理,再进行叠加,产生太阳光向外发散的感觉。
二、SunShaft区域判定

SunShaft提取区域的判定方式有三种:

[*]直接提取屏幕中高亮的区域。
    //提取亮度值
    half4 main=SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,i.texcoord);
    half region=step(_Range,Luminance(main.rgb));


[*]只渲染天空盒,剔除场景中物体所占的区域,并取高亮的部分。
        half TransformColor(half4 skyboxValue) {
                return dot(max(skyboxValue.rgb - _SunThreshold.rgb, half3(0, 0, 0)), half3(1, 1, 1)); // threshold and convert to greyscale
        }

        half4 frag_nodepth(v2f i) : SV_Target
        {
                UNITY_SETUP_INSTANCE_ID(I);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

                half4 sky = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_Skybox, i.uv.xy);
                half4 tex = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv.xy);

                        /// consider maximum radius
                half2 vec = _SunPosition.xy - i.uv.xy;
                half dist = saturate(_SunPosition.w - length(vec));

                half4 outColor = 0;

                if (Luminance(abs(sky.rgb - tex.rgb)) < 0.2)   //_DepthThreshold
                {
                        outColor = TransformColor(tex) * dist;
                }

                return outColor;
        }


[*]使用深度图,提取深度较大的区域。
        half4 frag_depth(v2f i) : SV_Target
      {
                UNITY_SETUP_INSTANCE_ID(i);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

                half depthSample = SampleSceneDepth(i.screenPos.xy / i.screenPos.w);

                half4 tex = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv.xy);
                depthSample = Linear01Depth(depthSample,_ZBufferParams);

                half2 vec = _SunPosition.xy - i.uv.xy;
                half dist = saturate(_SunPosition.w - length(vec.xy));

                half4 outColor = 0;

                // consider shafts blockers
                if (depthSample < 0.018) {   //_DepthThreshold
                        outColor = TransformColor(tex) * dist;
                }

                return outColor;
        }

三、特定方向的径向模糊

计算太阳的视口空间坐标:
      Vector3 v = Vector3.one * 0.5f;
      if (sunTransform != Vector3.zero) {
         v = camera.WorldToViewportPoint(sunTransform);
      }
      else {
         v = new Vector3(0.5f, 0.5f, 0.0f);
      }得到一个坐标值,再与RenderTexture的uv值去做相减得到一个方向。
        o.blurVector = (_SunPosition.xy - v.texcoord.xy) * _BlurRadius4.xy;再对提取区域做偏移模糊处理,根据当前其在 RenderTexture 的 uv,沿着向量往光源方向有间隔的取样并衰减,最后取结果的平均值为当前像素的颜色。
        half4 frag_radial(Varyings_radial i) : SV_Target
        {
                UNITY_SETUP_INSTANCE_ID(i);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

                half4 color = half4(0,0,0,0);
                for (int j = 0; j < SAMPLES_INT; j++)
                {
                        half4 tmpColor = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv.xy);
                        color += tmpColor;
                        i.uv.xy += i.blurVector;
                }
                return color / SAMPLES_FLOAT;
        }

四、后处理

实现思路:


RenderPass:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace UnityEngine.Rendering.URP
{
    internal class BlitPassSunShaftsSRP : ScriptableRenderPass
    {

      //SUN SHAFTS         
      public BlitSunShaftsSRP.BlitSettings.SunShaftsResolution resolution = BlitSunShaftsSRP.BlitSettings.SunShaftsResolution.Low;
      public BlitSunShaftsSRP.BlitSettings.ShaftsScreenBlendMode screenBlendMode = BlitSunShaftsSRP.BlitSettings.ShaftsScreenBlendMode.Screen;
      public Vector3 sunTransform = new Vector3(0f, 0f, 0f); // Transform sunTransform;
      public int radialBlurIterations = 2;
      public Color sunColor = Color.white;
      public Color sunThreshold = new Color(0.87f, 0.74f, 0.65f);
      public float sunShaftBlurRadius = 2.5f;
      public float sunShaftIntensity = 1.15f;
      public float maxRadius = 0.75f;
      public bool useDepthTexture = true;

      public enum RenderTarget
      {
            Color,
            RenderTexture,
      }

      public Material blitMaterial = null;
      public FilterMode filterMode { get; set; }

      private RenderTargetIdentifier source { get; set; }
      private RenderTargetHandle destination { get; set; }

      RenderTargetHandle m_TemporaryColorTexture;
      string m_ProfilerTag;


      //SUN SHAFTS
      RenderTexture lrColorB;
      RenderTargetHandle lrDepthBuffer;

      /// <summary>
      /// Create the CopyColorPass
      /// </summary>
      public BlitPassSunShaftsSRP(RenderPassEvent renderPassEvent, Material blitMaterial, string tag,BlitSunShaftsSRP.BlitSettings settings)
      {
            this.renderPassEvent = renderPassEvent;
            this.blitMaterial = blitMaterial;
            m_ProfilerTag = tag;
            m_TemporaryColorTexture.Init("_TemporaryColorTexture");

            //SUN SHAFTS
            this.resolution = settings.resolution;
            this.screenBlendMode = settings.screenBlendMode;
            this.sunTransform = settings.sunTransform;
            this.radialBlurIterations = settings.radialBlurIterations;
            this.sunColor = settings.sunColor;
            this.sunThreshold = settings.sunThreshold;
            this.sunShaftBlurRadius = settings.sunShaftBlurRadius;
            this.sunShaftIntensity = settings.sunShaftIntensity;
            this.maxRadius = settings.maxRadius;
            this.useDepthTexture = settings.useDepthTexture;
            //this.blend = settings.blend;
    }

      /// <summary>
      /// Configure the pass with the source and destination to execute on.
      /// </summary>
      /// <param name="source">Source Render Target</param>
      /// <param name="destination">Destination Render Target</param>
      public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
      {
            this.source = source;
            this.destination = destination;
      }


      connectSuntoSunShaftsURP connector;

      /// <inheritdoc/>
      public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
      {            
            //grab settings if script on scene camera
            if (connector == null)
            {
                connector = renderingData.cameraData.camera.GetComponent<connectSuntoSunShaftsURP>();
                if(connector == null && Camera.main != null)
                {
                  connector = Camera.main.GetComponent<connectSuntoSunShaftsURP>();                  
                }               
            }
            //Debug.Log(Camera.main.GetComponent<connectSuntoSunShaftsURP>().sun.transform.position);
            if (connector != null)
            {
                this.sunTransform = connector.sun.transform.position;
                this.screenBlendMode = connector.screenBlendMode;
                //public Vector3 sunTransform = new Vector3(0f, 0f, 0f);
                this.radialBlurIterations = connector.radialBlurIterations;
                this.sunColor = connector.sunColor;
                this.sunThreshold = connector.sunThreshold;
                this.sunShaftBlurRadius = connector.sunShaftBlurRadius;
                this.sunShaftIntensity = connector.sunShaftIntensity;
                this.maxRadius = connector.maxRadius;
                this.useDepthTexture = connector.useDepthTexture;
            }


            CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
            
            RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
            opaqueDesc.depthBufferBits = 0;

            if (destination == UnityEngine.Rendering.Universal.RenderTargetHandle.CameraTarget)
            {
                cmd.GetTemporaryRT(m_TemporaryColorTexture.id, opaqueDesc, filterMode);
                RenderShafts(context, renderingData, cmd, opaqueDesc);
            }
      }

      /// <inheritdoc/>
      public override void FrameCleanup(CommandBuffer cmd)
      {
            if (destination == UnityEngine.Rendering.Universal.RenderTargetHandle.CameraTarget)
            {
                cmd.ReleaseTemporaryRT(m_TemporaryColorTexture.id);
                cmd.ReleaseTemporaryRT(lrDepthBuffer.id);
            }
      }


      //SUN SHAFTS
      public void RenderShafts(ScriptableRenderContext context, RenderingData renderingData, CommandBuffer cmd, RenderTextureDescriptor opaqueDesc)
      {
            opaqueDesc.depthBufferBits = 0;

            Material material = blitMaterial;

            Camera camera = Camera.main;
            if (useDepthTexture)
            {
                camera.depthTextureMode |= DepthTextureMode.Depth;
            }

            int divider = 8;
            if (this.resolution == BlitSunShaftsSRP.BlitSettings.SunShaftsResolution.Normal)
                divider = 4;
            else if (this.resolution == BlitSunShaftsSRP.BlitSettings.SunShaftsResolution.High)
                divider = 2;

            Vector3 v = Vector3.one * 0.5f;
            if (sunTransform != Vector3.zero) {
                v = camera.WorldToViewportPoint(sunTransform);
            }
            else {
                v = new Vector3(0.5f, 0.5f, 0.0f);
            }

            //v0.1
            int rtW = opaqueDesc.width/divider;
            int rtH = opaqueDesc.height/divider;
   
            cmd.GetTemporaryRT(lrDepthBuffer.id, opaqueDesc, filterMode);

            material.SetVector("_BlurRadius4", new Vector4(1.0f, 1.0f, 0.0f, 0.0f) * sunShaftBlurRadius);
            material.SetVector("_SunPosition", new Vector4(v.x, v.y, v.z, maxRadius));
            material.SetVector("_SunThreshold", sunThreshold);

            if (!useDepthTexture)
            {
                var format = camera.allowHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
                RenderTexture tmpBuffer = RenderTexture.GetTemporary(rtW, rtH, 0, format);
                RenderTexture.active = tmpBuffer;
                GL.ClearWithSkybox(false, camera);

                material.SetTexture("_Skybox", tmpBuffer);
                Blit(cmd, source, lrDepthBuffer.Identifier(), material, 3);

                RenderTexture.ReleaseTemporary(tmpBuffer);
            }
            else
            {
                Blit(cmd, source, lrDepthBuffer.Identifier(), material, 2);
            }

            Blit(cmd, source, m_TemporaryColorTexture.Identifier()); //KEEP BACKGROUND

            radialBlurIterations = Mathf.Clamp(radialBlurIterations, 1, 4);

            float ofs = sunShaftBlurRadius * (1.0f / 768.0f);

            material.SetVector("_BlurRadius4", new Vector4(ofs, ofs, 0.0f, 0.0f));
            material.SetVector("_SunPosition", new Vector4(v.x, v.y, v.z, maxRadius));

            for (int it2 = 0; it2 < radialBlurIterations; it2++)
            {
               lrColorB = RenderTexture.GetTemporary(rtW, rtH, 0);               
               
                Blit(cmd, lrDepthBuffer.Identifier(), lrColorB, material, 1);

                cmd.ReleaseTemporaryRT(lrDepthBuffer.id);
                ofs = sunShaftBlurRadius * (((it2 * 2.0f + 1.0f) * 6.0f)) / 768.0f;
                material.SetVector("_BlurRadius4", new Vector4(ofs, ofs, 0.0f, 0.0f));

                cmd.GetTemporaryRT(lrDepthBuffer.id, opaqueDesc, filterMode);

                Blit(cmd, lrColorB, lrDepthBuffer.Identifier(), material, 1);

               RenderTexture.ReleaseTemporary(lrColorB);

                ofs = sunShaftBlurRadius * (((it2 * 2.0f + 2.0f) * 6.0f)) / 768.0f;
                material.SetVector("_BlurRadius4", new Vector4(ofs, ofs, 0.0f, 0.0f));
            }
            
            // put together:

            if (v.z >= 0.0f)
            {
                material.SetVector("_SunColor", new Vector4(sunColor.r, sunColor.g, sunColor.b, sunColor.a) * sunShaftIntensity);
            }
            else
            {
                material.SetVector("_SunColor", Vector4.zero); // no backprojection !
            }

            cmd.SetGlobalTexture("_ColorBuffer", lrDepthBuffer.Identifier());

            Blit(cmd, m_TemporaryColorTexture.Identifier(), source, material, (screenBlendMode == BlitSunShaftsSRP.BlitSettings.ShaftsScreenBlendMode.Screen) ? 0 : 4);

            cmd.ReleaseTemporaryRT(lrDepthBuffer.id);
            cmd.ReleaseTemporaryRT(m_TemporaryColorTexture.id);

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);

            RenderTexture.ReleaseTemporary(lrColorB);

      }
    }
}
RenderFeature:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace UnityEngine.Rendering.URP
{
    public class BlitSunShaftsSRP : ScriptableRendererFeature
    {
      
      public class BlitSettings
      {
            public RenderPassEvent Event = RenderPassEvent.AfterRenderingTransparents;
            
            public Material blitMaterial = null;
            public Target destination = Target.Color;
            public string textureId = "_BlitPassTexture";

            /////SUN SHAFTS
            //
            // public float blend = 0.5f;

            public enum SunShaftsResolution
            {
                Low = 0,
                Normal = 1,
                High = 2,
            }

            public enum ShaftsScreenBlendMode
            {
                Screen = 0,
                Add = 1,
            }

            public SunShaftsResolution resolution = SunShaftsResolution.Low;
            public ShaftsScreenBlendMode screenBlendMode = ShaftsScreenBlendMode.Screen;
            public Vector3 sunTransform =new Vector3(0f, 0f, 0f); // Transform sunTransform;
            public int radialBlurIterations = 2 ;
            public Color sunColor = Color.white ;
            public Color sunThreshold =new Color(0.87f, 0.74f, 0.65f) ;
            public float sunShaftBlurRadius = 2.5f ;
            public float sunShaftIntensity = 1.15f ;
            public float maxRadius = 0.75f ;
            public bool useDepthTexture = true ;
      }
      
      public enum Target
      {
            Color,
            Texture
      }

      public BlitSettings settings = new BlitSettings();
      RenderTargetHandle m_RenderTextureHandle;

      BlitPassSunShaftsSRP blitPass;

      
      public override void Create()
      {
            var passIndex = settings.blitMaterial != null ? settings.blitMaterial.passCount - 1 : 1;      

            blitPass = new BlitPassSunShaftsSRP(settings.Event, settings.blitMaterial, name, settings);//, settings.blitMaterialPassIndex
            m_RenderTextureHandle.Init(settings.textureId);
      }

      public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
      {
            var src = renderer.cameraColorTarget;
            var dest = (settings.destination == Target.Color) ? RenderTargetHandle.CameraTarget : m_RenderTextureHandle;

            if (settings.blitMaterial == null)
            {
                Debug.LogWarningFormat("Missing Blit Material. {0} blit pass will not execute. Check for missing reference in the assigned renderer.", GetType().Name);
                return;
            }

            blitPass.Setup(src, dest);
            renderer.EnqueuePass(blitPass);
      }
    }
}

SunShaft Shader中的pass结构:
Subshader
        {
                Tags
      {
            "RenderPipeline"="UniversalPipeline"
                        "Queue"="Transparent"
                        "IgnoreProjector"="True"
                        "RenderType"="Transparent"
      }

                ZTest Always
                Cull Off
                ZWrite Off

                Pass //0
                {
                        Name "Fragment Screen"

                        HLSLPROGRAM
                              #pragma target 3.5
                        #pragma multi_compile_instancing
                       
                        #pragma vertex vert
                        #pragma fragment fragScreen

                        ENDHLSL
                }
               
                Pass // 1
                {
                        Name "Fragment Radial"

                        HLSLPROGRAM
                        #pragma target 3.5
                        #pragma multi_compile_instancing
                       
                        #pragma vertex vert_radial
                        #pragma fragment frag_radial
                       
                        ENDHLSL
                }

                Pass // 2
                {
                        Name "Fragment Depth"

                        HLSLPROGRAM
                        #pragma target 3.5
                        #pragma multi_compile_instancing
                       
                        #pragma vertex vert
                        #pragma fragment frag_depth
                       
                        ENDHLSL
                }

                Pass // 3
                {
                        Name "Fragment No Depth"

                        HLSLPROGRAM
                        #pragma target 3.5
                        #pragma multi_compile_instancing
                       
                        #pragma vertex vert
                        #pragma fragment frag_nodepth
                       
                        ENDHLSL
                }

                Pass // 4
                {
                        Name "Fragment Add"

                        HLSLPROGRAM
                        #pragma target 3.5
                        #pragma multi_compile_instancing
                       
                        #pragma vertex vert
                        #pragma fragment fragAdd
                       
                        ENDHLSL
                }
        }

五、参考
页: [1]
查看完整版本: 【Unity Shader】后处理——SunShaft容积光