Chapter12_屏幕後處理效果

1.定義:

屏幕後處理通常指在渲染完整個場景得到屏幕圖像後,再對這個圖像進行各種操作,實現各種屏幕特效。使用屏幕後處理技術,可以實現景深(Depth of Field),運動模糊(Motion Blur)等。

2.實現步驟

2.1 先抓取屏幕圖像,調用方法如下:

                    monoBehaviour.OnRenderImage(RenderTexture src,RenderTexture dest)

Unity會把當前渲染完場景得到的屏幕紋理 或 上一步處理後得到的渲染紋理存儲到 src 中。然後再經過一系列操作後再把 dest渲染到屏幕上。

2.2OnRenderImage函數中利用Graphics.Blit函數對 src 紋理進行操作。

public static void Blit(Texture source, RenderTexture dest);
public static void Blit(Texture source, RenderTexture dest, Material mat, int pass = -1);
public static void Blit(Texture source, Material mat, int pass = -1);
public static void Blit(Texture source, RenderTexture dest, Vector2 scale, Vector2 offset);
  • 參數source是屏幕紋理 或 上一步處理後得到的渲染紋理
  • 參數dest是目標渲染紋理,如果dest = null,會直接渲染到屏幕上。
  • 參數mat是使用的材質,source會被傳遞到Shader中的_MainTex的紋理屬性。
  • 參數pass默認= - 1,標識一次調用Shader中的所有Pass,否則只會調用給定索引的Pass

默認情況下 OnRenderImage函數在所有的不透明和透明的的Pass執行完畢後被調用,以便對場景中所有物體都產生影響,但有時希望在不透明的Pass執行完畢後被調用,這樣不對透明物體產生影響。此時可以在OnRenderImage添加ImageEffectOpaque函數來實現目的。

https://docs.unity3d.com/2018.3/Documentation/ScriptReference/Graphics.Blit.html


實踐:

前期準備,在傳入Shader參數時候,初始化一個Material,用於屏幕後處理。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class PostEffectsBase_Chan : MonoBehaviour {
    
	protected void CheckResources()
	{
		bool isSurrorted = SystemInfo.supportsImageEffects;
		if (!isSurrorted) {
			NotSupported ();
		}
	}

	protected void NotSupported()
	{

		this.enabled = false;
	}

	protected void Start()
	{
		CheckResources ();		
	}

	//指定一個Shader,並創建一個材質使用這個Shader
	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);
			_material.hideFlags = HideFlags.DontSave;
			if (_material) {
				return _material;
			}
			return null;
		}
	}
}

1.調整屏幕圖像的亮度,飽和度和對比度

1.1 寫一個可調節圖像亮度,飽和度,對比度的Shader。

Shader "Chan/Brightness Saturation And Contrast" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Brightness ("Brightness", Float) = 1
		_Saturation("Saturation", Float) = 1
		_Contrast("Contrast", Float) = 1
	}
	SubShader {
		Pass {  
			ZTest Always Cull Off 
			ZWrite Off
			
			CGPROGRAM  
			#pragma vertex vert  
			#pragma fragment frag  
			  
			#include "UnityCG.cginc"  
			  
			sampler2D _MainTex;  
			half _Brightness;
			half _Saturation;
			half _Contrast;
			  
			struct v2f {
				float4 pos : SV_POSITION;
				half2 uv: TEXCOORD0;
			};
			  
			v2f vert(appdata_img v) {
				v2f o;
				
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.uv = v.texcoord;
						 
				return o;
			}
		
			fixed4 frag(v2f i) : SV_Target {
				fixed4 renderTex = tex2D(_MainTex, i.uv);  
				  
				// Apply brightness
				fixed3 finalColor = renderTex.rgb * _Brightness;
				
				// Apply saturation
				fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
				fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
				finalColor = lerp(luminanceColor, finalColor, _Saturation);
				
				// Apply contrast
				fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
				finalColor = lerp(avgColor, finalColor, _Contrast);
				
				return fixed4(finalColor, renderTex.a);  
			}  
			  
			ENDCG
		}  
	}
	
	Fallback Off
}

