2019-08-06 16:48:44 qq_39588003 阅读数 86

Time.deltaTime表示每一帧的时间

Unity中的 Update() 方法为系统所调用,每一帧调用一次。(一般每帧的时间都不相同)

假设一秒有20帧,那么一秒钟就会调用该方法20次

 

假如想让物体沿Z轴以 2米/秒 的速度移动,则:

void Update()

{

    transform.Translate(0, 0, Time.deltaTime * 2);

}

 

假设每秒有20帧,每帧用t1、t2、t3······t20表示

那么 t1 + t2 + t3 + ······t20 = 1秒,所以:

(t1 + t2 + t3 + ······t20)* 2 = 2米

即:transform.Translate(0, 0, Time.deltaTime * 2) 表示以每秒两米的速度移动

2016-08-25 23:29:45 PirateCode 阅读数 1316

1.什么是DrawCall?
CPU调用一次图形学接口(OpenGL、DX)称为一个DrawCall。
2.图像在计算机显示的过程?
CPU——->图形学接口———>GPU
3.为什么降低DrawCall可以提升性能?
性能提升表现在流畅度上,要保证流畅度,就需要达到一定的FPS(帧率),即一秒钟需要播放一定量的画面,当每帧DrawCall较高时,就表示CPU调用图形学的接口次数高,花费的时间就会越多,每帧的消耗时间也就越多,每一秒播放的画面就越少,帧率就变低,就是出现卡顿。
4.Unity对DrawCall的优化方案?
①Dynamic Batching(动态批处理)
a).Mesh顶点数(Verts)少于300;
b).使用同一材质(Material),且使用Shader为单Pass,若使用一组材质,每个都必须是单Pass;
c).使用网格渲染器(Mesh Renderer);
d).模型没有参与实时烘焙;
e).与网格(Mesh)无关
d).与纹理(Texture)无关
e).与模型缩放无关(5.0后)
②Static Batching(静态批处理)
a).物体必须为静态(勾选静态选项)
b).同一材质,Pass通道可单也可多,但多Pass通道是从2个以上物体以后才开始优化的。
c).与纹理无关

2013-12-27 16:34:14 luyuncsd123 阅读数 6963

Unity3d优化

检测方式:

一,Unity3D 渲染统计窗口

Game视窗的Stats去查看渲染统计的信息:

1、FPS

fps其实就是 frames per second,也就是每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

 

2、Draw calls

batching之后渲染mesh的数量,和当前渲染到的网格的材质球数量有关。

 

3、Saved by batching 

渲染的批处理数量,这是引擎将多个对象的绘制进行合并从而减少GPU的开销;

很多GUI插件的一个好处就是合并多个对象的渲染,从而降低DrawCalls ,保证游戏帧数。

 

4、Tris 当前绘制的三角面数

 

5、Verts 当前绘制的顶点数

 

6、Used Textures 当前帧用于渲染的图片占用内存大小

 

7、Render Textures 渲染的图片占用内存大小,也就是当然渲染的物体的材质上的纹理总内存占用

 

8、VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存

 

9、VBO Total 渲染过程中上载到图形卡的网格的数量,这里注意一点就是缩放的物体可能需要额外的开销。

 

10、Visible Skinned Meshes 蒙皮网格的渲染数量

 

11、Animations 播放动画的数量

注意事项:

1,运行时尽量减少 Tris 和 Draw Calls

预览的时候,可点开 Stats,查看图形渲染的开销情况。特别注意 Tris 和 Draw Calls 这两个参数。

一般来说,要做到:

Tris 保持在 7.5k 以下,有待考证。

Draw Calls 保持在 20 以下,有待考证。

2,FPS,每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

3,Render Textures 渲染的图片占用内存大小。

4,VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存。

 

二,代码优化

1. 尽量避免每帧处理

比如:

function Update() { DoSomeThing(); }

可改为每5帧处理一次:

function Update() { if(Time.frameCount % 5 == 0) { DoSomeThing(); } }

2. 定时重复处理用 InvokeRepeating 函数实现

比如,启动0.5秒后每隔1秒执行一次 DoSomeThing 函数:

 

function Start() { InvokeRepeating("DoSomeThing", 0.5, 1.0); }

 

3. 优化 Update, FixedUpdate, LateUpdate 等每帧处理的函数

函数里面的变量尽量在头部声明。

比如:

function Update() { var pos: Vector3 = transform.position; }

可改为

private var pos: Vector3; function Update(){ pos = transform.position; }

 

4. 主动回收垃圾

给某个 GameObject 绑上以下的代码:

function Update() { if(Time.frameCount % 50 == 0) { System.GC.Collect(); } }

 

5. 优化数学计算

比如,如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等

 

6,减少固定增量时间

将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。

7,减少GetComponent的调用

使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为"缓存"的引用)。例如,如果您使用如下的代码:

function Update () {

transform.Translate(0, 1, 0);

 

}

通过下面的更改您将获得更好的性能:

 

var myTransform : Transform;

function Awake () {

myTransform = transform;

}

function Update () {

myTransform.Translate(0, 1, 0);

}

 

8,避免分配内存

您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于"传值"操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。

 

9,使用iOS脚本调用优化功能

UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。从Mono的脚本调用 C/C++函数也存在着一定的性能开销。您可以使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。此设置的选项有:

Slow and Safe – Mono内部默认的处理异常的调用

 

