网易首页 > 网易号 > 正文 申请入驻

一文掌握 Unity Shader 变体

0
分享至

本文转载自 Unity 社区大佬 ForgemasterGua。全方位解读 Unity Shader 变体的由来、编译流程、剔除流程、加载流程等。ForgemasterGua 在 Unity 中国开发者社区持续更新技术内容中,点击阅读原文,前往 ForgemasterGua 的社区主页,阅读更多干货文章。

Shader 变体的由来

首先引用官方对 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关键字 的不同组合。

关键字 Keyword

既然 Unity 是通过 shader_feature 和 multi_compile 关键字来管理变体的,那么我们如何查看呢?在 Editor 下,我们可以通过直接在Inspector 面板里查看 Shader 的 Keywords。

关键字分为OverridableNot Overridable。具有全局范围的局部关键字(在实际着色器文件中定义的关键字)可以被具有匹配名称的全局着色器关键字覆盖。相反,如果它们是在本地范围内定义的(通过使用 multi_compile_local 或 shader_feature_local),则无法覆盖它们,并且将显示在下面的 Not overridable (不可覆盖) 部分中。全局着色器关键字由 Unity 引擎提供,并且它们是可重写的。由于可以在构建过程中的任何时候添加它们,因此并非所有全局关键字都可能显示在此列表中。

我们还可以在关键字后面加上更多限制关键词,来做更精细的控制。

这里要注意一下,在 OpenGL、OpenGL ES、Vulkan 这些后缀会被忽略。

Shader 变体编译流程

既然我们知道了 Shader 变体怎么来的, 还要知道 Shader 变体是怎么用的 。在我们实际构建游戏之前,也就是开发阶段是不会编译 shader 变体的。我们可以通过Compile and show code来查看具体平台的 shader 变体,这可以帮助我们提前检查问题。另外我们还可以把生成的代码粘贴到 GPU 的性能分析工具中(比如 PVRShaderEditor、Mali Offline Compiler),来进一步优化 shader 性能。

在构建游戏时,Unity 将根据其功能、引擎设置和其他因素的所有可能排列来确定每个着色器的变体组合。然后,这些组合将传递给预处理器以进行多次剥离。这可以使用IPreprocessShaders回调进行扩展,以创建自定义逻辑以从构建中剔除变体。

如果是包含在 Always-included shaders ,(在 Project Settings > Graphics 下)的着色器将包含其所有变体。因此,最好仅在绝对必要时才使用它,因为它很容易导致生成大量变体。

需要注意的是,构建过程中将经历一个称为重复数据删除的过程,会识别同一 Pass 中的相同变体,并确保它们指向相同的字节码。这将减小磁盘大小,但相同的变体仍会对构建时间、加载时间和运行时内存使用产生负面影响。

下图为构建游戏时提示的变体构建进度:

构建完成后,我们可以在 Editor.log 文件中搜索 shader 变体的打包信息,可以直接在 log 中搜索 "Compiling shader"。


如果项目设置支持多个图形 API,我们还可以在每个 shader 编译的最后信息里面看到最终的压缩内存大小:

如果项目使用的是 URP 渲染管线,还可以在 URP Global Settings 下,设置 Shader Variant Log Level。

如果选择了下面的 Export Shader Variants 选项,那么在构建后还会生成一个JSON 文件,其中会包含所有的 Shader 变体的编译结果,位置在 Temp/shader-stripping.json,这样如果我们在游戏中发现某个 shader 变体没有找到,就可以在这个 json 里面确认一下是否被意外剔除了。


上述只是记录了打包的 shader 变体,为了了解在运行时实际为 GPU 编译了哪些着色器,可以在 Project Settings > Graphics 下启用Log Shader Compilation选项。


这将导致游戏在运行时会编译着色器变体显示在玩家日志中。它仅适用于开发版本和 Debug 模式,如工具提示中所述。如下图所示,在 android studio 中搜索 Compiled shader 可以看到进入场景时,编译的 shader 变体都有哪些:


