大世界场景优化和加载策略

大世界场景优化和加载策略

本期聚集话题:大世界场景优化和加载策略、Physics2D.ConvertCollision2DForScript优化、检测Shader消耗性能、关闭NGUI物理碰撞检测...


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

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


制作

Q1:关于大世界 (SECTR + Terrain) 我有几个问题:
1)市面上是否有手游(比如吃鸡)使用了 SECTR 和 Terrain 来解决大世界的问题,并且实现效果还不错的呢?另外是否有其他现成插件也适用大世界的?
2)Terrain的Draw Call有没有什么优化的建议?我注意到Terrain自带的树和草似乎都比较费。另外,水平视角的时候能看到较多场景中的内容,有什么一般性的策略来减轻渲染负担?
3)在SECTR的框架下,可以很容易给一个地图分块,但应该如何解决地表分层的问题(比如地面、建筑、装饰等)?这里的层似乎已经不符合SECTR中Sector的概念,而是另一个维度。
4)大地图中用何种手段解决LOD的问题比较好?SECTR提供了一个简单的LOD方案,只是对组件(尤其是Renderer)根据其Bound来进行显隐。SECTR是否在GC方面有坑?
5)性能上,通过AssetBundle频繁加载和卸载子场景(这需要修改 SECTR_Chunk),如何能减少卡顿?

首先需要说明,虽然我在博客里推荐过SECTR这套插件,但是我们没有在项目中用,所以对于这套插件的了解只是在“玩过”的水平,所以下面的内容有不少是凭借经验的猜想,仅供题主参考。

1)目前貌似在手游上用SECTR+Terrain的项目还不多,反正我还没了解到真正这么在做的。其一是SECTR对于移动端可能没有做过特定优化,大部分做无缝大世界的项目可能会参考插件自己实现一套,而非直接使用插件;其二,个人的经验来说,Terrain在移动端上还是比较耗的,尤其是Unity这样不方便自己改实现的商业引擎,后期优化的压力可能会比较大,而且对于没有源码的小团队来说只能调整配置...关于其他插件,我了解过的还有World Streamer,原理和SECTR略有不同,也是供参考。我的感觉是这些插件都是针对PC或者主机平台实现的,虽说在移动端上也许已经可以使用了,但是未必针对移动端做过很好的优化。

2)Terrain的使用,反正在一年多前我们项目立项的时候,在手机上使用Terrain还不是一件性能上推荐的事情,随着吃鸡的普及以及硬件性能的提升,应该有一些项目已经在大量地使用Terrain了吧,优化的Guideline就留给有经验的这些朋友来回答吧。老的思路就是转换成Mesh然后减面,这个是之前比较常规的手游做法。Speed Tree生成植被,这个在移动平台上就更耗了,特别是美术不节制地使用的情况下。可以考虑配合GPU Instance等技术可以优化DrawCall等,不过这块经验不多,就不多聊了。
水平自由视角,是一个对于程序很大的挑战,当然美术工作量也很大,常用的方式应该大家都比较了解了:更加全面的LOD,视锥远平面裁剪,高低配分级来保证低配效率等等。(LOD这块就有不少取巧的策略,比如楚留香,有些情况下远处物体LOD不仅仅是减面和消隐,有时候会做一个远景的遮挡物,比如大树,在远处用来弥补远景物体的不足,走近的时候这东西反而消失掉……)

3)多层的概念可以参考前文提到的World Streamer,它基于距离来做消隐,可以通过layer/tag来区分地表、大物体、常规物体、细节物体等等,不同物体的消隐距离不同。印象中SECTR中也有基于距离的Loader,这块如果不满足可能要自己改造了。

