Unity Shader 屏幕后处理-调整亮度、饱和度、对比度

Unity Shader系列文章:Unity Shader目录-初级篇

Unity Shader系列文章:Unity Shader目录-中级篇

效果:
左图:原效果。右图:调整了亮度(值为1.2)、饱和度(值为1.6)和对比度(值为1.2)后的效果
原理:

屏幕后处理,通常指的是在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列操作,实现各种屏幕特效。因此,想要实现屏幕后处理的基础在于得到渲染后的屏幕图像,即抓取屏幕,而Unity提供了这样一个方便的接口 OnRenderlmage 函数:

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 sre, Material mat, int pass = -1);

其中,参数src对应了源纹理,在屏幕后处理技术中,这个参数通常就是当前屏幕的渲染纹理或是上一步处理后得到的渲染纹理。参数dest是目标渲染纹理,如果它的值为null就会直接将结果显示在屏幕上。参数mat是我们使用的材质,这个材质使用的Unity Shader将会进行各种屏幕后处理操作,而sre纹理将会被传递给Shader中名为_ MainTex 的纹理属性。参数pass的默认值为-1,表示将会依次调用Shader内的所有Pass。否则,只会调用给定索引的Pass。

在默认情况下,OnRenderlmage函数会在所有的不透明和透明的Pass执行完毕后被调用,以便对场景中所有游戏对象都产生影响。但有时,我们希望在不透明的Pass (即渲染队列小于等于2500的Pass,内置的Background、Geometry 和AlphaTest渲染队列均在此范围内)执行完毕后立即调用OnRenderImage函数,从而不对透明物体产生任何影响。此时,我们可以在OnRenderlmage函数前添加[ImageEffectOpaque]属性来实现这样的目的。

因此,要在Unity中实现屏幕后处理效果,过程通常如下:首先需要在摄像中添加一一个用于屏幕后处理的脚本。在这个脚本中,我们会实现OnRenderlmage函数来获取当前屏幕的渲染纹理。然后,再调用Graphics.Blit函数使用特定的Unity Shader 来对当前图像进行处理,再把返回的渲染纹理显示到屏幕上。对于一些复杂的屏幕特效,我们可能需要多次调用Graphics.Blit 函数来对上一步的输出结果进行下一步处理。
但是,在进行屏幕后处理之前,我们需要检查一系列条件是否满足, 例如当前平台是否支持渲染纹理和屏幕特效,是否支持当前使用的Unity Shader等。为此,我们创建了一个用于屏幕后处理效果的基类,在实现各种屏幕特效时,我们只需要继承自该基类,再实现派生类中不同的操作即可。

ScreenPostEffectsBase基类代码:

using UnityEngine;

/// 
/// 屏幕后处理效果基类
/// 
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class ScreenPostEffectsBase : MonoBehaviour
{
    public Shader Shader;
    public Material Material
    {
        get
        {
            return CheckAndCreateMaterial();
        }
    }
    private Material _material;

    protected void Start()
    {
        CheckResources();
    }

    /// 
    /// 检查资源
    /// 
    protected void CheckResources()
    {
        if (!CheckSupport())
        {
            NotSupported();
        }
    }

    /// 
    /// 检查支持
    /// 
    /// 
    protected bool CheckSupport()
    {
        bool isSupported = SystemInfo.supportsImageEffects;
        return isSupported;
    }

    /// 
    /// 不支持
    /// 
    protected void NotSupported()
    {
        enabled = false;
    }

    /// 
    /// 检查和创建Material
    /// 
    /// 
    protected Material CheckAndCreateMaterial()
    {
        if (!Shader || !Shader.isSupported)
        {
            return null;
        }

        if (_material && _material.shader == Shader)
        {
            return _material;
        }

        _material = new Material(Shader);
        _material.hideFlags = HideFlags.DontSave;
        return _material;
    }
}

ScreenBrightnessSaturationAndContrast派生类代码:

using UnityEngine;

/// 
/// 屏幕亮度、饱和度、对比度
/// 
public class ScreenBrightnessSaturationAndContrast : ScreenPostEffectsBase
{
    /// 
    /// 亮度
    /// 
    [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;

    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (Material)
        {
            Material.SetFloat("_Brightness", Brightness);
            Material.SetFloat("_Saturation", Saturation);
            Material.SetFloat("_Contrast", Contrast);

            Graphics.Blit(src, dest, Material);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}

Shader代码:

// 屏幕亮度、饱和度、对比度
Shader "Custom/BrightnessSaturationAndContrast"
{
    Properties
    {
        _MainTex ("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"

            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);
                finalColor = lerp(luminanceColor, finalColor, _Saturation);

                // 调整对比度
                fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
                finalColor = lerp(avgColor, finalColor, _Contrast);

                return fixed4(finalColor, renderTex.a);
            }

            ENDCG

        }
    }

    Fallback Off
}

你可能感兴趣的:(Unity Shader 屏幕后处理-调整亮度、饱和度、对比度)