最后,您可以使用Memory Profiler 包在游戏运行时拍摄游戏的快照,然后大致了解内存中当前加载的着色器及其大小。按大小排序通常可以很好地指示哪些着色器引入的变体最多,并且值得优化。


现在 2021、2022、2023 的 LTS 版本另外还支持 Shader 变体的动态压缩,更灵活地控制 shader 内存。构建期间的数据块大小(以 MB 为单位)和在运行时为每个着色器同时保持解压缩的最大数据块数。这两个设置都可以全局配置,并针对每个平台进行覆盖。默认值为每个数据块 16 MB,数据块数量不受限制。如果是 0 个块则视为无限制。

Shader 变体剔除流程

利用引擎剔除

Unity 构建过程中,将删除与游戏未使用的图形功能相关的着色器变体。如果您使用的是 Built-in Render Pipeline 或 URP,则该过程会略有变化。要定义这些参数,请转到 Project Settings > Graphics。在这里,在使用内置渲染管线时,您可以选择游戏支持的光照贴图和雾模式。

如果将它们设为Automatic,Unity 就可以根据构建中包含的场景来确定要剥离的变体。

如果您不确定自己正在使用哪些功能,还可以使用Import from Current Scene按钮让 Unity 找出您需要的功能。当然,仅当所有场景都使用相同的设置时,这才有用,因此请确保在使用此选项时选择代表性场景。

如果您使用的是 URP,则其中一些选项将被隐藏。相反,您可以直接在Pipeline Settings(工作流设置) 资源中定义游戏所需的功能。例如,禁用 Terrain Holes 将导致所有 Terrain Holes Shader 变体被剥离,从而缩短构建时间。

URP 对要包含在游戏中的功能提供了更精细的控制,从而有可能产生更优化的版本和更少的未使用变体。

基于图形层的剥离

注意:这仅在使用Built-in Render Pipeline时相关。使用可编程渲染管道(如 URP)时,将忽略这些设置。

图形层用于根据运行游戏的硬件应用不同的图形设置(不要与 Quality Settings 混淆)。游戏启动时,Unity 将根据硬件功能、图形 API 和其他因素确定您的设备图形层。

可以在 Project Settings > Graphics > Tier Settings 中设置它们。

基于这些,Unity 会将以下三个关键字添加到所有着色器中:

UNITY_HARDWARE_TIER1

UNITY_HARDWARE_TIER2

UNITY_HARDWARE_TIER3

然后,它会为定义的每个图形层生成着色器变体。如果不使用图形层并希望避免使用它们的相关变体,则需要确保所有图形层都设置为完全相同的设置,以便 Unity 跳过这些变体。

如前所述,Unity 将尝试删除相同的重复变体,因此,例如,如果三个层中的两个具有相同的设置,这将导致磁盘大小减小,即使仍会生成所有变体。您可以选择强制 Unity 为给定的着色器和图形渲染器 API 生成层变体,使用如下所示hardware_tier_variants:

// Direct3D 11/12
#pragma hardware_tier_variants d3d11

基于图形 API 的剥离,Unity 会为构建中包含的每个图形 API 编译一组着色器变体,因此在某些情况下,手动选择 API 并排除不需要的 API 是有益的。为此,请转到Project Settings > Player。默认情况下,Auto Graphics API 处于选中状态,Unity 将包含一组内置图形 API,并在运行时根据设备功能选择一个。例如,在 Android 上,Unity 将首先尝试使用 Vulkan,如果设备不支持它,则引擎将回退到 GLES3.2、GLES3.1 或 GLES3.0(尽管这些 GLES 版本的变体是相同的)。相反,请为相关平台禁用 Auto Graphics API,并手动选择要包含的 API。然后,Unity 将优先考虑列表中的第一个 ID。


缺点是您可能会限制支持您的游戏的设备数量,因此请确保您知道在更改此设置时要做什么并在各种设备上进行测试。

*修改着色器变体匹配

