1. 原理和过程
屏幕后处理是绑定摄像机的,通过抓取当前摄像机渲染的图像作为 SrcTextrue,然后按需依次调用处理接口,对 SrcTexture 进行处理,最后将处理完成的 DstTexture 显示到屏幕上,整个过程的调度通过 C# 脚本完成。
抓取摄像机当前渲染图像使用的接口如下:
OnRenderImage(RenderTexture _src, RenderTexture _dst)
其中:
- _src为抓取到的当前绑定摄像机的渲染图像
- _dst为处理结束时的目标纹理
调用的处理接口如下:
Graphics.Blit(Texture _src, RenderTexture _dst)
Graphics.Blit(Texture _src, RenderTexture _dst, Material _mat)
Graphics.Blit(Texture _src, Material _mat, int _passIndex = -1)
其中:
- _src 为要处理的原始图像
- _dst 为处理结束后存储到的目标纹理
- _mat 为本次处理指定的材质,其主要作用是为本次处理提供使用的Shader,需要注意的是,_src会被赋值给该_mat所携带Shader的_MainTex变量,因此在实现Shader时,也需要声明对应名字的变量用于接收原始图像纹理
- _passIndex 为本次处理指定使用的Pass索引,默认为-1,表示按照顺序依次执行Pass,否则为使用指定索引的Pass
2. 后处理脚本父类
我们可以建立一个后处理的父类,提供所有后处理脚本的基础功能,如检查效果可用性、初始化后处理所用材质等等。
脚本如下:
using UnityEngine;[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectBase : MonoBehaviour
{void Start(){CheckResource();}void CheckResource(){bool _supported = CheckDefaultSupported() && CheckSpecificSupported();if (!_supported) OnNotSurported();}/// <summary>/// 用于后处理模块通用设置/// 可被子类重写,因为可能存在某个子类不需要指定条件的情况/// </summary>/// <returns></returns>protected virtual bool CheckDefaultSupported(){return true;}/// <summary>/// 不同后处理子类可以分别实现各自特殊的检查/// </summary>/// <returns></returns>protected virtual bool CheckSpecificSupported(){return true;}/// <summary>/// 不满足后处理启用条件时禁用脚本/// </summary>void OnNotSurported(){enabled = false;}/// <summary>/// 初始化后处理使用的材质和Shader/// </summary>/// <param name="_shader"></param>/// <param name="_material"></param>/// <returns></returns>protected Material CheckShaderAndMaterial(Shader _shader, Material _material){if (null == _shader || !_shader.isSupported) return null;if (null == _material || _material.shader != _shader){_material = new Material(_shader);_material.hideFlags = HideFlags.DontSave;}return _material;}
}
3. 调整亮度、饱和度、对比度
下面的例子中,我们实现一个用于调整屏幕亮度、饱和度以及对比度的后处理效果。
首先,从Shader出发,考虑如何实现对于上述三项的调整:
- 对纹理(抓取的屏幕图像)进行采样,得到原始颜色
- 调整亮度:用原始颜色乘以亮度系数 _Brightness,即可得到调整后的处理颜色①
- 调整饱和度:通过 0.2125 * R + 0.7154 * G + 0.0721 * B 公式对原始颜色进行处理,得到纹理灰度值,然后按照饱和度系数 _Saturation 从灰度值到处理颜色①进行插值,即可得到增加了饱和度变化的处理颜色②
- 调整对比度:构建一个 (0.5, 0.5, 0.5) 的对比度为 0 的颜色,按照对比度系数 _Contrast 向处理颜色②进行插值,得到最终的颜色
然后,创建调用后处理的脚本,继承自上文后处理父类:
- 为脚本指定所需Shader
- 实现OnRenderImage方法,为Shader所需的 _Brightness、 _Saturation、_Contrast 赋值,并调用 Blit 方法执行后处理
测试脚本:
using UnityEngine;public class PostEffect_BrightnessSaturationContrast : PostEffectBase
{public Shader BscShader;public Material BscMat;[Range(0.0f, 3.0f)]public float Brightness = 1;[Range(0.0f, 3.0f)]public float Saturation = 0.5f;[Range(0.0f, 3.0f)]public float Contrast = 0.5f;private void OnRenderImage(RenderTexture src, RenderTexture dest){Material _mat = CheckShaderAndMaterial(BscShader, BscMat);if (null == _mat) Graphics.Blit(src, dest);else{_mat.SetFloat("_Brightness", Brightness);_mat.SetFloat("_Saturation", Saturation);_mat.SetFloat("_Contrast", Contrast);Graphics.Blit(src, dest, _mat);}}
}
测试Shader:
Shader "MyShader/Chapter_12/Chapter_12_BSC_Shader"
{Properties{_MainTex("MainTex", 2D) = "white"{}}SubShader{Pass{Tags{"LightMode" = "ForwardBase"}ZTest AlwaysZWrite OffCull OffCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase#include "UnityCG.cginc"struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};sampler2D _MainTex;float4 _MainTex_ST;fixed _Brightness;fixed _Saturation;fixed _Contrast;v2f vert(appdata_img v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;return o;}fixed4 frag(v2f i) : SV_Target{fixed3 _samplerColor = tex2D(_MainTex, i.uv).rgb;fixed3 _finalColor = _samplerColor * _Brightness;_samplerColor *= _Brightness;//0.2125 * R + 0.7154 * G + 0.0721 * Bfixed _luminance = 0.2125 * _samplerColor.r + 0.7154 * _samplerColor.g + 0.0721 * _samplerColor.b;_finalColor = lerp(float3(_luminance, _luminance, _luminance), _finalColor, _Saturation);_finalColor = lerp(float3(0.5, 0.5, 0.5), _finalColor, _Contrast);return fixed4(_finalColor, 1);}ENDCG}}
}
测试效果:
亮度:
饱和度:
对比度: