一、什么是屏幕后处理
在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列的操作,实现各种屏幕特效。常用于处理景深、描边,图像模糊等特效。
二、实现原理
想要实现屏幕后处理的基础在于得到渲染后的整个屏幕的图像,即抓取屏幕,在Unity中为我们提供了一个方便的接口——OnRenderImage函数:
MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest)
src表示源纹理,即没有经过处理之前的纹理,dest表示目标纹理,即处理后的纹理,
目标纹理可以在Camera中的Target Texture设置,如下图:
默认情况下OnRenderImage是在整个屏幕渲染完以后(也就是在不透明和透明物体之后都渲染完以后)调用,在一些特殊的情况下,我们需要在渲染完不透明物体的Pass(即渲染队列小于等于2500的pass,例如内置的
AlphaTest,Background,Geometry,等都在这个范围内)后立即就调用OnRenderImage,换句话说就是我们不希望将透明物体也包括在屏幕后处理中,这时我们需要在OnRenderImage函数前面添加[ImageEffectOpaque]属性来达到目的。
在OnRenderImage函数中我们通常通过Graphics.Blit函数对纹理进行处理,它有3种函数声明:
Graphics.Blit(Texture src,RenderTexture dest);
Graphics.Blit(Texture src,RenderTexture dest,Material mat ,int pass = -1);
Graphics.Blit(Texture src,Material mat ,int pass = -1);
src表示当前屏幕纹理或者上一步处理后得到的渲染纹理,dest表示目标渲染纹理,如果他的值为null,那么就会直接将处理后的纹理渲染到屏幕上面。mat是使用的材质,这个材质使用的shader将会对源纹理进行各种特效处理。而源纹理(src)将会被传递给shader中的_MainTex的纹理属性。pass的默认参数是-1,表示将会依次调用shader中的每个pass,否则,只会调用给定索引的pass。
三、实例
1、基类实现
在进行屏幕后处理前我们需要一系列的条件检测,例如当前平台是否支持渲染纹理和屏幕特效,是否支持当前使用的unity shader等。为此,我们创建一个用于屏幕后处理效果的基类(PostEffectsBase.cs),在实现屏幕效果时,我们只需要继承这个基类,再实现派生类中不同操作即可。
基类实现PostEffectsBase.cs:
using UnityEngine;
using System.Collections;
//在编辑器状态下执行该脚本
[ExecuteInEditMode]
//刚需组件(Camera)
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {
// 在Start()中调用
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;
}
// 当不支持的时候,将脚本的enabled设置为false
protected void NotSupported() {
enabled = false;
}
protected void Start() {
CheckResources();
}
// 检测Material和Shader,在派生类中调用,绑定材质和shader
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;
}
}
}
2、调节亮度,饱和度,对比度
(1)创建脚本(BrightnessSaturationAndContrast.cs),继承基类,重写OnRenderImage函数
using UnityEngine;
using System.Collections;
public class BrightnessSaturationAndContrast : PostEffectsBase {
//绑定的shader
public Shader briSatConShader;
private Material briSatConMaterial;
public Material material {
get {
//调用基类的CheckShaderAndCreateMaterial方法绑定shader与Material
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;
//重写OnRenderImage方法
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
//设置shader中的各个值
material.SetFloat("_Brightness", brightness);
material.SetFloat("_Saturation", saturation);
material.SetFloat("_Contrast", contrast);
//将源纹理通过material处理,复制到目标纹理中
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
}
(2)编写Shader特效
Shader "Change Brightness Saturation And Contrast" {
Properties {
//Graphics.Blit函数传入的src
_MainTex ("Source Texture", 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"
//CG中代码块中声明对应的变量
sampler2D _MainTex;
half _Brightness;
half _Saturation;
half _Contrast;
struct v2f {
float4 pos : SV_POSITION;
half2 uv: TEXCOORD0;
};
//顶点着色器,坐标转换以及获取uv值
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;
//使用该亮度值创建一个饱和度为0的颜色
fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
//将之前的颜色与该颜色进行插值运算,得到调整饱和度后的颜色
finalColor = lerp(luminanceColor, finalColor, _Saturation);
// 创建一个对比度度为0的颜色
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
//将之前的颜色与该颜色进行插值运算,得到调整对比度后的颜色
finalColor = lerp(avgColor, finalColor, _Contrast);
//返回最终颜色
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
Fallback Off
}
(3)返回unity编辑器,将ChangeBrightnessSaturationAndContrast.shader赋值给BrightnessSaturationAndContrast.cs脚本的briSatConShader变量,调整亮度值,饱和度,对比度的值,查看效果