4)LOD是个很重要的技术,做得好了可以很大程度地提升运行效率(不仅仅是渲染的),做得不好,做出负优化来也是很正常的。Unity默认提供了LodGroup这样的组件来做LOD,原理应该跟你说的SECTR的方案一样。根据包围盒在视野中的渲染比例来做LOD是比较科学的做法,比单纯的距离要更加科学一些,只是在实际使用中,可能不如距离直观和设置简单。实现得好的LOD,一方面对于大世界来说如果能够和Stream结合起来做对于内存和加卸载的消耗来说更加友好,比如可以做到远景只会加载低LOD的模型;另外一个方面,好的LOD结果需要美术进行非常细致的工作来调整和设置,这里有非常大的工作量。
所以对于LOD来说,技术方面可能没有那么深,反而是广度部分要做好的优化需要考虑各个方面,包括Render、Shadow、Animation、ParticleSystem、Material、PostEffect各个方面,再乘以高中低配置形成一个矩阵,针对这个矩阵进行定制和优化。这也代表了越好的LOD效果意味着越多的工作量。

5)GC方面的坑,因为没有使用所以不清楚,还是留给真正用过的朋友来回答吧,建议题主真正使用之前做下实际的测试。不过GC这块,能Profile出来的基本都可以自己改进优化,改进不了的也就没有什么好的方式,所以个人感觉不是特别的重要。(个人体验,大部分插件在GC方面都有优化空间……尤其这种为了兼容更宽泛的需求支持的,插件层面有些优化不好做。)

6)顿卡方面,我们自己基于AssetBundle加载的主要优化手段还是做类似LRU的缓存,内存多的设备就多做一些,少的就少做一些。没有做预加载,因为也比较难预测。这里的权衡就是chunk切分是要比较细还是比较粗,各有利弊,需要根据项目实际情况来做取舍。
聊了很多,大都是常规做法,可能很多内容题主也都知道。整体来说,看题主对于SECTR已经有了不少的了解,结合项目的需求来进行改进和重新实现应该问题都不大。从原理上来说,大世界动态加载很简单(基于Unity不自己做非常细致的场景管理的情况下),但是在实际操作中会遇到很多要处理。3D自由视角的技术挑战还是很大的,祝题主好运。
感谢贾伟昊@UWA问答社区提供了回答

UWA:就目前我们优化过的超大地形(8kx8k)的移动游戏来看,基本上都没有使用SECTR+Terrain这一解决方案的,而是全部转换成Mesh来进行无缝拼接。我们没有使用过SECTR这一插件,所以对其分拆机理和组织方式并不了解。但是对于Terrain来说,一般研发团队更倾向于使用Mesh来进行替换。

Terrain的优势在于编辑十分方便,通过一些插件(比如Terrain Composer)可以快速生成基础地形等等,但是它的Draw Call并不容易控制,至少很不直观,内存同样较之同等复杂程度的Mesh要大,且TerrainData的加载效率也并不高。所以,对于前期通过Terrain来进行地形编辑的团队,后续往往会将其导出成Mesh来进行动态分块加载。这是目前我们较为推荐的方案。

对于场景的加载,只要AssetBundle中的内容不多,Size不大,现在通过LoadFromFile+LZ4的方式来进行加载已经相当快了,现在的顿卡一般不出现在AssetBundle加载上,而是出现在AB.Load和实例化上。对此,有效地办法是通过预加载、缓存和自己制作流式加载来缓解卡顿问题。前两者伟昊在他的回答中已经很详细了。流式加载则是指控制每帧中加载和实例化的资源数量,这需要根据具体的内容、情况来进行具体分析了,并没有统一的准则可遵循,唯一要做的就是多测试、多试验。

除上述之外,还需要以下几点了解一下(以下为偏题话):
(1)地形切得不要过于细碎,否则会加大Culling以及后续引擎场景规划(CreateSharedRenderScene)的开销;
(2)超大地形的游戏(吃鸡、沙盒等)中,UI模块的开销较之其他游戏(MMO、ARPG、卡牌等)要明显降低,这其实可以给其他模块贡献出更多的计算空间;
(3)物理模块的耗时在大幅提升,如果在加上一些车体载具,那么其往往会在不注意之间加入大量的物理开销,这是研发团队在面对大地形开发时会遇到的新话题。

