问答 使用 GPU Instancing 屏幕花屏问题

侑虎科技 · 2020年05月28日 · 1871 次阅读

1)使用 GPU Instancing 屏幕花屏问题
2)如何优化 AssetBundle 大小
3)如何使用 GPU Skinning 提升性能
4)iOS 上 Shader 里 tex2D 采样偏移的问题
5)如何管理销毁拍摄的内置深度图


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

GPU Instancing

Q:机器:魅族 MX5
Unity 版本:2019.1.5f1
渲染设置:OpenGL ES3,Dynamic Batching,GPU Instancing

问题表现:场景有时候会突然花屏随机闪烁, 通过排除法发现是在渲染其中一个树的时候导致的(树的材质勾选了 GPU Instancing,只要不渲染这个树就不闪烁)。

尝试改变渲染设置:仅关闭动态批处理或者 GPU Instancing 就都不花屏了, 两者同时存在就会花屏。

不知道这是 Unity 的 Bug 还是魅族机器有问题。
附件:Demo 工程(请戳原问答链接查看)

A:根据题主提供的信息,我们做了以下尝试。
测试机:魅族 5;对比测试机:小米 6
测试场景:客户提供

测试现象
魅族 5 开启了 Dynamic 和 Instancing,会花屏,UBO 数组长度为 2,有 Log 报错。

小米 6 和 OPPO K1 均不花屏,UBO 数组长度均为 128,无 Log 报错。

魅族 5 在 RenderDoc 的数据
当开启 GPU Instancing 的时候,在 Vertex Shader 里面会有 2 个 UBO(Uniform Buffer Object)。

它们的内容分别如下:



第一个记录的是 SH 函数的系数,第二个是每个 Instance 的变换矩阵。

在魅族 5 上面数组长度为 2,可以理解为魅族 5 不支持 Instance。而在小米 6 和 OPPO K1,是支持 128 个 Instance 的,如下图所示:



说明它们是支持 Instancing 的。

在移动平台上,一个 Buffer 的大小上限是 16KB,一个 Instance 需要记录 ObjectToWorld 和 WorldToObject 的矩阵,总共是 16x4x2=128 Byte,所以总的 Instance 上限数量是 16x1024 Byte/128 Byte = 128。

从 RenderDoc 上的信息里面可以看到确实是长度为 128 的数组,而在魅族 5 上面只有 2。

而且在魅族 5 上面运行测试场景的时候,通过 Logcat 可以看到会有报错的 Log:
GLSL: unexpected struct parameter 'unity_Builtins2Array[1].unity_SHCArray’
GLSL:unexpected struct parameter 'unity_Builtins0Array[1].hlslcc_mtx4x4unity_WorldToObjectArray[0]'

这两个正是 Instancing 需要的 UBO 里面的数据。

还有一点需要说明的是,在测试场景中,使用的是 239 个三角形的那个 Mesh,在魅族 5 上面同样会花屏。不论有没有开启 Dynamic Batching,都会有上面的报错,所以本质上是魅族 5 不支持 Instancing 导致的

创建了一个场景,场景中有 3 棵相同的树,而且开始了 Instancing,可以看到 OpenGL 使用的接口是 glDrawElements。

在小米 6 上调用的接口是 glDrawElementsInstannced,如下图:

没有对模拟器进行测试,理论上是一样的情况。

该回答由 UWA 提供


Asset

Q:我们 AssetBundle 超过 3.8G 了,也做了分类,但是资源太多导致总大小超过 3.8G,很多玩家安装不上。有没有相关解决思路?

A1:随着硬件的发展,玩家对画质要求的提高,基本上包体越来越大是必然的,关键还是看大的是否合理,基本上可以顺着几个点去检查下。

(1)首先使用 UWA 的资源检测工具看下是否 AssetBundle 内重复资源过多,如果有,可以根据检测结果调整,并定期提交 UWA 测试。

(2)配合一些资源分析的工具在编辑器内人工的工程内部资源大致过一遍,看看是否有冗余的资源。这点还是很常见的,随着游戏开发版本不断迭代,经常会出现之前做的一些东西被推翻了或者大改了(策划或美术需要),这时候就会产生一些资源可能没有被用到但是仍然留在工程中,如果打包时没有相应地去掉,就会有无用的资源进版。

(3)检查资源导入是否合理。比如:贴图是否使用了正确的压缩格式,动画文件是否进行了合理的压缩,打图集是否合理,图集里是否有大量的浪费等等。

(4)如果经过检查仍然没有什么进展,那只能说你们的项目真的需要这么大的资源包,为了让用户能有较好的游戏体验,可以根据伟昊说的,规划一部分资源在启动后下载。这块可以结合产品需要,做成启动后一次性下载完或者根据游戏进程一部分一部分下载。
感谢范君@UWA问答社区提供了回答

A2:说一个资源重复可能有些人没注意到的点,就是同一份资源经常被改名放到不同的地方被不同的资源引用,经过实际检测,这个重复量大过了我的预测,大家可以自测一下自己的项目。

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


动画

Q:我们的游戏参考了 UWA Blog 上的那篇《GPU Skinning 加速骨骼动画》,发现真机环境帧数提升不是很明显,连上 Profiler 发现开启/关闭 GPU Skinning 总批次居然没啥变化,SetPass Calls 倒是变化明显,在编辑器切到 Android 平台我测试过总批次会提升 100+,有点奇怪为什么到了 Android 真机里没什么变化?

PS:原来角色动画 Animator + SkinMeshRenderer,使用 GPU Skinning 后换成了 MeshRenderer 同时把 Animator 勾去掉的。

