[游戏开发]性能优化

一、渲染模块

降低Draw Call
DC DrwaCall Unity DC

Draw Call是渲染模块优化方面的重中之重,一般来说,Draw Call越高,则渲染模块的CPU开销越大。降低Draw Call的方法则主要是减少所渲染物体的材质种类,并通过Draw Call Batching来减少其数量。
游戏性能并非Draw Call越小越好。这是因为,决定渲染模块性能的除了Draw Call之外,还有用于传输渲染数据的总线带宽。当我们使用Draw Call Batching将同种材质的网格模型拼合在一起时,可能会造成同一时间需要传输的数据(Texture、VB/IB等)大大增加,以至于造成带宽“堵塞”,在资源无法及时传输过去的情况下,GPU只能等待,从而反倒降低了游戏的运行帧率。
Draw Call和总线带宽是天平的两端,我们需要做的是尽可能维持天平的平衡,任何一边过高或过低,对性能来说都是无益的。

简化资源
简化资源是非常行之有效的优化手段。在大量的移动游戏中,其渲染资源其实是“过量”的,过量的网格资源、不合规的纹理资源等等。可以在UWA测评报告中对资源的使用进行了详细的展示(每帧渲染的三角形面片数、网格和纹理资源的具体使用情况等),以快速查找和完善存在问题的资源。

层级细节(LOD level of detail )
它是根据物体在游戏画面中所占视图的百分比来调用不同复杂度的模型的。简单而言,就是当一个物体距离摄像机比较远的时候使用低模,当物体距离摄像机比较近的时候使用高模。当然层次细节(LOD)也是有缺点的,容易占用大量内存。使用这个技术,一般是在解决运行时流畅度的问题,采用的是空间换时间的方式。

视锥体剔除(Frustum Culling)
视锥体剔除的基本思想:判断对象是否在相机视锥体内(相交也算),在则不剔除,不在则剔除。判断的方法也有很多,比较常见的方法是判断对象的BoundingBox与相机视锥体的六个剪裁平面的关系,来判断对象是否在视锥体中。为此Unity也提供了原生API以支持基于视锥体的剔除方案。

遮罩剔除(Occlusion Culling)
遮挡剔除是一个过程,它阻止Unity对被其他游戏对象完全隐藏(遮挡)的游戏对象执行渲染计算。
每一帧,相机都执行剔除操作,检查场景中的渲染器,排除(剔除)那些不需要被绘制的。默认情况下,摄像机执行视锥剔除,排除所有不在摄像机视图视锥范围内的渲染器。然而,视锥剔除并不检查渲染器是否被其他游戏对象遮挡,所以Unity仍然可以在渲染操作上浪费CPU和GPU时间,这些渲染器在最后一帧不可见。遮挡剔除阻止Unity执行这些浪费的操作。

二、加载模块

加载模块同样也是任何游戏项目中所不可缺少的组成成分。加载模块的性能开销比较集中,主要出现于场景切换处,且CPU占用峰值均较高。
这里,我们先来说说场景切换时,其性能开销的主要体现形式。对于目前的Unity版本而言,场景切换时的主要性能开销主要体现在两个方面,前一场景的场景卸载和下一场景的场景加载。
下面,我们就具体来说说这两个方面的性能瓶颈:

场景卸载
场景卸载一般是由引擎自动完成的,即当我们调用类似Application.LoadLevel的API时,引擎即会开始对上一场景进行处理,其性能开销主要被以下几个部分占据:

  1. Destroy:引擎在切换场景时会收集未标识成“DontDestoryOnLoad”的GameObject及其Component,然后进行Destroy。同时,代码中的OnDestory被触发执行,这里的性能开销主要取决于OnDestroy回调函数中的代码逻辑。
  2. Resources.UnloadUnusedAssets:一般情况下,场景切换过程中,该API会被调用两次,一次为引擎在切换场景时自动调用,另一次则为用户手动调用(一般出现在场景加载后,用户调用它来确保上一场景的资源被卸载干净)。在我们测评过的大量项目中,该API的CPU开销主要集中在500ms~3000ms之间。其耗时开销主要取决于场景中Asset和Object的数量,数量越多,则耗时越慢。

