着色器变体

着色器变体

汇总一下shader变体相关的内容

为什么要变体

最根本的原因是因为着色器在运行时产生分支的开销太大,有必要为了不同的条件产生不同的shader,减轻GPU的压力,用空间换时间。
主要是有以下几个目的

  • 可以将同一着色器分配给不同的材质,然后为不同的材质配置不同的关键字,就可以做到在同一个地方维护代码,提高复用性。
  • 可以在运行时通过开关关键字改变着色器行为。
  • 利用变体禁用不必要的逻辑,优化运行效率,如没有法线贴图,那就没必要再去采用一次。
  • 开关一些特定功能,如光照贴图等等

例如URP的lit就包含了一大堆关键字

// -------------------------------------
// Material Keywords
#pragma shader_feature_local _NORMALMAP
#pragma shader_feature_local _PARALLAXMAP
#pragma shader_feature_local _RECEIVE_SHADOWS_OFF
#pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED
#pragma shader_feature_local_fragment _SURFACE_TYPE_TRANSPARENT
#pragma shader_feature_local_fragment _ALPHATEST_ON
#pragma shader_feature_local_fragment _ _ALPHAPREMULTIPLY_ON _ALPHAMODULATE_ON
#pragma shader_feature_local_fragment _EMISSION
#pragma shader_feature_local_fragment _METALLICSPECGLOSSMAP
#pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
#pragma shader_feature_local_fragment _OCCLUSIONMAP
#pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF
#pragma shader_feature_local_fragment _ENVIRONMENTREFLECTIONS_OFF
#pragma shader_feature_local_fragment _SPECULAR_SETUP

// -------------------------------------
// Universal Pipeline keywords
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ EVALUATE_SH_MIXED EVALUATE_SH_VERTEX
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING
#pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION
#pragma multi_compile_fragment _ _SHADOWS_SOFT
#pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION
#pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3
#pragma multi_compile_fragment _ _LIGHT_LAYERS
#pragma multi_compile_fragment _ _LIGHT_COOKIES
#pragma multi_compile _ _FORWARD_PLUS
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RenderingLayers.hlsl"


// -------------------------------------
// Unity defined keywords
#pragma multi_compile _ LIGHTMAP_SHADOW_MIXING
#pragma multi_compile _ SHADOWS_SHADOWMASK
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile _ DYNAMICLIGHTMAP_ON
#pragma multi_compile_fragment _ LOD_FADE_CROSSFADE
#pragma multi_compile_fog
#pragma multi_compile_fragment _ DEBUG_DISPLAY

指令

一般有4个

  • multi_compile
  • shader_feature
    区别下文再详细描述

启用与禁用

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

其他条件判断

静态分支

编译时判断。
使用如用#if、#elif、#else和#endif预处理器指令,或#ifdef和#ifndef等,在编译时就确定的条件。
不影响性能。

动态分支

运行时判断。
可以使用关键字来作为动态分支的条件。
也可以单纯使用if来创建。

变体

根据条件来编译成多个着色器变体,在运行时选择与条件匹配的着色器程序。

关键字

可以不声明而直接使用关键字。但这样就不会产生对应的变体。

关键字集

一组关键字合称为关键字集。不同的关键字集合组合起来就会产生数量相乘的组合变体。

声明

声明时可以选择是用于变体还是动态分支。

  • dynamic branch : 用于动态分支
  • multi compile : 创建一组变体
  • shader feature : 如果不启用就不会生成对应的变体

后两者顾名思义,前者是多重编译,即要为每种组合生成变体,而后者仅是开关着色器功能,如果编辑时不开启,那运行时也不会有。

范围

除了上面的编译时的区别,还有范围的区别。有局部和全局的差别。
这决定了是否可以在运行时通过全局着色器关键字来重写。
默认是全局,但如果指定了局部,那就无非通过全局的方式来修改。
具体是添加“_local”

阶段

有以下几个阶段

  • _vertex
  • _fragment
  • _hull
  • _domain
  • _geometry
  • _raytracing

可以指定变体生成的阶段,顶点还是片元。
虽然unity会自动判别,假如如果一个变体仅在片元中生效,那会自动删除重复的顶点着色器,但这会浪费编译时间,所以最好顺手设定一下。

用关键字来作为条件

假使有如下代码

#pragma multi_compile QUALITY_LOW QUALITY_MED QUALITY_HIGH

if (QUALITY_LOW)
{
    // code for low quality setting
}

如果这里改用dynamic_branch,那unity其实是为每个关键字创建了一个布尔变量,要求GPU去动态的判断。
而如果是shader_feature or multi_compile ,则会依据当前的关键字状态,将对应的变体发送到GPU。

其他语句

除了if,还可以使用如下的hlsl语句

  • #if, #elif, #else and #endif.
  • #ifdef and #ifndef.

使用这些的话会导致以后更改#pragma更加困难,如更难将multi_compile 更改为 shader_feature。(存疑,没有体会到)

为禁用关键字创建变体

如果使用shader feature创建单关键字,那在禁用后还是会自动创建第二个启用的变体。即类似默认添加_。
而如果使用multi_compile创建单关键字,不声明则不会自动创建。

但如果是在使用multi_compile或者shader_feature创建两个或以上的关键字组,则可以在之前声明”_”,即为禁用全部关键字时创建一个变体。

shader_feature 如没有添加”_”,默认取第一个关键字,添加后则默认为空。 手动在mat中启用其他非默认的关键字,之前的默认关键字还是会生成变体。

这里规则相当的绕,牵涉各种特殊情况,又和shader中的skip unused shader_feature有关,建议还是通过查看shader文件的变体文件来确认最终有多少和那些变体。

快捷方式

untiy定义了一些快捷指令,以方便开关某些具体的功能,如点光源阴影,雾气,GPU实例化等等。

配合材质窗口

可以通过在Properties中使用Toggle、ToggleOff或者KeywordEnum等来方便的控制。


着色器变体
https://www.kuanmi.top/2023/06/14/ShaderVariants/
作者
KuanMi
发布于
2023年6月15日
许可协议