返回

Unity屏后处理

原理

在Unity的OnRenderImage方法中,将摄像机捕捉到的画面,传输给Shader进行处理后,再输出给屏幕

源码

我们这里使用高斯模糊做案例

  • PostEffectsBase 所有屏后处理的基类,主要用于检测设备环境是否支持,以及根据使用的Shader创建材质供子类使用

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    [ExecuteInEditMode]//编辑器下执行
    [RequireComponent(typeof(Camera))]//绑定目标需要依赖组件类型
    public class PostEffectsBase : MonoBehaviour
    {
        // Start is called before the first frame update
        void Start()
        {
            CheckResources();
        }
    
        /// <summary>
        /// 检测环境
        /// </summary>
        protected void CheckResources()
        {
            bool isSupported = CheckSupport();
            if (isSupported == false)
            {
                NotSupported();
            }
        }
    
        //检测是否支持
        protected bool CheckSupport()
        {
            //SystemInfo用于访问系统和硬件信息
            //是否支持图像效果||是否支持3D体积RenderTextures
            if (SystemInfo.supportsImageEffects == false || SystemInfo.supports3DRenderTextures == false)
            {
                Debug.LogWarning("此平台不支持图像效果或渲染纹理!");
                return false;
            }
            return true;
        }
    
        //禁用Behaviour更新
        protected void NotSupported()
        {
            //禁用 Behaviour 不可更新。
            enabled = false;
        }
        /// <summary>
        /// 检查着色器并创建材质
        /// </summary>
        /// <param name="shader"></param>
        /// <param name="material"></param>
        /// <returns></returns>
        protected Material CheckShaderAndCreateMaterial(Shader shader, Material material)
        {
            if (shader == null)
            {
                return null;
            }
    
            if (shader.isSupported && material && material.shader == shader)
            {
                return material;
            }
    
            if (!shader.isSupported)
            {
                return null;
            }
            else
            {
                material = new Material(shader);
                //HideFlags:位掩码,用于控制对象的销毁、保存和在 Inspector 中的可见性。
                //HideFlags.DontSave:该对象不保存到场景。加载新场景时,也不会销毁它。
                material.hideFlags = HideFlags.DontSave;
                if (material)
                {
                    return material;
                }
                else
                {
                    return null;
                }
            }
        }
    }
    
  • GaussianBlur 高斯模糊的屏后处理类,在该案例中具有三版修改后的OnRenderImage实现,其中第一版为基础功能实现。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    /// <summary>
    /// 高斯模糊
    /// </summary>
    public class GaussianBlur : PostEffectsBase
    {
        public Shader gaussianBlurShader;
        private Material gaussianBlurMaterial = null;
    
        public Material material
        {
            get
            {
                gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
                return gaussianBlurMaterial;
            }
        }
    
        /// <summary>
        /// 模糊迭代次数——更多的次数意味着更多的模糊
        /// </summary>
        [Range(0, 4)]
        public int iterations = 3;
    
        /// <summary>
        /// Shader中模糊强度——值越大意味着模糊越多(虚影)
        /// </summary>
        [Range(0.2f, 3.0f)]
        public float blurSpread = 0.6f;
    
        /// <summary>
        /// 缩放降采样(像素化)
        /// </summary>
        [Range(1, 8)]
        public int downSample = 2;
    
        /*
        //第一版,基础的OnRenderImage实现
        //在图像的所有渲染操作全部完成后调用
        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            if (material != null)
            {
                int rtW = source.width;
                int rtH = source.height;
                //根据屏幕大小创建缓冲区
                RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
    
                //使用Shader中的第一个Pass对source进行滤波,将结果存储到buffer
                Graphics.Blit(source, buffer, material, 0);
                //使用Shader中的第一个Pass对buffer进行滤波,将结果存储到destination
                Graphics.Blit(buffer, destination, material, 1);
    
                //释放缓冲区
                RenderTexture.ReleaseTemporary(buffer);
            }
            else
            {
                Graphics.Blit(source, destination);
            }
        }
        */
    
        /*
        //第二版,增加缩放降采样处理
        //在图像的所有渲染操作全部完成后调用
        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            if (material != null)
            {
                //New!
                int rtW = source.width/ downSample;
                int rtH = source.height/ downSample;
                //根据屏幕大小创建缓冲区
                RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
    
                //New!纹理的过滤模式
                // FilterMode.Bilinear 对纹理样本求平均值,纹理将变得模糊
                buffer.filterMode = FilterMode.Bilinear;
    
                //使用Shader中的第一个Pass对source进行滤波,将结果存储到buffer
                Graphics.Blit(source, buffer, material, 0);
                //使用Shader中的第一个Pass对buffer进行滤波,将结果存储到destination
                Graphics.Blit(buffer, destination, material, 1);
    
                //释放缓冲区
                RenderTexture.ReleaseTemporary(buffer);
            }
            else
            {
                Graphics.Blit(source, destination);
            }
        }
        */
    
        //第三版,增加Shader参数控制,增加迭代次数处理
        //在图像的所有渲染操作全部完成后调用
        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            if (material != null)
            {
                int rtW = source.width / downSample;
                int rtH = source.height / downSample;
                //根据屏幕大小创建缓冲区buffer0
                RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
    
                //纹理的过滤模式
                // FilterMode.Bilinear 对纹理样本求平均值,纹理将变得模糊
                buffer0.filterMode = FilterMode.Bilinear;
    
                //将source存储到buffer0中
                Graphics.Blit(source, buffer0);
    
                for (int i = 0; i < iterations; i++)
                {
                    //设置Shader的模糊强度参数
                    material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
    
                    //第一次创建buffer1
                    RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                    //使用Shader中的第一个Pass对buffer0进行滤波,将结果存储到buffer1
                    Graphics.Blit(buffer0, buffer1, material, 0);
    
                    //释放buffer0,然后将buffer1中数据重新保存到buffer0中
                    RenderTexture.ReleaseTemporary(buffer0);
                    buffer0 = buffer1;
    
                    //第二次创建buffer1
                    buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                    //使用Shader中的第二个Pass对buffer0进行滤波,将结果存储到buffer1
                    Graphics.Blit(buffer0, buffer1, material, 1);
    
                    //释放buffer0,然后将buffer1中数据重新保存到buffer0中
                    RenderTexture.ReleaseTemporary(buffer0);
                    buffer0 = buffer1;
                }
    
                //将buffer0存储到destination中
                Graphics.Blit(buffer0, destination);
    
                //释放缓冲区
                RenderTexture.ReleaseTemporary(buffer0);
            }
            else
            {
                Graphics.Blit(source, destination);
            }
        }
    }
    
    
  • 高斯模糊Shader

    Shader "FoxShader/GaussianBlur"
    {
        //-----------------------------------【属性 || Properties】------------------------------------------
        Properties
        {
            [PerRendererData] _MainTex ("Texture", 2D) = "white" { }
            _BlurSize ("Blur Size", Range(0, 20)) = 1.0
        }
    
        //-------------------------CG着色语言声明部分 || Begin CG Include Part----------------------
        CGINCLUDE
        sampler2D _MainTex;
        half4 _MainTex_TexelSize;//计算相邻像素的纹理坐标偏移量
        float _BlurSize;
    
        struct appdata
        {
            float4 vertex: POSITION;
            float2 uv: TEXCOORD0;
        };
    
        struct v2f
        {
            float2 uv[5]: TEXCOORD0;
            float4 pos: SV_POSITION;
        };
        //顶点着色器代码块Pass1
        v2f vertBlurVertical(appdata v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
    
            //使用垂直方向纹素大小进行偏移
            half2 uv = v.uv;
            //原点
            o.uv[0] = uv;
            //上方一格
            o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
            //下方一格
            o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
            //上方两格
            o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
            //下方两格
            o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
    
            return o;
        }
        //顶点着色器代码块Pass2
        v2f vertBlurHorizontal(appdata v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
    
            //使用垂直方向纹素大小进行偏移
            half2 uv = v.uv;
            //原点
            o.uv[0] = uv;
            //右方一格
            o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
            //左方一格
            o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
            //右方两格
            o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
            //左方两格
            o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
    
            return o;
        }
        //片元着色器代码块(共用)
        fixed4 fragBlur(v2f i): SV_Target
        {
            //高斯权重
            float weight[3] = {
                0.4026, 0.2442, 0.0545
            };
    
            //下面循环格子顺序:原点,右方一格,左方一格,右方两格,左方两格
            //滤波结果
            fixed4 sum = tex2D(_MainTex, i.uv[0]) * weight[0];
    
            for (int it = 1; it < 3; it ++)
            {
                sum += tex2D(_MainTex, i.uv[it * 2 - 1]) * weight[it];
                sum += tex2D(_MainTex, i.uv[it * 2]) * weight[it];
            }
    
            return sum;
        }
        ENDCG
    
        //-------------------结束CG着色语言声明部分  || End CG Programming Part-----------------
    
    
        //----------------------------------【子着色器 || SubShader】---------------------------------------
        SubShader
        {
            Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True" "CanUseSpriteAtlas" = "True" "PreviewType" = "Plane" }
            ZWrite Off ZTest Always Blend  SrcAlpha OneMinusSrcAlpha Cull Off
    
            Pass
            {
                NAME "GAUSSIAN_BLUR_VERTICAL"
    
                CGPROGRAM
    
                #pragma vertex vertBlurVertical
                #pragma fragment fragBlur
                #pragma fragmentoption ARB_precision_hint_fastest
    
                #include "UnityCG.cginc"
    
                ENDCG
    
            }
    
            Pass
            {
                NAME "GAUSSIAN_BLUR_HORIZONTAL"
    
                CGPROGRAM
    
                #pragma vertex vertBlurHorizontal
                #pragma fragment fragBlur
                #pragma fragmentoption ARB_precision_hint_fastest
    
                #include "UnityCG.cginc"
    
                ENDCG
    
            }
        }
        Fallback off
    }
    
Licensed under CC BY-NC-SA 4.0
0