1)本地资源检测,特效检测中 Overdraw 相关问题
2)Android 上如何加速判定 Bundle 文件是否存在
3)xLua pcall 异常捕获
4)Mipmap 和带宽
5)Timeline 卡顿严重时,Clip 被完全跳过没有执行
这是第 230 篇 UWA 技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间 10 分钟,认真读完必有收获。
UWA 问答社区:answer.uwa4d.com
UWA QQ 群 2:793972859(原群已满员)
Q:咱们这个特效资源检测 Overdraw 和实际检测差距有点大。是不是统计峰值比较好一些?有可能一个特效播放时间特别长,但是峰值就很短的时间,这样一平均 Overdraw 就很低了。
A:首先要谈谈 Overdraw 的定义问题:
1.Overdraw 是一个描述像素重复绘制次数的概念。单个像素的重复绘制次数很容易理解,但是对于一个特效的 Overdraw,笔者还没有找到什么公认的 “标准” 的计算公式,也不存在什么 “官方” 的定义。
对于一帧来说,Overdraw 值可以定义为该帧总体绘制像素的次数 / 屏幕上实际绘制的像素数。而这个值也不能简单理解为 “绘制的层数”。(试想如果总共在屏幕上绘制了 1000 个像素,其中只有一个像素绘制了 10 层,那这个重复绘制的像素对 Overdraw 值得贡献也是可以忽略的,计算结果为:1009/1000=1.009)。而对于整个特效播放过程中的 Overdraw,具体公式如何定义,又是一个问题。
2.sunbrando 的开源库的计算方法:整个播放过程中,
分子是对每一帧绘制像素的次数求和,分母是对每一帧屏幕上实际显示的像素数求和。
这个公式确实可以从整体上衡量一个特效播放过程中的 Overdraw。但是这种计算存在这样一个问题:有些帧特效占屏幕的比例很大,有些帧特效占屏幕的比例很小,而运算结果受屏幕占比大的帧影响比较大。比如第一帧绘制了 1000 个像素,只绘制了 1 层,而第二帧绘制了 2 个像素,绘制了 10 层,那么计算出的值为:(1000x1 + 2x10) / (1000 + 2) = 1.018 。
UWA 本地资源检测对特效 Overdraw 的计算与上述开源库计算公式是相同的,不同的是特效播放的逻辑与相机对准的方式。
影响两个工具计算结果不同的因素有:
1.相机的对准方式不同。UWA 的工具有一套相机自动对准的逻辑,相机的摆放不同,会造成 Overdraw 不同。
2.播放的时机不同。UWA 的工具会创建好场景后,自动加载并实例化特效,进行播放。而开源库 ParticleEffectProfiler 的方案是先把特效放到场景中,启动场景后再执行相关的逻辑,进行检测。楼主的案例中,除了相机对准方式不同导致两个工具计算结果不同之外,很大程度上受到了获取的特效播放时机的影响。
使用开源库工具进行检测,发现抓取相机的 Overdraw 数据前,已经进行了三次 Update,如下图所示。也就是说,特效的 Overdraw 是从第 4 帧开始统计的。这与该工具的代码逻辑有关,就不对原因进行分析了。
而前三帧恰恰又是在数值上贡献最大的帧,如下图所示,前三帧特效中有一个面片占了很大的屏幕空间。
而第四帧开始,特效的像素占比就小了很多。
这是两个工具即使使用相同的对准方式,Overdraw 计算结果也不相同的原因。
另外,UWA 的特效检测还存在一个问题:相机视锥体的 Size 是不断调整的,那么不同的帧特效的屏幕占比会因此而改变,像素的绘制量也不同,对上述 Overdraw 值计算公式的贡献也就不一样。也就是说,相机的 Size 越小的帧,特效屏幕占比越大,对结果的影响越大。
这里有两套解决方案:
1.换公式,每帧都计算出 OverdrawRate,再求平均。
2.用户自定义相机,相机的视锥体保持固定,这样就是放弃自动对准,使相机的设置更接近用户的实际使用环境。UWA 的工具会不断迭代,根据需求尽可能找到一套最佳策略来帮助开发者对项目进行规范。
如果在优化中要使用工具进行检测,并衡量优化的结果,建议使用一套工具即可。使用同一套标准至少可以对资源的性能进行一个排名,据此来选择优化的优先级,并使用同一套标准评估优化结果。
感谢 Prin@UWA 问答社区提供了回答
Q:我们在 Android 平台上打包时,把 Bundle 放到 Streaming Assets 中,Patch 文件放到持久化目录中。在启动时判断:Patch 目录有 Bundle 就用 Patch 目录的,没有就看 Streaming Assets 目录有没有。
问题是:Android 判断 Streaming Assets 下是否有文件,要用 www 或 UnityWebRequest。这两种都是异步读文件,等待异步操作结束需要几毫秒以上,导致判断逻辑耗时很长(Bundle 较多)。
想请教下大家是怎么处理 Android 上的文件判定问题呢?
A1:Streaming 里放个资源字典文件,用这个判断。
感谢 lanyt@UWA 问答社区提供了回答
A2:可以这样来处理:
有一个全局的 Bundle 资源的配置文件,里面记录了所有 Bundle 的信息,这里面会包含其相对路径。只有从包里面读这个文件是需要异步的,如果从 Patch 目录读这个文件也是同步的(例如使用 FileStream)。得到了所有 Bundle 的相对路径后,在实际需要加载某 Bundle 时,先使用 File.Exist 或者直接判断 AssetBundle.LoadFromFile 的返回值是否为 null 来判断 Patch 目录是否有该 Bundle,如果没有的话再使用包内路径重新调用 AssetBundle.LoadFromFile 即可。
经测有效,把文件判断分散开,Patch 目录可以直接用 File,不存在就认为是在 Streaming Assets 目录,最后利用 Load 的结果来更新,看是否存在的记录。
感谢范君@UWA问答社区提供了回答
A3:有一个不错的开源库,能够同步读取 StreamingAssets 下的文件:
《BetterStreamingAssets》
感谢张迪@UWA问答社区提供了回答
Q:想请问下在 xLua 中使用 pcall/xpcall 实现 C# 中的 Try Catch 功能的时候,在 iOS/Android/模拟器等平台上,有什么需要注意的地方。
由于对 Lua 底层不是很了解,想确认下,线上项目使用是否可行。还有这个的性能消耗有多少,Update 里可以用吗?
项目使用的是 xLua,版本是 2.1.14,Unity 版本是 2019.4。
A:大范围的使用中,pcall 和 xpcall 本身肯定有一定的性能影响,不过我们倒是没有做过很完备的性能测试对比。印象中之前做过一些检索,没有找到特别明确的答案。
Lua 中源码的实现对比可以参考这里:
https://stackoverflow.com/questions/16642073/whats-the-difference-behind-normal-function-call-and-pcall/16642612可以看到 pcall 的确比常规的 call 多做了一些事情,只是这是 Lua 做异常捕获的唯一方式,当你不想游戏逻辑被错误信息打断的时候,只能使用它。
pcall/xpcall 在各种平台和模拟器上除了略微的性能影响之外没有任何问题,因为它是 Lua 原生的方式,如果使用 LuaJIT,注意一下 Lua 和 LuaJIT 下的区别封装即可。
感谢贾伟昊@UWA问答社区提供了回答
Q1:请问,Mipmap 究竟能不能降低带宽,如果不能降低,能否详细告诉一下原因,谢谢。
A1:可以降低。Mipmap 是为了解决 Texel 和 Pixel 没有 1:1 对应的一种处理方式。一般来说如果处理远处物体的渲染,依然使用正常的纹理采样,那么其实 1 个 Pixel 就一般需要采样到多个不相邻的 Texel[1:n 的关系],那么不相邻这就可能涉及到一个 Cache Miss 的问题。而 Cache Miss 必然需要从显存中重新采样纹理数据,这自然就会消耗带宽。
感谢会丢锅的 Coder@UWA 问答社区提供了回答
A2:是可以降低带宽开销的。
主要是由于在渲染相邻 Pixel 的时候,采样的 Texel 可能是在内存中不连续的,而 GPU 的 Texture Cache 是很小的。如果没有 Mipmap,在渲染远处物体时,GPU 可能需要不停的在储存 Texture 的各段 Memory 不停的访问,Cache Miss 很严重。而使用了 Mipmap 后,相当于离线将每个 Texel 覆盖的 Texture 的面积变大,这样可以提升 Texture Cache 的命中率,从而减少直接从内存读取数据的次数,从而节省了带宽的开销。
感谢范君@UWA问答社区提供了回答
Q2:请问上文中:
1. “而使用了 Mipmap 后,相当于离线将每个 Texel 覆盖的 Texture 的面积变大”,这里是 Pixel 还是 Texel?
2. 还有就是 Memeory 是指内存是吧?不是已经把一张图提交给了 GPU 显存了吗?
A:可以看下我知乎的这个回答,解决你对 Pixel 和 Texel 的疑惑:
《shader 中,fwidth 或者说 ddx/ddy 到底是什么意思?》
感谢止戈@UWA问答社区提供了回答
Q:Timeline,如果在播放时卡顿严重,会出现前面位置的 Clip 被完全跳过没有执行,有没有人碰到过类似的问题?
Timeline 信号的这个描述,是不是即使信号跳过了,依旧会执行?
The Retroactive property, when enabled, will trigger the signal if the timeline begins playing after the signal’s time.
The Emit Once property, when enabled, will trigger the signal only once when the timeline loops.
A:勾上之后是会执行,具体逻辑可参考 TimeNotificationBehaviour 脚本(Timeline 中 Signal Track 的 mixer 逻辑脚本),如下做了简单注释:
如果需要倒放也会 Trigger,也可以参考这个扩展自己的 Timeline。
感谢羽飞@UWA问答社区提供了回答
封面图来源:Barracuda Style Transfer code sample
今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在 UWA 问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之 “石”,也能攻你之 “玉”。
官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
UWA 学堂:edu.uwa4d.com
官方技术 QQ 群:793972859(原群已满员)