1.2 腳本BrightnessSaturationAndContrast_Chan 繼承自 PostEffectsBase_Chan,並掛到Camera上。在腳本中調節Shader中的亮度,飽和度,對比度 三個變量的值。

using UnityEngine;
using System.Collections;

public class BrightnessSaturationAndContrast_Chan : PostEffectsBase_Chan {

	public Shader briSatConShader;
	private Material briSatConMaterial;
	public Material material {  
		get {
			briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
			return briSatConMaterial;
		}  
	}

	[Range(0.0f, 3.0f)]
	public float brightness = 1.0f;

	[Range(0.0f, 3.0f)]
	public float saturation = 1.0f;

	[Range(0.0f, 3.0f)]
	public float contrast = 1.0f;

	void OnRenderImage(RenderTexture src, RenderTexture dest) {
		if (material != null) {
			material.SetFloat("_Brightness", brightness);
			material.SetFloat("_Saturation", saturation);
			material.SetFloat("_Contrast", contrast);

			Graphics.Blit(src, dest, material);
		} else {
			Graphics.Blit(src, dest);
		}
	}
}

參數source是屏幕紋理 或 上一步處理後得到的渲染紋理。會傳遞給Shader中名字叫_MainTex的紋理,所以Shader中一定要定義名字爲_MainTex的紋理

由於屏幕後處理,其實是繪製了一個與屏幕同寬高的四邊形面片,因此需要關閉四邊形面片的深度寫入,爲了防止擋住場景中的物體。

2.邊緣檢測

邊緣檢測的原理是利用卷積覈對圖像上所有像素進行卷積操作,操作時候把卷積核中心放到該像素上。

將原始像素點"1",使用中間卷積核,進行卷積操作得到卷積結果"8"。 

常用卷積算子的優缺點:

Shader "Chan/Chapter 12/Edge Detection" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_EdgeOnly ("Edge Only", Float) = 1.0
		//邊緣顏色
		_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
		//背景顏色
		_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
	}
	SubShader {
		Pass {  
			ZTest Always Cull Off 
			ZWrite Off
			
			CGPROGRAM
			
			#include "UnityCG.cginc"
			
			#pragma vertex vert  
			#pragma fragment fragSobel
			
			sampler2D _MainTex;  
			uniform half4 _MainTex_TexelSize;
			fixed _EdgeOnly;
			fixed4 _EdgeColor;
			fixed4 _BackgroundColor;
			
			struct v2f {
				float4 pos : SV_POSITION;
				half2 uv[9] : TEXCOORD0;
			};

			v2f vert(appdata_img v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				half2 uv = v.texcoord;

				//由於卷積操作需要採樣相鄰區域的紋理,因此先取得相鄰區域的uv座標
				//採樣紋理座標放到頂點着色器中,提高性能
				o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
				o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
				o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
				o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
				o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
				o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
				o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
				o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
				o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
						 
				return o;
			}
			
			//將顏色轉換爲亮度(灰度)
			fixed luminance(fixed4 color) {
				return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
			}

			//卷子算子Sobel 進行邊緣檢測 得到當前像素的梯度值
			half Sobel(v2f i) {
				//水平方向的卷積核Gx
				const half Gx[9] = {-1,  0,  1,
										-2,  0,  2,
										-1,  0,  1};
				//垂直方向的卷積核Gy
				const half Gy[9] = {-1, -2, -1,
										0,  0,  0,
										1,  2,  1};		
				
				half texColor;
				half edgeX = 0;
				half edgeY = 0;
				for (int it = 0; it < 9; it++) {
					texColor = luminance(tex2D(_MainTex, i.uv[it]));
					edgeX += texColor * Gx[it];
					edgeY += texColor * Gy[it];
				}
				
				//1. 本來是開跟操作,處於性能考慮 使用絕對值代替
				//2. 1 - (橫豎方向梯度) = 值越小,越有可能是邊緣點
				half edge = 1 - abs(edgeX) - abs(edgeY);
				
				return edge;
			}
			
			fixed4 fragSobel(v2f i) : SV_Target {
				half edge = Sobel(i);

				//使用edge 梯度值計算原圖下的顏色值
				fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
				//使用edge 梯度值計算純色背景下的顏色值
				fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
				//使用_EdgeOnly 融合邊緣顏色和原圖顏色值
				return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
 			}
			
			ENDCG
		} 
	}
	FallBack Off
}

 

