本文转载自 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。
关键字分为Overridable和Not 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:
Project Settings > Player
。默认情况下,Auto Graphics API 处于选中状态,Unity 将包含一组内置图形 API,并在运行时根据设备功能选择一个。例如,在 Android 上,Unity 将首先尝试使用 Vulkan,如果设备不支持它,则引擎将回退到 GLES3.2、GLES3.1 或 GLES3.0(尽管这些 GLES 版本的变体是相同的)。相反,请为相关平台禁用 Auto Graphics API,并手动选择要包含的 API。然后,Unity 将优先考虑列表中的第一个 ID。
利用引擎剔除
添加自定义逻辑
来去除不必要的变体。这样,我们可以大大减少构建时间、构建大小和进入您的构建的变体总数。为此,需要
创建一个实现 IPreprocessShaders 接口的脚本,然后在 OnProcessShader 中编写剥离逻辑。例如,以下脚本将在发布版本上去除包含 DEBUG 着色器关键字的所有变体:
br/>
Shader 变体加载流程
每个材质可能需要一个具体的变体,这取决于材质的属性和所启用的特性。
着色器变体是由
关键字(keywords)
决定的。关键字可以开启或关闭特定的着色器代码块,生成不同的变体。每种关键字组合代表一个不同的变体。
预编译可以大大减少运行时编译的开销
。
1>解析着色器代码
:读取 HLSL 代码和着色器的配置。
2>应用关键字和设置
:根据材质的设置和启用的关键字,应用相应的变体配置。
3>生成 GPU 代码
:将处理后的 HLSL 代码转换为 GPU 能够理解和执行的代码。
减少动态编译的变体数量
对优化性能很重要。一旦着色器变体编译完成,Unity 会将编译后的变体应用于材质。材质会使用加载的着色器进行渲染。
img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2024%2F1104%2F40ac7209j00smfbg3001hd200ra00crg00hx008d.jpg&thumbnail=660x&quality=80&type=jpg"/>
br/>
Shader Variant Collection
shader 变体收集工具
,使用 SVC 可以帮助我们灵活控制 shader 变体。
img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2024%2F1104%2F1dd04fbbj00smfbg3000dd2007o007fg00hx00hb.jpg&thumbnail=660x&quality=80&type=jpg"/>
br/>
Shader Variant Collection
提供了优化着色器管理和性能的有效手段。它通过预编译、优化内存使用、提升构建和加载速度、提供更好的调试工具、增强控制和灵活性、减少冗余工作,以及改进整体性能,帮助开发者实现更高效、更流畅的游戏体验。
SVC
控制项目当中的变体了,具体要怎么用呢?有 2 种办法(更推荐第 2 种):
方法一
着色器加载
"部分,显示当前有多少个着色器处于活动状态。
"清除"
以获得更准确的样本,然后进入"播放"模式并进入场景,确保遇到需要特定着色器的所有游戏元素。这将增加跟踪计数器。然后,按下 "保存到资产... "按钮,将所有这些保存到 Collections 资产中。
img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2024%2F1104%2F25db6d62j00smfbg4001pd200u000jgg00hx00bm.jpg&thumbnail=660x&quality=80&type=jpg"/>
br/>
方法二
Assetsbundle
),并收集场景渲染器上的光照参数来综合获取变体。这里提供一个思路,即通过创建一个临时场景,增加符合项目的光源把收集到的所有材质渲染一帧,来间接的收集所有 shader 变体,再把收集到的 shader 变体保存到 SVC 上。
征集内容投稿
,欢迎与 Unity 官方分享你的技术笔记、项目 demo、行业经验、有趣案例,加入社区建设,繁荣内容生态,带领百万 Unity 中文开发者一同学习。
投稿方式:
br/>
Unity 官方微信
第一时间了解Unity引擎动向,学习进阶开发技能
每一个“在看”,都是我们前进的动力
到此这篇3dtiles文件可以用unity处理吗(3d模型可以导入unity吗)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/rgzn-zryycl/25712.html