[Unity Shader] 使用 Unity 的 ShaderVariantCollection

56 篇文章 2 订阅

英文原文:

https://asmaloney.com/2020/01/code/using-unitys-shadervariantcollection/

  最近我一直致力于将现有的 Unity 游戏移植到 macOS。这是一款名为 Blue Rider 的射击游戏,由来自阿根廷科尔多瓦的 Ravegan 开发。它最初在 PC (Steam)、Xbox、Playstation 4 和 Switch 上发布。

  要将 Blue Rider 带到 macOS,我需要做的主要工作是菜单系统、分辨率处理、输入处理以及一些优化以实现流畅的帧速率。

  (注意:我使用的是 Unity 5.6.7f1,因为 Blue Rider 是使用 5.x 版本编写的。我不是 100% 确定,但根据当前文档,我认为我在这里所做的仍然适用于最近版本。)

问题

  其中一项优化与着色器有关。当敌人出现和爆炸时,游戏有时会出现问题。它似乎只发生在某些时候,并且发生在一些敌人身上。

  我将解释我在分析 profiler 时发现了什么,然后我将提供一种使用 ShaderVariantCollection 的可能解决方案。

分析

  我启动了 profiler 并运行了整个游戏,直到我看到敌人出现和死亡时的尖峰。
在这里插入图片描述

  分析器将罪魁祸首确定为 Shader.EditorLoadVariant。

  具体来说,它是着色器变体的编译——Shader.CreateGPUProgram。

在这里插入图片描述
  游戏要求使用尚未编译的着色器变体,因此它会立即对其进行编译。因为这个过程很慢,它不能适应一帧,所以游戏感觉就像它暂停然后试图赶上。这被称为“hitching”。

  这通常仅在第一次编译着色器变体时发生。同一运行期间的后续使用要么在内存中,要么被缓存。不幸的是,Unity 似乎没有在运行之间缓存这些编译的着色器,所以每次运行游戏时都会发生这种情况。

  经过大量的在线搜索和阅读,这篇文章中的问题似乎没有任何答案。

一个办法

  我正在开发的游戏有数百个对象以及许多着色器和着色器变体,因此每个对象的解决方案似乎具有挑战性。我想出的解决方案是为每个游戏关卡使用 ShaderVariantCollection。

(这似乎是关于这些的全部官方文档——如果我遗漏了什么,请告诉我)。

生成集合

从文档:

ShaderVariantCollection 的典型用途是从编辑器(在图形设置下)记录play期间使用的着色器变体,将它们保存为资产,并添加到预加载着色器列表(再次在图形设置中)。此外,您可以手动在 ShaderVariantCollection 对象上调用 WarmUp。

  我选择为每个关卡创建一个着色器变体集合,然后加载它们并在每个关卡开始时调用 WarmUp。首先,我在 Assets/Resources 中创建了一个文件夹来保存这些文件。将其放入 Resources 可确保 Unity 将这些文件包含在构建中。我选择将它们放在 Assets/Resources/Shaders/Levels 中。

  在 graphics 设置中,我们想要的工具位于面板的最底部:
在这里插入图片描述
对于每个关卡:

  1. 就在您开始关卡之前,单击Clear按钮。
  2. 玩关卡,触发尽可能多的事情。您希望游戏为关卡加载所有可能的着色器和着色器变体。
  3. 完成后,单击保存到资产… 按钮。
  4. 将关卡的集合保存到我们在上面创建的文件夹中(Assets/Resources/Shaders/Levels) 使用关卡名称,以便我们稍后可以找到它。在我的项目中,这将是“map_01.shadervariants”、“map_02.shadervariants”等。

完成此操作后,每个关卡应该有一个“.shadervariants”文件。

  请记住,如果您更改关卡,您可能需要为该关卡重新执行此操作。 (这就是为什么,在我看来,这是一个 hacky 解决方案。Unity 没有提供任何真正的工具来更智能地管理这些集合。)

使用集合

  现在我们有了文件,我们只需在加载每个关卡后加载它们。

  在负责加载关卡的 MonoBehaviour 上,我创建了一个名为 OnSceneFinishedLoading 的方法,在场景加载完成时调用,并将其添加到 SceneManager 的 sceneLoaded 事件中:

SceneManager.sceneLoaded += OnSceneFinishedLoading;

这是 OnSceneFinishedLoading 方法:

void OnSceneFinishedLoading( Scene scene, LoadSceneMode mode )
{
   Debug.Log( "Scene loaded: " + scene.name );
 
   var path = "Shaders/Levels/" + scene.name;
   var collection = Resources.Load<ShaderVariantCollection>( path );
 
   if ( collection != null )
   {
      Debug.Log( "Shaders/variants: " + collection.shaderCount + "/" + collection.variantCount );
 
      collection.WarmUp();
 
      Resources.UnloadAsset( collection );
   }
}

  我正在根据关卡的名称从资源中加载集合。如果找到资源并且资源类型正确,我会在着色器变体集合上调用 WarmUp 来编译着色器变体。最后,我卸载了集合,因为我们不再需要它了。

  不幸的是,ShaderVariantCollection 类是非常基础的,并不能让我们以任何重要的方式访问内容。例如,我们不能为它创建一个加载栏,获取它包含的变体列表,或者获取变体的详细信息来对它们做任何事情。如果我们拥有这些功能,就有可能构建一个更智能的编辑器工具来使用它们。

结果

  以这种方式做事可能会导致关卡开始时有一点延迟,但这不太明显,因为我们大约在同一时间加载所有其他关卡;它还可以防止在最重要的游戏过程中"hitching"。

  这可能不是唯一的解决方案!作为 Unity 和 C# 的新手,我很可能错过了一些东西。如果您有任何建议或更正,请随时与我联系或在下面发表评论。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值