Writing Custom Effects 寫自定義效果 後期處理系列9

Writing Custom Effects 寫自定義效果

本文檔主要是對Unity官方手冊的個人理解與總結(其實以翻譯記錄爲主:>)
僅作爲個人學習使用,不得作爲商業用途,歡迎轉載,並請註明出處。
文章中涉及到的操作都是基於Unity2018.4版本
參考鏈接:https://docs.unity3d.com/Packages/[email protected]/manual/Writing-Custom-Effects.html

This framework allows you to write custom post-processing effects and plug them to the stack without having to modify the codebase. Of course, all effects written against the framework will work out-of-the-box with volume blending, and unless you need loop-dependent features they’ll also automatically work with upcoming Scriptable Render Pipelines!
這個框架允許您編寫定製的後處理效果並將其插入堆棧,而無需修改代碼庫。當然,所有針對框架編寫的效果都可以通過體積混合開箱即用,除非需要依賴於循環的特性,否則它們還可以自動使用即將到來的腳本化渲染管道!

Let’s write a very simple grayscale effect to show it off.
讓我們寫一個非常簡單的灰度效果來展示它。

Custom effects need a minimum of two files: a C# and a HLSL source files (note that HLSL gets cross-compiled to GLSL, Metal and others API by Unity so it doesn’t mean it’s restricted to DirectX).
定製效果至少需要兩個文件:一個c#和一個HLSL源文件(注意,HLSL通過Unity被交叉編譯爲GLSL、Metal和其他API,所以這並不意味着它僅限於DirectX)。

Note: this quick-start guide requires moderate knowledge of C# and shader programming. We won’t go over every detail here, consider it as an overview more than an in-depth tutorial.
注意:這個快速啓動指南需要適當的c#和着色器編程知識。我們不會在這裏詳細討論每一個細節,把它看作一個概述而不是一個深入的教程。

C#

using System;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;

[Serializable]
[PostProcess(typeof(GrayscaleRenderer), PostProcessEvent.AfterStack, "Custom/Grayscale")]
public sealed class Grayscale : PostProcessEffectSettings
{
    [Range(0f, 1f), Tooltip("Grayscale effect intensity.")]
    public FloatParameter blend = new FloatParameter { value = 0.5f };
}

public sealed class GrayscaleRenderer : PostProcessEffectRenderer<Grayscale>
{
    public override void Render(PostProcessRenderContext context)
    {
        var sheet = context.propertySheets.Get(Shader.Find("Hidden/Custom/Grayscale"));
        sheet.properties.SetFloat("_Blend", settings.blend);
        context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
    }
}

Important: this code has to be stored in a file named Grayscale.cs. Because of how serialization works in Unity, you have to make sure that the file is named after your settings class name or it won’t be serialized properly.
重要提示:此代碼必須存儲在名爲Grayscale.cs的文件中。因爲在Unity中序列化是如此工作的,您必須確保文件是以您的設置類名命名的,否則它將不能正確地序列化。

We need two classes, one to store settings (data) and another one to handle the rendering part (logic).
我們需要兩個類,一個用於存儲設置(數據),另一個用於處理渲染部分(邏輯)。

Settings

The settings class holds the data for our effect. These are all the user-facing fields you’ll see in the volume inspector.
settings類保存我們效果的數據。這些都是面向用戶的字段,您將在體積檢示器中看到。

[Serializable]
[PostProcess(typeof(GrayscaleRenderer), PostProcessEvent.AfterStack, "Custom/Grayscale")]
public sealed class Grayscale : PostProcessEffectSettings
{
    [Range(0f, 1f), Tooltip("Grayscale effect intensity.")]
    public FloatParameter blend = new FloatParameter { value = 0.5f };
}

First, you need to make sure this class extends PostProcessEffectSettings and can be serialized, so don’t forget the [Serializable] attribute!
首先,您需要確保該類繼承了PostProcessEffectSettings,並且可以序列化,所以不要忘記[Serializable]屬性!

