1)URP 关于多个摄相机的性能优化
​2)Unity Addressables 打包的时候如何设置 BuildAssetBundleOptions.DisableWriteTypeTree
3)Unreal 可以用于商业化游戏的热更新方案
4)UGUI SpriteAtlas 在使用中回调实例化,AtlasRequested 和 Start 的顺序颠倒
5)从 AssetBundle 中动态加载渲染管线,后期渲染异常问题


这是第 224 篇 UWA 技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间 10 分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ 群 2:793972859(原群已满员)

Rendering

Q1:URP7.4.3,除开主相机外,还有一个子相机,用于将照到的模型渲到游戏主界面 UI 上。在 Profiler 中看到以下情况:

可以看到,在子相机中也进行了包括对 LOD 的计算,但子相机的 CullingMask 只开了一个名为 RTModel 的 Layer,在这一层里只有一个 3D 对象。按说子相机 CullScriptable 这块开销不应该有才对。

目前怀疑可能的原因是 URP 会对每个 Base Camera 都进行这部分的计算,但如果用 Overlay 相机,又无法用原来的方式将相机的 targetTexture 渲到一张 RawImage 上了。

有人遇到过么?

A:题主的疑惑是:子相机的 CullScriptable 这块的开销不应该有那么大对吧(毕竟我只有一个物件)?

这里有两个问题:
1.Culling 到底做了啥,只有一个物件为啥要 Culling 那么久(难道只有一个物件也要做很多的准备工作)?
2.你在 Profiler 里面看到的数据真的是真实数据吗?也就是说,子相机的 Culling 真的做了 1.68ms 吗?

抛开这两个问题,也可以有更好的做法:

我们一共两个相机,主相机和 UI 相机,那么 UI 上显示的 3D 物件怎么办呢?
我们有个虚拟相机,所谓相机,其实就是做一个 VP 矩阵,做一个 RT,绘制可见的物件就可以了。那么使用 Unity 的 SRP,随便在什么地方,设置一下 VP 矩阵,设置 RT,接着绘制指定的物件(UI 中所有的 3D 物件都会挂在这个物件下面),然后这个 RT 就可以随意使用了。

假如一个 UI 上有两个 3D 物件,尽量都放在一个 RT 上,如果不行,就放在两个或更多的 RT 上。只是会多几个绘制命令,几个 RT(还不需要是全屏的),而且会多几个 Swap RT 的操作。由于我们项目没需求需要若干 RT,所以假设一下,在这种需要若干 RT 的情况下,也可以用一个 RT 加多个 Viewport 来解决的。这个代码都是现成的,参考一下 Cascade Shadow Map 的做法,这样 Swap RT 也就省了。

综上所述,既然你都知道自己要绘制什么,就不要给 Unity Culling 的机会了。

感谢王烁@UWA问答社区提供了回答

Q2:数据是在 Development Build 中连真机看到的性能数据, 目前在使用类似于 HLOD 的方式来减少掉这个 LOD 的巨大开销。楼上说的 “设置一下 VP 矩阵,设置 RT”,请问这个 VP 矩阵的操作具体是什么?可否详解下或者有相关资料吗?

A:一切皆有可能!但不过这个不重要。然后你提的 HLOD 和 LOD 和上面的 Culling 没关系。VP 矩阵就是 view 矩阵和 projection 矩阵。相机的作用就是提供这俩矩阵的。

如果你在管线里面设置了相应的矩阵,然后绘制指定的物件,就可以完全不用多一个相机,毕竟多一个相机就多一个 Culling。

如果你对 VP 矩阵不熟悉,不清楚怎么实现,也简单。依然用一个额外相机,关上这个相机的 Culling,然后在渲染 pass 中,不要绘制 cullingresult.visibleobject,而直接用 Graphics.DrawMesh 或者 CommandBuffer.DrawMesh 绘制你要显示的那个 3D Object 的物件就好了。

感谢王烁@UWA问答社区提供了回答


Addressable

Q:原来的 AssetBunlde 打包的时候可以设置 BuildAssetBundleOptions.DisableWriteTypeTree,使得包体小好多。今天项目尝试升级使用 Addressables,请问该如何设置 BuildAssetBundleOptions.DisableWriteTypeTree?

A1:首先 Addressable 打 Bundle 包主要的脚本是 BuildScriptPackedMode,在 DoBuild 函数内可以看到用了 AddressableAssetsBundleBuildParameters 这个类,这个类继承于 BuildParameters,而 BuildParameters 有一个成员变量是 public ContentBuildFlags ContentBuildFlags { get; set; },这个 ContentBuildFlags 就是用于设置 DisableWriteTypeTree 的,BuildParameters 是属于 ScriptableBuildPipline 的。

