通常方便的做法是保持大部分着色器代码固定,但也允许生成稍微不同的着色器“变体”。这种变体通常称为“大型着色器”或“超级着色器”,实现的方式是针对每种情况使用不同的预处理器指令多次编译着色器代码。
To achieve this in Unity, you can add a #pragma multi_compile
or #pragma shader_feature
directive to a shader snippet. This also works in surface shaders.
At run time, Unity picks up the appropriate shader variant from the Material keywords (Material.EnableKeyword and Material.DisableKeyword) or global shader keywords (Shader.EnableKeyword and Shader.DisableKeyword).
指令示例:
# pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
此指令示例生成两个着色器变体:一个定义了 FANCY_STUFF_OFF
,另一个定义了 FANCY_STUFF_ON
。在运行时,Unity 根据材质或全局着色器关键字来激活其中一个变体。如果这两个关键字均未启用,则 Unity 使用第一个关键字(在此示例中为 FANCY_STUFF_OFF
)。
可以在 multi_compile 行中添加两个以上的关键字。例如:
# pragma multi_compile SIMPLE_SHADING BETTER_SHADING GOOD_SHADING BEST_SHADING
此指令示例生成四个着色器变体:SIMPLE_SHADING
、BETTER_SHADING
、GOOD_SHADING
和 BEST_SHADING
。
为了生成未定义预处理器宏的着色器变体,请添加一个只有下划线 (__
) 的名称。这是避免用完两个关键字的常用方法,因为在一个项目中可以使用的关键字数量有限(请参阅后面的关键字限制部分)。例如:
# pragma multi_compile __ FOO_ON
此指令生成两个着色器变体:一个未定义任何关键字 (__
),另一个定义了 FOO_ON
。
shader_feature
与 multi_compile
非常相似。唯一的区别是 Unity 没有将 shader_feature
着色器的未用变体包含在最终构建中。因此,应该将 shader_feature
用于材质中设置的关键字,而 multi_compile
更适合通过代码来全局设置的关键字。
此外,有一个只包含一个关键字的速记符号:
# pragma shader_feature FANCY_STUFF
这只是 #pragma shader_feature _ FANCY_STUFF
的快捷方式。它会扩展为两个着色器变体(第一个没有定义;第二个有定义)。
如果提供 multi_compile
行,Unity 将会针对所有可能的行组合来编译生成的着色器。例如:
# 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_feature 和 multi_compile 的主要缺点是其中定义的所有关键字均会影响 Unity 的全局关键字计数上限(256 个全局关键字,外加 64 个本地关键字)。为了避免此问题,可以使用不同的着色器变体指令:__shader_feature_local__ 和 multi_compile_local。
本地指令将已定义的关键字保留在特定于该着色器的这些指令之下,而不是将这些关键字应用于整个项目。因此,应该使用本地关键字而不是全局关键字,除非计划通过全局 API 来启用这些特定关键字。
开始使用本地关键字时,您可能会发现性能有变化,但是此差异取决于项目的设置方式。每个着色器的本地和全局关键字总数会影响性能:在理想设置中,多用本地关键字和少用全局关键字可以减少每个着色器的关键字总数。
如果全局关键字和本地关键字同名,Unity 会优先考虑本地关键字。
不能将本地关键字与进行全局关键字更改的 API 一起使用(例如 Shader.EnableKeyword 或 CommandBuffer.EnableShaderKeyword)。
每个着色器最多有 64 个唯一性的本地关键字。
如果材质启用了本地关键字,并且其着色器变为不再声明的着色器,Unity 将创建新的全局关键字。
# pragma multi_compile_local __ FOO_ON
此指令生成两个着色器变体:一个未定义任何关键字 (__
),另一个定义了 FOO_ON
(本地关键字)。
启用本地关键字的过程与启用全局关键字的过程相同:
public Material mat;
Private void Start()
{
mat.EnableKeyword("FOO_ON");
}
有几个“快捷方式”符号用于编译多个着色器变体。这些变体主要处理 Unity 中的不同光源、阴影和光照贴图类型。请参阅有关渲染管线的文档以了解详细信息。
multi_compile_fwdbase
编译 PassType.ForwardBase 所需的所有变体。这些变体处理不同的光照贴图类型以及启用或禁用的方向光主要阴影。multi_compile_fwdadd
编译 PassType.ForwardAdd 的变体。这将编译变体来处理方向光、聚光灯或点光源类型,以及它们带有剪影纹理的变体。multi_compile_fwdadd_fullshadows
- 与 multi_compile_fwdadd
相同,但还能够让光源具有实时阴影。multi_compile_fog
扩展为多个变体以处理不同的雾效类型 (off/linear/exp/exp2)。大多数内置快捷方式会产生许多着色器变体。如果知道项目不需要这些变体,可以使用 #pragma skip_variants
来跳过对其中一些变体的编译。例如:
# pragma multi_compile_fwdadd
# pragma skip_variants POINT POINT_COOKIE
该指令会跳过包含 POINT
或 POINT_COOKIE
的所有变体。
Shader hardward variants allow you to provide a specially optimised set of variants for different levels of hardware capability. You can create simplified shaders that can run efficiently on both high-end and low-end hardware within a single target platform (such as OpenGL ES).
To enable the generation of shader hardware variants, add #pragma hardware_tier_variants renderer
, where renderer
is one of the available renderering platforms for shader program pragmas. With this #pragma
, Unity generates three shader variants for each shader, regardless of any other keywords. Each variant has one of the following defined:
UNITY_HARDWARE_TIER1
UNITY_HARDWARE_TIER2
UNITY_HARDWARE_TIER3
Use these to write conditional fallbacks or extra features for lower or higher-end hardware. In the Unity Editor, you can test any of the tiers in the Graphics Emulation menu, which allows you to change between each of the tiers.
To help keep the impact of these variants as small as possible, Unity only ever loads one set of shaders in the player. Shaders that are identical (for example, if you only write a specialised version for TIER1 and all others are the same) do not take up any extra space on disk.
At load time, Unity examines the GPU that it is using and auto-detects a tier value. It defaults to the highest tier if it can’t auto-detect the GPU. You can set Shader.globalShaderHardwareTier
to override this tier value, but you must do this before Unity loads any shaders you want to vary. A good place to set this is in a pre-load Scene before you load your main Scene.
These shader hardware tiers are not related to the Quality settings of the player. They are detected from the relative capability of the GPU that the player runs on.
Apart from tweaking your shader code for different hardware tiers, you might want to tweak Unity’s internal #defines (for example, you might want to force cascaded shadowmaps on mobiles). For details on how to do this, see documentation on UnityEditor.Rendering.PlatformShaderSettings, which provides a list of currently supported features for overriding per-tier.
Use UnityEditor.Rendering.EditorGraphicsSettings.SetShaderSettingsForPlatform to tweak Platform Shader Settings per-platform per-tier.
If PlatformShaderSettings
set to different tiers are not identical, then Unity generates tier variants for the shader, even if #pragma hardware_tier_variants
is missing.