unity shader学习---屏幕后处理

效果图

【边缘检测】
Edges Only = 0
在这里插入图片描述
Edges Only = 1
在这里插入图片描述
【高斯模糊】
在这里插入图片描述
Down Sample = 8,图像像素化
在这里插入图片描述
Blur spread = 20, 图像虚影

【Bloom效果】
在这里插入图片描述得到了一只发光的喵喵

理论

屏幕后处理脚本系統:
首先在摄像中添加一个用于屏幕后处理的脚本。在这 脚本中 ,实现 OnRenderlmage抓取屏幕。然后再调用 Graphics.Blit 函数使用特定的 Unity Shader 来对当前图像进行处理,返回的渲染纹理显示到屏幕上。一些复杂的屏幕特效需要多次调用 Graphics.Blit。

  • OnRenderlmage

MonoBehaviour .OnRenderimage (RenderTexture src, RenderTexture dest)

Unity 会把当前渲染得到的图像存储在第一 参数对应的源渲染纹理中,通过函数中的一系列操作后,再把目标渲染纹理即第二 参数对应的渲染纹理显示到屏幕上

  • Grapbics.Bllt

参数 src 对应了源纹理(传递给 Shader 中名为 MainTex 的纹理属性),参数 dest 是目标渲染纹理,参数 mat 我们使用的材质,这个材质使用 Unity Shader 将会进行各种屏幕后处理操作,参数 pass 的默认值-1, 表示将会 Shader 的所有 Pass ,否则只会调用给定索引的 Pass

边缘检测

【原理】
利用一些边缘检测算子对图像进行卷积 (convolution) 操作
在这里插入图片描述
它们都包含了两个方向的卷积核,分别用于检测水
平方向和竖直方向上的边缘信息,整体的梯度为:
在这里插入图片描述
出于性能的考虑,有时会使用绝对值操作来代替开根号操作:
在这里插入图片描述
梯度值越大,越有可能是边缘点

【代码解析】

  • 摄像机的CS脚本
using UnityEngine;
using System.Collections;

// 继承基类
public class EdgeDetection : PostEffectsBase {
   // 声明该效果需要的 Shader, 并据此创建相应的材质:
	public Shader edgeDetectShader;
	private Material edgeDetectMaterial = null;
	public Material material {  
		get {
			edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
			return edgeDetectMaterial;
		}  
	}

	// 在脚本中提供用于调整边缘线强度、描边颜色以及背景颜色的参数:
	[Range(0.0f, 1.0f)]
	public float edgesOnly = 0.0f; // edgesOnly 值为0时,边缘将会叠加在原渲染图像上:当 dgesOnly 值为1时,则会只显示边缘

	public Color edgeColor = Color.black;

	// public Color backgroundColor = Color.white; 先声明,再在OnRenderImage里使用,决定了相机的控制面板,跟shader里的properties类似

	void OnRenderImage (RenderTexture src, RenderTexture dest) {
		if (material != null) {
			material.SetFloat("_EdgeOnly", edgesOnly);
			material.SetColor("_EdgeColor", edgeColor);
			// material.SetColor("_BackgroundColor", backgroundColor);

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

大概的流程为:继承基类、声明该效果需要的 Shader, 并据此创建相应的材质、提供参数、定义 OnRenderlmage 函数来进行真正的特效处理

每当 OnRenderlmage 函数被调用时 ,它会检查材质是否可用。如果可用,就把参数传递给材质,再调用 Graphics Blit 进行处理; 否则直接把原图像显示到屏幕上,不做任何处理。

  • shader
    状态设置: 屏幕后处理的 shader 的“标配”: ZTest Always Cull Off ZWrite Off
    顶点着色器: 通常比较简单,输出Pose,uv[9]
    片元着色器:调用 Sobel 函数计算当前像素的梯度值 edge,然后利用_EdgeOnly 在背景为原图纯色之间插值
    关闭该 Unity Shader Fallback:
Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_EdgeOnly ("Edge Only", Float) = 1.0 // // edgesOnly 值为0时,边缘将会叠加在原渲染图像上:当 dgesOnly 值为1时,则会只显示边缘
		_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
		_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
	}
SubShader {
		Pass {  
			ZTest Always Cull Off ZWrite Off

用于屏幕后处理的 shader 的“标配”:
ZTest Always: 无论被不被遮住,都一直绘制
ZWrite Off:为了防止它“挡住”在其后面被渲染的物体。例如,如果当前的 OnRenderlmage 函数在所有不透明的 Pass 执行完毕后立即被调用,不关闭深度写入就会影响后面透明的 Pass 的渲染。

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;

	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;
}

维数为9的纹理数组,对应了使用 Sobel 算子采样时需要的9个邻域纹理坐标
计算采样纹理坐标的代码从FS移到VS中,可以减少运算,提高性能。
从顶点着色器到片元着色器的插值是线性的,因此这样的转移并不会影响纹理坐标的计算结果。

VS

fixed4 fragSobel(v2f i) : SV_Target {
	half edge = Sobel(i); // 调用 Sobel 函数计算当前像素的梯度值 edge
	
	fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge); // 背景为原图
	fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge); // 背景为纯色
	return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly); // 上面两幅图的插值
}