参考 SBP 的 CompatibilityBuildPipline 有以下操作:

if ((options & BuildAssetBundleOptions.DisableWriteTypeTree) != 0)
   parameters.ContentBuildFlags |= ContentBuildFlags.DisableWriteTypeTree;
IBundleBuildResults results; 
ReturnCode exitCode = ContentPipeline.BuildAssetBundles(parameters, content, out results); 

我们在 BuildScriptPackedMode.cs 里也可以加上:

var buildParams = new AddressableAssetsBundleBuildParameters(
                    aaContext.Settings,
                    aaContext.bundleToAssetGroup,
                    buildTarget, 
                    buildTargetGroup,
                    aaContext.Settings.buildSettings.bundleBuildPath); 
                buildParams.ContentBuildFlags = UnityEditor.Build.Content.ContentBuildFlags.DisableWriteTypeTree; 

但是我在这儿测试了一下,并没有什么用,不知道是最底层没有支持还是我的测试用例有问题。建议用同样的方法,测试一下。

另外说一下,我看了一圈代码,在打包和压缩部分 SBP 并没有用到这个参数。而在处理场景依赖的地方,这个参数会被合到 BuildSettings 结构体提供给 C++ 部分调用。所以这个参数应该最终传到 C++ 层起作用。

感谢黄程@UWA问答社区提供了回答

A2:Addressable 似乎没有给外部的接口直接来修改这个设置项,应该是需要自己写代码来修改打包方式了。需要自己写一个 BuildMode,可以参考以下这个帖子中第五个问题的回答:https://answer.uwa4d.com/question/5e649911438f7d0db495c724#5e64a4e8438f7d0db495c725

核心的代码是要 DoBuild 中修改 buildParams 的设置项,buildParams.ContentBuildFlags |= UnityEditor.Build.Content.ContentBuildFlags.DisableWriteTypeTree;如下图:

做了一下测试,发现是有效果的:

上面是默认的打包,修改后重新打包,变成下面的结果:

整体都变小了一点点,而且使用解开 AssetBundle 后,可以看到里面的内容变得很简洁。

感谢 Xuan@UWA 问答社区提供了回答


Unreal

Q:Unreal 方面,目前有什么可以用于商业化游戏的热更方案吗?

A1:因为我们公司是自己研发的一套框架,所以我也没有太在意是否有开源的 Unreal 热更新方案,印象中好像是没有的。

但基于 Unity 引擎的开源方案倒是蛮多,你们可以按照 Unity 的开源方案的思路实现一套,基本思路就是使用反射预先生成引擎的导出 Lua 接口。

如何利用反射呢?简单来说就是根据反射得到引擎接口的函数名,返回值类型、参数名和参数类型,由工具生成解析参数类型以及它的个数和顺序,返回正确结果的 Lua 导出函数 int xxx(lua_State *L) 函数。对于类成员函数需要导出到 Table 里,根据 C++ 类层级关系利用 Metatable 可以找到父类的 Lua 导出成员函数。

Unity 开源方案如此,Unreal 也可以如此,区别只是 Unreal 使用 UClass UProperty 这套,而 Unity 使用 C# 而已。你们可以尝试着实现一下。

感谢王远明@UWA问答社区提供了回答

A2:可以参考以下这个链接:
https://github.com/Tencent/puerts

感谢 lanyt@UWA 问答社区提供了回答

A3:腾讯有两款 Lua 热更新的框架可供使用:

  1. sLuaUnreal
    据说是《和平精英》手游采用的框架;

  2. UnLua
    较 sLua 后推出的一个框架。

感谢 Vest@UWA 问答社区提供了回答


Script

Q:请问 UGUI SpriteAtlas 使用中,AtlasRequested 和 Start 的顺序颠倒,如下图:

测试工程可戳原问答获取,测试环境 Unity 2019.4。

A:可以使用 Timeline 看到具体的执行时机,在 Main.Update 里面进行实例化的时候(我测试的时候把 Main.Update 里面的 interval 去掉了),Atlas 的回调跑到第二帧了,而 TestAtlasSprite 的 Awake、OnEnable 和 Start 都在第一帧。在 Main.Start 里面实例化的时候,这些打印都在第一帧,因为 Atlas 的回调是在 EarlyUpdate.SpriteAtlasManagerUpdate 里面,而实例化出来的 TestSpriteAtlas.Start 是在 FixedUpdate.ScriptRunDelayedFixedFrameRate 下面的,所以就跑到了 Atlas 回调的后面。