Fast and Exceptions Unsupported –一个快速执行的Mono内部调用。不过,它并不支持异常,因此应谨慎使用。它对于不需要显式地处理异常(也不需要对异常进行处理)的应用程序来说,是一个理想的候选项。

 

10,

优化垃圾回收

 

如上文所述,您应该尽量避免分配操作。但是,考虑到它们是不能完全杜绝的,所以我们提供两种方法来让您尽量减少它们在游戏运行时的使用:

如果堆比较小,则进行快速而频繁的垃圾回收

这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用。如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),但它们可以快速处理并且对游戏的影响很小:

if (Time.frameCount % 30 == 0)

{

System.GC.Collect();

}

 

但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间

如果堆比较大,则进行缓慢且不频繁的垃圾回收

这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。但是,Mono运行时会尽可能地避免堆的自动扩大。因此,您需要通过在启动过程中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的"无用"对象):

 

function Start() {

 

var tmp = new System.Object[1024];

 

// make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks

 

for (var i : int = 0; i < 1024; i++)

 

tmp[i] = new byte[1024];

 

// release reference

 

tmp = null;

 

}

 

游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被完全占满。所以,当这种游戏暂停发生时,您可以显式请求一次垃圾回收:

 

System.GC.Collect();

 

另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。

 

三,模型

1,压缩 Mesh

导入 3D 模型之后,在不影响显示效果的前提下,最好打开 Mesh Compression。

Off, Low, Medium, High 这几个选项,可酌情选取。

2,避免大量使用 Unity 自带的 Sphere 等内建 Mesh

Unity 内建的 Mesh,多边形的数量比较大,如果物体不要求特别圆滑,可导入其他的简单3D模型代替。

 

2015-06-02 23:19:35 lfh719852029 阅读数 2663

检测方式:

一,Unity3D 渲染统计窗口

Game视窗的Stats去查看渲染统计的信息:

1、FPS

fps其实就是 frames per second,也就是每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

 

2、Draw calls

batching之后渲染mesh的数量,和当前渲染到的网格的材质球数量有关。

 

3、Saved by batching 

渲染的批处理数量,这是引擎将多个对象的绘制进行合并从而减少GPU的开销;

很多GUI插件的一个好处就是合并多个对象的渲染,从而降低DrawCalls ,保证游戏帧数。

 

4、Tris 当前绘制的三角面数

 

5、Verts 当前绘制的顶点数

 

6、Used Textures 当前帧用于渲染的图片占用内存大小

 

7、Render Textures 渲染的图片占用内存大小,也就是当然渲染的物体的材质上的纹理总内存占用

 

8、VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存

 

9、VBO Total 渲染过程中上载到图形卡的网格的数量,这里注意一点就是缩放的物体可能需要额外的开销。

 

10、Visible Skinned Meshes 蒙皮网格的渲染数量

 

11、Animations 播放动画的数量

注意事项:

1,运行时尽量减少 Tris 和 Draw Calls

预览的时候,可点开 Stats,查看图形渲染的开销情况。特别注意 Tris 和 Draw Calls 这两个参数。

一般来说,要做到:

Tris 保持在 7.5k 以下,有待考证。

Draw Calls 保持在 20 以下,有待考证。

2,FPS,每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

3,Render Textures 渲染的图片占用内存大小。

4,VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存。

 

二,代码优化

1. 尽量避免每帧处理

比如:

function Update() { DoSomeThing(); }

可改为每5帧处理一次:

function Update() { if(Time.frameCount % 5 == 0) { DoSomeThing(); } }

2. 定时重复处理用 InvokeRepeating 函数实现

比如,启动0.5秒后每隔1秒执行一次 DoSomeThing 函数:

 

function Start() { InvokeRepeating("DoSomeThing", 0.5, 1.0); }

 

3. 优化 Update, FixedUpdate, LateUpdate 等每帧处理的函数

函数里面的变量尽量在头部声明。

比如:

function Update() { var pos: Vector3 = transform.position; }

可改为

private var pos: Vector3; function Update(){ pos = transform.position; }

 

4. 主动回收垃圾

给某个 GameObject 绑上以下的代码:

function Update() { if(Time.frameCount % 50 == 0) { System.GC.Collect(); } }

 

5. 优化数学计算

比如,如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等

 

6,减少固定增量时间

将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。

7,减少GetComponent的调用

使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为"缓存"的引用)。例如,如果您使用如下的代码:

function Update () {

transform.Translate(0, 1, 0);

 

}

通过下面的更改您将获得更好的性能:

 

var myTransform : Transform;

function Awake () {

myTransform = transform;

}

function Update () {

myTransform.Translate(0, 1, 0);

}

 

8,避免分配内存

您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于"传值"操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。

 

9,使用iOS脚本调用优化功能

UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。从Mono的脚本调用 C/C++函数也存在着一定的性能开销。您可以使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。此设置的选项有:

Slow and Safe – Mono内部默认的处理异常的调用

 

Fast and Exceptions Unsupported –一个快速执行的Mono内部调用。不过,它并不支持异常,因此应谨慎使用。它对于不需要显式地处理异常(也不需要对异常进行处理)的应用程序来说,是一个理想的候选项。

 

10,

优化垃圾回收

 

如上文所述,您应该尽量避免分配操作。但是,考虑到它们是不能完全杜绝的,所以我们提供两种方法来让您尽量减少它们在游戏运行时的使用:

