屏幕后处理,顾名思义,通常指的是在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列的操作,实现各种屏幕特效。因此,想要实现屏幕后处理的基础在于得到渲染后的屏幕图像,即抓取屏幕,Unity提供了接口——OnRenderImage函数,声明如下:MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest)。
当我们在脚本中声明此函数后,Unity会把当前渲染得到的图像存储在第一个参数对应的源渲染纹理中,通过函数重点一系列操作后,再把目标渲染纹理,即第二个参数对应的渲染纹理显示到屏幕上。在OnRenderImage函数中,我们通常是利用Grapics.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,Material 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和AlphaTest均在此列)执行完毕后立即调用OnRenderImage函数,从而不对透明物体产生任何影响。此时,我们可以在OnRenderImage函数前添加ImageEffectOpaque属性来实现这样的目的。
因此,要在Unity中实现屏幕后处理效果,过程通常如下:我们首先需要在摄像中添加一个用于屏幕后处理的脚本。在这个脚本中,我们会实现OnRenderImage函数来获取当前屏幕的渲染纹理。然后,再调用Graphics.Blit函数使用特定的Unity Shader来对当前的图像进行处理,再把返回的渲染纹理显示到屏幕上。
using UnityEngine;
[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class PostEffectBase:MonoBehaviour
{
public bool enable = false;
protected void CheckResources()
{
bool isSupported = CheckSupport();
if(isSupported == false)
{
NotSupported();
}
}
protected bool CheckSupport()
{
if(SystemInfo.supportsImageEffects == false)
{
Debug.LogWarning("This platform does not support image effects or rendertextures.");
return false;
}
return true;
}
protected void NotSupported()
{
enable = false;
}
protected void Start()
{
CheckResources();
}
//第一个参数制定了该特效需要使用的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;
}
}
}
}
实现调整屏幕亮度,饱和度和对比度
C#代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BrightnessSaturationAndContrast : PostEffectBase
{
public Shader briSatConShader;
private Material briSatConMaterial;
public Material material
{
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;
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
material.SetFloat("_Brightness", brightness);
material.SetFloat("_Saturation", saturation);
material.SetFloat("_Contrast", contrast);
Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
}
Shader:
Shader "Unlit/BrightnessSaturationAndContrast"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Brightness ("Brightness", Float) = 1
_Saturation ("Saturation", Float) = 1
_Contrast ("Contrast", Float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
//屏幕后处理实际上是在场景中绘制了一个与屏幕同宽同高的四边形面片,为了防止它对其他物体产生影响,我们需要设置相关的渲染状态。关闭深度写入,是为了防止它“挡住”在其后面被渲染的物体。这些设置可以认为是用于屏幕后处理Shader的标配。
ZTest Always
Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half _Brightness;
half _Saturation;
half _Contrast;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//首先,我们得到对原屏幕图像的采样结果renderTex。
fixed4 renderTex = tex2D(_MainTex, i.uv);
//然后,利用_Brightness属性来调整亮度。亮度的调整非常简单,我们只需要把源颜色诚意亮度系数_Brightness即可。
//Apply brightness
fixed3 finalColor = renderTex.rgb * _Brightness;
//然后,我们计算该像素对应的亮度值,通过对每个颜色分量乘以一个特定的系数再相加得到的。我们使用该亮度值创建了一个饱和度为0的颜色值,并使用_Saturation属性在其和上一步得到的颜色之间进行插值,从而得到希望的饱和度颜色。
//Apply saturation
fixed3 luminance = 0.2125 * renderTex + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
finalColor = lerp(luminanceColor, finalColor, _Saturation);
//对比度的处理类似,我们首先创建一个对比度为0的颜色值(各分量均为0.5)。再使用_Contrast属性在其和上一步得到颜色进行插值。
//Apply contrast
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
}
来源:CSDN
作者:songtianqi123
链接:https://blog.csdn.net/songtianqi123/article/details/103796505