这篇总结一下常用的后处理Shader,套上主Camera就能让游戏画面效果上升一个Level那种,整容效果等同于美图秀秀的滤镜(并没有!)。
后处理的具体实现原理:
1、先把场景里东西给渲染了
2、把渲染好的屏幕图像“截个屏”(我是主要用OnRenderImage)
3、把刚刚的“截屏”进行处理,再放到屏幕上
后处理C#脚本基类
首先写一个万用的后处理C#脚本基类PostEffectsBase。来自于冯乐乐女神的书《UnityShader入门精要》,我吹爆这本书,写的很棒,各种知识点讲的深入浅出,萌新的必备之选,千言万语汇成一句话,乐乐女神就是我偶像!
using UnityEngine;
public class PostEffectsBase : MonoBehaviour
{
protected void CheckResources()
{
bool isSupported = CheckSupport();
if (!isSupported)
{
NotSupported();
}
}
protected bool CheckSupport()//检测平台是否支持后处理
{
if (SystemInfo.supportsImageEffects == false)
{
Debug.LogWarning("This platform does not support image effects!");
return false;
}
return true;
}
protected void NotSupported()
{
enabled = false;
}
protected void Start()
{
CheckResources();
}
protected Material CheckShaderAndMaterial(Shader shader, Material material)
{
if (shader == null)
return null;
if (shader.isSupported && material != null && material.shader == shader)
return material;
else
{
material = new Material(shader)
{
hideFlags = HideFlags.DontSave
};
return material;
}
}
}
之后写的各种C#脚本继承自这个基类就可以了。
饱和度/对比度/明度调节
这个是最基础的后处理,非常简单。
主要思路:
1、饱和度,计算当前图像0饱和度的颜色: 0.2125 * r + 0.7154 * g + 0.0721 * b,再把输入图像颜色与该颜色按饱和度进行插值输出
2、对比度,把输入图像颜色与(0.5,0.5,0.5,1.0)的灰色按对比度进行插值输出
3、明度,把输入图像颜色与明度相乘输出
Shader
Shader "Custom/BrightnessSaturationAndContrast"
{
Properties
{
_MainTex ("Base(RGB)", 2D) = "white" {}
}
SubShader
{
Pass
{
ZTest Always
Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Brightness;
float _Saturation;
float _Contrast;
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);
fixed3 finalColor = renderTex.rgb * _Brightness;//亮度:直接相乘
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);//饱和度:将颜色值转化,再进行插值
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);//对比度:与灰色插值
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
Fallback Off
}
C#
using UnityEngine;
[ExecuteInEditMode]
public class BrightnessSaturationAndContrast : PostEffectsBase
{
public Shader briSatConShader;
private Material briSatConMaterial;
public Material BriSatConMaterial
{
get
{
briSatConMaterial = CheckShaderAndMaterial(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(BriSatConMaterial != null)
{
BriSatConMaterial.SetFloat("_Brightness", brightness);
BriSatConMaterial.SetFloat("_Saturation", saturation);
BriSatConMaterial.SetFloat("_Contrast", contrast);
Graphics.Blit(src, dest, BriSatConMaterial);
}
else
{
Graphics.Blit(src, dest);
}
}
}
高斯模糊Gaussian Blur
高斯模糊比上面那个要复杂一点,不过也算比较容易的,而且很多后处理都会用到高斯模糊,所以可以把高斯模糊的Pass命名,在其他后处理Shader中就可以很方便的使用了。
高斯模糊后处理涉及到一点计算机图像处理的知识,要使用高斯滤波来处理输入的屏幕图像再输出。
简而言之就是把3x3像素点范围内的每个像素点颜色,乘上高斯滤波中对应的权重,相加后得到中心像素点的输出颜色,就可以得到模糊后的图像。外圈点里中心点越远,模糊程度越高,通过调整这些点的范围来调整模糊程度。
又因为高斯滤波横向纵向数值相同,所以实际只储存三个值,分别对纵向三个点和横向三个点进行两次计算即可。通过调节Shader里
另外还可以通过重复高斯模糊的次数来调节模糊程度,另外还有对屏幕图像进行降采样来调节模糊程度,这样可以节约性能。
C#
using UnityEngine;
public class Blur : PostEffectsBase
{
public Shader shader;
private Material material;
public Material Material
{
get
{
material = CheckShaderAndCreateMaterial(shader, material);
return material;
}
}
[Range(0, 4.0f)]
public int iterations = 2; //模糊迭代次数
[Range(0, 3.0f)]
public float blurSpread = 1.0f; //模糊范围
[Range(1, 8)]
public int downSample = 1; //降采样倍数
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
int height = source.height / downSample;
int width = source.width / downSample;
if (Material != null)
{
Material.SetInt("_Iterations", iterations);
Material.SetFloat("_BlurSpread", blurSpread);
RenderTexture buffer0 = RenderTexture.GetTemporary(width, height);
RenderTexture buffer1 = RenderTexture.GetTemporary(width, height);
buffer0.filterMode = FilterMode.Bilinear;
buffer1.filterMode = FilterMode.Bilinear;
Graphics.Blit(source, buffer0);
for (int i = 0; i < iterations; i++)
{
Material.SetFloat("_BlurSize", i * blurSpread);
buffer1 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(buffer0, buffer1, Material, 0);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(buffer0, buffer1, Material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
Graphics.Blit(buffer0, destination);
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
Graphics.Blit(source, destination);
}
}
}
Shader
Shader "Custom/GaussianBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
ZTest Always Cull Off ZWrite Off
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
float2 _MainTex_TexelSize;
float _BlurSize;
struct v2f
{
float4 pos : SV_POSITION;
half2 uv[5] : TEXCOORD0;
};
v2f vert_Vertical(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + half2(0, _MainTex_TexelSize.y * 1)*_BlurSize;
o.uv[2] = uv + half2(0, _MainTex_TexelSize.y * -1)*_BlurSize;
o.uv[3] = uv + half2(0, _MainTex_TexelSize.y * 2)*_BlurSize;
o.uv[4] = uv + half2(0, _MainTex_TexelSize.y * -2)*_BlurSize;
return o;
}
v2f vert_Horizontal(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + half2(_MainTex_TexelSize.x * 1, 0)*_BlurSize;
o.uv[2] = uv + half2(_MainTex_TexelSize.x * -1, 0)*_BlurSize;
o.uv[3] = uv + half2(_MainTex_TexelSize.x * 2, 0)*_BlurSize;
o.uv[4] = uv + half2(_MainTex_TexelSize.x * -2, 0)*_BlurSize;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
const half weights[3] = {0.4026,0.2442,0.0545};
fixed3 sum = fixed3(0,0,0);
sum += tex2D(_MainTex, i.uv[0]).rgb * weights[0];
sum += tex2D(_MainTex, i.uv[1]).rgb * weights[1];
sum += tex2D(_MainTex, i.uv[2]).rgb * weights[1];
sum += tex2D(_MainTex, i.uv[3]).rgb * weights[2];
sum += tex2D(_MainTex, i.uv[4]).rgb * weights[2];
return fixed4(sum, 1.0);
}
ENDCG
Pass
{
NAME "BLUR_VERTICAL"
CGPROGRAM
#pragma vertex vert_Vertical
#pragma fragment frag
ENDCG
}
Pass
{
NAME "BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vert_Horizontal
#pragma fragment frag
ENDCG
}
}
FallBack Off
}
Bloom
Bloom算是非常常用的一个后处理了。主要原理是先提取屏幕图像亮部,然后对亮度进行高斯模糊,再与原图像混合。就能得到一种模糊光晕的效果,运用很是广泛。
这里的模糊是直接用的上面写的高斯模糊的shader,少写点重复的代码。
(示例参数调狠了,miku后面书架的细节都给糊没了……)
C#
using UnityEngine;
public class Bloom : PostEffectsBase
{
public Shader shader;
private Material material;
public Material Material
{
get
{
material = CheckShaderAndCreateMaterial(shader, material);
return material;
}
}
[Range(0.0f, 1.0f)]
public float bloomSize = 0.6f;
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f; //模糊范围
[Range(0, 4)]
public int iterations = 3; //模糊迭代次数
[Range(1, 8)]
public int downSample = 2; //降采样倍数
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (Material != null)
{
int width = source.width/downSample;
int height = source.height/downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(width, height);
buffer0.filterMode = FilterMode.Bilinear;
RenderTexture buffer1 = RenderTexture.GetTemporary(width, height);
Material.SetFloat("_BloomSize", bloomSize);
Graphics.Blit(source, buffer0, Material, 0);
for (int i = 0; i < iterations; i++)
{
Material.SetFloat("_BlurSize", i * blurSpread);
buffer1 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(buffer0, buffer1, Material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(buffer0, buffer1, Material, 2);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
Material.SetTexture("_Bloom", buffer0);
Graphics.Blit(source, destination, Material, 3);
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
Graphics.Blit(source, destination);
}
}
}
Shader
Shader "Custom/Bloom"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Cull Off ZWrite Off ZTest Always
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half2 _MainTex_TexelSize;
sampler2D _Bloom;
float _BlurSize;
float _BloomSize;
struct v2f
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
v2f vert_Extract(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 frag_Extract(v2f i) : SV_Target
{
fixed4 texColor = tex2D(_MainTex,i.uv);
fixed brightness = clamp(luminance(texColor) - _BloomSize, 0.0 , 1.0);
return texColor * brightness;
}
struct v2fBloom
{
float4 pos : SV_POSITION;
half4 uv : TEXCOORD0;
};
v2fBloom vert_Blend(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)
o.uv.w = 1.0 - o.uv.w;
#endif
return o;
}
fixed4 frag_Blend(v2fBloom i) : SV_Target
{
return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom,i.uv.zw);
}
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert_Extract
#pragma fragment frag_Extract
ENDCG
}
UsePass "Custom/GaussianBlur/BLUR_VERTICAL"
UsePass "Custom/GaussianBlur/BLUR_HORIZONTAL"
Pass
{
CGPROGRAM
#pragma vertex vert_Blend
#pragma fragment frag_Blend
ENDCG
}
}
Fallback Off
}
Bloom扩展
基于Bloom的思路可以做很多有意思的后处理,比如这个十字光斑
主要思路是先提取亮部,然后分别把纵向模糊和横向模糊结果存在两张RT上,再传给shader,采样后跟原图像混合即可。代码也跟Bloom的大同小异,新加入了一个brightness参数控制亮部的亮度,因为太暗了效果不明显
(Miku那张图整张的亮度主次不是很清,所以用了这张樱花,不过效果也不是太好,适用于那种有小面积亮光的场景,比如透过树叶的缝隙洒下阳光这种)
C#
using UnityEngine;
public class Bloom_Extra : PostEffectsBase
{
public Shader shader;
private Material material;
public Material Material
{
get
{
material = CheckShaderAndCreateMaterial(shader, material);
return material;
}
}
[Range(0.0f, 1.0f)]
public float bloomSize = 0.6f;
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f; //模糊范围
[Range(0, 20)]
public int iterations = 3; //模糊迭代次数
[Range(1, 8)]
public int downSample = 2; //降采样倍数
[Range(0, 10)]
public float brightness = 0.5f; //控制亮部亮度
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (Material != null)
{
int width = source.width / downSample;
int height = source.height / downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(width, height);
buffer0.filterMode = FilterMode.Bilinear;
RenderTexture buffer1 = RenderTexture.GetTemporary(width, height);
buffer1.filterMode = FilterMode.Bilinear;
RenderTexture buffer2 = RenderTexture.GetTemporary(width, height);
RenderTexture buffer3 = RenderTexture.GetTemporary(width, height);
Material.SetFloat("_BloomSize", bloomSize);
Material.SetFloat("_Brightness", brightness);
Graphics.Blit(source, buffer0, Material, 0);
Graphics.Blit(source, buffer1, Material, 0);
for (int i = 0; i < iterations; i++)
{
Material.SetFloat("_BlurSize", i * blurSpread);
buffer2 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(buffer0, buffer2, Material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer2;
buffer3 = RenderTexture.GetTemporary(width, height);
Graphics.Blit(buffer1, buffer3, Material, 2);
RenderTexture.ReleaseTemporary(buffer1);
buffer1 = buffer3;
}
Material.SetTexture("_Bloom_Vertical", buffer0);
Material.SetTexture("_Bloom_Horizontal", buffer1);
Graphics.Blit(source, destination, Material, 3);
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
Graphics.Blit(source, destination);
}
}
}
shader
Shader "Custom/Bloom_Extra"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Cull Off ZWrite Off ZTest Always
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half2 _MainTex_TexelSize;
sampler2D _Bloom_Vertical;
sampler2D _Bloom_Horizontal;
float _BlurSize;
float _BloomSize;
float _Brightness;
struct v2f
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
v2f vert_Extract(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 frag_Extract(v2f i) : SV_Target
{
fixed4 texColor = tex2D(_MainTex,i.uv);
fixed brightness = clamp(luminance(texColor) - _BloomSize, 0.0 , 1.0);
return texColor * brightness * _Brightness;
}
struct v2fBloom
{
float4 pos : SV_POSITION;
half4 uv : TEXCOORD0;
};
v2fBloom vert_Blend(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)
o.uv.w = 1.0 - o.uv.w;
#endif
return o;
}
fixed4 frag_Blend(v2fBloom i) : SV_Target
{
return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom_Vertical,i.uv.zw) + tex2D(_Bloom_Horizontal,i.uv.zw);
}
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert_Extract
#pragma fragment frag_Extract
ENDCG
}
UsePass "Custom/GaussianBlur/BLUR_VERTICAL"
UsePass "Custom/GaussianBlur/BLUR_HORIZONTAL"
Pass
{
CGPROGRAM
#pragma vertex vert_Blend
#pragma fragment frag_Blend
ENDCG
}
}
Fallback Off
}
这篇先写这么多吧,剩下的总结2再写……