如果堆比较小,则进行快速而频繁的垃圾回收

这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用。如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),但它们可以快速处理并且对游戏的影响很小:

if (Time.frameCount % 30 == 0)

{

System.GC.Collect();

}

 

但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间

如果堆比较大,则进行缓慢且不频繁的垃圾回收

这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。但是,Mono运行时会尽可能地避免堆的自动扩大。因此,您需要通过在启动过程中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的"无用"对象):

 

function Start() {

 

var tmp = new System.Object[1024];

 

// make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks

 

for (var i : int = 0; i < 1024; i++)

 

tmp[i] = new byte[1024];

 

// release reference

 

tmp = null;

 

}

 

游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被完全占满。所以,当这种游戏暂停发生时,您可以显式请求一次垃圾回收:

 

System.GC.Collect();

 

另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。

 

三,模型

1,压缩 Mesh

导入 3D 模型之后,在不影响显示效果的前提下,最好打开 Mesh Compression。

Off, Low, Medium, High 这几个选项,可酌情选取。

2,避免大量使用 Unity 自带的 Sphere 等内建 Mesh

Unity 内建的 Mesh,多边形的数量比较大,如果物体不要求特别圆滑,可导入其他的简单3D模型代替。


原文:点击打开链接

-------------------------------------------------------

深入浅出聊优化:从Draw Calls到GC

前言:

刚开始写这篇文章的时候选了一个很土的题目。。。《Unity3D优化全解析》。因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的余地就少了很多。但又觉得这块是不得不提的一个地方,平时见到很多人对此处也给予了忽略了事,需要时才去网上扒一些只言片语的资料。也恰逢年前,寻思着周末认真写点东西遇到节假日没准也没什么人读,所以索性就写了这篇临时的文章。题目很土,因为用了指向性很明确的“Unity3D”,让人少了遐(瞎)想的空间,同时用了“高大全”这样的构词法,也让匹夫有成为众矢之的的可能。。。所以最后还是改成了现在各位看到的题目。话不多说,下面就开始正文~正所谓“草蛇灰线,伏脉千里”。那咱们首先~~~~~~

看看优化需要从哪里着手?

匹夫印象里遇到的童靴,提Unity3D项目优化则必提DrawCall,这自然没错,但也有很不好影响。因为这会给人一个错误的认识:所谓的优化就是把DrawCall弄的比较低就对了。

对优化有这种第一印象的人不在少数,drawcall的确是一个很重要的指标,但绝非全部。为了让各位和匹夫能达成尽可能多的共识,匹夫首先介绍一下本文可能会涉及到的几个概念,之后会提出优化所涉及的三大方面:

  • drawcall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西。所以,是谁去调用这些接口呢?CPU。
  • fragment是啥?经常有人说vf啥的,vertex我们都知道是顶点,那fragment是啥呢?说它之前需要先说一下像素,像素各位应该都知道吧?像素是构成数码影像的基本单元呀。那fragment呢?是有可能成为像素的东西。啥叫有可能?就是最终会不会被画出来不一定,是潜在的像素。这会涉及到谁呢?GPU。
  • batching是啥?都知道批处理是干嘛的吧?没错,将批处理之前需要很多次调用(drawcall)的物体合并,之后只需要调用一次底层图形程序的接口就行。听上去这简直就是优化的终极方案啊!但是,理想是美好的,世界是残酷的,一些不足之后我们再细聊。
  • 内存的分配:记住,除了Unity3D自己的内存损耗。我们可是还带着Mono呢啊,还有托管的那一套东西呢。更别说你一激动,又引入了自己的几个dll。这些都是内存开销上需要考虑到的。

好啦,文中的几个概念提前讲清楚了,其实各位也能看的出来匹夫接下来要说的匹夫关注的优化时需要注意的方面:

所以,这篇文章也会按照CPU---->GPU---->内存的顺序进行。

CPU的方面的优化:

上文中说了,drawcall影响的是CPU的效率,而且也是最知名的一个优化点。但是除了drawcall之外,还有哪些因素也会影响到CPU的效率呢?让我们一一列出暂时能想得到的:

  • DrawCalls
  • 物理组件(Physics)
  • GC(什么?GC不是处理内存问题的嘛?匹夫你不要骗我啊!不过,匹夫也要提醒一句,GC是用来处理内存的,但是是谁使用GC去处理内存的呢?)
  • 当然,还有代码质量

DrawCalls:

前面说过了,DrawCall是CPU调用底层图形接口。比如有上千个物体,每一个的渲染都需要去调用一次底层接口,而每一次的调用CPU都需要做很多工作,那么CPU必然不堪重负。但是对于GPU来说,图形处理的工作量是一样的。所以对DrawCall的优化,主要就是为了尽量解放CPU在调用图形接口上的开销。所以针对drawcall我们主要的思路就是每个物体尽量减少渲染次数,多个物体最好一起渲染。所以,按照这个思路就有了以下几个方案:

  1. 使用Draw Call Batching,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。具体下面会介绍。
  2. 通过把纹理打包成图集来尽量减少材质的使用。
  3. 尽量少的使用反光啦,阴影啦之类的,因为那会使物体多次渲染。

Draw Call Batching

首先我们要先理解为何2个没有使用相同材质的物体即使使用批处理,也无法实现Draw Call数量的下降和性能上的提升。

