57.屏幕后期处理效果基类

57.屏幕后期处理效果-屏幕后处理基类


57.1 知识点

知识回顾:屏幕后期处理效果的基本实现

屏幕后期处理效果的基本实现原理是利用 OnRenderImage 函数和 Graphics.Blit 函数,获取当前屏幕画面并通过 Shader 对该纹理进行自定义处理。

捕获画面的关键
void OnRenderImage(RenderTexture source, RenderTexture destination)

实现效果的关键
Graphics.Blit (Texture source, RenderTexture dest, Material mat, int pass= -1);

知识点补充

Shader.isSupported

用于判断 Shader 在目标平台和硬件上是否能正确运行。通过获取 Shader 对象中的 isSupported 属性可以判断:

  • 返回 false:不支持
  • 返回 true:支持

[ExecuteInEditMode] 特性

允许脚本在编辑器模式下也能执行。

[RequireComponent(typeof(组件名))] 特性

指定某个脚本所依赖的组件,确保当脚本附加到游戏对象时,所需组件也会自动添加。如果组件已存在,则不会重复添加。
一般用于后处理脚本,通常依赖摄像机。

材质球中的 HideFlags 枚举

HideFlags 枚举可以设置对象的隐藏和保存属性:

  • HideFlags.None:对象完全可见且可编辑(默认值)。
  • HideFlags.HideInHierarchy:对象在层级视图中被隐藏,但仍存在于场景中。
  • HideFlags.HideInInspector:对象在检查器中被隐藏,但仍存在于层级视图中。
  • HideFlags.DontSaveInEditor:对象不会被保存到场景中(仅影响编辑模式)。
  • HideFlags.NotEditable:对象在检查器中为只读,不能修改。
  • HideFlags.DontSaveInBuild:对象不会包含在构建中。
  • HideFlags.DontUnloadUnusedAsset:对象在资源清理时不会被卸载,即使未引用。
  • HideFlags.DontSave:对象不会保存到场景或构建中,也不会在编辑器中保存(DontSaveInEditor | DontSaveInBuild | DontUnloadUnusedAsset 的组合)。

如果需要设置多个条件,可以使用位或运算符 |

为什么要实现屏幕后处理基类

原因一

实现屏幕后期处理效果时,通常需要:

  1. 创建一个继承自 MonoBehaviour 的自定义 C# 脚本。
  2. 关联对应的材质球或 Shader。
  3. 实现 OnRenderImage 函数。
  4. OnRenderImage 函数中使用 Graphics.Blit 函数。

这些重复的步骤可以抽象到基类中,以后只需在子类中实现特定逻辑。

原因二

基类中可以用代码动态创建材质球,避免每次手动创建。只需在 Inspector 窗口关联 Shader 即可。

原因三

屏幕后处理之前需要检查一系列条件是否满足,例如当前平台是否支持 Unity Shader。这些检查可以在基类中实现,避免重复逻辑。

注意

在旧版本中,基类还需要判断目标平台是否支持屏幕后处理和渲染纹理(通过 Unity 的 SystemInfo 类)。
但是随着时代发展,目前几乎所有的现代图形硬件都是支持屏幕后处理和渲染纹理了,因此只需判断 Shader 是否被支持。

官方文档:SystemInfo 类

实现基类功能

主要步骤

  1. 声明基类
    让基类依赖 Camera,并允许其在编辑模式下运行,确保可以随时查看效果。

  2. 声明公共 Shader
    在基类中声明一个公共 Shader,用于在 Inspector 窗口进行关联。

  3. 声明私有 Material
    声明一个私有的材质球(Material),用于动态创建。

  4. 实现 Shader 判断与动态创建
    在基类中实现逻辑,判断 Shader 是否可用,并动态创建材质球。

  5. 实现 OnRenderImage 虚方法
    在基类中实现 OnRenderImage 的虚方法,完成屏幕后处理效果的基本逻辑。

