【Unity Shader入门精要 第12章】屏幕后处理效果(一)

发布于:2024-06-02 ⋅ 阅读:(162) ⋅ 点赞:(0)

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出发,考虑如何实现对于上述三项的调整:

  1. 对纹理(抓取的屏幕图像)进行采样,得到原始颜色
  2. 调整亮度:用原始颜色乘以亮度系数 _Brightness,即可得到调整后的处理颜色①
  3. 调整饱和度:通过 0.2125 * R + 0.7154 * G + 0.0721 * B 公式对原始颜色进行处理,得到纹理灰度值,然后按照饱和度系数 _Saturation 从灰度值到处理颜色①进行插值,即可得到增加了饱和度变化的处理颜色②
  4. 调整对比度:构建一个 (0.5, 0.5, 0.5) 的对比度为 0 的颜色,按照对比度系数 _Contrast 向处理颜色②进行插值,得到最终的颜色

然后,创建调用后处理的脚本,继承自上文后处理父类:

  1. 为脚本指定所需Shader
  2. 实现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 Always
            ZWrite Off
            Cull Off
            
            CGPROGRAM
            #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 * B
                fixed _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
        }
    }
}

测试效果:
亮度:
在这里插入图片描述
饱和度:
在这里插入图片描述
对比度:
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到