通常,在运行时,如果完全匹配不可用或已从播放器构建中剥离,Unity 会尝试加载最接近请求的关键字集的变体。虽然这很方便,但它也隐藏了着色器关键字设置的潜在问题。

从 Unity 2022.3 开始,您可以在 Project Settings > Player 中选择 Strict Shader Variant Matching,以确保 Unity 仅尝试加载所需本地和全局关键字组合的精确匹配。

如果未找到,它将使用 Error Shader 并在控制台中打印包含着色器、子着色器索引、实际通道和请求的关键字的错误。当您需要追踪实际需要的缺失变体时,这非常方便。与通常的剥离一样,这仅在 Player 中有效,在 Editor 中不起作用。

利用引擎剔除

每当着色器即将编译到您的游戏版本中时,Unity 都会调度一个回调。这种情况在 Player 和 Asset Bundle 构建中都会发生。我们可以使用 IPreprocessShaders.OnProcessShader 和IPreprocessComputeShaders.OnProcessComputeShaders 方便地监听这些内容,并添加自定义逻辑来去除不必要的变体。这样,我们可以大大减少构建时间、构建大小和进入您的构建的变体总数。为此,需要创建一个实现 IPreprocessShaders 接口的脚本,然后在 OnProcessShader 中编写剥离逻辑。例如,以下脚本将在发布版本上去除包含 DEBUG 着色器关键字的所有变体:

public class StripDebugVariantsPreprocessor : IPreprocessShaders
{
public int callbackOrder => 0;

ShaderKeyword keywordToStrip;

public StripDebugVariantsPreprocessor()
{
keywordToStrip = new ShaderKeyword("DEBUG");
}


public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList data )
{
if (EditorUserBuildSettings.development)
{
return;
}

for (int i = data.Count - 1; i >= 0; i--)
{
if (data[i].shaderKeywordSet.IsEnabled(keywordToStrip))
{
data.RemoveAt(i);
}
}
}
}

回调顺序允许您定义应首先运行哪个预处理脚本,从而允许您创建多步骤剥离过程。优先级较低的脚本将首先执行。

Shader 变体加载流程

当一个游戏对象的材质需要使用特定的着色器时,Unity 会确定所需的着色器变体。每个材质可能需要一个具体的变体,这取决于材质的属性和所启用的特性。着色器变体是由关键字(keywords)决定的。关键字可以开启或关闭特定的着色器代码块,生成不同的变体。每种关键字组合代表一个不同的变体。

Unity 会从项目资源中加载所需的着色器。如果着色器已经在资源中(例如,在 AssetBundle 或 Resources 文件夹中),Unity 会从这些资源中加载着色器。如果之前已经编译了该着色器变体(例如,通过 Shader Variant Collection),Unity 会直接使用预编译的代码。预编译可以大大减少运行时编译的开销

Unity 会检查所需的着色器变体是否已经存在。如果该变体已经存在于内存中,Unity 会直接使用它。

如果所需的着色器变体未预编译或尚不存在,Unity 会在运行时动态编译该变体。编译过程包括:

1>解析着色器代码:读取 HLSL 代码和着色器的配置。

2>应用关键字和设置:根据材质的设置和启用的关键字,应用相应的变体配置。

3>生成 GPU 代码:将处理后的 HLSL 代码转换为 GPU 能够理解和执行的代码。

动态编译可能导致性能开销,因此减少动态编译的变体数量对优化性能很重要。一旦着色器变体编译完成,Unity 会将编译后的变体应用于材质。材质会使用加载的着色器进行渲染。


Shader Variant Collection

SVC 是 Unity 提供的shader 变体收集工具,使用 SVC 可以帮助我们灵活控制 shader 变体。


Shader Variant Collection提供了优化着色器管理和性能的有效手段。它通过预编译、优化内存使用、提升构建和加载速度、提供更好的调试工具、增强控制和灵活性、减少冗余工作,以及改进整体性能,帮助开发者实现更高效、更流畅的游戏体验。