声明基类,让其依赖Camera,并且让其在编辑模式下可运行,保证我们可以随时看到效果

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectBase : MonoBehaviour
{
}

声明 公共 Shader,用于在Inspector窗口关联,声明 私有 Material,用于动态创建

//屏幕后处理效果会使用的Shader
public Shader shader;

//一个用于动态创建出来的材质球 就不用再工程中手动创建了
private Material _material;

实现保护Material属性,判断Shader是否可用,并且动态创建Material的方法

protected Material material
{
    get
    {
        //如果shader 没有 或者有但是不支持当前平台
        if (shader == null || !shader.isSupported)
            return null;

        //避免每次调用属性都去new材质球
        //如果之前new过了,并且shader也没有变化
        //那就不用new了 直接返回使用即可
        if (_material != null && _material.shader == shader)
            return _material;
        
        //除非材质球是空的 或者shader变化了 才会走下面的逻辑

        //用支持的shader动态创建一个材质球 用于渲染
        _material = new Material(shader);

        //不希望材质球被保存下来 因此我们家一个标识
        _material.hideFlags = HideFlags.DontSave;

        return _material;
    }
}

实现OnRenderImage的虚方法,完成基本逻辑

protected virtual void OnRenderImage(RenderTexture source, RenderTexture destination)
{
    //判断这个材质球是否为空 如果不为空 就证明这个shader能用来处理屏幕后处理效果
    if (material != null)
    {
        Graphics.Blit(source, destination, material);
    }
    //如果为空 就不用处理后处理效果了 直接显示原画面就可以了
    else
    {
        Graphics.Blit(source, destination);
    }
}

赋值动态生成的国际象棋棋盘shader,可以看到效果


57.2 知识点代码