场景加载
场景加载过程的性能开销又可细分成以下几个部分:

  1. 资源加载:资源加载几乎占据了整个加载过程的90%时间以上,其加载效率主要取决于资源的加载方式(Resource.Load或AssetBundle加载)、加载量(纹理、网格、材质等资源数据的大小)和资源格式(纹理格式、音频格式等)等等。不同的加载方式、不同的资源格式,其加载效率可谓千差万别。
  2. Instantiate实例化:在场景加载过程中,往往伴随着大量的Instantiate实例化操作,比如UI界面实例化、角色/怪物实例化、场景建筑实例化等等。在Instantiate实例化时,引擎底层会查看其相关的资源是否已经被加载,如果没有,则会先加载其相关资源,再进行实例化,这其实是大家遇到的大多数“Instantiate耗时问题”的根本原因,所以提倡资源依赖关系打包并进行预加载,从而来缓解Instantiate实例化时的压力(关于AssetBundle资源的加载)。
  3. 序列化:Instantiate实例化的性能开销还体现在脚本代码的序列化上,如果脚本中需要序列化的信息很多,则Instantiate实例化时的时间亦会很长。

三、代码效率

在项目优化过程中,我们经常会想知道,到底是哪些函数占据了大量的CPU开销。同时,绝大多数的项目中其性能开销都遵循着“二八原则”,即80%的性能开销都集中在20%的函数上。

GC 垃圾回收:
unity的GC优化,原理及方式

  1. C# Dictionary 取值
    如果频率涉及到在Dictionary里取值,安全的做法是首先要判断对应的Key是否存在,存在返回Value,否则如果Key不存在,直接取值会报错;
    更加有效率的做法是用TryGetValue(key, var out value),它省去了对Key的判断经过测试:当Key存在时,花费时间前者大约是后者的1.5倍,当Key不存在时,花费时间基本相等,这样在一帧内存在几千几万次调用时,它的时间成本还是很可观的
  2. getcomponent,GetChild,find等获取性能千万不要在update或循环中做。会产生大量gc。
  3. 字典的key不能用枚举,会有gc
  4. 一些方法要经常获取就应该统一一个地方,一次性获取。减少后面获取的数量
  5. string不能直接用+来拼接,用string.format来实现
  6. update中的执行逻辑尽量简单
    就算有复杂的情况也应该把复杂的情况缓存起来。获取name会有gc,缓存该缓存的东西,depth一样就不去设置这样才能0个gc
  7. 不是因为动静分离的话,panel尽量只用一个,因为不同的panel同样的图集同样的深度也是会截断的,就会产生截断的多余的DrawCall。
  8. 尽量用对象池
    并且对象池的原则是如果对象池没有则创建一个对象,用完放到对象池并隐藏,需要的时候再拿出来。如果需要使用多个则查看当前的特效是否隐藏,隐藏就拿出来用,如果是显示的则再创建一个,然后放到对象池。然后用lru的方式并控制对象池的上限。
  9. 字符串比较不用直接用==,会产生装箱而导致有gc。用compareto或者indexof

四,其他优化

  1. 人物身上少挂载组件,用一个组件触发其他类执行会更好。
    比如,创建人物移动的类,这个类不继承mono,然后用一个人物的组件(统管整个人物的组件)的update触发去触发人物移动的类的update。这样人物移动也可以做相应行为。这样可以减少人物挂载的组件。
    少用projector的方式做投影显示。
    因为每个projector都会有个相机,这样会产生很多gc。无法合并dc。用面片(quad)然后用rvocontroller可以让他贴在地面上。

  2. 不在主场景就把主场景的摄像机给关了,不在相关的页面则隐藏相关的页面

  3. 显示不出来的shader中的pass(因为在ui上如果不写深度的话一些pass会在ui后面)就不要渲染,可以关闭该pass减少dc

  4. RenderTexture能用低质量的就用低质量的
    比如用ARGB4444能显示正常的就不用全质量,能降采样的就降采样,因为rt一般比较可能用在摄像机的targetrendertexture里,所以本身的rt越小性能越好。而且如果之渲染纯色的话可以把rt的深度给关了

  5. 不需要投射阴影或接收阴影的并且场景有关照就要关闭相关设置

  6. 把放在片元着色器执行的代码放到定点着色器

  7. 开启多线程渲染

  8. shader尽量剔除背面

你可能感兴趣的:(龙之介Unity学习,操作系统,图像处理,计算机视觉)