本例子仅仅利用了屏幕中的颜色信息,而在实际应用中,物体的纹理、阴影等信息均会影响边缘检测的结果,使得结果包含许多非预期的描边。
在这里插入图片描述

高斯模糊

【原理】
均值模糊:
使用了卷积操作,卷积核中的各个元素值都相等,且相加等于1, 卷积后得到的像素值是其邻域内各个像素值的平均值。
中值模糊:
选择邻域内对所有像素排序后的中值替换掉原颜色。
高斯模糊:
在这里插入图片描述
邻域像素距离越近,对当前处理像素的影响程度越大
可以把二维高斯函数拆分成两个一维函数,先后对图像进行滤波,它们得到的结果和直接使用二维高斯核是的,但采样次数只需要 2xNxWxH 。同时,两个一维高斯核中包含了很多重复的权重,实际只需要记录3个权重值即可

【代码解析】
第一个 Pass 将会使用竖直方向的一维高斯核对图像进行滤波
第二个 Pass 再使用水平方向的维高斯核对图像进行滤波,得到最终的目标图像。

  • 摄像机CS脚本
    继承基类、声明该效果需要的 Shader, 并据此创建相应的材质、提供了调整高斯模糊迭代次数 模糊范围和缩放系数的参数、定义 OnRenderlmage 函数来进行真正的特效处理
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 函数使用特定的 Unity Shader 来对当前图像进行处理,把返回的渲染纹理显示到屏幕上
		Graphics.Blit(src, buffer0);

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

			RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

			// Render the vertical pass
			Graphics.Blit(buffer0, buffer1, material, 0); // 第一个Pass

			RenderTexture.ReleaseTemporary(buffer0);
			buffer0 = buffer1;
			buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

			// Render the horizontal pass
			Graphics.Blit(buffer0, buffer1, material, 1); // 第二个Pass

			RenderTexture.ReleaseTemporary(buffer0);
			buffer0 = buffer1;
		}

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

迭代次数 模糊范围和缩放系数
1、BlurSize:
BlurSize越大,模糊程度越高,但采样数却不会受到影响,但过大会造成虚影
2、downSample:
声明缓冲区的大小时 使用了小于原屏幕分辨率的尺寸,downSample 越大,需要处理的像素数越少,同时也能进一步提高模糊程度过大的 downSample 可能会使图像像素化
3、迭代次数:
利用两个临时缓存在迭代之间进行交替。在迭代开始前,定义buffer0, 并把 src 中的图像缩放后存储到 buffer0 中。在迭代过程中,定义 bufferl 。
在执行第一个 Pass 时,输入 buffer0, 输出是 bufferl, 完毕后首先把 buffer0 释放,再把结果值 buffer1 存储到 buffer0 中,重新分配 bufferl, 然后再调用第二个Pass, 重复上述过程。

在这里插入图片描述

  • shader
Properties {
	_MainTex ("Base (RGB)", 2D) = "white" {}
	_BlurSize ("Blur Size", Float) = 1.0
}