Second, you’ll need to tell Unity that this is a class that holds post-processing data. That’s what the [PostProcess()] attribute is for. First parameter links the settings to a renderer (more about that in the next section). Second parameter is the injection point for the effect. Right now you have 3 of those available:
其次,您需要告訴Unity這是一個包含後處理數據的類。這就是[PostProcess()]屬性的作用。First參數將設置鏈接到渲染器(下一節將詳細介紹)。第二個參數是效果的注入點。現在你有3種:

  • BeforeTransparent: the effect will only be applied to opaque objects before the transparent pass is done.
    該效果只會在透明pass之前應用於不透明對象。
  • BeforeStack: the effect will be applied before the built-in stack kicks-in. That includes anti-aliasing, depth-of-field, tonemapping etc.
    該效果將在內置棧啓動之前應用。包括抗鋸齒、景深、色調映射等。
  • AfterStack: the effect will be applied after the builtin stack and before FXAA (if it’s enabled) & final-pass dithering.
    此效果將在內置棧之後和FXAA(如果啓用)和final-pass抖動之前應用。

The third parameter is the menu entry for the effect. You can use / to create sub-menu categories.
第三個參數是效果的菜單項。您可以使用/創建子菜單類別。

Finally, there’s an optional fourth parameter allowInSceneView which, as its name suggests, enables the effect in the scene view or not. It’s set to true by default but you may want to disable it for temporal effects or effects that make level editing hard.
最後,還有一個可選的第四個參數allowInSceneView,顧名思義,它支持或不支持場景視圖中的效果。默認情況下它被設置爲true,但是您可能想要禁用它來處理臨時效果或使關卡編輯變得困難的效果。

For parameters themselves you can use any type you need, but if you want these to be overridable and blendable in volumes you’ll have to use boxed fields. In our case we’ll simply add a FloatParameter with a fixed range going from 0 to 1. You can get a full list of builtin parameter classes by browsing through the ParameterOverride.cs source file in /PostProcessing/Runtime/, or you can create your own quite easily by following the way it’s done in that same source file.
對於參數本身,您可以使用所需的任何類型,但是如果您希望這些參數在體積中可覆蓋和可混合,則必須使用已裝箱的字段。在本例中,我們只需添加一個浮動參數,其範圍固定從0到1。您可以通過在/PostProcessing/Runtime/中瀏覽ParameterOverride.cs源文件來獲得完整的內置參數類列表,也可以在同一源文件中按照下面方式完成輕鬆創建自己的內置參數。

Note that you can also override the IsEnabledAndSupported() method of PostProcessEffectSettings to set your own requirements for the effect (in case it requires specific hardware) or even to silently disable the effect until a condition is met. For example, in our case we could automatically disable the effect if the blend parameter is 0 like this:
注意,您還可以覆蓋PostProcessEffectSettings的IsEnabledAndSupported()方法來設置您自己對效果的需求(如果需要特定的硬件),甚至在滿足條件之前靜默禁用效果。例如,在我們的例子中,如果混合參數爲0,我們可以像這樣自動禁用效果:

public override bool IsEnabledAndSupported(PostProcessRenderContext context)
{
    return enabled.value
        && blend.value > 0f;
}

That way the effect won’t be executed at all unless blend > 0.
這樣效果將不會執行,除非 blend> 0。

Renderer 渲染

Let’s look at the rendering logic now. Our renderer extends PostProcessEffectRenderer, with T being the settings type to attach to this renderer.
現在讓我們看看渲染邏輯。我們的渲染器擴展了PostProcessEffectRenderer,其中T是要附加到這個渲染器的設置類型。

public sealed class GrayscaleRenderer : PostProcessEffectRenderer<Grayscale>
{
    public override void Render(PostProcessRenderContext context)
    {
        var sheet = context.propertySheets.Get(Shader.Find("Hidden/Custom/Grayscale"));
        sheet.properties.SetFloat("_Blend", settings.blend);
        context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
    }
}

