着色器变体和关键字

可以编写着色器代码片段来共享通用代码,但在启用或禁用给定关键字时具有不同功能。Unity 编译这些着色器代码片段时,它将为已启用和已禁用关键字的不同组合创建单独的着色器程序。这些各个着色器程序被称为着色器变体。

由于项目工作流程的原因,着色器变体可能会很有用;可以将同一着色器分配给不同材质,但要为每种材质配置不同关键字。这意味着可以在同一个地方编写和维护着色器代码,并减少项目中的着色器资源。还可以使用着色器变体,通过启用或禁用关键字在运行时更改着色器行为。

具有大量变体的着色器被称为“大型着色器”或“超级着色器”。Unity 的标准着色器就是此类着色器的一个示例。

使用着色器变体和关键字

创建着色器变体

要创建着色器变体,请将以下指令之一添加到着色器代码片段:

1
2
3
4
#pragma multi_compile
#pragma multi_compile_local
#pragma shader_feature
#pragma shader_feature_local

可以将这些指令用于常规着色器(包括表面着色器)和计算着色器。

然后,Unity 使用不同的预处理器指令来多次编译同一着色器代码。

启用和禁用着色器关键字

要启用和禁用着色器关键字,请使用以下 API:

  • Shader.EnableKeyword:启用全局关键字
  • Shader.DisableKeyword:禁用全局关键字
  • CommandBuffer.EnableShaderKeyword:使用 CommandBuffer 来启用全局关键字
  • CommandBuffer.DisableShaderKeyword:使用 CommandBuffer 来禁用全局关键字
  • Material.EnableKeyword:为常规着色器启用本地关键字
  • Material.DisableKeyword:为常规着色器禁用本地关键字
  • ComputeShader.EnableKeyword:为计算着色器启用本地关键字
  • ComputeShader.DisableKeyword:为计算着色器禁用本地关键字

启用或禁用关键字时,Unity 会使用相应变体。

multi_compile 的工作方式

指令示例:

1
# pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON

此指令示例生成两个着色器变体:一个定义了 FANCY_STUFF_OFF,另一个定义了 FANCY_STUFF_ON。在运行时,Unity 根据材质或全局着色器关键字来激活其中一个变体。如果这两个关键字均未启用,则 Unity 使用第一个关键字(在此示例中为 FANCY_STUFF_OFF)。

可以在 multi_compile 行中添加两个以上的关键字。例如:

1
# pragma multi_compile SIMPLE_SHADING BETTER_SHADING GOOD_SHADING BEST_SHADING

此指令示例生成四个着色器变体:SIMPLE_SHADING、BETTER_SHADING、GOOD_SHADING 和 BEST_SHADING。

为了生成未定义预处理器宏的着色器变体,请添加一个只有下划线 (__) 的名称。这是避免用完两个关键字的常用方法,因为在一个项目中可以使用的关键字数量有限(请参阅后面的关键字限制部分)。例如:

1
# pragma multi_compile __ FOO_ON

此指令生成两个着色器变体:一个未定义任何关键字 (__),另一个定义了 FOO_ON。

shader_feature 与 multi_compile 之间的区别

shader_feature 与 multi_compile 非常相似。唯一的区别是 Unity 没有将 shader_feature 着色器的未用变体包含在最终构建中。因此,应该将 shader_feature 用于材质中设置的关键字,而 multi_compile 更适合通过代码来全局设置的关键字。

此外,有一个只包含一个关键字的速记符号:

1
# pragma shader_feature FANCY_STUFF

这只是 #pragma shader_feature _ FANCY_STUFF 的快捷方式。它会扩展为两个着色器变体(第一个没有定义;第二个有定义)。

合并多个 multi_compile 行

如果提供 multi_compile 行,Unity 将会针对所有可能的行组合来编译生成的着色器。例如:

1
2
# pragma multi_compile A B C
# pragma multi_compile D E

这会为第一行生成三个变体,为第二行生成两个变体。总共生成六个着色器变体(A+D、B+D、C+D、A+E、B+E、C+E)。

每个 multi_compile 行可以视为用于控制单个着色器“功能”。请记住,着色器变体的总数会以这种方式急速增长。例如,十个各有两个选项的 multi_compile 功能总共生成 1024 个着色器变体。

关键字限制

使用着色器变体时,Unity 中的关键字数量上限是 256,Unity 将大约 60 个关键字保留供内部使用(因此降低了可用上限)。关键字会在整个 Unity 项目中全局启用,因此在多个不同着色器中定义多个关键字时,请注意不要超过限制。

本地关键字

shader_featuremulti_compile 的主要缺点是其中定义的所有关键字均会影响 Unity 的全局关键字计数上限(256 个全局关键字,外加 64 个本地关键字)。为了避免此问题,可以使用不同的着色器变体指令:shader_feature_localmulti_compile_local

  • shader_feature_local:类似于 shader_feature,但是枚举的关键字为本地关键字。
  • multi_compile_local:类似于 multi_compile,但是枚举的关键字为本地关键字。

本地指令将已定义的关键字保留在特定于该着色器的这些指令之下,而不是将这些关键字应用于整个项目。因此,应该使用本地关键字而不是全局关键字,除非计划通过全局 API 来启用这些特定关键字。

开始使用本地关键字时,您可能会发现性能有变化,但是此差异取决于项目的设置方式。每个着色器的本地和全局关键字总数会影响性能:在理想设置中,多用本地关键字和少用全局关键字可以减少每个着色器的关键字总数。

如果全局关键字和本地关键字同名,Unity 会优先考虑本地关键字。

限制

不能将本地关键字与进行全局关键字更改的 API 一起使用(例如 Shader.EnableKeyword 或 CommandBuffer.EnableShaderKeyword)。

每个着色器最多有 64 个唯一性的本地关键字。

如果材质启用了本地关键字,并且其着色器变为不再声明的着色器,Unity 将创建新的全局关键字。

示例

1
# pragma multi_compile_local __ FOO_ON

此指令生成两个着色器变体:一个未定义任何关键字 (__),另一个定义了 FOO_ON(本地关键字)。

启用本地关键字的过程与启用全局关键字的过程相同:

1
2
3
4
5
public Material mat;
Private void Start()
{
mat.EnableKeyword("FOO_ON");
}

内置 multi_compile 快捷方式

有几个“快捷方式”符号用于编译多个着色器变体。这些变体主要处理 Unity 中的不同光源、阴影和光照贴图类型。请参阅有关渲染管线的文档以了解详细信息。

  • multi_compile_fwdbase 编译 PassType.ForwardBase 所需的所有变体。这些变体处理不同的光照贴图类型以及启用或禁用的方向光主要阴影。
  • multi_compile_fwdadd 编译 PassType.ForwardAdd 的变体。这将编译变体来处理方向光、聚光灯或点光源类型,以及它们带有剪影纹理的变体。
  • multi_compile_fwdadd_fullshadowsmulti_compile_fwdadd 相同,但还能够让光源具有实时阴影。
  • multi_compile_fog 扩展为多个变体以处理不同的雾效类型 (off/linear/exp/exp2)。

大多数内置快捷方式会产生许多着色器变体。如果知道项目不需要这些变体,可以使用 #pragma skip_variants 来跳过对其中一些变体的编译。例如:

1
2
# pragma multi_compile_fwdadd
# pragma skip_variants POINT POINT_COOKIE

该指令会跳过包含 POINT 或 POINT_COOKIE 的所有变体。

原文链接:https://docs.unity3d.com/cn/2019.4/Manual/SL-MultipleProgramVariants.html