使用 CGINCLUDE 来组织代码,码不需要包含在任何 Pass 语义块中
在这里插入图片描述

sampler2D _MainTex;  
half4 _MainTex_TexelSize;
float _BlurSize;

VS:

 struct v2f {
	float4 pos : SV_POSITION;
	half2 uv[5]: TEXCOORD0; // 5维高斯核
};

// 计算采样纹理坐标
v2f vertBlurVertical(appdata_img v) { 
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	half2 uv = v.texcoord;
	o.uv[0] = 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;
}

v2f vertBlurHorizontal(appdata_img v) {

FS:

fixed4 fragBlur(v2f i) : SV_Target {
	float weight[3] = {0.4026, 0.2442, 0.0545}; // 对称性,我们只需要记录 3 个高斯权重
	
	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);
}

两个 Pass 共用的片元着色器

两个Pass

ZTest Always Cull Off ZWrite Off

Pass {
	// 设置渲染状态,name语义
	// 方便再其他Shader 中直接通过它们的名字来使用该 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

Bloom效果

【原理】
让画面中较亮区域“扩散”到周围的区域中,造成一种朦胧的效果在这里插入图片描述
Bloom 的实现:
首先根据一个阙值提取出图像中的较亮区域 ,把它们存储在一张渲染纹理中,再利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果 最后再将其和原图像进行混合得到最终的效果

【代码解析】
摄像机CS脚本
流程:继承基类、声明使用的shader, 创建相应材质、材质参数、定义 OnRenderlmage 函数来进行真正的特效处理

  • OnRenderImage部分
void OnRenderImage (RenderTexture src, RenderTexture dest) {
	if (material != null) {
		material.SetFloat("_LuminanceThreshold", luminanceThreshold); // 第一个Pass,按照一定阈值提取出较亮区域

		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, material, 0);
		
		for (int i = 0; i < iterations; i++){ // iterations
			material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
			
			RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
			
			// Render the vertical pass 
			Graphics.Blit(buffer0, buffer1, material, 1); // 第二个Pass

			RenderTexture.ReleaseTemporary(buffer0);
			buffer0 = buffer1;
			buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

			// Render the horizontal pass 
			Graphics.Blit(buffer0, buffer1, material, 2); // 第三个Pass

			RenderTexture.ReleaseTemporary(buffer0);
			buffer0 = buffer1;
		}

		material.SetTexture ("_Bloom", buffer0);  
		Graphics.Blit (src, dest, material, 3);  // 第四个Pass, 把高斯模糊后的较亮区域与原图混合

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

Bloom 效果是建立在高斯模糊的基础上的,因此脚本中提供的参数 只增加了一个luminanceThreshold 来控制提取较亮区域时使用的阙值大小(一般情况下,图像的亮度值不会超过1,开启了HDR,精度会更高)

第一个Pass,按照一定阈值提取出较亮区域
第二个Pass,使用竖直方向的一维高斯核对图像进行滤波
第三个Pass,使用水平方向的一维高斯核对图像进行滤波
第四个Pass,把高斯模糊后的较亮区域与原图混合

  • shader
Properties {
	_MainTex ("Base (RGB)", 2D) = "white" {}
	_Bloom ("Bloom (RGB)", 2D) = "black" {}
	_LuminanceThreshold ("Luminance Threshold", Float) = 0.5
	_BlurSize ("Blur Size", Float) = 1.0
}

提取较亮区域需要使用的顶点着色器和片元着色器

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 {
	fixed4 c = tex2D(_MainTex, i.uv);
	fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);
	
	return c * val;
}

在片元着色器中,将采样得到的亮度值减去阐值Luminance Threshold, 并把结果截取到 0-1 范围内。然后把该值和原像素值相乘,得到提取后的亮部区域

  • 混合亮部图像和原图像的顶点着色器和片元着色器
struct v2fBloom {
	float4 pos : SV_POSITION; 
	half4 uv : TEXCOORD0;
};

v2fBloom vertBloom(appdata_img v) { // 混合原图
	v2fBloom o;
	
	o.pos = UnityObjectToClipPos (v.vertex);
	o.uv.xy = v.texcoord;		
	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);
} 

