Ricky:我试着修改了一下Stripping Level似乎没什么改变,感觉虽占用内存大但不会影响游戏运行。我们暂时忽略它吧(- -)!
Ricky:虽占用较大内存,但这也是必备项,没办法优化。继续忽略吧(- -)!!
Ricky:重点监控对象,不要让它超过20MB,否则可能会有性能问题!
Ricky:这个东西大家都比较熟悉了,忽略它吧。
Ricky:重点监控对象,这里就是你要监控的哪些预设在序列化中在内存中占用大小,根据需求进行优化。
Ricky:与持久化数据相关,比如AssetBundle之类的。注意监控相关的文件。
Ricky:无法优化。
Ricky:随着音频文件的增多而增大。
Ricky:重点优化对象,播放时长较长的音乐文件需要进行压缩成.mp3或.ogg格式,时长较短的音效文件可以使用.wav 或.aiff格式。
Ricky:这个一般在天空盒中比较常见,我也不知道如何优化这个。。。
Ricky:主要检查是否有重复的资源,还有尽量减少点面数。
Ricky:注意网格模型的点面数,能合并的mesh尽量合并。
1)ManagedHeap.UsedSize: 移动游戏建议不要超过20MB.
2)SerializedFile: 通过异步加载(LoadFromCache、WWW等)的时候留下的序列化文件,可监视是否被卸载.
3)WebStream: 通过异步WWW下载的资源文件在内存中的解压版本,比SerializedFile大几倍或几十倍,不过我们现在项目中展示没有。
4)Texture2D: 重点检查是否有重复资源和超大Memory是否需要压缩等.
5)AnimationClip: 重点检查是否有重复资源.
6)Mesh: 重点检查是否有重复资源.
1.Device.Present:
1)GPU的presentdevice确实非常耗时,一般出现在使用了非常复杂的shader.
2)GPU运行的非常快,而由于Vsync的原因,使得它需要等待较长的时间.
3)同样是Vsync的原因,但其他线程非常耗时,所以导致该等待时间很长,比如:过量AssetBundle加载时容易出现该问题.
4)Shader.CreateGPUProgram:Shader在runtime阶段(非预加载)会出现卡顿(华为K3V2芯片).
5)StackTraceUtility.PostprocessStacktrace()和StackTraceUtility.ExtractStackTrace(): 一般是由Debug.Log或类似API造成,游戏发布后需将Debug API进行屏蔽。
2.Overhead:
1)一般情况为Vsync所致.
2)通常出现在Android设备上.
3.GC.Collect:
原因:
1)代码分配内存过量(恶性的)
2)一定时间间隔由系统调用(良性的).
占用时间:
1)与现有Garbage size相关
2)与剩余内存使用颗粒相关(比如场景物件过多,利用率低的情况下,GC释放后需要做内存重排)
4.GarbageCollectAssetsProfile:
1)引擎在执行UnloadUnusedAssets操作(该操作是比较耗时的,建议在切场景的时候进行)。
2)尽可能地避免使用Unity内建GUI,避免GUI.Repaint过渡GCAllow.
3)if(other.tag == a.tag)改为other.CompareTag(a.tag).因为other.tag为产生180B的GC Allow.
4)少用foreach,因为每次foreach为产生一个enumerator(约16B的内存分配),尽量改为for.
5)Lambda表达式,使用不当会产生内存泄漏.
5.尽量少用LINQ:
1)部分功能无法在某些平台使用.
2)会分配大量GC Allow.
6.控制StartCoroutine的次数:
1)开启一个Coroutine(协程),至少分配37B的内存.
2)Coroutine类的实例 -> 21B.
3)Enumerator -> 16B.
7.使用StringBuilder替代字符串直接连接.
8.缓存组件:
1)每次GetComponent均会分配一定的GC Allow.
2)每次Object.name都会分配39B的堆内存.
1、 由于实时对战游戏的数据包数量巨大,早期版本的帧同步策略会导致比较明显的卡顿,通过进行数据包的合并与优化逐渐解决了卡顿问题;
2、 频繁创建和销毁的小兵对象让CPU爆表了,大量的小兵如果采用实时内存的分配和回收,会产生大量的内存碎片和系统开销,解决方法之一就是采用高效的对象池进行优化,对每个内存对象的状态进行操作即可;
3、 性能分析过程中,发现单人同屏和多人同屏时的开销都很大,通过视野裁剪技术,使得玩家视野外的不必要的特效和渲染可以全部关闭,极大降低了CPU、GPU和内存的开销;
4、 在高中低三档机型上玩游戏时,分别加载不同层次的特效包,这也有助于降低CPU和内存的开销;性能分析过程中发现副本内wwise音频组件占了30%的CPU时间,果断抛弃之,采用Unity自带音频功能,优化很明显;
5、 游戏内界面采用了UGUI的方式实现,但大量的实时UI变化使得副本内每帧会有230以上的drawcall,导致中低端机型感受到明显卡顿,最终采用UGUI+自研究UI的组合拳,重写了一套紧密结合游戏自身特性的UI来实现战斗血条和浮动文字的效果。
6、 资源使用总量是否在合理范围之内。
7、 一个场景内的资源重复率。
8、 资源对象拷贝的数量是否合理。
9、 场景切换时保留的资源详情。
10、 网格、纹理、音频、动画、GameObject等资源是否超标。
11、 贴图:
12、 l 控制贴图大小,尽量不要超过 1024x1024;
13、 l 尽量使用2的n次幂大小的贴图,否则GfxDriver里会有2份贴图;
14、 l 尽量使用压缩格式减小贴图大小;
15、 l 若干种贴图合并技术;
16、 l 去除多余的alpha通道;
17、 l 不同设备使用不同的纹理贴图,分层显示;
18、
19、 模型:
20、 l 尽量控制模型的面数,小于1500会比较合适;
21、 l 不同设备使用不同的模型面数;
22、 l 尽量保持在30根骨骼内;
23、 l 一个网格不要超过3个material;
24、 动画:
25、 l N种动画压缩方法;
26、 l 尽量减少骨骼数量;
27、 声音:
28、 l 采用压缩MP3 和 wav;
29、 资源方面的优化:
30、 l 使用 Resource.Load 方法在需要的时候再读取资源;
31、 l 各种资源在使用完成后,尽快用Resource.UnloadAsset和UnloadUnusedAsset卸载掉;
32、 l 灵活运用AssetBundle的Load和Unload方法动态加载资源,避免主要场景内的初始化内存占用过高;(实现起来真的很难…)
33、 l 采用www加载了AssetBundle后,要用www.Dispose 及时释放;
34、 l 在关卡内谨慎使用DontDestroyOnLoad,被标注的资源会常驻内存;
35、 代码的优化:
36、 l 尽量避免代码中的任何字符串连接,因为这会给GC带来太多垃圾;
37、 l 用简单的“for”循环代替“foreach”循环;
38、 l 为所有游戏内的动态物体使用内存对象池,可以减少系统开销和内存碎片,复用对象实例,构建自己的内存管理模式,减少Instantiate和Destory;
39、 l 尽量不使用LINQ命令,因为它们一般会分配中间缓器,而这很容易生成垃圾内存;
40、 l 将引用本地缓存到元件中会减少每次在一个游戏对象中使用 “GetComponent” 获取一个元件引用的需求;
41、 l 减少角色控制器移动命令的调用。移动角色控制器会同步发生,每次调用都会耗损较大的性能;
42、 l 最小化碰撞检测请求(例如raycasts和sphere checks),尽量从每次检查中获得更多信息;
43、 l AI逻辑通常会生成大量物理查询,建议让AI更新循环设置低于图像更新循环,以减少CPU负荷;
44、 l 要尽量减少Unity回调函数,哪怕是空函数也不要留着;(例如空的Update、FixedUpdate函数)
45、 l 尽量少使用FindObjectsOfType函数,这个函数非常慢,尽量少用且一定不要在Update里调用;
46、 l 千万一定要控制mono堆内存的大小;
47、
48、 unity3D 对于移动平台的支持无可厚非,但是也有时候用Unity3D 开发出来的应用、游戏在移动终端上的运行有着明显的效率问题,比如卡、画质等各种问题。自己在做游戏开发的时候偶有所得。对于主要影响性能的因素做个总结。
49、
50、 主要因素有:
51、 1. Savedby batching 值过大 ---- > 这个值主要是针对Mesh的批处理,这个值越高,应用就越卡
52、 2. Drawcall值过大 ---- > Drawcall 值过大,所需要的 GPU 的处理性能较高,从而导致CPU的计算时间过长,于是就卡了
53、 3. 点、面过多 ----> 点、面过多,GPU 根据不同面的效果展开计算,并且CPU计算的数据也多,所以效果出来了,但是卡巴斯基
54、 由于 Saved by batching 和 Drawcall 值过大所引起的卡的问题我所做的优化方式有:
55、 1. 对于模型 :Mesh 合并,有个不错的插件(DrawCallMinimizer ---> 直接上AssetStore 下载即可,免费的,而且有文档,很容易上手)
56、 2. 对于UI : 尽量避免使用Unity3D自带的 GUI 换用 NGUI或者EZGUI;因为这两个UI插件对于UI中的图片处理是将UI图片放置在一个 Atlas中,一个 Atlas 对应一个Drawcall
57、 3. 对于灯光: 可以使用 Unity3D 自带的 Lightmapping插件来烘焙场景中的灯光效果到物体材质上
58、 4. 对于场景: 可以使用 Unity3D 自带的 OcclusionCulling 插件把静止不动的场景元素烘焙出来
59、 4. 对于特效:尽量把材质纹理合并
60、 对于Unity3D 在移动终端上支持的Drawcall 数到底多少,主要是跟机子性能有关的,当然也不是说值小性能就一定没问题(本人亲测,也有17就卡的,主要是模型材质纹理过大所引起的),目前我做的是70左右的,还OK,挺正常的
61、
62、 由于点、面过多所导致的性能问题,最好用简模,用四面体来做复杂的模型,但是面、点也别太多,至于Unity3D 到底支持多少点、面的说法各异,我也搞不懂,总之少些肯定OK
63、
64、
65、
66、 检测方式:
67、 一,Unity3D 渲染统计窗口
68、 Game视窗的Stats去查看渲染统计的信息:
69、 1、FPS
70、 fps其实就是 framesper second,也就是每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。
71、
72、 2、Draw calls
73、 batching之后渲染mesh的数量,和当前渲染到的网格的材质球数量有关。
74、
75、 3、Saved by batching
76、 渲染的批处理数量,这是引擎将多个对象的绘制进行合并从而减少GPU的开销;
77、 很多GUI插件的一个好处就是合并多个对象的渲染,从而降低DrawCalls ,保证游戏帧数。
78、
79、 4、Tris 当前绘制的三角面数
80、
81、 5、Verts 当前绘制的顶点数
82、
83、 6、Used Textures 当前帧用于渲染的图片占用内存大小
84、
85、 7、Render Textures 渲染的图片占用内存大小,也就是当然渲染的物体的材质上的纹理总内存占用
86、
87、 8、VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存
88、
89、 9、VBO Total 渲染过程中上载到图形卡的网格的数量,这里注意一点就是缩放的物体可能需要额外的开销。
90、
91、 10、VisibleSkinned Meshes 蒙皮网格的渲染数量
92、
93、 11、Animations 播放动画的数量
94、 注意事项:
95、 1,运行时尽量减少 Tris 和 Draw Calls
96、 预览的时候,可点开 Stats,查看图形渲染的开销情况。特别注意 Tris 和 Draw Calls 这两个参数。
97、 一般来说,要做到:
98、 Tris 保持在 7.5k 以下,有待考证。
99、 Draw Calls 保持在 20 以下,有待考证。
100、 2,FPS,每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。
101、 3,Render Textures 渲染的图片占用内存大小。
102、 4,VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存。
103、
104、 二,代码优化
105、 1. 尽量避免每帧处理
106、 比如:
107、 function Update() {DoSomeThing(); }
108、 可改为每5帧处理一次:
109、 function Update() { if(Time.frameCount% 5 == 0) { DoSomeThing(); } }
110、 2. 定时重复处理用InvokeRepeating 函数实现
111、 比如,启动0.5秒后每隔1秒执行一次 DoSomeThing 函数:
112、
113、 function Start() {InvokeRepeating("DoSomeThing", 0.5, 1.0); }
114、
115、 3. 优化 Update,FixedUpdate, LateUpdate 等每帧处理的函数
116、 函数里面的变量尽量在头部声明。
117、 比如:
118、 function Update() { var pos:Vector3 = transform.position; }
119、 可改为
120、 private var pos: Vector3;function Update(){ pos = transform.position; }
121、
122、 4. 主动回收垃圾
123、 给某个 GameObject 绑上以下的代码:
124、 function Update() {if(Time.frameCount % 50 == 0) { System.GC.Collect(); } }
125、
126、 5. 优化数学计算
127、 比如,如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等
128、
129、 6,减少固定增量时间
130、 将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。
131、 7,减少GetComponent的调用
132、 使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为"缓存"的引用)。例如,如果您使用如下的代码:
133、 function Update () {
134、 transform.Translate(0, 1, 0);
135、
136、 }
137、 通过下面的更改您将获得更好的性能:
138、
139、 var myTransform : Transform;
140、 function Awake () {
141、 myTransform = transform;
142、 }
143、 function Update () {
144、 myTransform.Translate(0, 1, 0);
145、 }
146、
147、 8,避免分配内存
148、 您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于"传值"操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。
149、
150、 9,使用iOS脚本调用优化功能
151、 UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。从Mono的脚本调用 C/C++函数也存在着一定的性能开销。您可以使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。此设置的选项有:
152、 Slow and Safe – Mono内部默认的处理异常的调用
153、
154、 Fast and Exceptions Unsupported–一个快速执行的Mono内部调用。不过,它并不支持异常,因此应谨慎使用。它对于不需要显式地处理异常(也不需要对异常进行处理)的应用程序来说,是一个理想的候选项。
155、
156、 10,
157、 优化垃圾回收
158、
159、 如上文所述,您应该尽量避免分配操作。但是,考虑到它们是不能完全杜绝的,所以我们提供两种方法来让您尽量减少它们在游戏运行时的使用:
160、 如果堆比较小,则进行快速而频繁的垃圾回收
161、 这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用。如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),但它们可以快速处理并且对游戏的影响很小:
162、 if (Time.frameCount % 30 == 0)
163、 {
164、 System.GC.Collect();
165、 }
166、
167、 但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间
168、 如果堆比较大,则进行缓慢且不频繁的垃圾回收
169、 这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。但是,Mono运行时会尽可能地避免堆的自动扩大。因此,您需要通过在启动过程中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的"无用"对象):
170、
171、 function Start() {
172、
173、 var tmp = newSystem.Object[1024];
174、
175、 // make allocations in smallerblocks to avoid them to be treated in a special way, which is designed forlarge blocks
176、
177、 for (var i : int = 0; i <1024; i++)
178、
179、 tmp[i] = new byte[1024];
180、
181、 // release reference
182、
183、 tmp = null;
184、
185、 }
186、
187、 游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被完全占满。所以,当这种游戏暂停发生时,您可以显式请求一次垃圾回收:
188、
189、 System.GC.Collect();
190、
191、 另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。
192、
193、 三,模型
194、 1,压缩 Mesh
195、 导入 3D 模型之后,在不影响显示效果的前提下,最好打开 Mesh Compression。
196、 Off, Low, Medium, High 这几个选项,可酌情选取。
197、 2,避免大量使用 Unity 自带的 Sphere 等内建 Mesh
198、 Unity 内建的 Mesh,多边形的数量比较大,如果物体不要求特别圆滑,可导入其他的简单3D模型代替。
199、
200、 1不是每个主流手机都支持的技术(就是如果可以不用就不用或有备选方案)
201、 屏幕特效
202、 动态的pixel光照计算(如法线)
203、 实时的阴影
204、
205、 2优化建议
206、 2.1渲染
207、 1.不使用或少使用动态光照,使用light mapping和light probes(光照探头)
208、 2.不使用法线贴图(或者只在主角身上使用),静态物体尽量将法线渲染到贴图
209、 3.不适用稠密的粒子,尽量使用UV动画
210、 4.不使用fog,使用渐变的面片(参考shadowgun)
211、 5.不要使用alpha–test(如那些cutout shader),使用alpha-blend代替
212、 6.使用尽量少的material,使用尽量少的pass和render次数,如反射、阴影这些操作
213、 7.如有必要,使用Per-LayerCull Distances,Camera.layerCullDistances
214、 8.只使用mobile组里面的那些预置shader
215、 9.使用occlusionculling
216、 11.远处的物体绘制在skybox上
217、 12.使用drawcallbatching:
218、 对于相邻动态物体:如果使用相同的shader,将texture合并
219、 对于静态物体,batching要求很高,详见Unity Manual>Advanced>Optimizing Graphics Performance>Draw Call Batching
220、
221、 规格上限
222、 1. 每个模型只使用一个skinnedmesh renderer
223、 2. 每个mesh不要超过3个material
224、 3. 骨骼数量不要超过30
225、 4. 面数在1500以内将得到好的效率
226、 2.2物理
227、 1.真实的物理(刚体)很消耗,不要轻易使用,尽量使用自己的代码模仿假的物理
228、 2.对于投射物不要使用真实物理的碰撞和刚体,用自己的代码处理
229、 3.不要使用meshcollider
230、 4.在edit->projectsetting->time中调大FixedTimestep(真实物理的帧率)来减少cpu损耗
231、 2.3脚本编写
232、 1.尽量不要动态的instantiate和destroyobject,使用object pool
233、 2.尽量不要再update函数中做复杂计算,如有需要,可以隔N帧计算一次
234、 3.不要动态的产生字符串,如Debug.Log("boo"+ "hoo"),尽量预先创建好这些字符串资源
235、 4.cache一些东西,在update里面尽量避免search,如GameObject.FindWithTag("")、GetComponent这样的调用,可以在start中预先存起来
236、 5.尽量减少函数调用栈,用x= (x > 0 ? x : -x);代替x = Mathf.Abs(x)
237、 6.下面的代码是几个gc“噩梦”
238、 String的相加操作,会频繁申请内存并释放,导致gc频繁,使用System.Text.StringBuilder代替
239、 functionConcatExample(intArray: int[]) {
240、 varline = intArray[0].ToString();
241、
242、 for(i = 1; i < intArray.Length; i++) {
243、 line+= ", " + intArray[i].ToString();
244、 }
245、
246、 returnline;
247、 }
248、 在函数中动态new array,最好将一个array、传进函数里修改
249、 functionRandomList(numElements: int) {
250、 varresult = new float[numElements];
251、
252、 for(i = 0; i < numElements; i++) {
253、 result[i]= Random.value;
254、 }
255、
256、 returnresult;
257、 }
258、
259、 2.4 shader编写
260、 1.数据类型
261、 fixed / lowp -for colors, lighting information and normals,
262、 half / mediump -for texture UV coordinates,
263、 float / highp -avoid in pixel shaders, fine to use in vertex shader for position calculations.
264、 2.少使用的函数:pow,sin,cos等
265、 2.4 GUI
266、 1.不要使用内置的onGUii函数处理gui,使用其他方案,如NGUI
267、
268、 3.格式
269、 1.贴图压缩格式:ios上尽量使用PVRTC,android上使用ETC
270、 最简单的优化建议:
1.PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU。
2.如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unlit目录下的。它们更高效。
3.尽可能共用材质。
4.将不需要移动的物体设为Static,让引擎可以进行其批处理。
5.尽可能不用灯光。
6.动态灯光更加不要了。
7.尝试用压缩贴图格式,或用16位代替32位。
8.如果不需要别用雾效(fog)
9.尝试用OcclusionCulling,在房间过道多遮挡物体多的场景非常有用。若不当反而会增加负担。
10.用天空盒去“褪去”远处的物体。
11.shader中用贴图混合的方式去代替多重通道计算。
12.shader中注意float/half/fixed的使用。
13.shader中不要用复杂的计算pow,sin,cos,tan,log等。
14.shader中越少Fragment越好。
15.注意是否有多余的动画脚本,模型自动导入到U3D会有动画脚本,大量的话会严重影响消耗CPU计算。
16.注意碰撞体的碰撞层,不必要的碰撞检测请舍去。
1.为什么需要针对CPU(中央处理器)与GPU(图形处理器)优化?
CPU和GPU都有各自的计算和传输瓶颈,不同的CPU或GPU他们的性能都不一样,所以你的游戏需要为你目标用户的CPU与GPU能力进行针对开发。
2.CPU与GPU的限制
GPU一般具有填充率(Fillrate)和内存带宽(Memory Bandwidth)的限制,如果你的游戏在低质量表现的情况下会快很多,那么,你很可能需要限制你在GPU的填充率。
CPU一般被所需要渲染物体的个数限制,CPU给GPU发送渲染物体命令叫做DrawCalls。一般来说DrawCalls数量是需要控制的,在能表现效果的前提下越少越好。通常来说,电脑平台上DrawCalls几千个之内,移动平台上DrawCalls几百个之内。这样就差不多了。当然以上并不是绝对的,仅作一个参考。
往往渲染(Rendering)并不是一个问题,无论是在GPU和CPU上。很可能是你的脚本代码效率的问题,用Profiler查看下。
关于Profiler介绍:http://docs.unity3d.com/Documentation/Manual/Profiler.html
需要注意的是:
在GPU中显示的RenderTexture.SetActive()占用率很高,是因为你同时打开了编辑窗口的原因,而不是U3D的BUG。
3.关于顶点数量和顶点计算
CPU和GPU对顶点的计算处理都很多。GPU中渲染的顶点数取决于GPU性能和SHADER的复杂程度,一般来说,每帧之内,在PC上几百万顶点内,在移动平台上不超过10万顶点。
CPU中的计算主要是在蒙皮骨骼计算,布料模拟,顶点动画,粒子模拟等。GPU则在各种顶点变换、光照、贴图混合等。
【个人认为,具体还是看各位的项目需求,假设你项目的是3d游戏。你游戏需要兼容低配置的硬件、流畅运行、控制硬件发热的话,还要达到一定效果(LIGHTMAP+雾效),那么顶点数必定不能高。此时同屏2W顶点我认为是个比较合适的数目,DRAWCALL最好低于70。另,控制发热请控制最高上限的帧率,流畅的话,帧率其实不需要太高的。】
4.针对CPU的优化——减少DRAW CALL 的数量
为了渲染物体到显示器上,CPU需要做一些工作,如区分哪个东西需要渲染、区分开物体是否受光照影响、使用哪个SHADER并且为SHADER传参、发送绘图命令告诉显示驱动,然后发送命令告诉显卡删除等这些。
假设你有一个上千三角面的模型却用上千个三角型模型来代替,在GPU上花费是差不多的,但是在CPU上则是极其不一样,消耗会大很多很多。为了让CPU更少的工作,需要减少可见物的数目:
a.合并相近的模型,手动在模型编辑器中合并或者使用UNITY的Draw call批处理达到相同效果(Draw call batching)。具体方法和注意事项查看以下链接:
Draw call batching :http://docs.unity3d.com/Documentation/Manual/DrawCallBatching.html
b.在项目中使用更少的材质(material),将几个分开的贴图合成一个较大的图集等方式处理。
如果你需要通过脚本来控制单个材质属性,需要注意改变Renderer.material将会造成一份材质的拷贝。因此,你应该使用Renderer.sharedMaterial来保证材质的共享状态。
有一个合并模型材质不错的插件叫Mesh Baker,大家可以考虑试下。
c.尽量少用一些渲染步骤,例如reflections,shadows,per-pixel light 等。
d.Draw call batching的合并物体,会使每个物体(合并后的物体)至少有几百个三角面。
假设合并的两个物体(手动合并)但不共享材质,不会有性能表现上的提升。多材质的物体相当于两个物体不用一个贴图。所以,为了提升CPU的性能,你应该确保这些物体使用同样的贴图。
另外,用灯光将会取消(break)引擎的DRAW CALL BATCH,至于为什么,查看以下:
Forward Rendering Path Details:
http://docs.unity3d.com/Documentation/Components/RenderTech-ForwardRendering.html
e.使用相关剔除数量直接减少Draw Call数量,下文有相关提及。
5.优化几何模型
最基本的两个优化准则:
a.不要有不必要的三角面。
b.UV贴图中的接缝和硬边越少越好。
需要注意的是,图形硬件需要处理顶点数并跟硬件报告说的并不一样。不是硬件说能渲染几个点就是几个点。模型处理应用通展示的是几何顶点数量。例如,一个由一些不同顶点构成的模型。在显卡中,一些集合顶点将会被分离(split)成两个或者更多逻辑顶点用作渲染。如果有法线、UV坐标、顶点色的话,这个顶点必须会被分离。所以在游戏中处理的实际数量显然要多很多。
6.关于光照
若不用光肯定是最快的。移动端优化可以采用用光照贴图(Lightmapping)去烘培一个静态的贴图,以代替每次的光照计算,在U3D中只需要非常短的时间则能生成。这个方法能大大提高效率,而且有着更好的表现效果(平滑过渡处理,还有附加阴影等)。
在移动设备上和低端电脑上尽量不要在场景中用真光,用光照贴图。这个方法大大节省了CPU和GPU的计算,CPU得到了更少的DRAWCALL,GPU则需要更少顶点处理和像素栅格化。
Lightmapping : http://docs.unity3d.com/Documentation/Manual/Lightmapping.html
7.对GPU的优化——图片压缩和多重纹理格式
Compressed Textures(图片压缩):
http://docs.unity3d.com/Documentation/Components/class-Texture2D.html
图片压缩将降低你的图片大小(更快地加载更小的内存跨度(footprint)),而且大大提高渲染表现。压缩贴图比起未压缩的32位RGBA贴图占用内存带宽少得多。
之前U3D会议还听说过一个优化,贴图尽量都用一个大小的格式(512 * 512 , 1024 * 1024),这样在内存之中能得到更好的排序,而不会有内存之间空隙。这个是否真假没得到过测试。
MIPMAps(多重纹理格式):
http://docs.unity3d.com/Documentation/Components/class-Texture2D.html
跟网页上的略缩图原理一样,在3D游戏中我们为游戏的贴图生成多重纹理贴图,远处显示较小的物体用小的贴图,显示比较大的物体用精细的贴图。这样能更加有效的减少传输给GPU中的数据。
8.LOD 、 Per-Layer Cull Distances 、 Occlusion Culling
LOD (Level Of Detail) 是很常用的3D游戏技术了,其功能理解起来则是相当于多重纹理贴图。在以在屏幕中显示模型大小的比例来判断使用高或低层次的模型来减少对GPU的传输数据,和减少GPU所需要的顶点计算。
摄像机分层距离剔除(Per-Layer Cull Distances):为小物体标识层次,然后根据其距离主摄像机的距离判断是否需要显示。
遮挡剔除(Occlusion Culling)其实就是当某个物体在摄像机前被另外一个物体完全挡住的情况,挡住就不发送给GPU渲染,从而直接降低DRAW CALL。不过有些时候在CPU中计算其是否被挡住则会很耗计算,反而得不偿失。
以下是这几个优化技术的相关使用和介绍:
Level Of Detail :
http://docs.unity3d.com/Documentation/Manual/LevelOfDetail.html
Per-Layer Cull Distances :
http://docs.unity3d.com/Documentation/ScriptReference/Camera-layerCullDistances.html
Occlusion Culling :
http://docs.unity3d.com/Documentation/Manual/OcclusionCulling.html
9.关于Realtime Shadows(实时阴影)
实时阴影技术非常棒,但消耗大量计算。为GPU和CPU都带来了昂贵的负担,细节的话参考下面:
http://docs.unity3d.com/Documentation/Manual/Shadows.html
10.对GPU优化:采用高效的shader
a.需要注意的是有些(built-in)Shader是有mobile版本的,这些大大提高了顶点处理的性能。当然也会有一些限制。
b.自己写的shader请注意复杂操作符计算,类似pow,exp,log,cos,sin,tan等都是很耗时的计算,最多只用一次在每个像素点的计算。不推荐你自己写normalize,dot,inversesqart操作符,内置的肯定比你写的好。
c.需要警醒的是alpha test,这个非常耗时。
d.浮点类型运算:精度越低的浮点计算越快。
在CG/HLSL中--
float :32位浮点格式,适合顶点变换运算,但比较慢。
half:16位浮点格式,适合贴图和UV坐标计算,是highp类型计算的两倍。
fixed: 10位浮点格式,适合颜色,光照,和其他。是highp格式计算的四倍。
写Shader优化的小提示:
http://docs.unity3d.com/Documentation/Components/SL-ShaderPerformance.html
11.另外的相关优化:
a.对Draw Call Batching的优化
http://docs.unity3d.com/Documentation/Manual/DrawCallBatching.html
b.对Rendering Statistics Window的说明和提示:
http://docs.unity3d.com/Documentation/Manual/RenderingStatistics.html
c.角色模型的优化建议
用单个蒙皮渲染、尽量少用材质、少用骨骼节点、移动设备上角色多边形保持在300~1500内(当然还要看具体的需求)、PC平台上1500~4000内(当然还要看具体的需求)。
http://docs.unity3d.com/Documentation/Manual/ModelingOptimizedCharacters.html
渲染顺序
U3D的渲染是有顺序的,U3D的渲染顺序是由我们控制的,控制好U3D的渲染顺序,你才能控制好DrawCall
一个DrawCall,表示U3D使用这个材质/纹理,来进行一次渲染,那么这次渲染假设有3个对象,那么当3个对象都使用这一个材质/纹理的 时候,就会产生一次DrawCall,可以理解为一次将纹理输送到屏幕上的过程,(实际上引擎大多会使用如双缓冲,缓存这类的手段来优化这个过程,但在这 里我们只需要这样子认识就可以了),假设3个对象使用不同的材质/纹理,那么无疑会产生3个DrawCall
接下来我们的3个对象使用2个材质,A和B使用材质1,C使用材质2,这时候来看,应该是有2个DrawCall,或者3个DrawCall。 应该是2个DrawCall啊,为什么会有3个DrawCall???而且是有时候2个,有时候3个。我们按照上面的DrawCall分析流程来分析一 下:
1.渲染A,使用材质1
2.渲染B,使用材质1
3.渲染C,使用材质2
在这种情况下是2个DrawCall,在下面这种情况下,则是3个DrawCall
1.渲染A,使用材质1
2.渲染C,使用材质2
3.渲染B,使用材质1
因为我们没有控制好渲染顺序(或者说没有去特意控制),所以导致了额外的DrawCall,因为A和B不是一次性渲染完的,而是被C打断了,所以导致材质1被分为两次渲染
那么是什么在控制这个渲染顺序呢?首先在多个相机的情况下,U3D会根据相机的深度顺序进行渲染,在每个相机中,它会根据你距离相机的距离,由远到近进行渲染,在UI相机中,还会根据你UI对象的深度进行渲染
那么我们要做的就是,对要渲染的对象进行一次规划,正确地排列好它们,规则是,按照Z轴或者深度,对空间进行划分,然后确定好每个对象的Z轴和深度,让使用同一个材质的东西,尽量保持在这个空间内,不要让其他材质的对象进入这个空间,否则就会打断这个空间的渲染顺序
在这个基础上,更细的规则有:
场景中的东西,我们使用Z轴来进行空间的划分,例如背景层,特效层1,人物层,特效层2
NGUI中的东西,我们统一使用Depth来进行空间的划分
人物模型,当人物模型只是用一个材质,DrawCall只有1,但是用了2个以上的材质,DrawCall就会暴增(或许对材质的RenderQueue 进行规划也可以使DrawCall只有2个,但这个要拆分好才行),3D人物处于复杂3D场景中的时候,我们的空间规则难免被破坏,这只能在设计的时候尽 量去避免这种情况了
使用了多个材质的特效,在动画的过程中,往往会引起DrawCall的波动,在视觉效果可以接受的范围内,可以将特效也进行空间划分,假设这个特效是2D显示,那么可以使用Z轴来划分空间