原理
在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 }