Writing Custom Effects 写自定义效果
本文档主要是对Unity官方手册的个人理解与总结(其实以翻译记录为主:>)
仅作为个人学习使用,不得作为商业用途,欢迎转载,并请注明出处。
文章中涉及到的操作都是基于Unity2018.4版本
参考链接:https://docs.unity3d.com/Packages/com.unity.postprocessing@2.1/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就不能正常工作。