Lesson57_屏幕后期处理效果_屏幕后处理基类.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson57_屏幕后期处理效果_屏幕后处理基类 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾

        //屏幕后期处理效果的基本实现原理
        //就是利用 OnRenderImage函数 和 Graphics.Blit函数
        //来获取当前屏幕画面并利用Shader对该纹理进行自定义处理

        //捕获画面的关键 —— void OnRenderImage(RenderTexture source, RenderTexture destination)

        //实现效果的关键 —— Graphics.Blit (Texture source, RenderTexture dest, Material mat, int pass= -1);

        #endregion

        #region 知识点补充

        //1.Shader.isSupported
        //  如何判断Shader在目标平台和硬件上是否能正确运行
        //  我们可以通过获取Shader对象中的isSupported属性判断
        //  如果返回false,不支持
        //  如果返回true,支持

        //2.[ExecuteInEditMode]特性
        //  用于使脚本在编辑器模式下也能执行

        //3.[RequireComponent(typeof(组件名))]特性
        //  指定某个脚本所依赖的组件,它确保当你将脚本附加到游戏对象时,
        //  所需的组件也会自动添加到该游戏对象中
        //  如果这些组件已经存在,它们不会被重复添加
        //  因为后处理脚本一般添加到摄像机上,因此我们用于依赖摄像机

        //4.材质球中的 HideFlags 枚举
        //  从材质球对象中可以点出 HideFlags 枚举
        //  HideFlags.None: 对象是完全可见和可编辑的。这是默认值。
        //  HideFlags.HideInHierarchy: 对象在层级视图中被隐藏,但仍然存在于场景中。
        //  HideFlags.HideInInspector: 对象在检查器中被隐藏,但仍然存在于层级视图中。
        //  HideFlags.DontSaveInEditor: 对象不会被保存到场景中。适用于编辑器模式,不会影响播放模式。
        //  HideFlags.NotEditable: 对象在检查器中是只读的,不能被修改。
        //  HideFlags.DontSaveInBuild: 对象不会被包含在构建中。
        //  HideFlags.DontUnloadUnusedAsset: 对象在资源清理时不会被卸载,即使它没有被引用。
        //  HideFlags.DontSave: 对象不会被保存到场景中,不会在构建中保存,也不会在编辑器中保存。
        //                      这是 DontSaveInEditor | DontSaveInBuild | DontUnloadUnusedAsset 的组合。
        //  如果想要设置枚举满足多个条件 直接多个枚举 进行位或运算即可 |

        #endregion

        #region 知识点一 为什么要实现屏幕后处理基类

        //原因一:
        //为了实现屏幕后期处理效果
        //我们每次都需要做的事情一定是
        //1.实现一个继承子MonoBehaviour的自定义C#脚本
        //2.关联对应的材质球或者Shader
        //3.实现OnRenderImage函数
        //4.在OnRenderImage函数中使用Graphics.Blit函数
        //那么这些共同点我们完全可以抽象到一个基类中去完成
        //以后只需要在子类中实现各自的基本逻辑即可

        //原因二:
        //我们可以在基类中用代码动态创建材质球
        //不需要为每个后处理效果都手动创建材质球
        //只需要在Inspector窗口关联对应使用的Shader即可

        //原因三:
        //在进行屏幕后处理之前,我们往往需要检查一系列条件是否满足
        //比如:
        //当前平台是否支持当前使用的Unity Shader
        //我们可以在基类中进行判断,避免每次书写相同逻辑

        //注意:
        //在一些老版本中,你可能还会在基类中判断目标平台是否支持屏幕后处理和渲染纹理
        //一般通过Unity中的SystemInfo类判断
        //该类可以用于确定底层平台和硬件相关的功能是否被支持
        //官方说明:https://docs.unity.cn/cn/2022.3/ScriptReference/SystemInfo.html

        //但是随着时代发展,目前几乎所有的现代图形硬件都是支持屏幕后处理和渲染纹理了
        //因此我们无需再进行类似的判断的
        //只需要判断Shader是否被支持即可

        #endregion

        #region 知识点二 实现基类功能

        //主要目标
        //1.声明基类,让其依赖Camera,并且让其在编辑模式下可运行,保证我们可以随时看到效果
        //2.基类中声明 公共 Shader,用于在Inspector窗口关联
        //3.基类中声明 私有 Material,用于动态创建
        //4.基类中实现判断Shader是否可用,并且动态创建Material的方法
        //5.基类中实现OnRenderImage的虚方法,完成基本逻辑

        #endregion
    }
}

PostEffectBase.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectBase : MonoBehaviour
{
    //屏幕后处理效果会使用的Shader
    public Shader shader;

    //一个用于动态创建出来的材质球 就不用再工程中手动创建了
    private Material _material;

    protected virtual void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        //在进行渲染之前 先更新属性 在子类中重写即可
        UpdateProperty();

        //判断这个材质球是否为空 如果不为空 就证明这个shader能用来处理屏幕后处理效果
        if (material != null)
        {
            Graphics.Blit(source, destination, material);
        }
        //如果为空 就不用处理后处理效果了 直接显示原画面就可以了
        else
        {
            Graphics.Blit(source, destination);
        }
    }


    /// <summary>
    /// 更新材质球属性
    /// </summary>
    protected virtual void UpdateProperty()
    {

    }


    protected Material material
    {
        get
        {
            //如果shader 没有 或者有但是不支持当前平台
            if (shader == null || !shader.isSupported)
                return null;

            //避免每次调用属性都去new材质球
            //如果之前new过了,并且shader也没有变化
            //那就不用new了 直接返回使用即可
            if (_material != null && _material.shader == shader)
                return _material;

            //除非材质球是空的 或者shader变化了 才会走下面的逻辑

            //用支持的shader动态创建一个材质球 用于渲染
            _material = new Material(shader);

            //不希望材质球被保存下来 因此我们家一个标识
            _material.hideFlags = HideFlags.DontSave;

            return _material;
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