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问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5f914de75d9aea5aa9719769
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问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5f8ec5959424416784ef2870
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热更新的框架可供使用:
- sLuaUnreal
据说是《和平精英》手游采用的框架;
- UnLua
较sLua后推出的一个框架。
感谢Vest@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5f8fa49d9424416784ef28b5
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问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5f743b739424416784ef24d6
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问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5f89a0aa9424416784ef2817
封面图来源于网络
今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。
官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
UWA学堂:edu.uwa4d.com
官方技术QQ群:793972859(原群已满员)