Unity优化方向——优化Unity游戏中的图形渲染(译)
简介
渲染的简要介绍
- 中央处理器,即CPU,计算出必须绘制什么以及如何绘制。
- CPU向图形处理单元(即GPU)发送指令。
- GPU根据CPU的指令绘制物体。
- CPU检查场景中的每个对象,已确定是否应该渲染它。对象只有在满足某些条件时才被渲染;例如,它的包围盒的某些部分必须位于摄像机的视锥体内。将不渲染的对象称为剔除对象。有关视锥体和视锥体剔除的更多信息,请参见此页。
- CPU收集关于将要渲染的每个对象的信息,并将这些数据排序为称为draw call的命令。一个draw call包含关于单个网格的数据以及如何渲染该网格;例如,应该使用哪些纹理。在某些情况下,共享设置的对象可以组合到同一个draw call中。将不同object的数据组合到同一个draw call中称为批处理。
- CPU为每个draw call创建一个称为批处理的数据包。批处理有时可能包含draw call调用之外的数据,但是这些情况不太可能导致常见的性能问题,因此我们在本文中不考虑这些情况。
- CPU可以向GPU发送一个命令来更改一些变量,这些变量统称为渲染状态。这个命令称为SetPass call。SetPass call告诉GPU使用哪些设置渲染下一个网格。只有当渲染的下一个网格需要从前一个网格更改渲染状态时,才会发送SetPass call。
- CPU将draw call发送到GPU。draw call指示GPU使用最近SetPass调用中定义的设置来渲染指定的网格。
- 在某些情况下,批处理可能需要不只一个pass。pass是着色器代码的一部分,新的pass需要对渲染状态进行更改。对于批处理中的每一个pass,CPU必须发送一个新的SetPass call,然后必须再次发送draw call。
- GPU按照从CPU发送的顺序处理任务。
- 如果当前任务是SetPass call,GPU将更新渲染状态。
- 如果当前任务是一个draw call,GPU渲染网格。这是分阶段进行的,由着色器代码的不同部分定义。这部分渲染是很复杂的,我们不会详细讨论它,但是理解一段叫做顶点着色器(vertex shader)的代码是很有帮助的。顶点着色器告诉GPU如何处理网格的顶点,然后一段称为片段着色器(fragment shader)告诉GPU如何画出每个像素。
- 这个过程重复进行,直到所有从CPU发送的任务都被GPU处理。
渲染问题的类型
理解渲染问题
Profiler Window
Frame Debugger
发现造成性能问题的原因
- 决定绘制什么
- 为GPU准备命令
- 向GPU发送命令
图形工作
向GPU发送命令
- 在大多数情况下,减少批处理和/或让更多的对象共用相同的渲染状态设置将减少SetPass call的数量。
- 在大多数情况下,减少SetPass call的数量将提高CPU性能。
- 减少要渲染对象的数量可能会同时减少批处理和SetPass call。
- 减少每个对象必须渲染的次数通常会减少SetPass call的数量。
- 将必须渲染的更少批处理对象的数据组合起来将减少批处理的数量。
减少渲染对象的数量
- 简单地减少场景中可见对象的数量是一个有效的解决方案。例如,如果我们在人群中渲染大量不同的角色,我们可以简单地在场景中减少这些角色。如果场景看起来仍然很好,性能得到了改善,那么这可能是比更复杂的技术更快的解决方案。
- 我们可以使用相机的远裁剪面属性来减少相机的绘制距离。此属性是摄像机不再渲染对象的距离,如果我们想要掩盖遥远物体不再可见的事实,我们可以尝试用雾来掩盖遥远物体的缺乏。
- 对于基于距离的更细粒度的隐藏对象的方法,我们可以使用相机的Layer Cull Distance属性为位于不同层的对象提供自定义的裁剪距离。如果我们有很多小的前景装饰细节,这种方法是有用的;我们可以在比大型地形特征更短的距离内隐藏这些细节。
- 我们可以使用一种称为遮挡剔除(occlusion culling)的技术来禁用被其他对象遮挡的对象的渲染。例如,如果我们的场景中有一个大型建筑,我们可以使用遮挡剔除来禁用其背后的对象的渲染。Unity的遮挡剔除并不适用于所有场景,这可能会导致额外的CPU开销,设置起来也可能很复杂,但它可以大大提高某些场景的性能。这有一篇关于在Unity中采用遮挡剔除的最佳实践。除了使用Unity的遮挡剔除,我们还可以通过手动去使玩家看不到的对象变得无效来实现我们自己的遮挡剔除。例如,如果我们的场景包含用于过场动画的对象,但是在之前或之后不可见,我们应该停用它们。使用我们自己的游戏知识总是比要求Unity动态解决问题更有效。
减少每个对象必须渲染的次数
- Unity中的动态照明是一个非常复杂的主题,深入讨论它超出了本文的范围,但是这个教程是对这个主题的一个很好的介绍,Unity手册的这一页详细介绍了常见的照明优化。
- 动态照明是昂贵的,当我们的场景包含不移动的对象时,例如风景,我们可以使用一种称为烘焙的技术来预先计算场景的光照,这样就不需要运行时计算光照计算了。这一篇教程介绍了这种技术,Unity手册中也有对烘焙光照这部分主题的详细介绍。
- 如果我们希望在游戏中使用实时阴影,这可能是我们需要提高性能的方面。Unity手册的这一页是一个很好的参考,可以调整阴影属性的质量设置,以及这些设置如何影响外观和性能。例如,我们可以使用阴影距离属性确保只有附近的对象投射阴影。
- 反射探针 可以创建真实的反射,但是在批处理方面花费的成本非常高。在考虑性能的情况下,最好将反射的使用控制在最低限度,并在使用反射的情况下尽可能地优化反射。Unity手册的这一页提供了一些有关优化反射探针的指导。
将对象合并成更少的批处理
- 共享相同的材质实例
- 有相同的材质设置(例如,纹理,着色器和着色器参数)
- 静态批处理是这样一种技术,它允许Unity批处理附近不移动的符合条件的对象。可以从静态批处理中获益的一个很好的例子是一堆类似的对象,例如巨石。Unity手册的这一页包含了在我们的游戏中设置静态的说明。静态批处理会导致更高的内存使用,所以在分析我们的游戏时,我们应该牢记这个成本。
- 动态批处理是另一种允许Unity批处理合适对象的技术,不管对象是否移动。对于使用这种技术批处理的对象有一些限制。这些限制和说明一起列在Unity手册这一页上。动态批处理对CPU使用有影响,这可能导致它在CPU时间上的开销大于节省的时间。在试验这种技术时,我们应该牢记这一成本,并谨慎使用它。
- 批处理Unity的UI元素有点复杂,因为它可能会受到UI布局的影响。这个来自2015年Unite Bangkok的视频很好地概述了这个主题,还有这一个教程提供了关于如何确保UI批处理按照我们的意愿工作的深入信息。
- GPU instancing 是一种允许对大量相同对象进行高效批处理的技术。它的使用是有限的,并不是所有的硬件都支持它,但是如果我们的游戏在屏幕上同时有许多相同的对象,我们可能会从这项技术中收益。Unity的这一页包含了对Unity3d中GPU实例化的介绍,详细介绍了如何使用它,哪些平台支持它,以及在什么情况下它可能会对我们的游戏有帮助。
- 纹理图集(Texturing atlasing)是一种将多个纹理合并为一个较大纹理的技术。它通常用于2D游戏和UI系统,但也可以用于3D游戏。如果我们在为我们的游戏创造美术资源时使用这种技术,我们就可以确保共享纹理的对象可以进行批处理。Unity有一个内置的纹理图集工具,叫做Sprite Packer,用于2D游戏。
- 可以在Unity编辑器中或者在运行时通过代码手动组合共享相同材质和纹理的网格。当以这种方式组合网格时,我们必须意识到阴影、光照和剔除仍然会在每个对象上运行。这意味着,合并网格带来的性能提升可能会被抵消,因为不这样做,这些对象就无法被渲染。如果我们希望研究这种方法,我们应该去查看Mesh.CombineMeshes这个函数。Unity的Standard Assets资源包里面的CombineChildren就是应用了这项技术的一个例子。
- 我们在脚本中访问Renderer.material时必须非常小心。这会复制一个材质并且返回新副本的引用。这样做将打破批处理,如果渲染器不再具有对同一材质实例的引用。如果我们希望在脚本中访问批处理对象的材质,我们应该使用Renderer.sharedMaterial。
剔除、分类和批处理
- 剔除(culling)本身不太可能是非常昂贵的,但是减少不必要的剔除可能有助于性能。对于所有活动的场景对象,甚至那些没有被渲染的图层上的对象每个对象都有一个相机开销。为了减少这种情况,我们应该禁用摄像机,并禁用当前未使用的渲染器。
- 批处理可以大大提高向GPU发送命令的速度,但有时会在其他地方增加不必要的开销。如果批处理操作导致我们的游戏受到CPU限制,我们可能希望限制游戏中手动或者自动批处理操作的数量。
Skinned meshes
- 我们应该考虑是否需要为当前正在使用的对象添加SkinnedMeshRenderer组件。例如,我们可能已经导入了一个使用SkinnedMeshRenderer组件的模型,但实际上我们并没有对它采用动画。在这种情况下,用MeshRenderer组件替换SkinnedMeshRenderer组件将有助于提高性能。在将模型导入Unity时,如果我们选择不在model Import Settings 中导入动画那么模型将会带有一个MeshRenderer而不是SkinnedMeshRenderer组件。
- 如果我们只是在某些时候在模型上使用动画(例如,只有在启动或只有当它是在相机的一定距离内),我们可以切换它的网格为一个低细节版本或把MeshRender组件替换SkinnedMeshRenderer。SkinnedMeshRenderer组件有一个BakeMesh函数,可以以匹配的姿态创建一个网格,这对于在不同的网格或渲染器之间进行交换非常有用,而不会对对象产生任何可见的改变。
- Unity手册中的这一页面包含了关于优化使用蒙皮网格的动画角色的建议,而在Unity手册在SkinnedMeshRenderer组件的页面包含了可以提高性能的调整。除了这些页面的建议外,值得注意的是,网格蒙皮的成本增加是基于每个顶点的;因此,在我们的模型中使用更少的顶点可以减少必须完成的计算量。
- 在某些平台上,蒙皮可以由GPU而不是CPU来处理。如果我们在GPU上有很大的容量,这个选项可能值得一试。我们可以在Player Settings中为current platform和quality target启用GPU蒙皮。
与渲染无关的主线程操作
如果我们的游戏达到了GPU性能边界
填充率 Fill rate
- 部署运行游戏并记录GPU时间。
- 降低Player Settings中的显示分辨率。
- 再次部署运行游戏。如果性能得到改善,那么填充率可能就是问题所在。
- 片段着色器是着色器代码中告诉GPU如何绘制单个像素的部分。这段代码由GPU逐像素执行的,因此如果代码效率低下,那么性能问题就很容易堆积起来。复杂的片段着色器导致填充率问题的常见原因;
- 如果我们的游戏使用内置的着色器,我们应该尽可能使用最简单和最优化的着色器来获得我们想要的视觉效果。在分组为mobile的着色器是高度优化的;我们应该尝试使用它们,看看这是否能够在不影响游戏外观的情况下提高性能。这些着色器是为移动平台设计的,但是它们适用于任何项目。在非移动平台上使用“mobile”着色器来提高性能是完全可以的。如果它们提供了项目所需的视觉效果的话。
- 如果我们游戏中的对象使用Unity的标准着色器,理解Unity基于当前材质设置编译这个着色器是很重要的。只编译当前使用的特性。这意味着删除detail maps之类的特性可以得到更简单的片段着色器代码,从而大大提高性能。同样,如果我们的游戏是这样的情况的,我们应该尝试这么做,看看我们是否能够在不影响视觉质量的情况下提高性能。
- 如果我们的项目使用定制的着色器,我们应该尽可能优化它们。优化着色器是一个复杂的主题,但是Unity手册的这一页和这一页包含了优化着色器代码的参考。
- 过度绘制(Overdraw)是指同一个像素被多次绘制的情况。当对象被绘制在其他对象之上时就会发生这种情况,这对填充率问题有很大的影响,为了理解过度绘制,我们必须理解Unity在场景中绘制对象的顺序。一个对象的着色器决定它的绘制顺序,通常是通过指定对象所在的渲染队列。Unity使用这些信息以严格的顺序绘制对象,详见Unity手册的这一页 。此外,在绘制不同渲染队列中的对象之前,对它们进行不同的排序。例如,Unity在Geometry队列中对其中对象进行从前到后排序,以最小化过度绘制,但在Transparent队列中对对象进行从后到前进行排序,以达到所需的视觉效果。这种从后到前的排序实际上可以最大化透明队列中对象的过度绘制。过度绘制是一个复杂的课题,没有一个大小合适所有的方法来解决过度绘制问题,但是减少Unity不能自动排序的重叠对象的数量是关键。最好从Unity的场景试图开始调查这个问题;有一个绘制模式,允许我们看到我们的场景过度绘制,并在那里,确定我们可以从这个地方入手来减少过度绘制。过度绘制最常见的罪魁祸首是透明材质、未优化的粒子和重叠的UI元素,所以我们应该尝试优化或减少这些。Unity的学习网站上这篇主要有关Unity UI优化的文章,但也包含了关于过度绘制的一般性指导。
- 使用image effects会大大增加填充率的问题,特别是如果我们使用多个image effect。如果我们的游戏使用了Image effect,并且在填充率问题上遇到了困难,我们可能希望尝试使用不同的设置或image effect的更优化版本(如Bloom(Optimized)代替Boom)。如果我们的游戏在同一个相机上使用多个image effect,这将导致多个着色器Pass。在这种情况下,把我们的着色器代码和image effect合并到一个Pass中,比如在Unity's PostProcessing Stack 。如果我们优化了image effect,但仍然存在填充率问题,我们可能需要考虑禁用image effect,特别是在低端设备上。
存储带宽
- 配置运行游戏并记录GPU时间。
- 在Quality Settings中降低纹理质量。
- 再次配置运行游戏并记录GPU时间。如果性能得到了改善,那么内存带宽可能就是问题所在了。
- 纹理压缩是一种可以大大减少磁盘和内存中纹理大小的技术。如果我们的游戏中内存带宽是一个问题,使用纹理压缩来减少内存中纹理的大小可以帮助提高性能。Unity中有很多不同的纹理压缩格式和设置,每个纹理都可以有单独的设置。一般来说,只要有可能,就应该使用某种形式的纹理压缩;然而,尝试为不同的纹理进行不同的设置以达到最好的效果。Unity手册中关于不同压缩格式和设置的文章。
- Mipmaps是Unity中可以在远距离物体上使用的纹理的低分辨率版本。如果我们的场景包含远离摄像机的对象,我们可以使用mipmaps来缓解内存带宽的问题。场景视图中的mipmaps绘制模式允许我们查看场景中的哪些对象可以从mipmaps中受益,Unity手册中的这一页包含了更多关于为纹理启用Mipmaps的信息。
- 首先,我们应该致力于减少不必要的网格复杂性。如果我们使用的网格具有在游戏中无法看到的细节,或者由于创建错误而具有太多顶点的低效率网格,这对GPU来说是浪费工作。减少顶点处理成本的最简单的方法是在我们的3D建模工具中创建顶点数较低的网格。
- 我们可以尝试一种叫做法线贴图的技术,在这种技术中纹理被用来在网格上创建更复杂的几何效果。尽管这种技术有一些GPU开销,但在许多情况下会带来性能的提高。Unity手册的这一页介绍了使用法线贴图来模拟复杂的几何网格。
- 如果我们的游戏中的一个网格没有使用法线贴图,我们通常可以在网格导入设置中禁用该网格的顶点切线。这减少了为每个顶点发送到GPU的数据量。
- 细节层次(LOD),是一种优化技术,其中原理摄像机的网格减少复杂性。这在不影响游戏视觉质量的情况下减少了GPU渲染顶点的数量。Unity手册中LOD页面包含了更多关于如何在游戏中设置LOD的信息。
- 顶点着色器是告诉GPU如何绘制每个顶点的着色器代码块。如果我们的游戏受到顶点处理的限制,那么减少顶着色器的复杂性可能会有所帮助。
- 如果我们的游戏使用内置的着色器,我们应该尽可能使用最简单和最优化的着色器来获得我们想要的视觉效果。例如,带有(mobile)的着色器是高度优化了的;我们应该尝试使用它们,看看这是否能够在不影响游戏外观的情况下提高性能。
- 如果我们的项目使用定制的着色器,我们应该尽可能优化它们。优化着色器是一个复杂的主题,但是Unity手册的这一页和这一页的着色器优化部分包含了优化着色器代码的有用信息。
Unity优化方向——优化Unity游戏中的图形渲染(译)的更多相关文章
- Unity优化方向——优化Unity游戏中的脚本(译)
原文地址:https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-scripts-unity ...
- Unity优化方向——优化Unity游戏中的垃圾回收(译)
介绍 当我们的游戏运行时,它使用内存来存储数据.当不再需要该数据时,存储该数据的内存将被释放,以便可以重用.垃圾是用来存储数据但不再使用的内存的术语.垃圾回收是该内存再次可用以进行重用的进程的名称. ...
- 浅谈Unity的渲染优化(1): 性能分析和瓶颈判断(上篇)
http://www.taidous.com/article-667-1.html 前言 首先,这个系列文章做个大致的介绍,题目"浅谈Unity",因为公司和国内大部分3D手游开发 ...
- 喵的Unity游戏开发之路 - 推球:游戏中的物理
很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始学.为此我们精选了一套国外优秀的Unity3D游戏开发教程,翻译整理后放送给大家,教您从零开始一步一步掌握Unity3D游戏开发. 本文不 ...
- Unity 几种优化建议
转: http://user.qzone.qq.com/289422269/blog/1453815561?ptlang=2052 Unity 几种优化建议 最简单的优化建议: 1.PC平台的话保持场 ...
- 通过profiler对unity进行针对性优化
转 : http://user.qzone.qq.com/289422269/blog/1453815629?ptlang=2052 通过profiler对unity进行针对性优化 1. CPU U ...
- [Unity优化] Unity CPU性能优化
前段时间本人转战unity手游,由于作者(Chwen)之前参与端游开发,有些端游的经验可以直接移植到手游,比如项目框架架构.代码设计.部分性能分析,而对于移动终端而言,CPU.内存.显卡甚至电池等硬件 ...
- Unity 绘图性能优化 - Draw Call Batching
Unity 绘图性能优化 - Draw Call Batching Unity官方链接:http://docs.unity3d.com/Manual/DrawCallBatching.html 转载请 ...
- Unity UI性能优化技巧
本文将介绍一些提升Unity UI性能的技巧.更多优化技巧,可以观看Unity工程师Ian Dundore在Unite Europe 2017的演讲<使用Unity性能提升技巧>. 1.划 ...
随机推荐
- Spring读取资源的接口Resource笔记
这个是Resource接口的继承体系图.这个接口就是一个资源描述符,抽象的描述了类路径下或者是文件系统中的文件.比如一个Resource接口的实现类的一个实例就代表一个的资源,比如用一个Resourc ...
- BZOJ5329:[SDOI2018]战略游戏(圆方树,虚树)
Description 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战略游戏的地图由n个城市以及m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着 ...
- Python - 格式化字符串的用法
0. 摘要 Python支持多种格式化字符串的方法,包括%-fromatting.str.format().f-strings三种,f-strings是Python3.6以后出现的一种新方法,相比其他 ...
- Docker实战(一)之使用Docker镜像
镜像是Docker三大核心概念中最为重要的,自Docker诞生之日起“镜像”就是相关社区最为热门的关键字. Docker运行容器前需要本地存在对应的镜像,如果镜像没有保存至本地,Docker会尝试先从 ...
- Python的编码注释【转】
格式有多种,但必须有coding:[编码]或者coding=[编码],如: # -*- coding:utf-8 -*- # coding:utf-8 # coding=utf-8 转自:https ...
- unittest中setUp与setUpClass执行顺序
最基础的概念 1.setUP(self)看下面的执行顺序 import unittest class TestGo(unittest.TestCase): def setUp(self): print ...
- UCOS时钟节拍的讲究
其实这个值取适中即可,100,200都行,看你的片子是什么,Cortex-M3的片子取200较合适这个值太小,系统调度周期较长,各个任务之间切换较慢,适时性降低,而太大了,中断周期与调试周期接近了,那 ...
- block本质探寻七之内存管理
说明: <1>阅读本问,请参照block前述文章加以理解: <2>环境:ARC: <3>变量类型:基本数据类型或者对象类型的auto局部变量: 一.三种情形 //代 ...
- 「iOS」你会用几种方法实现计时器
1.NSTimer 存在一定的误差,不管是一次性的还是周期性的timer得实际触发事件的时间,都会与所加入的runloop和runloopMode有关,如果此runloop正在执行一个连续性的运算,t ...
- 打开 CRM 时,出现错误:"Invalid Action – The selected action was not valid"
今天当所有用户在打开CRM时,都出现了一个错误提示 “Invalid Action – The selected action was not valid”. 打开服务器的 event viewer查 ...