因为被“批处理”的2个物体的网格模型需要使用相同材质的目的,在于其纹理是相同的,这样才可以实现同时渲染的目的。因而保证材质相同,是为了保证被渲染的纹理相同。

因此,为了将2个纹理不同的材质合二为一,我们就需要进行上面列出的第二步,将纹理打包成图集。具体到合二为一这种情况,就是将2个纹理合成一个纹理。这样我们就可以只用一个材质来代替之前的2个材质了。

而Draw Call Batching本身,也还会细分为2种。

Static Batching 静态批处理

看名字,猜使用的情景。

静态?那就是不动的咯。还有呢?额,听上去状态也不会改变,没有“生命”,比如山山石石,楼房校舍啥的。那和什么比较类似呢?嗯,聪明的各位一定觉得和场景的属性很像吧!所以我们的场景似乎就可以采用这种方式来减少draw call了。

那么写个定义:只要这些物体不移动,并且拥有相同的材质,静态批处理就允许引擎对任意大小的几何物体进行批处理操作来降低描绘调用。

那要如何使用静态批来减少Draw Call呢?你只需要明确指出哪些物体是静止的,并且在游戏中永远不会移动、旋转和缩放。想完成这一步,你只需要在检测器(Inspector)中将Static复选框打勾即可,如下图所示:

至于效果如何呢?

举个例子:新建4个物体,分别是Cube,Sphere, Capsule, Cylinder,它们有不同的网格模型,但是也有相同的材质(Default-Diffuse)。

首先,我们不指定它们是static的。Draw Call的次数是4次,如图:

我们现在将它们4个物体都设为static,在来运行一下:

如图,Draw Call的次数变成了1,而Saved by batching的次数变成了3。

静态批处理的好处很多,其中之一就是与下面要说的动态批处理相比,约束要少很多。所以一般推荐的是draw call的静态批处理来减少draw call的次数。那么接下来,我们就继续聊聊draw call的动态批处理。

Dynamic Batching 动态批处理

有阴就有阳,有静就有动,所以聊完了静态批处理,肯定跟着就要说说动态批处理了。首先要明确一点,Unity3D的draw call动态批处理机制是引擎自动进行的,无需像静态批处理那样手动设置static。我们举一个动态实例化prefab的例子,如果动态物体共享相同的材质,则引擎会自动对draw call优化,也就是使用批处理。首先,我们将一个cube做成prefab,然后再实例化500次,看看draw call的数量。

for(int i = 0; i < 500; i++)
{
    GameObject cube;
    cube = GameObject.Instantiate(prefab) as GameObject;
}

draw call的数量:

可以看到draw call的数量为1,而 saved by batching的数量是499。而这个过程中,我们除了实例化创建物体之外什么都没做。不错,unity3d引擎为我们自动处理了这种情况。

但是有很多童靴也遇到这种情况,就是我也是从prefab实例化创建的物体,为何我的draw call依然很高呢?这就是匹夫上文说的,draw call的动态批处理存在着很多约束。下面匹夫就演示一下,针对cube这样一个简单的物体的创建,如果稍有不慎就会造成draw call飞涨的情况吧。

我们同样是创建500个物体,不同的是其中的100个物体,每个物体的大小都不同,也就是Scale不同。

复制代码
for(int i = 0; i < 500; i++)
{
    GameObject cube;
    cube = GameObject.Instantiate(prefab) as GameObject;
    if(i / 100 == 0)
    {
        cube.transform.localScale = new Vector3(2 + i, 2 + i, 2 + i);
    }
}
复制代码

draw call的数量:

我们看到draw call的数量上升到了101次,而saved by batching的数量也下降到了399。各位看官可以看到,仅仅是一个简单的cube的创建,如果scale不同,竟然也不会去做批处理优化。这仅仅是动态批处理机制的一种约束,那我们总结一下动态批处理的约束,各位也许也能从中找到为何动态批处理在自己的项目中不起作用的原因:

  1. 批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体。
  2. 如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体。
  3. 不要使用缩放。分别拥有缩放大小(1,1,1) 和(2,2,2)的两个物体将不会进行批处理。
  4. 统一缩放的物体不会与非统一缩放的物体进行批处理。
  5. 使用缩放尺度(1,1,1) 和 (1,2,1)的两个物体将不会进行批处理,但是使用缩放尺度(1,2,1) 和(1,3,1)的两个物体将可以进行批处理。
  6. 使用不同材质的实例化物体(instance)将会导致批处理失败。
  7. 拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。
  8. 多通道的shader会妨碍批处理操作。比如,几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它们有效地开辟多个通道。
  9. 预设体的实例会自动地使用相同的网格模型和材质

所以,尽量使用静态的批处理。

物理组件

曾几何时,匹夫在做一个策略类游戏的时候需要在单元格上排兵布阵,而要侦测到哪个兵站在哪个格子匹夫选择使用了射线,由于士兵单位很多,而且为了精确每一帧都会执行检测,那时候CPU的负担叫一个惨不忍睹。后来匹夫果断放弃了这种做法,并且对物理组件产生了心理的阴影。

这里匹夫只提2点匹夫感觉比较重要的优化措施:

1.设置一个合适的Fixed Timestep。设置的位置如图:

 

