Unity Shader 屏幕后处理调整亮度、饱和度、对比度
在进行屏幕后处理之前,需要检查一系列条件是否满足,比如是否支持渲染纹理和屏幕特效,是否支持当前使用的Unity shader等.所以需要创建一个基类,用来检查:
屏幕后处理基类
using UnityEngine;
[ExecuteInEditMode]//ExecuteInEditMode属性的作用是在EditMode下也可以执行脚本
[RequireComponent (typeof(Camera))]//所有屏幕后处理效果都需要绑定在某个摄像机上
public class PostEffectsBase : MonoBehaviour
{
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 render textures.");
return false;
}
return true;
}
protected void NotSupported()//不支持就禁用
{
enabled = false;
}
protected void Start()
{
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;
}
}
}
调整屏幕亮度、饱和度和对比度
首先创建一个脚本,继续自刚才的基类:
public class BrightnessSaturationAndContrast : PostEffectsBase
声明该效果需要的Shader,并创建相应的材质:
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;
要实现屏幕后处理,就是要得到渲染后的屏幕图像, Unity提供了一个方便的接口-OnRenderImage
函数,声明如下:
void OnRenderImage(RenderTexture src, RenderTexture dest)
当我们在脚本调用此函数后,Unity会把当前渲染得到的图像存储在第一个参数对应的源渲染纹理中,通过函数中的一系列操作后,再把目标渲染纹理,即第二个参数对应的渲染纹理显示到屏幕上.
默认情况下,OnRenderImage会在所有的不透明和透明的Pass执行完毕后被调用
在OnRenderImage
函数中,我们通常是利用Graphics.Blit来完成对渲染纹理的处理,
public static void Blit(Texture source, RenderTexture dest, Material mat, [Internal.DefaultValue("-1")] int pass);
public static void Blit(Texture source, RenderTexture dest, Material mat);
public static void Blit(Texture source, Material mat);
src对应源纹理,在屏幕后处理中,就是当前屏幕的渲染纹理或是上一步处理后得到的渲染纹理.
dest是目标渲染纹理,如果它的值为null就会直接将结果显示在屏幕上,mat是我们使用的材质,这个材质使用的Unity Shader将会进行各种屏幕后处理操作,src纹理将会被传递给Shader中名为_MainTex的纹理属性,pass的默认值为-1,表示将会调用Shader内的所有Pass.
我们定义OnRenderImage函数进行真正的特效处理:
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部分:
首先声明本例使用的各个属性:
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_Brightness ("Brightness", Float) = 1
_Saturation("Saturation", Float) = 1
_Contrast("Contrast", Float) = 1
}
src纹理将会被传递给Shader中名为_MainTex的纹理属性,所以我们必须声明这个纹理属性.
除此之外,还声明了亮度、饱和度和对比度的属性.
然后定义用于屏幕后处理的Pass:
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off
屏幕后处理实际上是在场景中绘制了一个与屏幕同宽通告的四边形面片,为了防止它对其他物体产生英雄,我们关闭了深度写入,防止它挡住在其后面渲染的物体.比如,如果当前的OnRenderImage函数在所有不透明的Pass执行完毕后立即被调用,不关闭深度写入就会影响后面透明的Pass的渲染.
接下来在CG代码块中声明对应的变量:
sampler2D _MainTex;
half _Brightness;
half _Saturation;
half _Contrast;
定义顶点着色器,只需要进行必须的顶点变化:
struct v2f
{
float4 pos : SV_POSITION;
half2 uv: TEXCOORD0;
};
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);//饱和度为0的颜色值
finalColor = lerp(luminanceColor, finalColor, _Saturation);//进行插值,从而得到希望的饱和度颜色
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);//对比度为0的颜色值
finalColor = lerp(avgColor, finalColor, _Contrast);//插值得到希望的对比度
return fixed4(finalColor, renderTex.a);
}
完整代码如下:
Shader "Brightness Saturation And Contrast" {
Properties
{
_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;
};
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);//饱和度为0的颜色值
finalColor = lerp(luminanceColor, finalColor, _Saturation);//进行插值,从而得到希望的饱和度颜色
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);//对比度为0的颜色值
finalColor = lerp(avgColor, finalColor, _Contrast);//插值得到希望的对比度
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
Fallback Off
}
对比效果如下