Unity Shader 学习笔记(23) 运动模糊

44 篇文章 5 订阅
36 篇文章 5 订阅

Unity Shader 学习笔记(23) 运动模糊

参考书籍:《Unity Shader 入门精要》


运动模糊

两种常见方法:

  • 积累缓存(accumulation buffer),混合连续多张图像。即需要同一帧里渲染多次场景,性能消耗较大。
  • 速度缓存(velocity buffer),存储各个像素当前移动速度,利用该值判断模糊方向和大小,但曲线运动较大时会出现错误。

积累缓存

保持之前渲染结果不断叠加混合,模拟出运动轨迹(幻影)的视觉效果。即不用一帧里多次渲染。
在这里插入图片描述

MotionBlur类:

using UnityEngine;

public class MotionBlur : PostEffectsBase
{

    [Range(0.0f, 0.9f)]                         // 为1的时候完全代替当前帧的渲染结果
    public float blurAmount = 0.5f;             // 模糊参数

    private RenderTexture accumulationTexture;  // 保存之前图像的叠加效果

    void OnDisable()
    {
        DestroyImmediate(accumulationTexture);  // 用完就销毁,下一次开始应用这个重新叠加
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (TargetMaterial != null)
        {
            // 创建积累图像
            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;          // 变量不显示在Hierarchy中,也不会保存到场景
                Graphics.Blit(src, accumulationTexture);        // 原始图像存入积累纹理
            }

            // 表明需要进行一个恢复操作。渲染恢复操作:发生在渲染到纹理,而该纹理有没有被提前情况或销毁情况下。
            accumulationTexture.MarkRestoreExpected();          // accumulationTexture就不需要提前清空了

            TargetMaterial.SetFloat("_BlurAmount", 1.0f - blurAmount);

            // 混合当前屏幕和之前存的混合图像
            Graphics.Blit(src, accumulationTexture, TargetMaterial);
            // 最后输出混合图像
            Graphics.Blit(accumulationTexture, dest);
        }
        else
            Graphics.Blit(src, dest);
    }
}

Shader:

Properties {
	_MainTex ("Base (RGB)", 2D) = "white" {}
	_BlurAmount ("Blur Amount", Float) = 1.0
}
SubShader {
	CGINCLUDE
	
	...
	
	// 更新RGB,当前图像。A通道设为模糊值,方便后面混合
	fixed4 fragRGB (v2f i) : SV_Target {
		return fixed4(tex2D(_MainTex, i.uv).rgb, _BlurAmount);
	}
	
	// 更新A,直接返回(保护纹理的A通道,不受混合时透明度影响)
	half4 fragA (v2f i) : SV_Target {
		return tex2D(_MainTex, i.uv);
	}
	
	ENDCG
	
	ZTest Always Cull Off ZWrite Off
	
	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
	}
}


速度缓存

两种方法:

  • 把场景所有物体的速度渲染到一张纹理中。缺点是需要修改所有物体的Shader,计算速度并输出到一张纹理。
  • 利用深度纹理计算每个像素世界空间下的位置。使用前一帧的变换矩阵计算,就可以得到前一帧该点的位置。通过这两个点就可以计算得到速度值。缺点是要在片元着色器中进行两次矩阵乘法。

使用深度纹理实现

在这里插入图片描述

MotionBlurWithDepthTexture类:

using UnityEngine;

// 使用深度纹理计算运动模糊
public class MotionBlurWithDepthTexture : PostEffectsBase
{
    [Range(0.0f, 1.0f)]
    public float blurSize = 0.5f;

    private Camera targetCamera;
    public Camera TargetCamera { get { return targetCamera = targetCamera == null ? GetComponent<Camera>() : targetCamera; } }

    private Matrix4x4 previousViewProjectionMatrix;         // 上一帧摄像机的 视角x投影 矩阵

    void OnEnable()
    {
        TargetCamera.depthTextureMode |= DepthTextureMode.Depth;  // 设置状态以获取摄像机的深度纹理
        previousViewProjectionMatrix = TargetCamera.projectionMatrix * TargetCamera.worldToCameraMatrix;
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (TargetMaterial != null)
        {
            TargetMaterial.SetFloat("_BlurSize", blurSize);

            // 上一帧的矩阵
            TargetMaterial.SetMatrix("_PreviousViewProjectionMatrix", previousViewProjectionMatrix);

            // 投影矩阵 * 视角矩阵 ,用于给下一帧计算该帧时的位置
            Matrix4x4 currentViewProjectionMatrix = TargetCamera.projectionMatrix * TargetCamera.worldToCameraMatrix;
            // 矩阵取逆,用于计算该帧的位置
            Matrix4x4 currentViewProjectionInverseMatrix = currentViewProjectionMatrix.inverse;
            TargetMaterial.SetMatrix("_CurrentViewProjectionInverseMatrix", currentViewProjectionInverseMatrix);
            previousViewProjectionMatrix = currentViewProjectionMatrix;
        }
        Graphics.Blit(src, dest, TargetMaterial);
    }
}

Shader:

Properties {
	_MainTex ("Base (RGB)", 2D) = "white" {}
	_BlurSize ("Blur Size", Float) = 1.0
}
SubShader {
	CGINCLUDE
	...
	
	struct v2f {
		float4 pos : SV_POSITION;
		half2 uv : TEXCOORD0;
		half2 uv_depth : TEXCOORD1;
	};
	
	v2f vert(appdata_img v) {
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		
		o.uv = v.texcoord;
		o.uv_depth = v.texcoord;
		
		#if UNITY_UV_STARTS_AT_TOP
		if (_MainTex_TexelSize.y < 0)
			o.uv_depth.y = 1 - o.uv_depth.y;
		#endif
				 
		return o;
	}
	
	fixed4 frag(v2f i) : SV_Target {
		// 深度值。通过摄像机的深度纹理和纹理坐标计算(映射)出来
		float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
		// 构建像素的NDC坐标,xy像素的纹理坐标映射,
		float4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d * 2 - 1, 1);
		// 当前帧的 视角x投影 矩阵的逆矩阵变换
		float4 D = mul(_CurrentViewProjectionInverseMatrix, H);
		// 并除w得到世界空间坐标 
		float4 worldPos = D / D.w;
		
		// 当前视角位置 
		float4 currentPos = H;

		// 上一帧位置
		float4 previousPos = mul(_PreviousViewProjectionMatrix, worldPos);
		previousPos /= previousPos.w;
		
		// 前一帧和当前帧位置差 求速度
		float2 velocity = (currentPos.xy - previousPos.xy)/2.0f;
		
		// 邻域像素采样,相加求平均
		float2 uv = i.uv;
		float4 c = tex2D(_MainTex, uv);
		uv += velocity * _BlurSize;
		for (int it = 1; it < 3; it++, uv += velocity * _BlurSize) {
			float4 currentColor = tex2D(_MainTex, uv);
			c += currentColor;
		}
		c /= 3;
		
		return fixed4(c.rgb, 1.0);
	}
	
	ENDCG
	
	Pass {      
		ZTest Always Cull Off ZWrite Off
		    	
		CGPROGRAM  
		
		#pragma vertex vert  
		#pragma fragment frag  
		  
		ENDCG  
	}
} 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值