那何谓“合适”呢?首先我们要搞明白Fixed Timestep和物理组件的关系。物理组件,或者说游戏中模拟各种物理效果的组件,最重要的是什么呢?计算啊。对,需要通过计算才能将真实的物理效果展现在虚拟的游戏中。那么Fixed Timestep这货就是和物理计算有关的啦。所以,若计算的频率太高,自然会影响到CPU的开销。同时,若计算频率达不到游戏设计时的要求,有会影响到功能的实现,所以如何抉择需要各位具体分析,选择一个合适的值。

2.就是不要使用网格碰撞器(mesh collider):为啥?因为实在是太复杂了。网格碰撞器利用一个网格资源并在其上构建碰撞器。对于复杂网状模型上的碰撞检测,它要比应用原型碰撞器精确的多。标记为凸起的(Convex )的网格碰撞器才能够和其他网格碰撞器发生碰撞。各位上网搜一下mesh collider的图片,自然就会明白了。我们的手机游戏自然无需这种性价比不高的东西。

当然,从性能优化的角度考虑,物理组件能少用还是少用为好。

处理内存,却让CPU受伤的GC

在CPU的部分聊GC,感觉是不是怪怪的?其实小匹夫不这么觉得,虽然GC是用来处理内存的,但的确增加的是CPU的开销。因此它的确能达到释放内存的效果,但代价更加沉重,会加重CPU的负担,因此对于GC的优化目标就是尽量少的触发GC。

首先我们要明确所谓的GC是Mono运行时的机制,而非Unity3D游戏引擎的机制,所以GC也主要是针对Mono的对象来说的,而它管理的也是Mono的托管堆。 搞清楚这一点,你也就明白了GC不是用来处理引擎的assets(纹理啦,音效啦等等)的内存释放的,因为U3D引擎也有自己的内存堆而不是和Mono一起使用所谓的托管堆。

其次我们要搞清楚什么东西会被分配到托管堆上?不错咯,就是引用类型咯。比如类的实例,字符串,数组等等。而作为int,float,包括结构体struct其实都是值类型,它们会被分配在堆栈上而非堆上。所以我们关注的对象无外乎就是类实例,字符串,数组这些了。

那么GC什么时候会触发呢?两种情况:

  1. 首先当然是我们的堆的内存不足时,会自动调用GC。
  2. 其次呢,作为编程人员,我们自己也可以手动的调用GC。

所以为了达到优化CPU的目的,我们就不能频繁的触发GC。而上文也说了GC处理的是托管堆,而不是Unity3D引擎的那些资源,所以GC的优化说白了也就是代码的优化。那么匹夫觉得有以下几点是需要注意的:

  1. 字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。
  2. 尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。
  3. 不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。
  4. 使用“池”,以实现空间的重复利用。
  5. 最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。而且我很讨厌LINQ的一点就是它有可能在某些情况下无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。

代码?脚本?

聊到代码这个话题,也许有人会觉得匹夫多此一举。因为代码质量因人而异,很难像上面提到的几点,有一个明确的评判标准。也是,公写公有理,婆写婆有理。但是匹夫这里要提到的所谓代码质量是基于一个前提的:Unity3D是用C++写的,而我们的代码是用C#作为脚本来写的,那么问题就来了~脚本和底层的交互开销是否需要考虑呢?也就是说,我们用Unity3D写游戏的“游戏脚本语言”,也就是C#是由mono运行时托管的。而功能是底层引擎的C++实现的,“游戏脚本”中的功能实现都离不开对底层代码的调用。那么这部分的开销,我们应该如何优化呢?

  1. 以物体的Transform组件为例,我们应该只访问一次,之后就将它的引用保留,而非每次使用都去访问。这里有人做过一个小实验,就是对比通过方法GetComponent<Transform>()获取Transform组件, 通过MonoBehavor的transform属性去取,以及保留引用之后再去访问所需要的时间:
    • GetComponent = 619ms
    • Monobehaviour = 60ms
    • CachedMB = 8ms
    • Manual Cache = 3ms

   2.如上所述,最好不要频繁使用GetComponent,尤其是在循环中。

   3.善于使用OnBecameVisible()和OnBecameVisible(),来控制物体的update()函数的执行以减少开销。

   4.使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0);

   5.对于方法的参数的优化:善于使用ref关键字。值类型的参数,是通过将实参的值复制到形参,来实现按值传递到方法,也就是我们通常说的按值传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。

好啦,CPU的部分匹夫觉得到此就介绍的差不多了。下面就简单聊聊其实匹夫并不是十分熟悉的部分,GPU的优化。

GPU的优化

GPU与CPU不同,所以侧重点自然也不一样。GPU的瓶颈主要存在在如下的方面:

  1. 填充率,可以简单的理解为图形处理单元每秒渲染的像素数量。
  2. 像素的复杂度,比如动态阴影,光照,复杂的shader等等
  3. 几何体的复杂度(顶点数量)
  4. 当然还有GPU的显存带宽

那么针对以上4点,其实仔细分析我们就可以发现,影响的GPU性能的无非就是2大方面,一方面是顶点数量过多,像素计算过于复杂。另一方面就是GPU的显存带宽。那么针锋相对的两方面举措也就十分明显了。

  1. 少顶点数量,简化计算复杂度。
  2. 缩图片,以适应显存带宽。

减少绘制的数目

那么第一个方面的优化也就是减少顶点数量,简化复杂度,具体的举措就总结如下了:

  • 保持材质的数目尽可能少。这使得Unity更容易进行批处理。
  • 使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
  • 如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
  • 使用光照纹理(lightmap)而非实时灯光。
  • 使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。
  • 遮挡剔除(Occlusion culling)
  • 使用mobile版的shader。因为简单。