https://blog.csdn.net/zouxy09/article/details/49080029

https://blog.csdn.net/tigerda/article/details/61192943

3.高斯模糊

利用高斯卷積核進行卷積計算,高斯核是正方形大小的濾波核。每個元素計算都基於二維高斯方程。

高斯函數二維正態分佈: 

 

 模擬了距離越近的元素,對中心點的影響越大。

出於性能考慮,將二維高斯核改爲兩個一維高速核。同時去掉一維高斯核中重複的權重。 

Shader "Chan/Chapter 12/Gaussian Blur" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BlurSize ("Blur Size", Float) = 1.0
	}
	SubShader {

		//CGINCLUDE ENDCG 類似C++頭文件
		CGINCLUDE
		
		#include "UnityCG.cginc"
		
		sampler2D _MainTex;  
		half4 _MainTex_TexelSize;
		float _BlurSize;
		  
		struct v2f {
			float4 pos : SV_POSITION;
			half2 uv[5]: TEXCOORD0;
		};
		
		// 由於使用的是5x5的高斯核進行模糊計算,因此取中心點相鄰的4個垂直uv區域,用於採樣紋理  
		v2f vertBlurVertical(appdata_img v) {
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			
			o.uv[0] = uv;//當前操作的紋理的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;
		}
		
		// 由於使用的是5x5的高斯核進行模糊計算,因此取中心點相鄰的4個水平uv區域,用於採樣紋理  
		v2f vertBlurHorizontal(appdata_img v) {
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			
			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};
			
			fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
			
			for (int it = 1; it < 3; it++) {
				sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
				sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
			}
			
			return fixed4(sum, 1.0);
		}
		    
		ENDCG
		
		ZTest Always Cull Off 
		ZWrite Off
		
		Pass {
			//使用Name語義,其他Shader 通過Name後的名字就可直接調用這個Pass
			NAME "GAUSSIAN_BLUR_VERTICAL"
			
			CGPROGRAM
			  
			#pragma vertex vertBlurVertical  
			#pragma fragment fragBlur
			  
			ENDCG  
		}
		
		Pass {  
			NAME "GAUSSIAN_BLUR_HORIZONTAL"
			
			CGPROGRAM  
			
			#pragma vertex vertBlurHorizontal  
			#pragma fragment fragBlur
			
			ENDCG
		}
	} 
	FallBack Off
}

註解: 

  •  本例中使用 5x5 的二維高斯核進行模糊計算,但是被分爲垂直方向和水平方向的兩個一維數組,因此Shader中 vertBlurVertical vertBlurHorizontal 分別取下圖中當前計算的UV區域(塗黑區域)相鄰的上下和左右2個UV區域
  • 由於“距離”計算中心點(塗黑區域)大小一樣的區域,具有相同的權重。橫豎兩個一維5個元素的數組,定義3個權重即可。
  • Shader 中fragBlur根據UV採樣到計算點(塗黑區域)相鄰的紋理,然後乘以區域相對應的權重

 配合使用的C#腳本:

using UnityEngine;
using System.Collections;

public class GaussianBlur : PostEffectsBase {

	public Shader gaussianBlurShader;
	private Material gaussianBlurMaterial = null;

	public Material material {  
		get {
			gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
			return gaussianBlurMaterial;
		}  
	}

