Unity Shader, Post Processing, Blur, Bloom, Depth of Field

掛在攝像機上的腳本,把source抄到中間臨時紋理,然後再到destination

 			void OnRenderImage (RenderTexture source, RenderTexture destination) {
	    		RenderTexture r = RenderTexture.GetTemporary(
					source.width, source.height, 0, source.format
				);
	    		
	    		Graphics.Blit(source, r);
				Graphics.Blit(r, destination);
				
				RenderTexture.ReleaseTemporary(r);
	    	}

Blur:利用GPU內置的雙線性過濾,最爲簡單並且運行效率最高。通過改變臨時紋理分辨率爲一半(除以2)downsampling,在回到destination,upsampling即可。
這樣可以除以2、4、8、16、32……用更小分辨率的中間紋理得到更模糊的圖像,但是效果不佳。
爲提高效果,要漸進處理不要直接使用source直接blit到小分辨率中間紋理上。

	[Range(1, 16)]
	public int iterations = 4;

	RenderTexture[] textures = new RenderTexture[16];

	void OnRenderImage (RenderTexture source, RenderTexture destination) {
		int width = source.width / 2;
		int height = source.height / 2;
		RenderTextureFormat format = source.format;

		RenderTexture currentDestination = textures[0] = 
			RenderTexture.GetTemporary(width, height, 0, format);
		Graphics.Blit(source, currentDestination);
		RenderTexture currentSource = currentDestination;

		int i = 1;
		for (; i < iterations; i++) {
			width /= 2;
			height /= 2;
			if (height < 2) {
				break;
			}
			currentDestination = textures[i] =
				RenderTexture.GetTemporary(width, height, 0, format);
			Graphics.Blit(currentSource, currentDestination);
			currentSource = currentDestination;
		}	

		for (i -= 2; i >= 0; i--) {
			currentDestination = textures[i];
			textures[i] = null;
			Graphics.Blit(currentSource, currentDestination);
			RenderTexture.ReleaseTemporary(currentSource);
			currentSource = currentDestination;
		}

		Graphics.Blit(currentSource, destination);
		RenderTexture.ReleaseTemporary(currentSource);
	}

改善GPU雙線性的效果
要用自己box filter kernel代替GPU的採樣,要使用自己的shader

Shader "Custom/Bloom" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
	}

	CGINCLUDE
		#include "UnityCG.cginc"

		sampler2D _MainTex;
		float4 _MainTex_TexelSize;

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

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

		// vs沒有特別用處,ps定義在不同的pass裏
		Interpolators VertexProgram (VertexData v) {
			Interpolators i;
			i.pos = UnityObjectToClipPos(v.vertex);
			i.uv = v.uv;
			return i;
		}

		// 採樣,只是rgb
		half3 Sample (float2 uv) {
			return tex2D(_MainTex, uv).rgb;
		}

		// box 採樣,通過delta來決定是up還是down
		half3 SampleBox (float2 uv, float delta) {
			float4 o = _MainTex_TexelSize.xyxy * float2(-delta, delta).xxyy;
			half3 s =
				Sample(uv + o.xy) + Sample(uv + o.zy) +
				Sample(uv + o.xw) + Sample(uv + o.zw);
			return s * 0.25f;
		}
	ENDCG

	SubShader {
		Cull Off
		ZTest Always
		ZWrite Off

		Pass { // 0
			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram
				
				// 傳1,down
				half4 FragmentProgram (Interpolators i) : SV_Target {
					return half4(SampleBox(i.uv, 1), 1);
				}
			ENDCG
		}

		Pass { // 1
			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram
				
				// 傳0.5,up
				half4 FragmentProgram (Interpolators i) : SV_Target {
					return half4(SampleBox(i.uv, 0.5), 1);
				}
			ENDCG
		}
	}
}