Everything happens in the Render() method that takes a PostProcessRenderContext as parameter. This context holds useful data that you can use and is passed around effects when they are rendered. Look into /PostProcessing/Runtime/PostProcessRenderContext.cs for a list of what’s available (the file is heavily commented).
Render()方法中發生的所有事情都以PostProcessRenderContext作爲參數。此上下文包含有用的數據,您可以使用這些數據,並在渲染效果時傳遞這些數據。查看/PostProcessing/Runtime/PostProcessRenderContext.cs,以獲得可用內容的列表(該文件有大量註釋)。

PostProcessEffectRenderer also have a few other methods you can override, such as:

  • void Init(): called when the renderer is created. 渲染器被創建時
  • DepthTextureMode GetLegacyCameraFlags(): used to set camera flags and request depth map, motion vectors, etc.
    用於設置相機Flag和請求深度圖、運動矢量等。
  • void ResetHistory(): called when a “reset history” event is dispatched. Mainly used for temporal effects to clear history buffers and whatnot. 在廣播“重置歷史”事件時調用。主要用於臨時效果以清除歷史緩衝區等。
  • void Release(): called when the renderer is destroyed. Do your cleanup there if you need it.渲染器銷燬,在你需要時清理

Our effect is quite simple. We need two things:

  • Send the blend parameter value to the shader. 將混合參數值發送到着色器。
  • Blit a fullscreen pass with the shader to a destination using our source image as an input.
    使用我們的源圖作爲輸入,將着色器pass全屏傳遞到目標。

Because we only use command buffers, the system relies on MaterialPropertyBlock to store shader data. You don’t need to create those yourself as the framework does automatic pooling for you to save time and make sure performances are optimal. So we’ll just request a PropertySheet for our shader and set the uniform in it.
因爲我們只使用命令緩衝區,所以系統依賴於MaterialPropertyBlock來存儲着色器數據。您不需要自己創建它們,因爲框架會自動爲您創建池,以節省時間並確保性能是最佳的。我們只需要爲着色器請求一個屬性表並在其中統一設置。

Finally we use the CommandBuffer provided by the context to blit a fullscreen pass with our source, destination, sheet and pass number.
最後,我們使用context提供的CommandBuffer和源圖、目標、屬性表和pass序號傳輸一個全屏pass。

And that’s it for the C# part.
這就是c#部分的內容。

Shader

Writing custom effect shaders is fairly straightforward as well, but there are a few things you should know before you get to it. This framework makes heavy use of macros to abstract platform differences and make your life easier. Compatibility is key, even more so with the upcoming Scriptable Render Pipelines.
編寫自定義效果着色器也相當簡單,但是在使用它之前,您應該知道一些事情。這個框架大量使用宏來抽象平臺差異,使您的工作更輕鬆。兼容性是關鍵,對於即將到來的腳本化呈現管道更是如此。

Shader "Hidden/Custom/Grayscale"
{
    HLSLINCLUDE

        #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"

        TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
        float _Blend;

        float4 Frag(VaryingsDefault i) : SV_Target
        {
            float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
            float luminance = dot(color.rgb, float3(0.2126729, 0.7151522, 0.0721750));
            color.rgb = lerp(color.rgb, luminance.xxx, _Blend.xxx);
            return color;
        }

    ENDHLSL

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

                #pragma vertex VertDefault
                #pragma fragment Frag

            ENDHLSL
        }
    }
}

First thing to note: we don’t use CG blocks anymore. If future compatibility with Scriptable Render Pipelines is important to you, do not use them as they’ll break the shader when switching over because CG blocks add hidden code you don’t want to the shader. Instead, use HLSL blocks.
首先要注意的是:我們不再使用CG塊了。如果將來與腳本渲染管道的兼容性對您很重要,那麼不要使用它們,因爲它們會在轉換時破壞着色器,因爲CG塊添加了您不想添加到着色器中的隱藏代碼。使用HLSL塊代替。