	//高斯模糊迭代次數
	[Range(0, 4)]
	public int iterations = 3;
	
	//模糊範圍
	[Range(0.2f, 3.0f)]
	public float blurSpread = 0.6f;
	
	//縮放係數
	[Range(1, 8)]
	public int downSample = 2;
	
	//
	void OnRenderImage(RenderTexture src, RenderTexture dest) {
		if (material != null) {
			int rtW = src.width;
			int rtH = src.height;
			// 使用RenderTexture.GetTemporary創建一個與屏幕大小相當的緩存
			RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);

			// 將Shader中第一個Pass對豎直方向進行高斯核卷積操作計算得到的圖像放到緩存中
			Graphics.Blit(src, buffer, material, 0);
			// 使用Shader中的第二個Pass對buffer進行水平方向的高斯核卷積操作
			Graphics.Blit(buffer, dest, material, 1);
			// 釋放創建的緩存
			RenderTexture.ReleaseTemporary(buffer);
		} else {
			Graphics.Blit(src, dest);
		}
	} 

	//圖片降採樣處理
	// void OnRenderImage (RenderTexture src, RenderTexture dest) {
	// 	if (material != null) {
	// 		int rtW = src.width/downSample;
	// 		int rtH = src.height/downSample;
	// 		// 截取的屏幕紋理縮小,並將紋理濾波模式設置爲雙線性。因此處理的像素數量變低
	// 		RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
	// 		buffer.filterMode = FilterMode.Bilinear;

	// 		// 將Shader中第一個Pass對豎直方向進行高斯核卷積操作計算得到的圖像放到緩存中
	// 		Graphics.Blit(src, buffer, material, 0);
	// 		// 使用Shader中的第二個Pass對buffer進行水平方向的高斯核卷積操作
	// 		Graphics.Blit(buffer, dest, material, 1);
	// 		// 釋放創建的緩存
	// 		RenderTexture.ReleaseTemporary(buffer);
	// 	} else {
	// 		Graphics.Blit(src, dest);
	// 	}
	// }

	// 圖片降採樣處理 + 增加高斯模糊的迭代次數
	// void OnRenderImage (RenderTexture src, RenderTexture dest) {
	// 	if (material != null) {
	// 		int rtW = src.width/downSample;
	// 		int rtH = src.height/downSample;

	// 		RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
	// 		buffer0.filterMode = FilterMode.Bilinear;

	// 		Graphics.Blit(src, buffer0);

	// 		for (int i = 0; i < iterations; i++) {
	// 			material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

	// 			//將Shader中第一個Pass對豎直方向進行高斯核卷積操作計算得到的圖像放到緩存中
	// 			RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
	// 			Graphics.Blit(buffer0, buffer1, material, 0);
	// 			RenderTexture.ReleaseTemporary(buffer0);

	// 			buffer0 = buffer1;

	// 			//使用Shader中的第二個Pass對buffer進行水平方向的高斯核卷積操作
	// 			buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
	// 			Graphics.Blit(buffer0, buffer1, material, 1);
	// 			RenderTexture.ReleaseTemporary(buffer0);
	// 			buffer0 = buffer1;
	// 		}

	// 		Graphics.Blit(buffer0, dest);
	// 		RenderTexture.ReleaseTemporary(buffer0);
	// 	} else {
	// 		Graphics.Blit(src, dest);
	// 	}
	// }
}

4.Bloom效果

原理:

  1. 根據一個閾值提取圖像紅較亮的區域,把它們存儲在一張渲染紋理中。
  2. 然後再利用高斯模糊對渲染紋理進行模糊處理,模擬管線擴散效果。
  3. 最後和原圖像進行混合,得到最終效果。

