屏幕后处理效果是实现屏幕特效的常见方法。
建立一个基本的屏幕后处理的脚本
屏幕后处理指的是在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列操作,实现各种屏幕特效。
想要实现屏幕后处理的基础在于抓取屏幕。Unity为我们提供了一个接口-OnRenderImage函数。
声明如下:MonoBehaviour.OnRenderImage(RenderTexture src,RenderTexture dest)
当我们在屏幕上运行此函数后,Unity会把当前渲染得到的图像存储在第一个参数对应的源渲染纹理中,通过函数中的一系列操作后,再把目标渲染纹理,即第二个参数对应的渲染纹理显示到屏幕上。在OnRenderImage中,我们通常是通过Graphics.Blit函数来完成对渲染纹理的处理。
public static void Blit(Texture src, RenderTexture dest);
public static void Blit(Texture src, RenderTexture dest, Material mat, int pass = -1);
public static void Blit(Texture src, Material mat, int pass = -1);
参数src对应了源纹理,在屏幕后处理技术中,这个参数通常就是当前屏幕的渲染纹理或是上一步处理后得到的渲染纹理。参数dest是目标渲染纹理,如果它的值为null就会直接将结果显示在屏幕上。参数mat是我们使用的材质,这个材质使用的Unity Shader将会进行各种屏幕后处理操作,而src纹理将会被传递给Shader中名为_MainTex的纹理属性。参数pass的默认值为-1,表示将会一次调用Shader内的所有Pass。否则,只会调用给定索引的Pass。
默认情况下,OnRenderImage函数会在所有不透明和透明的Pass执行完毕后被调用,以便对场景中所有游戏对象都产生影响。但有时我们希望在不透明的Pass执行完毕后立即调用OnRenderImage函数,从而不对透明物体产生任何影响。此时,我们可以在OnRenderImage函数前添加ImageEffectOpaque属性来实现这样的目的。
要实现屏幕后处理过程如下:
using UnityEngine;
using System.Collections;
//所有屏幕后处理效果都需要绑定在某个摄像机上,我们希望在编辑器状态下也可以查看该脚本
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectsBase : MonoBehaviour
{
//提前检查资源是否满足
protected void CheckResources()
{
bool isSupported = CheckSupport();
if (isSupported == false)
{
NotSupported();
}
}
protected bool CheckSupport()
{
//if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false)
//{
// Debug.LogWarning("This platform does not support image effects or render textures.");
// return false;
//}
return true;
}
protected void NotSupported()
{
enabled = false;
}
protected void Start()
{
CheckResources();
}
//指定一个Shader来创建一个用于处理渲染纹理的材质
//CheckShaderAndCreateMaterial接受两个参数,第一个参数指定了该特效需要使用的Shader,第二个参数则是用于后期处理的材质。该函数首先检查Shader可用性,检查通过后就返回一个使用了该Shader的材质,否则返回null
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;
else
return null;
}
}
}
调整屏幕的亮度、饱和度和对比度
Shader "Unity Shaders Book/Chapter 12/Brightness Saturation And Contrast" {
Properties {
//Graphics.Blit(src, dest, material)将第一个参数传递给Shader中名为_MainTex的属性
_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;
};
//使用Unity内置结构体appdata_img作为顶点着色器的输入,它只包含了图像处理时必须的顶点坐标和纹理坐标等
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f i) : SV_Target {
//得到对原屏幕图像采样结果renderTex
fixed4 renderTex = tex2D(_MainTex, i.uv);
//调整亮度
fixed3 finalColor = renderTex.rgb * _Brightness;
//计算该像素对应的亮度值luminance
//对每个颜色分量乘以一个特定的系数再相加
fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
//创建一个饱和度为0的颜色值
fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
//使用_Saturation属性和颜色之间进行插值,得到希望的饱和度颜色
finalColor = lerp(luminanceColor, finalColor, _Saturation);
//创建一个对比度为0的颜色值
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
//使用_Contrast属性在其和颜色值之间进行插值
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
Fallback Off
}
边缘检测
描边效果的一种实现方法。
边缘检测的原理是利用一些边缘检测算子对图像进行卷积操作。
什么是卷积
图像处理中,卷积操作指的是使用一个卷积核对一张图像中的每个像