找回密码
 立即注册
查看: 484|回复: 0

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

[复制链接]
发表于 2022-2-13 10:56 | 显示全部楼层 |阅读模式
一、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
    {
        [System.Serializable]
        public class BlitSettings
        {
            public RenderPassEvent Event = RenderPassEvent.AfterRenderingTransparents;
            
            public Material blitMaterial = null;
            public Target destination = Target.Color;
            public string textureId = "_BlitPassTexture";

            /////SUN SHAFTS
            // [Range(0f, 1f), Tooltip("SunShafts effect intensity.")]
            // 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
                }
        }

五、参考

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-12-23 07:44 , Processed in 0.092352 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表