At a minimum you’ll need to include StdLib.hlsl. This holds pre-configured vertex shaders and varying structs (VertDefault, VaryingsDefault) and most of the data you need to write common effects.
至少您需要包含StdLib.hlsl。它包含預先配置的頂點着色器和不同的結構體(VertDefault、VaryingsDefault),以及編寫通用效果所需的大部分數據。

Texture declaration is done using macros. To get a list of available macros we recommend you look into one of the api files in /PostProcessing/Shaders/API/.
貼圖聲明是使用宏來完成的。要獲得可用宏的列表,我們建議您查看/PostProcessing/Shaders/ api /中的一個api文件。

Other than that, the rest is standard shader code. Here we compute the luminance for the current pixel, we lerp the pixel color with the luminance using the _Blend uniform and we return the result.
除此之外,其餘的是標準的着色器代碼。在這裏,我們計算了當前像素的亮度,我們使用_Blend統一對像素顏色和亮度進行lerp,然後返回結果。

Important: if the shader is never referenced in any of your scenes it won’t get built and the effect will not work when running the game outside of the editor. Either add it to a Resources folder or put it in the Always Included Shaders list in Edit -> Project Settings -> Graphics.
重要提示:如果着色器在你的任何場景中都沒有被引用,當在編輯器之外運行遊戲時,它就不會被編譯,效果也不會起作用。要麼將其添加到Resources文件夾,要麼將其放入Edit -> Project Settings -> Graphics中的Always include着色器列表中。

Effect ordering 效果次序

Builtin effects are automatically ordered, but what about custom effects? As soon as you create a new effect or import it into your project it’ll be added to the Custom Effect Sorting lists in the Post Process Layer component on your camera(s).
內置效果是自動排序的,但是自定義效果呢?一旦你創建了一個新的效果或者將它導入到你的項目中,它就會被添加到你相機的後期處理Layer組件的自定義效果排序列表中。

They will be pre-sorted by injection point but you can re-order these at will. The order is per-layer, which means you can use different ordering schemes per-camera.
它們將按注入點預先排序,但您可以隨意重新排序。次序是每層的,這意味着您可以對每個相機使用不同的次序方案。

Custom editor 自定義編輯器

By default editors for settings classes are automatically created for you. But sometimes you’ll want more control over how fields are displayed. Like classic Unity components, you have the ability to create custom editors.
默認情況下,設置類的編輯器會自動爲您創建。但有時您需要對字段的顯示方式有更多的控制。與經典的Unity組件一樣,您也可以創建自定義編輯器。

Important: like classic editors, you’ll have to put these in an Editor folder.
重要提示:與經典編輯器一樣,您必須將這些文件放入編輯器文件夾中。

If we were to replicate the default editor for our Grayscale effect, it would look like this:
如果我們複製灰度效果的默認編輯器,它會是這樣的:

using UnityEngine.Rendering.PostProcessing;
using UnityEditor.Rendering.PostProcessing;

[PostProcessEditor(typeof(Grayscale))]
public sealed class GrayscaleEditor : PostProcessEffectEditor<Grayscale>
{
    SerializedParameterOverride m_Blend;

    public override void OnEnable()
    {
        m_Blend = FindParameterOverride(x => x.blend);
    }

    public override void OnInspectorGUI()
    {
        PropertyField(m_Blend);
    }
}

Additional notes 補充說明

For performance reasons, FXAA expects the LDR luminance value of each pixel to be stored in the alpha channel of its source target. If you need FXAA and wants to inject custom effects at the AfterStack injection point, make sure that the last executed effect contains LDR luminance in the alpha channel (or simply copy alpha from the incoming source). If it’s not FXAA won’t work correctly.
出於性能原因,FXAA希望每個像素的LDR亮度值存儲在源目標的alpha通道中。如果您需要FXAA並希望在棧後注入點注入自定義效果,請確保最後執行的效果包含alpha通道中的LDR亮度(或者只是從傳入源複製alpha)。如果不是,FXAA就不能正常工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章