A:可汗文章中单纯的 GPU Skinning 并不是去降低渲染 Draw Call 的,只有结合 GPU Instancing 才会降低 Draw Call。GPU Skinning 最主要降低的是 Animators.Update 和 MeshSkinning.Update 的耗时,如果在使用后,你发现这两个值没有变化或者优化不大,那么十有八九是用法不对。

下图是我们在 UWA DAY 2019《如何根据 UWA 制定技术选型》时做的具体的性能测试,里面讲解了多种不同方法所能达到的收益和限制,题主有兴趣可以详细查看。

同时,我们将之前做过的一些定量测试放在这里,希望大家对于 GPU Skinning 带来的性能改变有更为定量的理解。

——————————————

通过 GPU Skinning 方式

该方法是完全摒弃 Unity 引擎的 MeshSkinning 和 Animator 模块,自行对蒙皮网格进行采样,将骨骼结点的矩阵信息以纹理的方式进行储存,然后在 GPU 中完成顶点计算并直接进行渲染。优点在于极大地降低 SkinnedMesh.Update 和 Animator.Update 的 CPU 占用。将骨骼结点信息通过纹理来进行储存,因而数据量较之方案儿会大为降低。

开源库下载链接:
https://lab.uwa4d.com/lab/5bc6f85504617c5805d4eb0a

测试场景:
创建相同案例,场景模型数量分别为 50 和 200,各自测试 1000 帧,播放 Walk 动画。

结果:
该方案测试效率如下图所示,除 Camera.Render 外,MeshSkinning.Update 和 Animator.Update 已经消失,但增加了 GPUSkinning.Start 和 GPUSkinning.Update 函数。通过分析可知,在红米 Note2 设备上,开启多线程渲染功能,测试帧数总计 1000 帧,50 个模型的 CPU 平均耗时 0.6ms;200 个模型的 CPU 平均耗时 1.6ms。


图 1:使用 GPU Skinning 方案后,红米 Note2 上的 Top10 CPU 占用情况

使用 GPU Skinning 方案后,红米 Note2 上的 Camera.Render 耗时情况。


图 2:50 个模型


图 3:200 个模型

使用 GPU Skinning 方案后,红米 Note2 上的 50 个模型时的 Camera.Render 耗时情况。


图 4

同时,GPU Skinning.Start 和 GPU Skinning.Update 耗时在游戏运行过程中很小,如图 5 所示。



图 5:GPU Skinning.Start 和 GPU Skinning.Update 耗时在游戏运行过程中的 CPU 耗时

总结:
(1)该方案可以大幅降低 Animators.Update 和 MeshSkinning.Update 的 CPU 耗时,同时内存占用较小小。以 r_gunman 模型为例,其所有动画文件时长 8 秒,如果采样率为 30fps 时,通过纹理来进行记录,只需要 128x128 的纹理即可得到更为精细的动画数据;
(2)该方案对于 GPU 的压力更大,需要研发团队对 GPU 方面的压力进行进一步权衡。

该回答由 UWA 提供


Render

Q:我是用世界坐标去采样一张 Filter Mode 为 Point 的纹理,PC 和 Android 真机上采样是正确的,Mac 和 iPhone7/7Plus 上采样结果产生了偏移。

PC 上的截图(分别为输出 UV 和输出采样结果):


Mac 上的截图:


注意看网格线的对比,Mac 上的采样图出现了偏移,但 UV 是正确的(为了方便观察,UV 进行了缩放,采样是用未缩放的 UV)。

有人碰到过类似的问题吗?怎么处理的?

A1:问题根源并没有找到,目前采用了一个取巧的方案绕过该问题。

原本需求就是用世界坐标采样一张 Filter Mode 为 Point 的纹理,因此直接在 Shader 中棋盘化采样点,伪代码:

float2 uv = floor(worldPos.xz / rectSize) / texSize.xy + 1.0 / (3.0 * texSize.xy);

(rectSize 为棋盘格子的大小;texSize 为采样图的大小)

最后加的那个值(1.0 / (3.0 * texSize.xy))是测试发现如果没有该值,PC 采样结果和 Mac 下会有偏差,因此加上了三分之一个单位的偏移。
感谢珂@UWA问答社区提供了回答

A2:应该和这个问题类似吧,不过 half pixel offset 好像是 bilinear filter 才会有的,point 可能也有不同平台采样结果不一样的问题。

https://docs.microsoft.com/zh-cn/windows/win32/direct3d9/nearest-point-sampling

https://docs.microsoft.com/zh-cn/windows/win32/direct3d9/bilinear-texture-filtering?redirectedfrom=MSDN

可以看看上面两个链接有没有帮助。

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


RenderTexture

Q:在同一个场景我先后使用不同的摄像机不同的角度拍摄深度,那么应该得到多张不同的深度图,我想了解 Unity 对这多张深度图是怎么管理的?比如:我只保留其中一张,其它的深度图都不需要了,我该怎么处理?

A:Unity 默认渲染管线是多相机共享 RenderTexture 的方式,即多个相机逐一使用公共的 CameraDepthRenderTexture 绘制,每个相机绘制完是否 Clear,取决于你下一个相机的 ClearMode。至于 RenderTexture 的内存分配和管理,与你所有的相机是否开启绘制深度有关,全部关掉绘制深度底层会释放掉 RenderTexture。如果要保留一张给下一帧使用,那么建议拷贝出来,这样不会影响下一帧其它相机使用。

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


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

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册