这个图是在 Update 里面执行的情况。


这个是在 Start 里面执行的情况,可以看到都是在第一帧打印的。


这是在 Main.Start 里面执行实例化的 Timeline 的图,可以看到 TestSpriteAtlas.Start 是在 Atlas 的回调后面执行的。


这是在 Main.Update 里面执行实例化的 Timeline。

EarlyUpdate,FixedUpdate 这些回调的执行顺序可以参考:
https://medium.com/@thebeardphantom/unity-2018-and-playerloop-5c46a12a677

感谢 Xuan@UWA 问答社区提供了回答


Rendering

Q:有人遇到动态切换管线,显示错误的问题吗?
现有 UBP 项目需求在进入新的场景之后,切换到指定的渲染管线。在打包场景的时候将渲染管线一并打包到场景中,并通过以下代码来切换渲染管线。

GraphicsSettings.renderPipelineAsset = targetAsset;
QualitySettings.renderPipeline = targetAsset; 

但是通过 AssetBundle 加载出来的渲染流程:在后处理中,UberPost 缺少两个 Keyword。

造成如下的图像。

在 Project Setting -》Quality 里面手动设置成项目的 hero_show 管线则正常显示。


通过断点调试发现:在 Render 过程中,Keywords 其实都是有赋值的。二者的流程均一致。

在 PostProcessPass.cs 文件中:

void Render(CommandBuffer cmd, ref RenderingData renderingData) 


但就是在 Frame Profiler 中抓帧显示没有 Keywords。附上简单的项目测试:

默认是 Windows 平台。直接运行 defaultScene 即可。
如不是,按下面流程处理:
1. AssetBundle->build
2. 运行 defaultScene.

链接:https://pan.baidu.com/s/1q3s6mAUwE723wTJ3VmS81g
提取码:8kwn

A1:虽然没遇到过,但是能猜到原因。
先总结一下楼主的问题:
Project Setting -》Quality 里面手动设置渲染管线,显示正常;
以 AssetBundle 的形式加载渲染管线,显示不正常,原因是 Keyword 丢失。

那么答案就很简单了。
因为这两种模式 Unity 对渲染管线这个资源的处理方式是不同的:
以第一种方式,Unity 会认为渲染管线是个默认资源,将其全部打包进包体,并进行使用。
以第二种方式,Unity 会将渲染管线,特别是渲染管线关联的 Shader,判断其 Keyword 是否被使用,如果没使用,就优化掉了。

项目还没有看,我猜测楼主是在代码中开启的这两个 Keyword,这种代码开启 Keyword 的方式,Unity 检测不到,所以就丢掉了。

解决方法也简单,对付 Keyword 常用的方案,创建一个没用的材质球,使用相应的 Shader,在材质球中开启对这些 Keyword 的使用打进 AssetBundle,这样 Unity 就知道,这个 Shader 的这些 Keyword 是有用的,就不会 Skip 掉了(其实这个方法常被用于以防 instance_on 这个 Keyword 丢失)。

感谢王烁@UWA问答社区提供了回答

A2:想尝试楼上的方法,发现材质球并不能直接引用到 UberPost 这个 Shader,因为这个 Shader 是放在 Package 目录下,于是参考了Packages 目录下 Shader 打包这个帖子中的方法进行打包。使用编辑器的 SVC 采集方法采集了一下 SVC,SVC 里面的 UberPost 是有那两个 Keyword 的。将 UberPost 和这个 SVC 一起打包,拆包后可以看到 ubqScene 场景是引用了刚刚这个 SVC 中的 Shader 的,切场景后在 Awake 里面加载 SVC 并 Warmup,然而也还是没有解决问题。最后我把题主的原始工程打了一个 exe,运行发现并没有 Keyword 丢失的问题,渲染一切正常。另外打包成 APK 测试,在小米 9 上测试也是正常渲染的。

可能还是 Unity 编辑器的问题吧,类似于这个帖子:
UnityEditor 下加载 AssetBundle,材质球 Keywords 正常,但是某些属性不存在)。

感谢 Xuan@UWA 问答社区提供了回答

封面图来源于网络


今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在 UWA 问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之 “石”,也能攻你之 “玉”。

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
UWA 学堂:edu.uwa4d.com
官方技术 QQ 群:793972859(原群已满员)


↙↙↙阅读原文可查看相关链接,并与作者交流