Shader "Chan/Chapter 12/Bloom" {
	Properties
	{
		_MainTex("Base Tex",2D) = "white"{}
		//存儲高斯模糊後的較亮區域
		_Bloom("Bloom",2D) = "black"{}
		//提取較亮區域使用的閾值
		_LuminanceThreshold("Luminance Threshold",Float) = 0.5
		//控制不同迭代之間高斯模糊的模糊區域範圍
		_BlurSize("Blur Size",Float) = 1.0
	}

	SubShader
	{
		CGINCLUDE

		#include "UnityCG.cginc"

		sampler2D _MainTex;
		half4 _MainTex_TexelSize;
		sampler2D _Bloom;
		float _LuminanceThreshold;
		float _BlurSize;

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

		v2f vertExtractBright(appdata_img v)
		{
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv = v.texcoord;
			return o;
		}

		//將顏色轉化爲亮度(灰度)
		fixed luminance(fixed4 color)
		{
			return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
		}

		//提取得到的亮度值和原紋理顏色值相乘,得到亮部區域
		fixed4 fragExtractBright(v2f i):SV_Target
		{
			float4 c = tex2D(_MainTex,i.uv);
			fixed val = clamp(luminance(c) - _LuminanceThreshold,0.0,1.0);
			return c * val;
		}

		struct v2fBloom
		{
			float4 pos:SV_POSITION;
			half4 uv:TEXCOORD0;
		};

		v2fBloom vertBloom(appdata_img v)
		{
			v2fBloom o;
			o.pos = UnityObjectToClipPos(v.vertex);
			//獲得原紋理uv座標
			o.uv.xy = v.texcoord;
			//獲得bloom後的紋理uv座標
			o.uv.zw = v.texcoord;

			#if UNITY_UV_STARTS_AT_TOP			
			if (_MainTex_TexelSize.y < 0.0)
				o.uv.w = 1.0 - o.uv.w;
			#endif

			return o;
		}

		//原紋理和高斯模糊後的亮部區域紋理疊加
		fixed4 fragBloom(v2fBloom i):SV_Target
		{
			return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom,i.uv.zw);
		}

		ENDCG

		
		ZTest Always Cull Off ZWrite Off
		//根據閾值提取得到亮部紋理
		Pass
		{
			CGPROGRAM
			#pragma vertex vertExtractBright
			#pragma fragment fragExtractBright
			ENDCG
		}

		//垂直方向 & 水平方向 利用高斯核進行模糊計算 
		UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_VERTICAL"
		UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_HORIZONTAL"

		//原紋理和高斯模糊紋理疊加,得到最終輸出顯示紋理
		Pass
		{
			CGPROGRAM
			#pragma vertex vertBloom
			#pragma fragment fragBloom
			ENDCG
		}
	}
	FallBack Off
}

配合使用的C#:

using UnityEngine;
using System.Collections;

public class Bloom : PostEffectsBase {

	public Shader bloomShader;
	private Material bloomMaterial = null;
	public Material material {  
		get {
			bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
			return bloomMaterial;
		}  
	}

	// Blur iterations - larger number means more blur.
	[Range(0, 4)]
	public int iterations = 3;
	
	// Blur spread for each iteration - larger value means more blur
	[Range(0.2f, 3.0f)]
	public float blurSpread = 0.6f;

	[Range(1, 8)]
	public int downSample = 2;

	//設置一個閾值,提取紋理亮部
	[Range(0.0f, 4.0f)]
	public float luminanceThreshold = 0.6f;

