https://developer.unity.cn/projects/67160447edbc2a9ccb33996c
首先引用官方对Shader的定义,下图:

如图中所示,Unity的shader不仅仅包含GPU上执行的着色器代码,还包含了包括渲染状态、属性、Pass、变体等定义。而shader变体是本文讨论的主角,它是Unity在编译阶段,根据不同的图形设置、平台、keyword等生成的着色器代码。说简单点就是同一份着色器代码的不同分身,每个分身有不同的功能。 下图为官方对shader变体定义的说明:https://docs.unity.cn/2022.3/Documentation/Manual/SL-MultipleProgramVariants.html(dynamic_branch由于不会产生shader变体,所以不在本文的讨论之内)

那为什么Unity要搞出Shader变体这么个玩意呢?因为 GPU 非常擅长并行化可预测的代码,并且始终遵循相同的路径,从而提高并发量。如果编译的着色器程序中存在条件语句,则 GPU 将需要花费资源来执行预测任务、等待其他路径完成等,从而导致效率低下。所以为了解决shader不同效果的单独计算,unity提供了shader变体这一方式,也就是在编译期间根据判断(宏)来为shader编译不同的分身。下图为Unity Shader编译流程(橙色方框为unity提供的可编程变体剔除方法,允许用户自定义剔除规则):

虽然shader变体解决了shader并行计算的问题,节省了性能开销,但是也带来了额外的问题。比如随着项目的shader越来越多,变体越来越多,导致内存直线增长,顺带着构建过程越来越久,也就是我们常说的变体爆炸。我们通过下图来直观的感受一下变体爆炸的威力:

你没有看错,如果不进行任何的变体收集和剔除,单单一个urp自带的lit shader,变体数量就能达到355W个!如果开启选择skip shader features呢?如下图,竟也还有6w多个变体。

我们接着测试,把Graphics下Shader Stripping的剔除条件都关闭,再来看一下lit shader的变体数量,只剩593个了。


所以说,生成的变体数量的增加,具体取决于各种因素,包括定义的关键字和属性、质量设置、图形层、启用的图形 API、后处理效果、渲染管道、照明和雾模式以及是否启用 XR 等。而这些条件,归根结底都是shader_feature 和 multi_compile 关键字的不同组合。
既然Unity是通过shader_feature 和 multi_compile 关键字来管理变体的,那么我们如何查看呢?在Editor下,我们可以通过直接在Inspector面板里查看Shader的Keywords。
