本文项目地址:https://e.coding.net/man_man/PostProcessStackExample.git
创建自定义特效
创建自定义特系需要两个文件:c# 和HLSL文件
HLSL文件会被编程成GLSL,Metal 和其他API所以不用担心平台限制。
我们来看一个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);
}
}
重要:这段代码必须存储在一个名为Grayscale.cs
的文件中。因为在Unity中序列化的工作原理,你必须确保文件以你的设置类名称命名,否则无法正常序列化。
我们需要两个类,一个用来存储设置(数据),另一个用来处理渲染部分(逻辑)。
Settings
设置类保存了我们的效果数据。这些是你在Volumn inspector中看到的所有面向用户的字段。
[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 };
}
首先,你需要确保这个类是扩展了PostProcessEffectSettings
的类,并且可以被序列化,所以不要忘了[Serializable]
属性!
其次,你需要告诉Unity这是一个持有后处理数据的类。这就是[PostProcess()]
属性的作用。第一个参数是将设置链接到一个渲染器(下一节会有更多介绍)。第二个参数是效果的注入点。现在你有3个可用的参数:
BeforeTransparent
:效果将只在transparent pass
之前应用于不透明的对象。BeforeStack
:效果将在内置堆栈启动前应用。这包括抗锯齿、景深、色调映射等。AfterStack
:效果会在内置堆栈之后,在FXAA
(如果启用了的话)和final-pass dithering之前应用。
对于参数本身,你可以使用任何你需要的类型,但如果你想让这些参数可以被覆盖,并且在卷中可以混合,你就必须使用boxed fields。在本例中,我们将简单地添加一个固定范围为0
到1
的FloatParameter
。 你可以通过浏览/PostProcessing/Runtime/
中的ParameterOverride.cs
源文件来获得完整的内置参数类列表,或者你也可以按照同样的源文件中的方法创建自己的参数类。
注意,你也可以重写PostProcessEffectSettings的IsEnabledAndSupported()
方法来设置你自己对效果的要求(如果它需要特定的硬件),甚至可以在满足条件之前自动地禁用效果。例如,在我们的例子中,如果 blend 参数为 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
现在我们来看看渲染逻辑。我们的渲染器扩展了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);
}
}
一切都在Render()
方法中发生,该方法需要一个PostProcessRenderContext
作为参数。这个上下文保存着你可以使用的有用数据,当效果被渲染时,会被传递给其他效果。请查看 /PostProcessing/Runtime/PostProcessRenderContext.cs
中的可用数据列表(该文件有大量注释)。
PostProcessEffectRenderer<T>
还有一些其他的方法可以被覆盖,比如。
void Init()
:在创建渲染器时调用。
DepthTextureMode GetLegacyCameraFlags()
:用于设置摄像机标志,并请求深度贴图、运动向量等。
void ResetHistory()
:当 "重置历史 "事件被调度时调用。主要用于清除历史缓冲区之类的时间效果。
void Release()
:当渲染器被破坏时调用。如果你需要的话,在那里做清理工作。
我们的效果很简单。我们需要做两件事。
将 blend
参数值发送给着色器。
用我们的源图像作为输入,用着色器Blit一个全屏传递到目的地。
因为我们只使用command buffers,所以系统依赖MaterialPropertyBlock来存储着色器数据。你不需要自己创建这些数据,因为框架会自动为你做池化处理,以节省时间并确保性能最佳。所以我们只需要为我们的着色器请求一个PropertySheet
,并在其中设置统一。
最后,我们使用上下文提供的CommandBuffer
,用我们的源码、目的码、表和通证号来blit a fullscreen pass。
C#部分就到此为止了。
Shader
编写自定义效果着色器也是相当直接的,但在开始之前有几件事你应该知道。这个框架大量使用宏来抽象平台的差异,让你的生活更轻松。兼容性是关键,对于即将推出的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
}
}
}
首先要注意的是:我们已经不使用CG
块了。如果未来与Scriptable Render Pipelines的兼容性对你来说很重要,请不要使用它们,因为它们会在切换时破坏着色器,因为CG
块会在着色器中添加你不想要的隐藏代码。相反,使用HLSL
块。
至少你需要包含 StdLib.hlsl
。它包含了预先配置的顶点着色器和变化结构(VertDefault、VaryingsDefault
),以及大部分你需要的数据来编写常用效果。
纹理声明使用宏来完成。要获得可用的宏列表,我们建议你查看/PostProcessing/Shaders/API/
中的一个API文件。
除此之外,其他的都是标准的着色器代码。在这里我们计算出当前像素的亮度,然后用_Blend
uniform将像素的颜色与亮度进行对比,然后返回结果。
重要的是:如果在你的任何场景中都没有引用这个着色器,那么它将不会被建立,并且在编辑器外运行游戏时,效果将无法工作。将其添加到 Resources folder中,或者将其放在Edit -> Project Settings -> Graphics中的Always Included Shaders。
Effect ordering
内置效果是自动排序的,但是自定义效果呢?只要你创建一个新的效果或将其导入到你的项目中,它就会被添加到Post Process Layer
的Custom Effect Sorting list
中。
它们将按注入点进行预排序,但你可以随意重新排序。顺序是按层级排序的,这意味着你可以针对每个相机用不同的排序方式。
Custom editor
默认情况下,设置类的编辑器会自动为你创建。但有时你会希望对字段的显示方式有更多的控制权。就像经典的Unity组件一样,你可以创建自定义编辑器。
重要的是:和经典的编辑器一样,你必须将这些编辑器放在一个编辑器
文件夹中。
如果我们要为我们的Grayscale效果复制默认的编辑器,它的样子是这样的:
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
出于性能上的考虑,FXAA希望每个像素的LDR亮度值存储在其源目标的alpha通道中。如果你需要FXAA,并希望在AfterStack注入点注入自定义效果,请确保最后执行的效果在alpha通道中包含LDR亮度值(或者直接从传入的源中复制alpha)。如果不是的话FXAA将无法正常工作。
FXAA:FXAA全称为“Fast Approximate Anti-Aliasing”,翻译成中文就是“快速近似抗锯齿”。它是传统MSAA(多重采样抗锯齿)效果的一种高性能近似值。它是一种单程像素着色器,和MLAA一样运行于目标游戏渲染管线的后期处理阶段,但不像后者那样使用DirectCompute,而只是单纯的后期处理着色器,不依赖于任何GPU计算API。正因为如此,FXAA技术对显卡没有特殊要求,完全兼容NVIDIA、AMD的不同显卡(MLAA仅支持A卡)和DX9、DX10、DX11