四个Pass

ZTest Always Cull Off ZWrite Off	
Pass {  
	CGPROGRAM  
	#pragma vertex vertExtractBright  
	#pragma fragment fragExtractBright  
	
	ENDCG  
}

UsePass "Custom/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL" // UsePass要大写

UsePass "Custom/Gaussian Blur/GAUSSIAN_BLUR_HORIZONTAL"

Pass {  
	CGPROGRAM  
	#pragma vertex vertBloom  
	#pragma fragment fragBloom  
	
	ENDCG  
}
Fallback Off 

代码

调整亮度饱和度对比度

【CS】

using UnityEngine;
using System.Collections;

public class BrightnessSaturationAndContrast : PostEffectsBase{  // 继承基类
	// 声明该效果需要的 Shader, 并据此创建相应的材质
	public Shader briSatConShader; // 是我们指定的shder
	private Material briSatConMaterial; // 是创建的材质,
	public Material material{  // 我们提供了名为 material 的材质来访问 briSatConMaterial
		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;

	// 定义 OnRenderlmage 函数来进行真正的特效处理
	void OnRenderImage(RenderTexture src, RenderTexture dest) {
		// 检查材质是否可用。如果可用,就把参数传递给材质
		if (material != null) {
			material.SetFloat("_Brightness", brightness);
			material.SetFloat("_Saturation", saturation);
			material.SetFloat("_Contrast", contrast);

			// 调用 Graphics.Blit 函数使用特定的 Unity Shader 来对当前图像进行处理,把返回的渲染纹理显示到屏幕上
			Graphics.Blit(src, dest, material);
		} else {
			// 否则直接把原图像显示到屏幕上,不做任何处理。
			Graphics.Blit(src, dest);
		}
	}
}

【shader】

// 状态设置 
// 屏幕后处理的 shader 的“标配”: ZTest Always Cull Off ZWrite Off
// 
// 顶点着色器
// 通常比较简单,输出Pose,uv
// 
// 关闭该 Unity Shader Fallback:


Shader "Custom/C9_BrightnessSaturationAndContrast"{
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Brightness ("Brightness", Float) = 1
		_Saturation("Saturation", Float) = 1
		_Contrast("Contrast", Float) = 1
	}
	SubShader {
		Pass {  
			// 状态设置
			// 屏幕后处理的 shader 的“标配”
			// 关闭了深度写入,防止它“挡住”在其后面被渲染的物体
			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); // 创建了一个饱和度为0的颜色值
				finalColor = lerp(luminanceColor, finalColor, _Saturation); // 使用_Saturation 属性在其和上一步得到的颜色之间进行插值
				
				// Apply contrast
				fixed3 avgColor = fixed3(0.5, 0.5, 0.5); // 首先创建一个对比度为0的颜色值
				finalColor = lerp(avgColor, finalColor, _Contrast); // 再使用_Contrast 属性和上一步得到的颜色之间进行插值
				
				return fixed4(finalColor, renderTex.a);  
			}  
			  
			ENDCG
		}  
	}
	
	Fallback Off // 关闭
}

边缘检测

【CS】

using UnityEngine;
using System.Collections;

// 继承基类
public class EdgeDetection : PostEffectsBase {
   // 声明该效果需要的 Shader, 并据此创建相应的材质:
	public Shader edgeDetectShader;
	private Material edgeDetectMaterial = null;
	public Material material {  
		get {
			edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
			return edgeDetectMaterial;
		}  
	}

	// 在脚本中提供用于调整边缘线强度、描边颜色以及背景颜色的参数:
	[Range(0.0f, 1.0f)]
	public float edgesOnly = 0.0f; // edgesOnly 值为0时,边缘将会叠加在原渲染图像上:当 dgesOnly 值为1时,则会只显示边缘

	public Color edgeColor = Color.black;

	// public Color backgroundColor = Color.white; 先声明,再在OnRenderImage里使用,决定了相机的控制面板,跟shader里的properties类似