优化显存带宽

第二个方向呢?压缩图片,减小显存带宽的压力。

  • OpenGL ES 2.0使用ETC1格式压缩等等,在打包设置那里都有。
  • 使用mipmap。

MipMap

这里匹夫要着重介绍一下MipMap到底是啥。因为有人说过MipMap会占用内存呀,但为何又会优化显存带宽呢?那就不得不从MipMap是什么开始聊起。一张图其实就能解决这个疑问。

上面是一个mipmap 如何储存的例子,左边的主图伴有一系列逐层缩小的备份小图

是不是很一目了然呢?Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。

内存的优化

既然要聊Unity3D运行时候的内存优化,那我们自然首先要知道Unity3D游戏引擎是如何分配内存的。大概可以分成三大部分:

  1. Unity3D内部的内存
  2. Mono的托管内存
  3. 若干我们自己引入的DLL或者第三方DLL所需要的内存。

第3类不是我们关注的重点,所以接下来我们会分别来看一下Unity3D内部内存Mono托管内存,最后还将分析一个官网上Assetbundle的案例来说明内存的管理。

Unity3D内部内存

Unity3D的内部内存都会存放一些什么呢?各位想一想,除了用代码来驱动逻辑,一个游戏还需要什么呢?对,各种资源。所以简单总结一下Unity3D内部内存存放的东西吧:

  • 资源:纹理、网格、音频等等
  • GameObject和各种组件。
  • 引擎内部逻辑需要的内存:渲染器,物理系统,粒子系统等等

Mono托管内存

因为我们的游戏脚本是用C#写的,同时还要跨平台,所以带着一个Mono的托管环境显然必须的。那么Mono的托管内存自然就不得不放到内存的优化范畴中进行考虑。那么我们所说的Mono托管内存中存放的东西和Unity3D内部内存中存放的东西究竟有何不同呢?其实Mono的内存分配就是很传统的运行时内存的分配了:

  • 值类型:int型啦,float型啦,结构体struct啦,bool啦之类的。它们都存放在堆栈上(注意额,不是堆所以不涉及GC)。
  • 引用类型:其实可以狭义的理解为各种类的实例。比如游戏脚本中对游戏引擎各种控件的封装。其实很好理解,C#中肯定要有对应的类去对应游戏引擎中的控件。那么这部分就是C#中的封装。由于是在堆上分配,所以会涉及到GC。

而Mono托管堆中的那些封装的对象,除了在在Mono托管堆上分配封装类实例化之后所需要的内存之外,还会牵扯到其背后对应的游戏引擎内部控件在Unity3D内部内存上的分配。

举一个例子:

一个在.cs脚本中声明的WWW类型的对象www,Mono会在Mono托管堆上为www分配它所需要的内存。同时,这个实例对象背后的所代表的引擎资源所需要的内存也需要被分配。

一个WWW实例背后的资源:

  • 压缩的文件
  • 解压缩所需的缓存
  • 解压缩之后的文件

如图:

那么下面就举一个AssetBundle的例子:

Assetbundle的内存处理

以下载Assetbundle为例子,聊一下内存的分配。匹夫从官网的手册上找到了一个使用Assetbundle的情景如下:

复制代码
IEnumerator DownloadAndCache (){
        // Wait for the Caching system to be ready
        while (!Caching.ready)
            yield return null;

        // Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
        using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
            yield return www; //WWW是第1部分
            if (www.error != null)
                throw new Exception("WWW download had an error:" + www.error);
            AssetBundle bundle = www.assetBundle;//AssetBundle是第2部分
            if (AssetName == "")
                Instantiate(bundle.mainAsset);//实例化是第3部分
            else
                Instantiate(bundle.Load(AssetName));
                    // Unload the AssetBundles compressed contents to conserve memory
                    bundle.Unload(false);

        } // memory is freed from the web stream (www.Dispose() gets called implicitly)
    }
}
复制代码

内存分配的三个部分匹夫已经在代码中标识了出来:

  1. Web Stream:包括了压缩的文件,解压所需的缓存,以及解压后的文件。
  2. AssetBundle:Web Stream中的文件的映射,或者说引用。
  3. 实例化之后的对象就是引擎的各种资源文件了,会在内存中创建出来。

那就分别解析一下:

WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)
  1. 将压缩的文件读入内存中
  2. 创建解压所需的缓存
  3. 将文件解压,解压后的文件进入内存
  4. 关闭掉为解压创建的缓存
AssetBundle bundle = www.assetBundle;
  1. AssetBundle此时相当于一个桥梁,从Web Stream解压后的文件到最后实例化创建的对象之间的桥梁。
  2. 所以AssetBundle实质上是Web Stream解压后的文件中各个对象的映射。而非真实的对象。
  3. 实际的资源还存在Web Stream中,所以此时要保留Web Stream。
Instantiate(bundle.mainAsset);
  1. 通过AssetBundle获取资源,实例化对象

最后各位可能看到了官网中的这个例子使用了:

using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
}

这种using的用法。这种用法其实就是为了在使用完Web Stream之后,将内存释放掉的。因为WWW也继承了idispose的接口,所以可以使用using的这种用法。其实相当于最后执行了:

//删除Web Stream
www.Dispose();

OK,Web Stream被删除掉了。那还有谁呢?对Assetbundle。那么使用

