目录
[减少Draw Calls](#减少Draw Calls)
对象池
使用对象池:频繁地创建和销毁对象会导致性能下降和内存碎片化。对象池可以预先创建一些对象,然后在需要时从池中取出,不再使用时再放回池中。
减少Draw Calls
Draw Call是指CPU向GPU发送绘制命令的次数。减少Draw Call可以通过批处理,合并网格,使用贴图集等方法实现。
批处理
批处理(Batching)是在游戏开发和3D图形渲染中常用的一种性能优化技术,尤其在使用像Unity这样的游戏引擎时。它的核心目的是减少CPU向GPU发送的绘制指令(即Draw Calls)的数量,从而提升渲染效率。以下是详细的批处理概念和使用方法:
批处理的基本概念
-
Draw Call:Draw Call是CPU告诉GPU:"请绘制这个对象"的命令。每个Draw Call都涉及状态设置、顶点数据传输等操作,这些都会消耗时间和资源。
-
批处理:批处理的思想是将多个渲染操作组合成一个较大的批次(Batch),以减少Draw Calls的总数。这通常涉及将使用相同材质和纹理的多个对象渲染为一个大的绘制操作。
Unity中的批处理类型
在Unity中,主要有两种类型的批处理:
-
静态批处理(Static Batching):
- 用于优化静态(不动的)游戏对象。
- 在游戏构建过程中,Unity会自动将所有标记为"静态"的且共享相同材质的游戏对象合并成一个批次。
- 适用于场景中的建筑物、地形等不会移动的对象。
-
动态批处理(Dynamic Batching):
- 用于优化动态(会移动的)游戏对象。
- 在运行时,Unity会试图将小的、材质相同的游戏对象合并为单个批次。
- 有顶点数和总批次大小的限制(例如,Unity中通常是顶点数不超过900)。
实现批处理的技巧和最佳实践
-
共享材质:确保尽可能多的对象使用相同的材质。这是批处理能否成功的关键因素。
-
使用图集:将多个小纹理打包到一个大的纹理图集中,这样不同的对象即使使用不同的纹理,也仍然可以合批。
-
减少材质属性的变化:例如,避免频繁更改材质的颜色或其他属性。
-
优化网格:对于动态批处理,保持网格简单(低顶点数)是重要的。
-
标记静态对象:在Unity编辑器中,确保场景中不会移动的对象被标记为"静态"。
-
合理使用LOD和遮挡剔除:这些技术可以减少渲染的对象数量,间接减少Draw Calls。
-
性能监控:使用Unity的Profiler工具监控Draw Calls和其他性能指标,以评估批处理的效果。
面临的问题
- 内存使用:合批会增加内存使用,因为合并后的网格需要更多的内存来存储。
- 灵活性降低:合批后,单独操作原始对象变得更困难。
合并网格
合并多个网格为一个大网格是一种在3D图形和游戏开发中常用的优化技术。网格(Mesh)是由顶点、边和面组成的3D对象的结构,在3D渲染中非常基础。合并网格意味着将多个单独的3D模型(每个都有自己的网格)结合成一个单一的、更大的网格。这个过程的具体含义和优点如下:
含义
- 结构合并:将多个3D模型(如多个小物体)的顶点和面数据合并到一个单一的网格结构中。
- 减少Draw Calls:每个独立的网格在渲染时通常需要一个单独的Draw Call。合并网格可以使这些原本独立的模型在一个Draw Call中被渲染,从而减少总的Draw Call数量。
- 资源整合:合并网格通常伴随着纹理和材质的整合,例如使用纹理图集。
优点
- 提高渲染效率:减少Draw Call数量可以降低CPU到GPU的通信负担,提高渲染效率。
- 优化内存使用:通过减少资源(如材质和纹理)的重复使用,可以更高效地利用内存。
- 适用于静态场景:这种技术特别适用于静态的、不会动的场景元素,如建筑物、地面等。
缺点
- 灵活性降低:一旦网格被合并,单独操作原始网格中的一个部分变得更加困难。
- 可能增加内存占用:如果合并后的网格体积很大,它可能会占用更多的内存。
- 复杂度增加:处理一个大网格比处理多个小网格在逻辑上可能更复杂。
应用场景
- 静态环境:用于不动的环境元素,如游戏中的建筑物、地形等。
- 非交互元素:适用于玩家不需要与之交互的场景元素。
贴图集
在3D图形和游戏开发中,"使用贴图集(Texture Atlas)"是一种常用的优化技术。贴图集是将多个不同的纹理图像合并到一个单一的、更大的纹理图中的做法。以下是关于贴图集的详细解释:
贴图集的基本概念
- 贴图集(Texture Atlas):一个大的纹理图(通常是矩形),包含了多个小的纹理。这些小纹理可能是不同的游戏元素的纹理,如角色的服装、游戏场景中的物体等。
- 单一纹理调用 :使用贴图集意味着++多个对象可以共享同一个大纹理。++在渲染时,这允许GPU通过单一的纹理调用来访问多个纹理,从而减少Draw Calls。
如何减少Draw Calls
- 材质共享:由于多个对象可以共享同一个贴图集,这意味着它们也可以共享相同的材质。在图形渲染中,使用相同材质的多个对象可以被更容易地组合到一个批处理中。
- 减少纹理切换:在渲染过程中,切换纹理是一个代价高昂的操作。使用贴图集可以减少这种切换,因为更多的纹理细节都包含在同一张大纹理图中。
LOD
根据物体与摄像机的距离,动态调整物体的细节级别,从而减少渲染负担。
LOD(Level of Detail)技术是一种在3D图形渲染中常用的优化手段,旨在提高渲染效率,同时尽量保持视觉质量。LOD的基本原理是根据对象与观察点的距离,动态地调整对象的复杂度。这里是LOD技术的一些关键点:
基本原理
- 多版本模型:对于一个3D对象,创建多个不同复杂度的版本。这些版本从高到低详细度排序,例如:高、中、低多边形模型。
- 视距感知:根据对象与相机(观察点)的距离,实时选择合适的模型版本进行渲染。
应用
- 近处使用高详细度模型:当对象靠近相机时,使用高多边形、高分辨率纹理的模型,以提供更精细的视觉效果。
- 远处使用低详细度模型:当对象远离相机时,切换到低多边形、低分辨率纹理的模型。由于远距离的视觉效果不那么明显,这样做可以大幅减少渲染负担,同时对视觉效果的影响最小。
优点
- 提高渲染效率:通过减少远处对象的多边形数量,降低了渲染过程的计算负担。
- 节省内存和带宽:使用较低分辨率的纹理和模型可以减少内存的使用和数据传输量。
挑战
- 无缝过渡:在不同LOD级别之间切换时,需要小心处理,以避免突兀的视觉跳变。
- 平衡选择:合理选择何时切换LOD级别,以及每个级别的详细程度,是LOD技术的关键。
LightMap
Lightmap(光照贴图)是一种在3D图形和游戏开发中常用的技术,用于提高场景的光照效果的同时优化性能。在这种技术中,光照信息被预先计算并存储在一张或多张纹理中,这些纹理随后被应用到场景中的对象上。以下是关于Lightmap的更详细的解释:
基本概念
- 预计算的静态光照:Lightmap包含了场景中静态物体表面的光照信息,这些信息通常在游戏或应用的开发阶段预先计算。
- 纹理:光照信息被存储在一种特殊的纹理中,这种纹理被映射到3D对象上,以模拟复杂的光照效果,如软阴影、反射和间接光照。
如何工作
- 光照烘焙:在开发过程中,使用特殊的工具(如Unity的光照烘焙功能)计算场景的光照,并将结果"烘焙"到Lightmap中。
- 映射到几何体:每个对象的表面细节(如几何形状和材质)与Lightmap中的相应区域相结合,从而在渲染时显示预计算的光照效果。
优点
- 性能优化:由于光照信息是预先计算的,运行时不需要进行复杂的光照计算,这可以显著提高性能。
- 高质量的光照效果:可以实现高质量的光照效果,包括软阴影、光线传播和光线反射。
缺点
-
仅限于静态场景:Lightmap通常用于静态物体,因为它们是预先计算的。对于动态物体或变化的光源,需要其他光照技术。
-
内存使用:高质量的Lightmap可能占用大量的纹理内存。
-
烘
-
使用 GPU Instancing:使用 GPU 实例化技术可以将多个相同的物体实例化,减少 Draw Call。可以通过创建 MaterialPropertyBlock 对象并调用 MaterialPropertyBlock.SetVectorArray 方法来实现 GPU Instancing。
合并网格
GPU Instancing
资源异步加载
优化脚本
避免在Update函数中进行大量的计算或者频繁的内存分配,尽量减少使用Find系列函数,避免频繁的GC。
在Unity中,Find系列函数(如FindObjectOfType,Find,FindChild等)是非常消耗性能的操作,因为它们需要遍历整个场景或者对象的所有子对象。如果在Update或者频繁调用的函数中使用Find系列函数,会大大降低游戏的性能。
更好的做法是在Start或Awake函数中使用Find系列函数,将找到的对象保存在一个变量中,然后在需要的地方直接使用这个变量。这样就只需要在游戏开始时执行一次Find操作,而不是每帧都执行。
另外,如果可能的话,尽量使用public变量或者单例模式来引用需要的对象,这样可以完全避免使用Find系列函数。
使用Profiler工具:Profiler可以帮助你找到性能瓶颈,从而进行针对性的优化。
优化物理:减少物理模拟的复杂度,比如使用简化的碰撞体,减少不必要的物理计算
遮挡剔除
Occlusion Culling:隐藏摄像机看不见的物体,减少渲染负担。
-
使用Shader优化:使用更简单的Shader,或者针对特定平台优化Shader。
-
优化UI:避免频繁更新UI,尽量使用Canvas Group和Layout Group。
合并网格:将多个网格合并成一个网格,可以减少 Draw Call。可以使用 Unity 中的 Mesh.CombineMeshes 方法来实现网格的合并。
合并材质:将多个使用相同材质的物体合并成一个物体,可以减少 Draw Call。可以使用 Unity 中的 MaterialPropertyBlock 来实现材质的共享。
使用静态批处理:将多个静态物体合并为一个批次进行渲染,可以减少 Draw Call。可以在 Unity 中开启静态批处理来实现。
使用动态批处理:将多个动态物体合并为一个批次进行渲染,可以减少 Draw Call。可以在 Unity 中开启动态批处理来实现。
使用 GPU Instancing:使用 GPU 实例化技术可以将多个相同的物体实例化,减少 Draw Call。可以通过创建 MaterialPropertyBlock 对象并调用 MaterialPropertyBlock.SetVectorArray 方法来实现 GPU Instancing。
使用 Atlas 贴图:将多个小贴图合并成一个大贴图,可以减少 Draw Call。可以使用 Unity 中的 SpritePacker 工具来实现贴图的合并。
减少动态物体的数量:动态物体需要每帧重新绘制,因此数量过多会导致 Draw Call 增加。可以通过使用静态物体、使用 LOD 等方式来减少动态物体的数量。
减少透明物体的数量:透明物体需要额外的渲染步骤,因此数量过多会导致 Draw Call 增加。可以通过使用不透明物体、使用 Alpha Test 等方式来减少透明物体的数量。
使用 Occlusion Culling:根据摄像机视锥体内的可见 UI 元素,减少需要渲染的 UI 元素数量,从而提高渲染性能。