屏幕后处理,通常指的是在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列操作,实现各种屏幕特效,使用这种技术,可以为游戏画面添加更多的艺术效果,例如景深【depth of field】、运动模糊【motion blur】等。
因此,想要实现屏幕后处理的基础在于得到渲染后的屏幕图像,即抓取屏幕,而 unity 为我们提供了这样一个 方便的 借口 -----OnRenderImage 函数:
MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest)
当我们再脚本中生命此函数后,unity 会把当前渲染得到的图像存储在第一个参数对应的源渲染纹理中, 通过函数中的一系列操作后,再把目标渲染纹理,即第二参数对应的渲染纹理显示到屏幕上。在 OnRenderImage 函数中,我们通常是利用 Graphics.Blit 函数来完成对渲染纹理的处理。它 有 3 种函数声明:
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, Materical mat, int pass = -1)
其中,参数 src 对应了源纹理,在屏幕后处理技术种,这个参数通常就是当前屏幕的渲染纹理或是上一步处理后得到的渲染纹理。参数 dest 是目标渲染纹理,如果它的值为 null 就会直接将结果显示在屏幕上。 参数 mat 是我们使用的材质,这个材质使用的 unity shader 将会进行各种屏幕后处理操作,而 src 纹理将会被传递给 shader 中名为 _MainTex 的纹理属性。参数 pass 的默认值为 -1,表示将会依次调用 shader 内的所有 pass, 否则,只会调用给定索引的 pass。
在默认情况下, OnRenderImage函数会在所有的不透明和透明的 pass 执行完毕后被调用,以便对场景中所有游戏对象产生影响。但有时,我们希望在不透明的 pass 【即渲染队列小于等于2500的 pass,内置的 background、geometry和 alpha test渲染队列均在此范围内】执行完毕后立即调用 OnRenderImage 函数,从而对不对透明物体产生任何影响。此时,我们可以在 OnRenderImage 函数前添加 ImageFeectOpaque 属性来实现这样的目的。在再谈边缘检测的这节中,我们会利用深度和法线纹理进行边缘检测从而实现描边的效果,但我们不希望透明物体也被描边。
因此,要再 unity 中实现屏幕后处理效果,过程通常如下:我们首先需要在摄像机中添加一个用于屏幕后处理的脚本。在这个脚本中,我们会实现 OnRenderImage 函数来获取当前屏幕的渲染纹理。然后再调用 Graphics.Blit 函数使用特定的 unity shader 来对当前图像进行处理,再把返回的渲染纹理显示到屏幕上。对于一些复杂的屏幕特效,我们可能需要多次调用 Graphics.Blit 函数来对上一步的输出结果进行下一步处理。
但是,在进行屏幕后处理之前,我们需要检查一系列条件是否满足,例如当前平台是否支持渲染纹理和屏幕特效,是否支持当前使用的unity shader 等。为此,我们创建了一个用于 屏幕后处理效果的基类,在实现各种屏幕特效时,我们只需要继承自该基类,再实现派生类中不同的操作即可。
PostEffectsBase.cs
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectsBase : MonoBehaviour
{
protected void CheckResources(){
bool isSupported = this.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(){
this.enabled = false;
}
protected void Start(){
this.CheckResources ();
}
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;
}
}
}
1、首先,所有屏幕后处理效果都需要绑定在某个摄像机上,并且我们希望在编辑器状态下也可以执行该脚本来查看效果。
2、为了提前检查各种资源和条件是否满足,我们在 start 函数中调用 checkResources 函数。一些屏幕特效可能需要更多的设置,例如设置一些默认值等,可以重载 start、checkresources 或 checksupport 函数。
3、由于每个屏幕后处理效果通常都需要指定一个 shader 来创建一个用于处理渲染纹理的材质,因此基类中也提供了这样的方法:
CheckShaderAnCreateMaterial 函数接受两个参数,第一个参数指定了该特效需要使用的 shader,第二个参数则是用于后期处理的材质。该函数首先检查shader 的可用性,检查通过后就返回一个使用了该 shader 的材质,否则返回 null。