既然好处这么多,一旦项目中 Shader 体量过大,那么就很有必要来通过SVC控制项目当中的变体了,具体要怎么用呢?有 2 种办法(更推荐第 2 种):

方法一

在编辑器中运行时,Unity 会记录场景中当前使用的着色器和变体,并允许将其导出到 SVC 中。可以 Project Settings>Graphics 。在底部,你会看到 "着色器加载"部分,显示当前有多少个着色器处于活动状态。

确保事先点击"清除"以获得更准确的样本,然后进入"播放"模式并进入场景,确保遇到需要特定着色器的所有游戏元素。这将增加跟踪计数器。然后,按下 "保存到资产... "按钮,将所有这些保存到 Collections 资产中。


虽然这样做很方便,但根据我的经验,这样做也很容易出错。很难确保在一次游戏中遇到所有需要的变体,而且有些功能可能只能在特定情况下在设备上加载,因此列表并不一定准确。当游戏发生变化,关卡中添加了新元素或材质发生变化时,就需要更新这些 Collections。

方法二

编写自动化收集工具,扫描参与打包的所有材质(主要是Assetsbundle),并收集场景渲染器上的光照参数来综合获取变体。这里提供一个思路,即通过创建一个临时场景,增加符合项目的光源把收集到的所有材质渲染一帧,来间接的收集所有 shader 变体,再把收集到的 shader 变体保存到 SVC 上。

开源框架 Yooaseet 里有对 SVC 自动收集的代码工具,如果有兴趣可以看下其中具体实现用来参考:

https://github.com/tuyoogame/YooAsset

更多技术问题欢迎到 Unity 中文社区参与讨论并获取解答。

Unity 中文社区持续征集内容投稿,欢迎与 Unity 官方分享你的技术笔记、项目 demo、行业经验、有趣案例,加入社区建设,繁荣内容生态,带领百万 Unity 中文开发者一同学习。

投稿方式:

方式一:在 Unity 中文社区首页(https://developer.unity.cn/)创建个人账号,点击【写文章】,发表文章;

方式二:联系邮箱 learn-cn@unity.cn,投稿技术内容。

Unity 官方微信

第一时间了解Unity引擎动向,学习进阶开发技能

每一个“在看”,都是我们前进的动力

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
“下海”也难逃!黄润中被查

“下海”也难逃!黄润中被查

鲁中晨报
2024-12-26 21:28:06
有8只猛犸象被困孤岛上,持续繁衍6000年,直到3650年前才灭绝

有8只猛犸象被困孤岛上,持续繁衍6000年,直到3650年前才灭绝

怪罗
2024-12-26 21:25:12
曝陈戌源被押回上海老家服刑!临行前细节曝光,监狱环境较为优渥

曝陈戌源被押回上海老家服刑!临行前细节曝光,监狱环境较为优渥

体坛纪录片
2024-12-26 19:01:25
蛇年春晚彩排路透曝光!“春晚混子”再登台,网友:咋又是他?

蛇年春晚彩排路透曝光!“春晚混子”再登台,网友:咋又是他?

乌娱子酱
2024-12-25 17:11:23
倒计时25天,拜登签字推翻承诺,中方交涉失败,外交部回应2句话

倒计时25天,拜登签字推翻承诺,中方交涉失败,外交部回应2句话

猎火照狼山
2024-12-26 20:39:29
若我国经济陷入内循环,什么“最值钱”?内行人:这2样是硬通货

若我国经济陷入内循环,什么“最值钱”?内行人:这2样是硬通货

草莓啵啵奶2
2024-12-10 11:48:21
1-2!帕尔默造25年神迹,切尔西遭95分钟绝杀,利物浦喜从天降

1-2!帕尔默造25年神迹,切尔西遭95分钟绝杀,利物浦喜从天降

我的护球最独特
2024-12-27 00:59:54
零下8℃+8级大风!河南将进入“风冻”模式

零下8℃+8级大风!河南将进入“风冻”模式

大象新闻
2024-12-26 09:10:09
外逃乱港分子收通缉令,不到24小时,中方钓出大鱼,加拿大被狙击

外逃乱港分子收通缉令,不到24小时,中方钓出大鱼,加拿大被狙击

大白话瞰世界
2024-12-26 09:52:56
向全球宣布,中国六代机首飞,该由解放军主导未来空战了!

向全球宣布,中国六代机首飞,该由解放军主导未来空战了!

胖福的小木屋
2024-12-27 01:01:59
戏子误国!国家终出手,人民日报发声撕掉这3位明星的遮羞布

戏子误国!国家终出手,人民日报发声撕掉这3位明星的遮羞布

葡萄说史
2024-12-21 15:27:44
很大概率,中国将面临有史以来的一次长时间房价暴涨?

很大概率,中国将面临有史以来的一次长时间房价暴涨?

巢客HOME
2024-12-24 08:05:02
炸裂!老板性侵醉酒女员工4次,下体撕裂,获刑4年赔偿3000!

炸裂!老板性侵醉酒女员工4次,下体撕裂,获刑4年赔偿3000!

鋭娱之乐
2024-12-26 20:08:27
张兰人设崩塌?被曝给汪小菲暖房的家其实是一会所,难怪儿子发火

张兰人设崩塌?被曝给汪小菲暖房的家其实是一会所,难怪儿子发火

祝晓塬
2024-12-27 02:48:34
惊爆!美国封杀,中国无人机凭啥在全球 “高飞”?

惊爆!美国封杀,中国无人机凭啥在全球 “高飞”?

魏家东
2024-12-25 11:18:10
阿莫林:10人作战比赛很艰难;角球直接得分?奥纳纳被干扰了

阿莫林:10人作战比赛很艰难;角球直接得分?奥纳纳被干扰了

懂球帝
2024-12-27 04:31:08
他是庄则栋的前妻,也是著名钢琴家,患过癌症单身到老

他是庄则栋的前妻,也是著名钢琴家,患过癌症单身到老

细品名人
2024-12-24 07:29:27
俄罗斯外长:核战争没有赢家,但这不意味着任何人都可以挑战俄罗斯的耐心

俄罗斯外长:核战争没有赢家,但这不意味着任何人都可以挑战俄罗斯的耐心

环球网资讯
2024-12-26 18:41:33
抖音完成100部经典港片4K高清修复工作:已上线抖音、西瓜视频

抖音完成100部经典港片4K高清修复工作:已上线抖音、西瓜视频

IT之家
2024-12-26 08:16:11
大结局!置换亡妻金饰给新欢后续:儿子手撕父亲和表嫂,金饰拿回

大结局!置换亡妻金饰给新欢后续:儿子手撕父亲和表嫂,金饰拿回

鋭娱之乐
2024-12-25 12:43:05
2024-12-27 06:03:00
Unity incentive-icons
Unity
Unity中国官方帐户
2236文章数 6709关注度
往期回顾 全部

科技要闻

小米正搭建GPU万卡集群,大力投入AI大模型

头条要闻

5名中国游客在挪威大巴车祸中受轻伤

头条要闻

5名中国游客在挪威大巴车祸中受轻伤

体育要闻

再见,中超最后的超级巨星

娱乐要闻

57岁王祖贤分享近照,状态太好又被质疑整容

财经要闻

69亿订单"消失",卓然股份隐藏了什么?

汽车要闻

新物种iCAR V23的“尤里卡时刻”

态度原创

教育
时尚
旅游
亲子
房产

教育要闻

怎么没人说大学考的证书都能在花呗提额啊

今年最好看的搭配竟然是基础款?这样穿高级又时髦

旅游要闻

韩国考虑对一定范围内的中国团体游客试行免签政策

亲子要闻

男生测试小孩哥力度有多大,结果在最用力的时候抽手。

房产要闻

富力地产子公司欠薪风波:年关将至!员工深陷困境,呼吁尽快解决

无障碍浏览 进入关怀版