//删除AssetBundle
bundle.Unload(false);

原文:点击打开链接

--------------------------------------------------------

Unity3D如何通过CombineChildren和MeshCombineUtility优化场景


Unity3D如何通过CombineChildren和MeshCombineUtility优化场景?首先解释下联结的原理和意思:文档里说,显卡对于一个含100个面片的物体的和含1500个面片的物体的渲染消耗几乎是等价的。所以如果你有N个同一材质的东西,那么把他们联成同一个物体再统一用一个material那么对于显卡的渲染消耗就要降低N倍。

方法一:

1是直接在max等工具里联结好,贴上同一材质再导进来,这方法固然好却不灵活,而且通常不实用,因为项目里大量同一材质的东西都是Unity系统的树花等。

2、就是在Unity里再联结,经常看Island Demo项目的人应该很早就注意到里面的石头这些都是连在一起的,原因就在这里,他提供了现成就脚本实现联结。

先到Island Demo的Assets/Script下找到CombineChildren.cs和MeshCombineUtility.cs两个脚本复制到自己的项目文件(我们要用的只是前者,但他会调用后者,没有后者unity会报错,所以把后者扔到项目里不管就好)

然后把你项目里那些用同一Materials的东西扔到一个空物体里面去,再把CombineChildren.cs贴到那个空物体上,搞定!

Unity3D中此脚本的缺陷和使用注意,支持单一material,所以如果在MeshRenderer里用多个materials或里面的子物体含有不同的material就不行了,它会在父物体里生成多个叫Combined Mesh的东西,却无法把他们赋给父物体。所以,如果发现运行游戏后父物体还是没带MeshRenderer,请仔细检查你子物体是否带了不同的 material!

以上是官方文档里提主要改善方法,其他一些优化简要说下吧,

在人物模型方面,建议:

1、模型必须用一个Mesh Renderer(2个的渲染时间就翻倍)

2、1个Mesh Renderer尽量少用多个materials,一般2-3个就够了

3、每个模型使用30个左右的骨骼。

其他方面比如尽量不用像素光(Pixels Lights)啊、软阴影啊这些基本都可以通过调参数来解决。


原文:点击打开链接

--------------------------------------------------------

接下来是自己的一些项目里遇到的问题(纯属个人见解,如有错误,请指出谢谢)

上面两篇博客是我自己觉得比较有用的有关于资源优化,以及一些unity基础操作的科普,同时转载是觉得更好找到资源。

最近在做的项目也快到尾声了,但是随着而来的是资源优化的问题,作为一个刚入门不就的unity新人,有关资源优化的知识还是很欠缺的。


遇到的问题:

从美工那边导过来的模型,在操作以及实际运行的时候都很卡,然后不知从何下手。


解决过程:

这两天都在查资料以及向大神们询问。

1.首先在第一篇博文了解到了有关stats的知识,是最基础的。最重要的几个属性就是Draw Calls和Tris。

2.根据这两点询问大神,得到答复是:

   在移动平台(项目是移动平台上的),tris的限制在30K以下

   Draw Calls限制在150以下如果在UI界面的话,高一些也可以,但是不要超过200,战斗界面的话要低一些,最好120左右。都是实验测试出        来的。

   与第一篇博文大相径庭。我实际操作以及感觉后者为准。

3.根据此答复来修改项目,通过第二篇博文了解到,可以通过静态批处理来解决(因为项目里面涉及到的动态批处理比较少)。然后将整个场景里面的    静态模型全部设置为static,暂时将所有模型的材质都改为一个,发现其实并没有什么用,Draw Calls没有改变(网上找资料并没有什么有关            的,但是发现帖子里说“模型的verts超过了300所以无法合并draw calls,把模型拆成几个小模型后解决”,但是这是动态批处理,此处无下              文)。

4.后来发现了第3篇博文,按照其方法,将同一模型或预制体生成的go放在一个空物体下,将CombineChildren.cs挂载上去就行。

   成功将Draw Calls数降低到了标准值以下。


这篇博文主要写给自己,个人记性不好,收集的文章和资源都特别乱,这样方便查找。若有什么错误或者遗漏或者见解请大神们多多指教。



2015-05-19 13:56:49 yangyisen0713 阅读数 1019

