【unity shader 入门精要 读书笔记】运动模糊

运动模糊是真实世界中的摄像机的一种效果。如果在摄像机曝光时,拍摄场景发生了变化,就会产生模糊的画面。运动模糊在我们的日常生活中是非常常见的,只要留心观察,就可以法线无论是体育报道还是各个电影里,都有运动模糊的身影。运动模糊效果可以让物体运动看起来更加真实平滑,但在计算机产生图像中,由于不存在曝光这一物理现象,渲染出来的图像往往都棱角分明,缺少运动模糊。在一些诸如赛车类型的游戏中,为画面添加运动模糊是一种常见的处理方法。

 

运动模糊的实现有多种方法。

一种实现方法是利用一块 累积缓存【arrumulation buffer】来混合多张连续的图像。当物体快速移动产生多张图像后,我们取它们之间的平均值作为最后的运动模糊图像。然而,这种暴力的方法对性能的消耗很大,因为想要获取多张帧图像往往意味着我们需要在同一帧里渲染多次场景。

另一种应用广泛的方法是创建和使用 速度缓存【velocity buffer】,这个缓存中存储了各个像素当前的运动速度,然后利用该值来决定模糊的方向和大小。

 

在本节中,我们将使用类似上述第一种方法的实现来模拟运动模糊的效果。我们不需要在一帧中把场景渲染多次,但需要保存之前的渲染结果。不断把当前的渲染图像叠加到之前的渲染图像中,从而产生一种运动轨迹的视觉效果。这种方法与原始的利用累计缓存方法相比性能更好,但模糊效果可能会略有影响。

 

using UnityEngine;
using System.Collections;

public class MotionBlur : PostEffectsBase
{
    public Shader motionShader;
    private Material motionMaterial;
    public Material material
    {
        get
        {
            motionMaterial = CheckShaderAndCreateMaterial(motionShader, motionMaterial);
            return motionMaterial;
        }
    }
    /// <summary>
    /// 该值越大,运动拖尾的效果越明显,为了防止拖尾
    /// 效果完全替代当前帧的渲染结果,我们把它的值
    /// 截取在 0.0 ~0.9 范围内。
    /// </summary>
    [Range(0.0f, 0.9f)]
    public float blurAmount = 0.5f;

    /// <summary>
    /// 定义一个 RenderTexture 变量,保存之前图像叠加的结果。
    /// </summary>
    private RenderTexture accumulationTexture;

    void OnDisable()
    {
        //脚本不运行的时候,立即销毁accumulationTexture
        //这是因为,我们希望在下一次开始应用运动模糊时重新叠加图像
        DestroyImmediate(accumulationTexture);
    }

    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material != null)
        {
            if (accumulationTexture == null ||  //初始化 accumulationTexture
               accumulationTexture.width != src.width ||
               accumulationTexture.height != src.height)
            {
                DestroyImmediate(accumulationTexture);
                accumulationTexture = new RenderTexture(src.width, src.height, 0);
                //设置不显示在 hierarchy 上也不会保存到场景中
                accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
                //使用当前帧图像初始化 accumulationTexture
                Graphics.Blit(src, accumulationTexture);
            }
            //表面我们需要进行一个渲染纹理的恢复操作。
            accumulationTexture.MarkRestoreExpected();
            //将参数传递给材质
            material.SetFloat("_BlurAmount", 1.0f - blurAmount);
            //把当前屏幕图像叠加到accumulationTexture中
            Graphics.Blit(src, accumulationTexture, material);
            //把渲染结果显示到屏幕上
            Graphics.Blit(accumulationTexture, dest);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}

 

在确认材质可用后,我们首先判断用于混合图像的 accumulationTexture 是否满足条件。我们不仅判断它是否为空,还判断它是否与当前的屏幕分辨率相等,如果不满足,就说明我们需要重新创建一个适合安全分辨率 accumulationTexture 变量。创建完毕后,由于我们会自己控制该变量的销毁,因此可以把它的 hideFlags 设置为 HideFlags.HideAndDontSave,这意味着这个变量不会显示在 Hierarchy 中,也不会保存到场景中。然后,我们使用当前的帧图像初始化 accumulationTexture(使用Graphics.Blit(src, accumulationTexture)代码)

当得到了有效的 accumulation 变量后,我们调用了 accumulationTexture.MarkRestoreExpected 函数来表明我们需要进行一个渲染纹理的恢复操作。 恢复操作【restore operation】发生在渲染到纹理而该纹理又没有被提前清空或销毁的情况下。在本例中,我们每次调用 OnRenderImage 时都需要把当前的帧图像和 accumulationTexture 中的图像混合, accumulationTexture 纹理不需要提前清空,因为它保存了我们之前的混合结果。然后我们将参数传递给材质,并调用 Graphics.Blit(src, accumulationTexture, material) 把当前的屏幕图像 src 叠加到 accumulationTexture 中。最后使用 Graphics.Blit(accumulationTexture, dest) 把结果显示到屏幕上。

 

 

 

Shader "Custom/MotionBlur" 
{
	Properties
	{
		//输入的渲染纹理
		_MainTex("Base (RGB)", 2d) = "" {}
		//用来混合图像时使用的混合系数。
		_BlurAmount("BlurAmount", float) = 1.0
	}
	SubShader
	{
		CGINCLUDE
		#include "UnityCG.cginc"

		sampler2D _MainTex;
		fixed _BlurAmount;

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

		v2f vert(appdata_img v)
		{
			v2f o;
			o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
			o.uv = v.texcoord;
			return o;
		}

		//用于更新渲染纹理的RGB通道部分
		fixed4 fragRGB(v2f i) : SV_Target
		{
			return fixed4(tex2D(_MainTex, i.uv).rgb, _BlurAmount);
		}
		//用于更新渲染纹理的 A 通道部分
		half4 fragA(v2f i) : SV_Target 
		{
			return tex2D(_MainTex, i.uv);
		}
		ENDCG

		Pass 
		{
			Blend SrcAlpha OneMinusSrcAlpha
			ColorMask RGB
			CGPROGRAM
			#pragma vertex vert 
			#pragma fragment fragRGB
			ENDCG
		}

		Pass 
		{
			Blend One Zero 
			ColorMask A 
			CGPROGRAM
			#pragma vertex vert 
			#pragma fragment fragA
			ENDCG
		}
	}
}

 

 

RGB通道版本的 shader 对当前图像进行采样,并将 A 通道的值设为 _BlurAmount,以便在后面混合时可以使用它的透明通道进行混合。 A 通道版本的代码就更简单了,直接返回采样结果。实际上,这个版本只是为了维护渲染纹理的透明通道值,不让其受到混合时使用的透明度值的影响。

 

然后,我们定义了运动模糊所需的 pass ,在本例中,我们需要两个 pass, 一个用于更新渲染纹理的 RGB 通道,第一个用于更新 A 通道,之所以要把 A 通道和 RGB 通道分开,是因为在 更新 RGB 时,我们需要设置它的 A 通道来混合图像,但又不希望 A 通道的值写入渲染纹理中。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值