	void OnRenderImage (RenderTexture src, RenderTexture dest) {
		if (material != null) {
			material.SetFloat("_LuminanceThreshold", luminanceThreshold);

			int rtW = src.width/downSample;
			int rtH = src.height/downSample;
			
			RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
			buffer0.filterMode = FilterMode.Bilinear;
			
			//創建一個紋理,調用Shader 中的 Bloom的第一個Pass,計算高亮部分
			Graphics.Blit(src, buffer0, material, 0);
			
			for (int i = 0; i < iterations; i++) {
				material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
				
				RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
				
				//material 中shader的 第二pass進行垂直方向的莫斯計算
				//對應Shader Bloom的 UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_VERTICAL"
				Graphics.Blit(buffer0, buffer1, material, 1);
				
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
				
				//UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_HORIZONTAL"
				Graphics.Blit(buffer0, buffer1, material, 2);
				
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}

			//計算好的帶亮度的高斯模糊的紋理,傳入Shader中的_Bloom,調用第四個Pass,和原紋理疊加
			material.SetTexture ("_Bloom", buffer0);  
			Graphics.Blit (src, dest, material, 3);  

			RenderTexture.ReleaseTemporary(buffer0);
		} else {
			Graphics.Blit(src, dest);
		}
	}
}

 

5.運動模糊

實現方法:

  1. 利用一塊累積緩存來混合多張連續的圖像,當物體快速移動產生多張圖像後,取它們之間的平均值作爲最後的運動模糊圖像。此方法,性能消耗很大。
  2. 利用速度緩存,緩存中存儲了當前各個像素當前的運動速度,然後利用該值決定模糊的方向和大小。
Shader "Chan/Chapter 12/Motion Blur" {
	Properties
	{
		_MainTex("Base Tex",2D) = "white"{}
		//模糊參數
		_BlurAmount("Blur Amount",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 = UnityObjectToClipPos(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

		ZTest Always Cull Off 
		ZWrite Off

		Pass
		{
			//開啓混合,並設置混合模式
			Blend SrcAlpha OneMinusSrcAlpha
			//ColorMask 可以指定渲染結果的輸出通道,而不是通常的 RGBA 四個通道都被寫入。
			ColorMask RGB

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment fragRGB

			ENDCG
		}

		Pass 
		{
			//開啓混合,並設置混合模式
			Blend One Zero
			//ColorMask 可以指定渲染結果的輸出通道,而不是通常的 RGBA 四個通道都被寫入。
			ColorMask A

			CGPROGRAM

			//爲了保證顏色緩存的透明度不變,這樣保證後續屏幕後處理中有關透明度操作沒問題
			#pragma vertex vert
			#pragma fragment fragA

			ENDCG
		}

	}
	FallBack Off
}

註解:

分爲兩個Pass渲染,是爲了

            //爲了保證顏色緩存的透明度不變,這樣保證後續屏幕後處理中有關透明度操作沒問題
 

配合使用的C#

using UnityEngine;
using System.Collections;

public class MotionBlur : PostEffectsBase {

	public Shader motionBlurShader;
	private Material motionBlurMaterial = null;

	public Material material {  
		get {
			motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);
			return motionBlurMaterial;
		}  
	}

	//模糊參數
	[Range(0.0f, 0.9f)]
	public float blurAmount = 0.5f;
	
	private RenderTexture accumulationTexture;

	void OnDisable() {
		DestroyImmediate(accumulationTexture);
	}

	void OnRenderImage (RenderTexture src, RenderTexture dest) {
		if (material != 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;
				Graphics.Blit(src, accumulationTexture);
			}

			//表明我們需要進行一個渲染紋理的操作,但是爲了疊加紋理,不清空accumulationTexture 
			//爲了不讓Unity發出警告誤以爲是忘記清空了。所以寫這麼一句
			accumulationTexture.MarkRestoreExpected();

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

			//把當前屏幕圖像src疊加到 accumulationTexture中
			Graphics.Blit (src, accumulationTexture, material);
			//把混合結果顯示到屏幕中
			Graphics.Blit (accumulationTexture, dest);
		} else {
			Graphics.Blit(src, dest);
		}
	}
}

 註解:

            //進行一個渲染紋理的操作,但是爲了疊加紋理,不清空accumulationTexture 
            //爲了不讓Unity發出警告誤以爲是忘記清空了。所以寫這麼一句
            accumulationTexture.MarkRestoreExpected();

 

https://github.com/candycat1992/Unity_Shaders_Book/issues/51 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章