该问答来自UWA问答社区,欢迎大家转至社区进一步交流:
https://answer.uwa4d.com/question/5b4481362a652c518d8d5356


物理

Q2:请问Physics2D.ConvertCollision2DForScript造成的GC该怎么优化呢?
请输入链接描述

我是如下这样设置的:

请输入图片描述
请输入图片描述

在回调OnCollisionEnter2D时,Profiler中会看到Physics2D.ConvertCollision2DForScript的GC,所以如果不需要回调,在脚本中去掉这个函数。如果使用OnCollisionEnter2D,那对于这个回调产生的GC,貌似无法避免,官方论坛也对此有一些讨论:https://forum.unity.com/threads/physics-contacts-gc-activity.369625
当然题主也可以写自己的碰撞检测模块,可以利用Physics2D.GetContacts接口:
https://docs.unity3d.com/ScriptReference/Physics2D.GetContacts.html

感谢Saber@UWA问答社区提供回答,欢迎大家转至进一步交流:
https://answer.uwa4d.com/question/5b430f65f91024518ab77ae9


UGUI

Q3:OnBecameVisible这个回调能用在UGUI上吗?

从UGUI的渲染机制上来讲,是以Canvas为单位来合并大Mesh的(不同DrawCall对应不同的Submesh),渲染时也就按Submesh来提交DrawCall。所以CanvasRender组件实际上并不代表真正的渲染单位,理论上也就不存在包围盒的概念,OnBecameVisible应该也是不起作用的。

该回答由UWA提供

首先计算出UI控件的四个最值点的世界坐标。然后使用下面这个方法可以判断是否在Camera的视锥体内:

bool IsVisible(Camera camera, Vector3[] worldPositions)
{
    Matrix4x4 vp = camera.cullingMatrix;
    foreach (var wp in worldPositions)
    {
        Vector4 v = wp;
        v.w = 1;
        Vector4 p = vp * v;
        if (p.w > p.x && -p.w < p.x && p.w > p.y && -p.w < p.y && p.w > p.z && -p.w < p.z )
        {
            return true;
        }
    }
    return false;
}

感谢凯奥斯@UWA问答社区提供了回答

该问答来自UWA问答社区,欢迎大家转至社区进一步交流:
https://answer.uwa4d.com/question/5b430f65f91024518ab77ae9


Shader

Q4:我的Shader制作完后,如何可以准确地测试出它的消耗呢?

关于Shader的消耗统计,一种是用工具来进行非常精细的消耗分析,比如给出精准的指令数对比,方便横向对比两个Shader的消耗。
另外一个思路是在真机上根据实际效率的影响进行测试,针对真正的应用场景进行优化效果的验证。简单的方法就是针对想测试的部分做LOD区分,然后配置一个Debug按钮来做LOD的切换,直接在设备上统计帧率的变化和Camera.Render函数的消耗变化。不过真机的测试对于不是GPU Bound的情况下看不出来帧率的变化,另外一个角度来说它不是瓶颈的话,优化的优先级就可以考虑调低一些,当然耗电等方面还是可以有提升的。

感谢贾伟昊@UWA问答社区提供了回答,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5b430f65f91024518ab77ae9


物理

Q5:我的NGUI耗时特别厉害,一查才知道NGUI会默认带个什么碰撞检测的,请问这个设置在哪里呢?感谢。

1)UIPanel在Start方法中会根据UICamera的类型判断是否自动添加刚体组件,可以尝试避免自动添加。
2)在Unity菜单Edit/Project Settings/Physics中可以设置在哪些layer上进行碰撞检测,如图:
请输入图片描述

感谢client@UWA问答社区提供了回答,欢迎大家转至社区进一步交流:
https://answer.uwa4d.com/question/5b4724f4339d267d357c6b0f

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

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