shader通過一個材質給Blit使用:

	[NonSerialized]
	Material bloom;
	
	// Blit使用那個pass
	const int BoxDownPass = 0;
	const int BoxUpPass = 1;
	
	void OnRenderImage (RenderTexture source, RenderTexture destination) {
		if (bloom == null) {
			bloom = new Material(bloomShader);
			bloom.hideFlags = HideFlags.HideAndDontSave;
		}
	
		…
		Graphics.Blit(source, currentDestination, bloom, BoxDownPass);
		…
			Graphics.Blit(currentSource, currentDestination, bloom, BoxDownPass);
		…
			Graphics.Blit(currentSource, currentDestination, bloom, BoxUpPass);
		…
		Graphics.Blit(currentSource, destination, bloom, BoxUpPass);
		…
	}

Bloom
bloom的第一步就是blur,第二步就是把blur的圖像和原始圖做additive
增加第3個pass,和對原始紋理的採樣

sampler2D _MainTex, _SourceTex;
Pass { // 2
			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram

				half4 FragmentProgram (Interpolators i) : SV_Target {
					half4 c = tex2D(_SourceTex, i.uv);
					c.rgb += SampleBox(i.uv, 0.5);
					return c;
				}
			ENDCG
		}

腳本使用pass:

	const int BoxDownPass = 0;
	const int BoxUpPass = 1;
	const int ApplyBloomPass = 2;
	
	bloom.SetTexture("_SourceTex", source);
	Graphics.Blit(currentSource, destination, bloom, ApplyBloomPass);
	RenderTexture.ReleaseTemporary(currentSource);

threshold, soft threshold
只有很亮的地方纔應該收到bloom影響,要排除掉不受影響的像素就用threshold來調節。
在shader中加入prefilter來在blur前處理,這裏使用了threshold和soft threshold來控制最後的結果,公式爲:
(b-t+k)(b-t+k)/4k
b:亮度
t:threshold
k:threshold
softThreshold

half3 Prefilter (half3 c) {
			// 亮度,由rgb最大值決定
			half brightness = max(c.r, max(c.g, c.b));
			// 以下公式開始,變量叫knee由於公式的圖像爲膝蓋形……
			half knee = _Threshold * _SoftThreshold;
			half soft = brightness - _Threshold + knee;
			soft = clamp(soft, 0, 2 * knee); // clamp了一下
			soft = soft * soft / (4 * knee + 0.00001); // 防止除0
			half contribution = max(soft, brightness - _Threshold);
			// 除以亮度
			contribution /= max(brightness, 0.00001); // 防止除0
			return c * contribution;
		}

intensity
intensity爲強度,在各處乘一下。

...
c.rgb += _Intensity * SampleBox(i.uv, 0.5);
...
return half4(_Intensity * SampleBox(i.uv, 0.5), 1);

完整代碼:
爲了效率一部分公式運算從shader移出到了cs代碼,用一個vector。
bloom.SetVector("_Filter", filter);

BloomEffect.cs

using UnityEngine;
using System;

[ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class BloomEffect : MonoBehaviour {

	const int BoxDownPrefilterPass = 0;
	const int BoxDownPass = 1;
	const int BoxUpPass = 2;
	const int ApplyBloomPass = 3;
	const int DebugBloomPass = 4;

	public Shader bloomShader;

	[Range(0, 10)]
	public float intensity = 1;

	[Range(1, 16)]
	public int iterations = 4;

	[Range(0, 10)]
	public float threshold = 1;

	[Range(0, 1)]
	public float softThreshold = 0.5f;

	public bool debug;

	RenderTexture[] textures = new RenderTexture[16];

	[NonSerialized]
	Material bloom;

	void OnRenderImage (RenderTexture source, RenderTexture destination) {
		if (bloom == null) {
			bloom = new Material(bloomShader);
			bloom.hideFlags = HideFlags.HideAndDontSave;
		}

		float knee = threshold * softThreshold;
		Vector4 filter;
		filter.x = threshold;
		filter.y = filter.x - knee;
		filter.z = 2f * knee;
		filter.w = 0.25f / (knee + 0.00001f);
		bloom.SetVector("_Filter", filter);
		bloom.SetFloat("_Intensity", Mathf.GammaToLinearSpace(intensity));

		int width = source.width / 2;
		int height = source.height / 2;
		RenderTextureFormat format = source.format;

		RenderTexture currentDestination = textures[0] =
			RenderTexture.GetTemporary(width, height, 0, format);
		Graphics.Blit(source, currentDestination, bloom, BoxDownPrefilterPass);
		RenderTexture currentSource = currentDestination;

		int i = 1;
		for (; i < iterations; i++) {
			width /= 2;
			height /= 2;
			if (height < 2) {
				break;
			}
			currentDestination = textures[i] =
				RenderTexture.GetTemporary(width, height, 0, format);
			Graphics.Blit(currentSource, currentDestination, bloom, BoxDownPass);
			currentSource = currentDestination;
		}

		for (i -= 2; i >= 0; i--) {
			currentDestination = textures[i];
			textures[i] = null;
			Graphics.Blit(currentSource, currentDestination, bloom, BoxUpPass);
			RenderTexture.ReleaseTemporary(currentSource);
			currentSource = currentDestination;
		}

		if (debug) {
			Graphics.Blit(currentSource, destination, bloom, DebugBloomPass);
		}
		else {
			bloom.SetTexture("_SourceTex", source);
			Graphics.Blit(currentSource, destination, bloom, ApplyBloomPass);
		}
		RenderTexture.ReleaseTemporary(currentSource);
	}
}

Bloom.shader

Shader "Custom/Bloom" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
	}

	CGINCLUDE
		#include "UnityCG.cginc"

		sampler2D _MainTex, _SourceTex;
		float4 _MainTex_TexelSize;

		half4 _Filter;

		half _Intensity;

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

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

		Interpolators VertexProgram (VertexData v) {
			Interpolators i;
			i.pos = UnityObjectToClipPos(v.vertex);
			i.uv = v.uv;
			return i;
		}

		half3 Sample (float2 uv) {
			return tex2D(_MainTex, uv).rgb;
		}

		half3 SampleBox (float2 uv, float delta) {
			float4 o = _MainTex_TexelSize.xyxy * float2(-delta, delta).xxyy;
			half3 s =
				Sample(uv + o.xy) + Sample(uv + o.zy) +
				Sample(uv + o.xw) + Sample(uv + o.zw);
			return s * 0.25f;
		}

		half3 Prefilter (half3 c) {
			half brightness = max(c.r, max(c.g, c.b));
			half soft = brightness - _Filter.y;
			soft = clamp(soft, 0, _Filter.z);
			soft = soft * soft * _Filter.w;
			half contribution = max(soft, brightness - _Filter.x);
			contribution /= max(brightness, 0.00001);
			return c * contribution;
		}

	ENDCG

	SubShader {
		Cull Off
		ZTest Always
		ZWrite Off

		Pass { // 0
			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram

				half4 FragmentProgram (Interpolators i) : SV_Target {
					return half4(Prefilter(SampleBox(i.uv, 1)), 1);
				}
			ENDCG
		}

		Pass { // 1
			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram

				half4 FragmentProgram (Interpolators i) : SV_Target {
					return half4(SampleBox(i.uv, 1), 1);
				}
			ENDCG
		}

		Pass { // 2
			Blend One One

			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram

				half4 FragmentProgram (Interpolators i) : SV_Target {
					return half4(SampleBox(i.uv, 0.5), 1);
				}
			ENDCG
		}

		Pass { // 3
			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram

				half4 FragmentProgram (Interpolators i) : SV_Target {
					half4 c = tex2D(_SourceTex, i.uv);
					c.rgb += _Intensity * SampleBox(i.uv, 0.5);
					return c;
				}
			ENDCG
		}

		Pass { // 4
			CGPROGRAM
				#pragma vertex VertexProgram
				#pragma fragment FragmentProgram

				half4 FragmentProgram (Interpolators i) : SV_Target {
					return half4(_Intensity * SampleBox(i.uv, 0.5), 1);
				}
			ENDCG
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章