yukamu 发表于 2022-7-11 14:33

Unity屏幕后处理(基于累积缓存方案的运动模糊)

运动模糊指再真实世界中如果摄像机再曝光时拍摄场景发生了变化就会产生模糊的画面。游戏中实现运动模糊有多种方案。一种是利用累积缓存来混合多张连续的图像。当物体快速移动产生多张图像后,我们取他们之间的平均值作为最后的运动模糊图像。但是此种方法对性能消耗极大。另外一种方法是创建和使用速度缓存,这个缓存中存储了各个像素当前的运动速度,然后利用该值来决定模糊的方向和大小。
书中使用了类似于累积缓存的方案来实现运动模糊效果,区别是没有在一帧中把场景渲染多次,而是保存了之前的渲染结果,不断把动感前的渲染图像叠加到之前的渲染图像中。此方案虽然比累积缓存方案性能好,但是模糊效果会略有影响。接下来实战看下效果。
挂载在相机上的后处理脚本实现如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MotionBlur : PostEffectsBase
{
    public Shader MotionBlurShader;
   
   
    public float _BlurAmount = 0.5f; //越大拖尾效果越明显
   
    private Material _MotionBlurMaterial;
    private RenderTexture _AccumulationTexture;

    public Material material
    {
      get
      {
            _MotionBlurMaterial = CheckShaderAndCreateMaterial(MotionBlurShader, _MotionBlurMaterial);
            return _MotionBlurMaterial;
      }
    }



    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
      if (material != null)
      {
            //创建 _AccumulationTexture 纹理
            if (_AccumulationTexture == null || _AccumulationTexture.width != src.width || _AccumulationTexture.height != src.height)
            {
                DestroyImmediate(_AccumulationTexture);
                _AccumulationTexture = new RenderTexture(src.width, src.height, 0);
                _AccumulationTexture.hideFlags = HideFlags.HideAndDontSave;
                Graphics.Blit(src,_AccumulationTexture);
            }
            //表明我们需要进行一个渲染纹理的操作,来不让Unity发出警告误以为是忘记清空了, Unity官方建议 :这是一项代价高昂的操作,应该予以避免。
            _AccumulationTexture.MarkRestoreExpected();
            material.SetFloat("_BlurAmount",1.0f - _BlurAmount);
            
            Graphics.Blit(src,_AccumulationTexture, material);
            Graphics.Blit(_AccumulationTexture, dest);
      }
      else
      {
            Graphics.Blit(src,dest);
      }
    }
   
    private void OnDisable()
    {
      if (_AccumulationTexture)
      {
            DestroyImmediate(_AccumulationTexture);
      }
    }
}
_AccumulationTexture 就是我们用来叠加的纹理,而且_AccumulationTexture.MarkRestoreExpected();只是表明我们需要进行一个渲染纹理的操作,来不让Unity误以为是忘记清空了发出警告。
用来渲染后处理效果的Shader实现如下:
Shader "Unlit/MotionBlur"
{
        Properties {
                _MainTex ("Base (RGB)", 2D) = "white" {}
                _BlurAmount ("Blur Amount", Float) = 1.0
        }
    SubShader
    {
      CGINCLUDE
      #include "UnityCG.cginc"

      sampler2D _MainTex;
      fixed _BlurAmount;

      struct appdata
      {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
      };

      struct v2f
      {
            float2 uv : TEXCOORD0;
            float4 pos : SV_POSITION;
      };

      v2f vert(appdata v)
      {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.uv;
            return o;
      }

      fixed4 fragRGB(v2f i) : SV_Target
      {
            fixed4 col = fixed4(tex2D(_MainTex, i.uv).rgb, _BlurAmount);
            return col;
      }

      fixed4 fragA(v2f i) : SV_Target
      {
            fixed4 col = tex2D(_MainTex, i.uv);
            return col;
      }
      ENDCG

      ZTest Always
      ZWrite Off
      Cull Off

      Pass
      {
            Blend SrcAlpha OneMinusSrcAlpha

            ColorMask RGB // 对此通道启用仅写入 RGB 通道,这会禁用向 Alpha 通道写入

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragRGB
            ENDCG
      }
      Pass
      {
            Blend One Zero
            ColorMask A
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragA
            ENDCG
      }
    }
    Fallback Off
}渲染结果如下图:


上文Shader中的第一个Pass,使用了ColorMask RGB 命令,仅写入 RGB 通道,禁用向 Alpha 通道写入。已防止修改缓冲区的Alpha 通道。第二个Pass使用了ColorMask A 命令,仅写入Alpha通道,禁用RGB通道写入。书中说的是为了维护渲染纹理的透明通道值,不让其受到混合时使用的透明度值得影响。如果还有其它得疑问可以看下书中的Issuse。

此篇文章是在学习《Unity Shader入门精要》一书的笔记,加入了一些个人理解,如有错误之处,还请各位不吝赐教。
页: [1]
查看完整版本: Unity屏幕后处理(基于累积缓存方案的运动模糊)