	void OnRenderImage (RenderTexture src, RenderTexture dest) {
		if (material != null) {
			material.SetFloat("_EdgeOnly", edgesOnly);
			material.SetColor("_EdgeColor", edgeColor);
			// material.SetColor("_BackgroundColor", backgroundColor);

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

【shader】

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/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 无论被不被遮住,都一直绘制
			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;

				// 维数为9的纹理数组,对应了使用 Sobel 算子采样时需要的9个邻域纹理坐标??
				// 把计算采样纹理坐标的代码从FS移到VS中,可以减少运算,提高性能。
				// 从顶点着色器到片元着色器的插值是线性的,因此这样的转移并不会影响纹理坐标的计算结果。
				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; 
			}
			
			half Sobel(v2f i) {
				const half Gx[9] = {-1,  0,  1,
										-2,  0,  2,
										-1,  0,  1};
				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];
				}
				
				half edge = 1 - abs(edgeX) - abs(edgeY); // edge 值越小,表明该位置越可能是一个边缘点
				
				return edge;
			}
			
			fixed4 fragSobel(v2f i) : SV_Target {
				half edge = Sobel(i); // 调用 Sobel 函数计算当前像素的梯度值 edge
				
				fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge); // 背景为原图
				fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge); // 背景为纯色
				return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly); // 上面两幅图的插值
 			}
			
			ENDCG
		} 
	}
	FallBack Off
}

高斯模糊

【CS】

using UnityEngine;
using System.Collections;

public class GaussianBlur : PostEffectsBase { // 继承基类
	// 声明该效果需要的 Shader, 并据此创建相应的材质:
	public Shader gaussianBlurShader;
	private Material gaussianBlurMaterial = null;

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

	// 提供了调整高斯模糊迭代次数 模糊范围和缩放系数的参数
	// 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, 5.0f)]
	public float blurSpread = 0.6f;
	
	[Range(1, 8)]
	public int downSample = 2;

	/// 1st edition: just apply blur
	//	void OnRenderImage(RenderTexture src, RenderTexture dest) {
	//		if (material != null) {
	//			int rtW = src.width;
	//			int rtH = src.height;
	//          // 高斯模糊需要调用两个 Pass, 我们需要使用一块中间缓存来存储第一个 Pass 执行完毕后得到的模糊结果
	//			RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
	//
	//			// Render the vertical pass
	//			Graphics.Blit(src, buffer, material, 0); // buffer
	//			// Render the horizontal pass
	//			Graphics.Blit(buffer, dest, material, 1); // 
	//
	//			RenderTexture.ReleaseTemporary(buffer); // 释放内存
	//		} else {
	//			Graphics.Blit(src, dest);
	//		}
	//	} 

	/// 2nd edition: scale the render texture
	/// 声明缓冲区的大小时 使用了小于原屏幕分辨率的尺寸,
	/// 对图像进行降采样可以减少需要处理的像素个数,提高性能,而且还可以得到更好的模糊效果。
	/// 尽管 downSample 值越大,性能越好,但过大的 downSample 可能会造成图像像素化
	//	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; // 降采样
	//
	//			// Render the vertical pass
	//			Graphics.Blit(src, buffer, material, 0);
	//			// Render the horizontal pass
	//			Graphics.Blit(buffer, dest, material, 1);
	//
	//			RenderTexture.ReleaseTemporary(buffer);
	//		} else {
	//			Graphics.Blit(src, dest);
	//		}
	//	}

	//  定义 OnRenderlmage 函数来进行真正的特效处理
	/// 3rd edition: use iterations for larger blur
	/// 迭代次数
	/// 利用两个临时缓存在迭代之间进行交替。
    /// 在迭代开始前,定义bufferO, 并把 src 中的图像缩放后存储到 bufferO 中。
    /// 在迭代过程中,定义 bufferl 。
    /// 在执行第一个 Pass 时,输入 bufferO, 输出是 bufferl, 完毕后首先把 bufferO 释放,再把结果值 buffer 存储到 bufferO 中,重新分配 bufferl, 然后再调用第二个Pass, 重复上述过程
    /// 
	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 函数使用特定的 Unity Shader 来对当前图像进行处理,把返回的渲染纹理显示到屏幕上
			Graphics.Blit(src, buffer0);

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

				RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

				// Render the vertical pass
				Graphics.Blit(buffer0, buffer1, material, 0); // 第一个Pass

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

				// Render the horizontal pass
				Graphics.Blit(buffer0, buffer1, material, 1); // 第二个Pass

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}

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

【shader】

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Gaussian Blur" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BlurSize ("Blur Size", Float) = 1.0
	}
	SubShader {
		// 第一次用 CGINCLUDE,不需要包含Pass语义块
		CGINCLUDE
		
		#include "UnityCG.cginc"
		
		sampler2D _MainTex;  
		half4 _MainTex_TexelSize;
		float _BlurSize;
		  
		struct v2f {
			float4 pos : SV_POSITION;
			half2 uv[5]: TEXCOORD0; // 5*5维高斯核可以拆分成两个大小为5维高斯核,只需要计算5个纹理坐标即可
		};

		// 把计算采样纹理坐标的代码从片元着色器中转移到顶点着色器中,可以减少运算,提高性能
		 // 从顶点若色器到片元右色器的插值是线性的,因此这样的转移并不会影响纹理坐标的计算结果。
		v2f vertBlurVertical(appdata_img v) { 
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			
			// 和BlurSize相乘来控制采样距离。在高斯核维数不变的情况下, BlurSize 越大,模糊程度越高但采样数却不会受到影响。但过大的 BlurSize 值会造成虚影
			
			o.uv[0] = 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;
		}
		
		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}; // 对称性,我们只需要记录 3 个高斯权重
			
			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 中直接通过它们的名字来使用该 Pass, 而不需要再重复编写代码NAME "GAUSSIAN_BLUR_VERTICAL"
			NAME "GAUSSIAN_BLUR_VERTICAL"

			CGPROGRAM
			
			#pragma vertex vertBlurVertical  
			#pragma fragment fragBlur
			  
			ENDCG  
		}
		
		Pass {  
			NAME "GAUSSIAN_BLUR_HORIZONTAL" // 方便别人调用该Pass
			
			CGPROGRAM  
			
			#pragma vertex vertBlurHorizontal  
			#pragma fragment fragBlur
			
			ENDCG
		}
	} 
	FallBack Off
}

Bloom效果

【CS】

using UnityEngine;
using System.Collections;

public class Bloom : PostEffectsBase { // 继承基类
	// 声明使用的shader, 创建相应材质
	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; // 一般情况下,图像的亮度值不会超过1,开启了HDR,精度会更高

	// Bloom 效果是建立在高斯模糊的基础上的,代码差不多
	void OnRenderImage (RenderTexture src, RenderTexture dest) {
		if (material != null) {
			material.SetFloat("_LuminanceThreshold", luminanceThreshold); // 第一个Pass,按照一定阈值提取出较亮区域

			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, material, 0);
			
			for (int i = 0; i < iterations; i++){ // iterations
				material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
				
				RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
				
				// Render the vertical pass 
				Graphics.Blit(buffer0, buffer1, material, 1); // 第二个Pass

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

				// Render the horizontal pass 
				Graphics.Blit(buffer0, buffer1, material, 2); // 第三个Pass

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}

			material.SetTexture ("_Bloom", buffer0);  
			Graphics.Blit (src, dest, material, 3);  // 第四个Pass, 把高斯模糊后的较亮区域与原图混合

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

【shader】

// 让画面中较亮 区域“扩散”到周围的区域中
// Pass1:根据一个阙值提取出图像中的较亮区域 把它们存储在一张渲染纹理中
// Pass2 3:利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果
// Pass 4:将其和原图像进行混合

Shader "Custom/Bloom" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Bloom ("Bloom (RGB)", 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 {
			fixed4 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);
			o.uv.xy = v.texcoord;		
			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 "Custom/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL" // UsePass要大写
		
		UsePass "Custom/Gaussian Blur/GAUSSIAN_BLUR_HORIZONTAL"
		
		Pass {  
			CGPROGRAM  
			#pragma vertex vertBloom  
			#pragma fragment fragBloom  
			
			ENDCG  
		}
	}
	FallBack Off // 关闭
}

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值