检测方式:

  一,unity3d 渲染统计窗口

  Game视窗的Stats去查看渲染统计的信息:

  1、FPS

  fps其实就是 frames per second,也就是每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

  2、Draw calls

  batching之后渲染mesh的数量,和当前渲染到的网格的材质球数量有关。

  3、Saved by batching

  渲染的批处理数量,这是引擎将多个对象的绘制进行合并从而减少GPU的开销;

  很多GUI插件的一个好处就是合并多个对象的渲染,从而降低DrawCalls ,保证游戏帧数。

  4、Tris 当前绘制的三角面数

  5、Verts 当前绘制的顶点数

  6、Used Textures 当前帧用于渲染的图片占用内存大小

  7、Render Textures 渲染的图片占用内存大小,也就是当然渲染的物体的材质上的纹理总内存占用

  8、VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存

  9、VBO Total 渲染过程中上载到图形卡的网格的数量,这里注意一点就是缩放的物体可能需要额外的开销。

  10、Visible Skinned Meshes 蒙皮网格的渲染数量

  11、Animations 播放动画的数量

  注意事项:

  1,运行时尽量减少 Tris 和 Draw Calls

  预览的时候,可点开 Stats,查看图形渲染的开销情况。特别注意 Tris 和 Draw Calls 这两个参数。

  一般来说,要做到:

  Tris 保持在 7.5k 以下,有待考证。

  Draw Calls 保持在 20 以下,有待考证。

  2,FPS,每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

  3,Render Textures 渲染的图片占用内存大小。

  4,VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存。

  二,代码优化

  1. 尽量避免每帧处理

  比如:

  function Update() { DoSomeThing(); }

  可改为每5帧处理一次:

  function Update() { if(Time.frameCount % 5 == 0) { DoSomeThing(); } }

  2. 定时重复处理用 InvokeRepeating 函数实现

  比如,启动0.5秒后每隔1秒执行一次 DoSomeThing 函数:

  function Start() { InvokeRepeating(“DoSomeThing”, 0.5, 1.0); }

  3. 优化 Update, FixedUpdate, LateUpdate 等每帧处理的函数

  函数里面的变量尽量在头部声明。

  比如:

  function Update() { var pos: Vector3 = transform.position; }

  可改为

  private var pos: Vector3; function Update(){ pos = transform.position; }

  4. 主动回收垃圾

  给某个 GameObject 绑上以下的代码:

  function Update() { if(Time.frameCount % 50 == 0) { System.GC.Collect(); } }

  5. 优化数学计算

  比如,如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等

  6,减少固定增量时间

  将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。

  7,减少GetComponent的调用

  使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为“缓存”的引用)。例如,如果您使用如下的代码:

[AppleScript] 纯文本查看 复制代码
  function Update () {

  transform.Translate(0, 1, 0);

  }


  通过下面的更改您将获得更好的性能:

[AppleScript] 纯文本查看 复制代码
  var myTransform : Transform;

  function Awake () {

  myTransform = transform;

  }

  function Update () {

  myTransform.Translate(0, 1, 0);

  }


  8,避免分配内存

  您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于“传值”操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。

  9,使用iOS脚本调用优化功能

  UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。从Mono的脚本调用 C/C++函数也存在着一定的性能开销。您可以使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。此设置的选项有:

  Slow and Safe – Mono内部默认的处理异常的调用

  Fast and Exceptions Unsupported –一个快速执行的Mono内部调用。不过,它并不支持异常,因此应谨慎使用。它对于不需要显式地处理异常(也不需要对异常进行处理)的应用程序来说,是一个理想的候选项。

  10,

  优化垃圾回收

  如上文所述,您应该尽量避免分配操作。但是,考虑到它们是不能完全杜绝的,所以我们提供两种方法来让您尽量减少它们在游戏运行时的使用:

  如果堆比较小,则进行快速而频繁的垃圾回收

  这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用。如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),但它们可以快速处理并且对游戏的影响很小:

[AppleScript] 纯文本查看 复制代码
  if (Time.frameCount % 30 == 0)

  {

  System.GC.Collect();

  }


  但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间

  如果堆比较大,则进行缓慢且不频繁的垃圾回收

  这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。但是,Mono运行时会尽可能地避免堆的自动扩大[url=http://www.unitymanual.com/forum-develop-1.html]unity 3d开发心得[/url] 因此,您需要通过在启动过程中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的“无用”对象):

[AppleScript] 纯文本查看 复制代码
  function Start() {

  var tmp = new System.Object[1024];

  // make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks

  for (var i : int = 0; i < 1024; i++)

  tmp[i] = new byte[1024];

  // release reference

  tmp = null;

  }


  游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被完全占满。所以,当这种游戏暂停发生时,您可以显式请求一次垃圾回收:

  System.GC.Collect();

  另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。

  三,模型

  1,压缩 Mesh

  导入 3D 模型之后,在不影响显示效果的前提下,最好打开 Mesh Compression。

  Off, Low, Medium, High 这几个选项,可酌情选取。

  2,避免大量使用 Unity 自带的 Sphere 等内建 Mesh

  Unity 内建的 Mesh,多边形的数量比较大,如果物体不要求特别圆滑,可导入其他的简单3D模型代替。
        游戏蛮牛:http://www.unitymanual.com






最简单的优化建议:

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









[Unity3D]图形渲染优化、渲染管线优化、图形性能优化
   
         
 
 
转载请留下本文原始链接,谢谢。本文会不定期更新维护,最近更新于2013.11.09

主要内容也可以参考:
http://docs.unity3d.com/Documentation/Manual/OptimizingGraphicsPerformance.html

最简单的优化建议:

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)和内存带宽(MemoryBandwidth)的限制,如果你的游戏在低质量表现的情况下会快很多,那么,你很可能需要限制你在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 callbatching)。具体方法和注意事项查看以下链接:

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 、 OcclusionCulling

LOD (Level Of Detail)是很常用的3D游戏技术了,其功能理解起来则是相当于多重纹理贴图。在以在屏幕中显示模型大小的比例来判断使用高或低层次的模型来减少对GPU的传输数据,和减少GPU所需要的顶点计算。

摄像机分层距离剔除(Per-Layer CullDistances):为小物体标识层次,然后根据其距离主摄像机的距离判断是否需要显示。

遮挡剔除(OcclusionCulling)其实就是当某个物体在摄像机前被另外一个物体完全挡住的情况,挡住就不发送给GPU渲染,从而直接降低DRAWCALL。不过有些时候在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

没有更多推荐了,返回首页