原文链接1:https://www.cnblogs.com/zhenlong/p/4862869.html
原文链接2:http://www.xuanyusong.com/archives/3205
原文链接3:https://www.cnblogs.com/chwen/p/4396515.html
原文链接4:http://gad.qq.com/program/translateview/7196040
原文链接5:https://blog.uwa4d.com/archives/allinone.html
原文链接6:https://blog.uwa4d.com/archives/USparkle_Lua.html
为了防止原文被删&自己查找方便 总结一份 如有需求 请去原链接支持
文章包括 一些思路 和 一些技巧(实战中总结的一些技巧和插件):
**
**
**
**
DrawCalls:
DrawCall是CPU调用底层图形接口。比如有上千个物体,每一个的渲染都需要去调用一次底层接口,而每一次的调用CPU都需要做很多工作,那么CPU必然不堪重负。但是对于GPU来说,图形处理的工作量是一样的。所以对DrawCall的优化,主要就是为了尽量解放CPU在调用图形接口上的开销。所以针对drawcall我们主要的思路就是每个物体尽量减少渲染次数,多个物体最好一起渲染。所以,按照这个思路就有了以下几个方案:
1 . 使用Draw Call Batching,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。具体下面会介绍。
2 . 通过把纹理打包成图集来尽量减少材质的使用。
3 . 尽量少的使用反光啦,阴影啦之类的,因为那会使物体多次渲染。
4 . 实时光照和阴影可能增加Drawcall,带有光源计算的shader材质会因为光照产生多个Drawcall。使用灯光会打断Drawcall batching,尽量使用烘焙灯光贴图等技巧来实现灯光效果。
5 . SkinnedMeshRenderer : SkinnedMeshRender 负责输出 Mesh ,MeshRender 负责绘制Mesh 因为我们知道MeshRender是可以Batch的!!!
==解决的代码:SkinnedMeshRenderer.BakeMesh(MeshFilter.mesh);
6 . 简化资源是非常行之有效的优化手段. 在大量的移动游戏中,其渲染资源其实是“过量”的,过量的网格资源、不合规的纹理资源等等。
7 . 在NGUI的优化方面,UIPanel.LateUpdate为性能优化的重中之重,它是NGUI中CPU开销最大的函数,没有之一。
8 . 加载模块的性能开销比较集中,主要出现于场景切换处,且CPU占用峰值均较高。 (1)场景卸载 (onDestroy && Resources.UnloadUnusedAssets) . (2)场景加载(资源加载 && Instantiate实例化)
Draw Call Batching
首先我们要先理解为何2个没有使用相同材质的物体即使使用批处理,也无法实现Draw Call数量的下降和性能上的提升。
因为被“批处理”的2个物体的网格模型需要使用相同材质的目的,在于其纹理是相同的,这样才可以实现同时渲染的目的。因而保证材质相同,是为了保证被渲染的纹理相同。
因此,为了将2个纹理不同的材质合二为一,我们就需要进行上面列出的第二步,将纹理打包成图集。具体到合二为一这种情况,就是将2个纹理合成一个纹理。这样我们就可以只用一个材质来代替之前的2个材质了。
Static Batching 静态批处理
那要如何使用静态批来减少Draw Call呢?你只需要明确指出哪些物体是静止的,并且在游戏中永远不会移动、旋转和缩放。想完成这一步,你只需要在检测器(Inspector)中将Static复选框打勾即可
Dynamic Batching 动态批处理
聊完了静态批处理,肯定跟着就要说说动态批处理了。首先要明确一点,Unity3D的draw call动态批处理机制是引擎自动进行的,无需像静态批处理那样手动设置static。我们举一个动态实例化prefab的例子,如果动态物体共享相同的材质,则引擎会自动对draw call优化,也就是使用批处理。
很多时候动态批处理不生效
总结一下动态批处理的约束,各位也许也能从中找到为何动态批处理在自己的项目中不起作用的原因:
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 . 预设体的实例会自动地使用相同的网格模型和材质。
GPU Instancing(Unity5.3导入)
可以利用少量Draw Call绘制多个具有不同的位置、旋转以及其他着色器属性的相同对象。
物理组件
(原作者的项目 但是我觉得,逻辑数据控制排兵布阵 应该没多大问题)曾几何时,匹夫在做一个策略类游戏的时候需要在单元格上排兵布阵,而要侦测到哪个兵站在哪个格子匹夫选择使用了射线,由于士兵单位很多,而且为了精确每一帧都会执行检测,那时候CPU的负担叫一个惨不忍睹。后来匹夫果断放弃了这种做法,并且对物理组件产生了心理的阴影。
这里匹夫只提2点匹夫感觉比较重要的优化措施:
1 . 设置一个合适的Fixed Timestep。设置的位置如图:
那何谓“合适”呢?首先我们要搞明白Fixed Timestep和物理组件的关系。物理组件,或者说游戏中模拟各种物理效果的组件,最重要的是什么呢?计算啊。对,需要通过计算才能将真实的物理效果展现在虚拟的游戏中。那么Fixed Timestep这货就是和物理计算有关的啦。所以,若计算的频率太高,自然会影响到CPU的开销。同时,若计算频率达不到游戏设计时的要求,有会影响到功能的实现,所以如何抉择需要各位具体分析,选择一个合适的值。
2 . 就是不要使用网格碰撞器(mesh collider):为啥?因为实在是太复杂了。网格碰撞器利用一个网格资源并在其上构建碰撞器。对于复杂网状模型上的碰撞检测,它要比应用原型碰撞器精确的多。标记为凸起的(Convex )的网格碰撞器才能够和其他网格碰撞器发生碰撞。各位上网搜一下mesh collider的图片,自然就会明白了。我们的手机游戏自然无需这种性价比不高的东西。
当然,从性能优化的角度考虑,物理组件能少用还是少用为好。
代码?脚本?
Unity3D是用C++写的,而我们的代码是用C#作为脚本来写的,那么问题就来了~脚本和底层的交互开销是否需要考虑呢?也就是说,我们用Unity3D写游戏的“游戏脚本语言”,也就是C#是由mono运行时托管的。而功能是底层引擎的C++实现的,“游戏脚本”中的功能实现都离不开对底层代码的调用。那么这部分的开销,我们应该如何优化呢?
1 . 以物体的Transform组件为例,我们应该只访问一次,之后就将它的引用保留,而非每次使用都去访问。这里有人做过一个小实验,就是对比通过方法GetComponent()获取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这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。
复杂的脚本或者物理模拟
避免过度复杂的脚本 过度重复的物理模拟
**
**
GPU与CPU不同,所以侧重点自然也不一样。GPU的瓶颈主要存在在如下的方面:
1 . 填充率,可以简单的理解为图形处理单元每秒渲染的像素数量。
2 . 像素的复杂度,比如动态阴影,光照,复杂的shader等等
3 . 几何体的复杂度(顶点数量)
4 . 当然还有GPU的显存带宽
5 . shader 避免过多的逐像素计算 避免过多的fragment,overdraws
那么针对以上4点,其实仔细分析我们就可以发现,影响的GPU性能的无非就是2大方面,一方面是顶点数量过多,像素计算过于复杂。另一方面就是GPU的显存带宽。那么针锋相对的两方面举措也就十分明显了。
1 . 减少顶点数量,简化计算复杂度。
2 . 压缩图片,以适应显存带宽。
减少绘制的数目
-------顶点优化
== 保持材质的数目尽可能少。这使得Unity更容易进行批处理。
==优化几何体
== 使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。
== 遮挡剔除(Occlusion culling)
-------像素优化
== 使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
== 如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
== 使用光照纹理(lightmap)而非实时灯光。(具体可自行百度 可以试试 但是现在的手机性能 还是不太理想 某些特定场景 为了效果 其实可以试试开)
== 使用mobile版的shader。因为简单。(效率也经过一些优化 相对比pc的shader 高)
==控制绘制顺序: 最大限度的避免overdraws
==时刻警惕透明物体: 而对于透明对象,由于它本身的特性决定如果要得到正确的渲染效果,就必须从后往前渲染,而且抛弃了深度检验。这意味着,透明物体几乎一定会造成overdraws。如果我们不注意这一点,在一些机器上可能会造成严重的性能下面。
==少实时光照: 实时光照对于移动平台是个非常昂贵的操作。如果只有一个平行光还好,但如果场景中包含了太多光源并且使用了很多多Passes的shader,那么很有可能会造成性能下降。而且在有些机器上,还要面临shader失效的风险。
优化显存带宽
== OpenGL ES 2.0使用ETC1格式压缩等等,在打包设置那里都有。
== MipMap 这里要着重介绍一下MipMap到底是啥。因为有人说过MipMap会占用内存呀,但为何又会优化显存带宽呢?那就不得不从MipMap是什么开始聊起。一张图其实就能解决这个疑问。
----Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。
使用God Rays
场景中很多小型光源效果都是靠这种方法模拟的。它们一般并不是真的光源产生的,很多情况是通过透明纹理进行模拟.
相关链接:https://blog.csdn.net/candycat1992/article/details/42127811
**
**
既然要聊Unity3D运行时候的内存优化,那我们自然首先要知道Unity3D游戏引擎是如何分配内存的。大概可以分成三大部分:
1 . Unity3D内部的内存
2 . Mono的托管内存
3 . 若干我们自己引入的DLL或者第三方DLL所需要的内存。
第3类不是我们关注的重点,所以接下来我们会分别来看一下Unity3D内部内存和Mono托管内存,最后还将分析一个官网上Assetbundle的案例来说明内存的管理。(原作者没关心 其实我们也没关心 但是用到的话 这也是个优化点 可以考虑)
Unity3D内部内存
简单总结一下Unity3D内部内存存放的东西吧:
1 . 资源:纹理、网格、音频等等 纹理格式可以参考链接
2 . GameObject和各种组件。
3 . 引擎内部逻辑需要的内存:渲染器,物理系统,粒子系统等等
4 . 一般情况下,真正占据较大内存开销的是这两处:WebStream 和 SerializedFile。其绝大部分的内存分配则是由AssetBundle加载资源所致。因此,当项目中存在通过new WWW加载多个AssetBundle文件,且AssetBundle又无法及时释放时,WebStream的内存可能会很大,这是研发团队需要时刻关注的。
5 . 对于WebStream和SerializedFile,你需要关注以下两点:(1) . 是否存在AssetBundle没有被清理干净的情况。 (2) . 对于占用WebStream较大的AssetBundle文件(如UI Atlas相关的AssetBundle文件等),建议使用LoadFromCacheOrDownLoad或CreateFromFile来进行替换,即将解压后的AssetBundle数据存储于本地Cache中进行使用。这种做法非常适合于内存特别吃紧的项目,即通过本地的磁盘空间来换取内存空间。
6 . 资源冗余
同一份资源被打入到多份AssetBundle文件中。
在Unity引擎中,当我们修改了一些特定GameObject的资源属性时,引擎会为该GameObject自动实例化一份资源供其使用,比如Material、Mesh等。以Material为例,我们在研发时经常会有这样的做法:在角色被攻击时,改变其Material中的属性来得到特定的受击效果。这种做法则会导致引擎为特定的GameObject重新实例化一个Material,后缀会加上(instance)字样。其本身没有特别大的问题,但是当有改变Material属性需求的GameObject越来越多时,其内存中的冗余数量则会大量增长。如下图所示,随着游戏的进行,实例化的Material资源会增加到333个。虽然Material的内存占用不大,但是过多的冗余资源却为Resources.UnloadUnusedAssets API的调用效率增加了相当大的压力。
============
解决方案: 一般情况下,资源属性的改变情况都是固定的,并非随机出现。比如,假设GameObject受到攻击时,其Material属性改变随攻击类型的不同而有三种不同的参数设置。那么,对于这种需求,我们建议你直接制作三种不同的Material,在Runtime情况下通过代码直接替换对应GameObject的Material,而非改变其Material的属性。这样,你会发现,成百上千的instance Material在内存中消失了,取而代之的,则是这三个不同的Material资源。
Mono托管内存
(重要):目前Unity所使用的Mono版本存在一个很严重的问题. Mono的堆内存一旦分配,就不会返还给系统。这意味着Mono的堆内存是只升不降的。
避免一次性堆内存的过大分配。
避免不必要的堆内存开销。
一些避免过大Mono内存的注意事项:
高频率地 New Class/Container/Array等。研发团队切记不要在Update、FixUpdate或较高调用频率的函数中开辟堆内存,这会对你的项目内存和性能均造成非常大的伤害。
Log输出。我们发现在大量的项目中,仍然存在大量Log输出的情况。建议研发团队对自身Log的输出进行严格的控制,仅保留关键Log,以避免不必要的堆内存分配。
其实Mono的内存分配就是很传统的运行时内存的分配了:
==== 值类型:int型啦,float型啦,结构体struct啦,bool啦之类的。它们都存放在堆栈上(注意额,不是堆所以不涉及GC)。
==== 引用类型:其实可以狭义的理解为各种类的实例。比如游戏脚本中对游戏引擎各种控件的封装。其实很好理解,C#中肯定要有对应的类去对应游戏引擎中的控件。那么这部分就是C#中的封装。由于是在堆上分配,所以会涉及到GC。
而Mono托管堆中的那些封装的对象,除了在在Mono托管堆上分配封装类实例化之后所需要的内存之外,还会牵扯到其背后对应的游戏引擎内部控件在Unity3D内部内存上的分配。
举一个例子:
一个在.cs脚本中声明的WWW类型的对象www,Mono会在Mono托管堆上为www分配它所需要的内存。同时,这个实例对象背后的所代表的引擎资源所需要的内存也需要被分配。
Assetbundle的内存处理
以下载Assetbundle为例子,聊一下内存的分配。从官网的手册上找到了一个使用Assetbundle的情景如下:
IEnumerator DownloadAndCache (){
2 // Wait for the Caching system to be ready
3 while (!Caching.ready)
4 yield return null;
5
6 // Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
7 using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
8 yield return www; //WWW是第1部分
9 if (www.error != null)
10 throw new Exception("WWW download had an error:" + www.error);
11 AssetBundle bundle = www.assetBundle;//AssetBundle是第2部分
12 if (AssetName == "")
13 Instantiate(bundle.mainAsset);//实例化是第3部分
14 else
15 Instantiate(bundle.Load(AssetName));
16 // Unload the AssetBundles compressed contents to conserve memory
17 bundle.Unload(false);
18
19 } // memory is freed from the web stream (www.Dispose() gets called implicitly)
20 }
21 }
内存分配的三个部分匹夫已经在代码中标识了出来:
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);
通过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);
处理内存,却让CPU受伤的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当做垃圾回收。改用string.format,或stringbuilder
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平台上也许会报错。
6 . 尽量使用struct而非class,因为struct是栈区,class是堆区。
**
**
注意消息发送的细粒度。避免发送相同的数据。我们在做强实时游戏中,需要将场景快照发送到客户端,场景快照的大小尽量减小,而将一些消息封装为别的类型信息,当改变的时候再进行发送可以减少流量消耗的同时提高游戏性能。
拒绝发送冗余数据:例如在Moba游戏中,迷雾之中的角色同步消息我们不需要了解,就不需要实时发送消息。
**
**
可以参考链接排查. 文章只写一下简单的思路:
https://blog.uwa4d.com/archives/optimzation_memory_2.html
检查资源的使用情况,特别是纹理、网格等资源的使用
相同场景之间的资源比较 / 不同场景的资源 / 常驻资源 等 可以用unity自带的工具查找 也可以用收费工具内存快照
通过Profiler来检测WebStream或SerializedFile的使用情况
AssetBundle的管理不当也会造成一定的内存泄露。直接通过Profiler Memory中的Take Sample来对其进行检测,通过直接查看WebStream或SerializedFile中的AssetBundle名称,即可判断是否存在“泄露”情况。
通过Android PSS/iOS Instrument反馈的App线程内存来查看
Unity Profiler反馈的是引擎的真实分配的物理内存,而PSS中记录的则包括系统的部分缓存。一般情况下,Android或iOS并不会及时将所有App卸载数据进行清理,为了保证下次使用时的流畅性,OS会将部分数据放入到缓存,待自身内存不足时,OS Kernel会启动类似LowMemoryKiller的机制来查询缓存甚至杀死一些进程来释放内存。因此,并不能通过一两次的PSS内存没有完全回落来说明内存泄露问题。
我们推荐的测试方式是在两个场景之间来回不停切换 , 如果出现了PSS/Instrument内存持续增长的情况,则需要大家注意了.
Unity引擎自身的内存泄露问题。这种概率很小
第三方插件在使用时出现了内存泄露。这种概率较大. 因为Profiler仅能对Unity自身的内存进行监控
**
**
**
**
原文链接:https://blog.uwa4d.com/archives/USparkle_Lua.html
有需求 可以去原文支持一下 , 我只是做一遍总结
大部分结论都是基于uLua+CsToLua的测试得出来的,sLua都是基于其源码来分析,但没有做过深入测试,如有问题的话欢迎交流。
(我们使用的是tolua, 文章的优化效果 还是挺明显的)
既然是Lua+Unity,那性能好不好,基本上要看两大点:
Lua与C#交互篇
从致命的gameobj.transform.position = pos说起
像gameobj.transform.position = pos这样的写法,在Unity中是再常见不过的事情。但是在uLua中,大量使用这种写法是非常糟糕的。为什么呢?
因为短短一行代码,却发生了非常非常多的事情,为了更直观一点,我们把这行代码调用过的关键Lua API以及uLua相关的关键步骤列出来(以uLua+CsToLua导出为准,gameobj是GameObject类型,pos是Vector3):
就这么一行代码,竟然做了这么一大堆的事情!如果是C++,a.b.c = x这样经过优化后无非就是拿地址然后内存赋值的事。但是在这里,频繁的取值、入栈、C#到Lua的类型转换,每一步都是满满的CPU时间,还不考虑中间产生了各种内存分配和后面的GC!
下面我们会逐步说明,其中有一些东西其实是不必要的,可以省略的。我们可以最终把他优化成:lua_isnumber + lua_tonumber 4次,全部完成。
在Lua中引用C#的Object,代价昂贵
从上面的例子可以看到,仅仅想从gameobj拿到一个transform,就已经有很昂贵的代价。C#的Object,不能作为指针直接供c操作(其实可以通过GCHandle进行pinning来做到,不过性能如何未测试,而且被pinning的对象无法用GC管理),因此主流的Lua+Unity都是用一个ID表示C#的对象,在C#中通过dictionary来对应ID和object。同时因为有了这个dictionary的引用,也保证了C#的object在Lua有引用的情况下不会被垃圾回收掉。
因此,每次参数中带有object,要从Lua中的ID表示转换回C#的object,就要做一次dictionary查找;每次调用一个object的成员方法,也要先找到这个object,也就要做dictionary查找。
如果之前这个对象在Lua中有用过而且没被GC,那还就是查下dictionary的事情。但如果发现是一个新的在Lua中没用过的对象,那就是上面例子中那一大串的准备工作了。
如果你返回的对象只是临时在Lua中用一下,情况更糟糕!刚分配的userdata和dictionary索引可能会因为Lua的引用被GC而删除掉,然后下次你用到这个对象又得再次做各种准备工作,导致反复的分配和GC,性能很差。
例子中的gameobj.transform就是一个巨大的陷阱,因为.transform只是临时返回一下,但是你后面根本没引用,又会很快被Lua释放掉,导致你后面每次.transform一次,都可能意味着一次分配和GC。
在Lua和C#间传递Unity独有的值类型(Vector3/Quaternion等)更加昂贵
既然前面说了Lua调用C#对象缓慢,如果每次vector3.x都要经过C#,那性能基本上就处于崩溃了,所以主流的方案都将Vector3等类型实现为纯Lua代码,Vector3就是一个{x,y,z}的table,这样在Lua中使用就快了。
但是这样做之后,C#和Lua中对Vector3的表示就完全是两个东西了,所以传参就涉及到Lua类型和C#类型的转换,例如C#将Vector3传给Lua,整个流程如下:
一个简单的传参就要完成3次push参数、表内存分配、3次表插入,性能可想而知。那么如何优化呢?
我们的测试表明,直接在函数中传递三个float,要比传递Vector3要更快。例如void SetPos(GameObject obj, Vector3 pos)改为void SetPos(GameObject obj, float x, float y, float z)。具体效果可以看后面的测试数据,提升十分明显。
Lua和C#之间传参、返回时,尽可能不要传递以下类型
严重类: Vector3/Quaternion等Unity值类型,数组
次严重类:bool string 各种object
建议传递:int float double
虽然是Lua和C#的传参,但是从传参这个角度讲,Lua和C#中间其实还夹着一层C(毕竟Lua本身也是C实现的),Lua、C、C#由于在很多数据类型的表示以及内存分配策略都不同,因此这些数据在三者间传递,往往需要进行转换(术语parameter mashalling),这个转换消耗根据不同的类型会有很大的不同。
先说次严重类中的 bool和 string类型,涉及到C和C#的交互性能消耗,根据微软官方文档,在数据类型的处理上,C#定义了Blittable Types和Non-Blittable Types,其中bool和string属于Non-Blittable Types,意思是他们在C和C#中的内存表示不一样,意味着从C传递到C#时需要进行类型转换,降低性能,而string还要考虑内存分配(将string的内存复制到托管堆,以及utf8和utf16互转)。大家可以参考https://msdn.microsoft.com/zh-cn/library/ms998551.aspx,这里有更详细的关于C和C#交互的性能优化指引。
而严重类,基本上是uLua等方案在尝试Lua对象与C#对象对应时的瓶颈所致。
Vector3等值类型的消耗,前面已经有所提及。
而数组则更甚,因为Lua中的数组只能以table表示,这和C#下完全是两码事,没有直接的对应关系,因此从C#的数组转换为Lua table只能逐个复制,如果涉object/string等,更是要逐个转换。
频繁调用的函数,参数的数量要控制
无论是Lua的pushint/checkint,还是C到C#的参数传递,参数转换都是最主要的消耗,而且是逐个参数进行的,因此,Lua调用C#的性能,除了跟参数类型相关外,也跟参数个数有很大关系。一般而言,频繁调用的函数不要超过4个参数,而动辄十几个参数的函数如果频繁调用,你会看到很明显的性能下降,手机上可能一帧调用数百次就可以看到10ms级别的时间。
优先使用static函数导出,减少使用成员方法导出
前面提到,一个object要访问成员方法或者成员变量,都需要查找Lua userdata和C#对象的引用,或者查找metatable,耗时甚多。直接导出static函数,可以减少这样的消耗。
像obj.transform.position = pos。我们建议的方法是,写成静态导出函数,类
class LuaUtil{
static void SetPos(GameObject obj, float x, float y, float z){obj.transform.position = new Vector3(x, y, z); }
}
然后在Lua中LuaUtil.SetPos(obj, pos.x, pos.y, pos.z),这样的性能会好非常多,因为省掉了transform的频繁返回,而且还避免了transform经常临时返回引起Lua的GC。
注意Lua拿着C#对象的引用时会造成C#对象无法释放,这是内存泄漏常见的起因
前面说到,C# object返回给Lua,是通过dictionary将Lua的userdata和C# object关联起来,只要Lua中的userdata没回收,C# object也就会被这个dictionary拿着引用,导致无法回收。最常见的就是gameobject和component,如果Lua里头引用了他们,即使你进行了Destroy,也会发现他们还残留在mono堆里。不过,因为这个dictionary是Lua跟C#的唯一关联,所以要发现这个问题也并不难,遍历一下这个dictionary就很容易发现。uLua下这个dictionary在ObjectTranslator类、SLua则在ObjectCache类。
考虑在Lua中只使用自己管理的ID,而不直接引用C#的Object
想避免Lua引用C# Object带来的各种性能问题的其中一个方法就是自己分配ID去索引Object,同时相关C#导出函数不再传递Object做参数,而是传递int。这带来几个好处:
合理利用out关键字返回复杂的返回值
在C#向Lua返回各种类型的东西跟传参类似,也是有各种消耗的。比如 Vector3 GetPos(GameObject obj) 可以写成 void GetPos(GameObject obj, out float x, out float y, out float z)。表面上参数个数增多了,但是根据生成出来的导出代码(我们以uLua为准),会从:LuaDLL.tolua_getfloat3(内含get_field + tonumber 3次) 变成 isnumber + tonumber 3次。get_field本质上是表查找,肯定比isnumber访问栈更慢,因此这样做会有更好的性能。
一些实测
我们重写了一个简化版的GameObject2和Transform2。
class Transform2{
public Vector3 position = new Vector3();
}
class GameObject2{
public Transform2 transform = new Transform2();
}
然后我们用几个不同的调用方式来设置transform的position
方式1:gameobject.transform.position = Vector3.New(1,2,3)
方式2:gameobject:SetPos(Vector3.New(1,2,3))
方式3:gameobject:SetPos2(1,2,3)
方式4:GOUtil.SetPos(gameobject, Vector3.New(1,2,3))
方式5:GOUtil.SetPos2(gameobjectid, Vector3.New(1,2,3))
方式6:GOUtil.SetPos3(gameobjectid, 1,2,3)
分别进行100万次,结果如下(测试环境是Windows版本,CPU是i7-4770,luajit的jit模式关闭,手机上会因为luajit架构、IL2CPP等因素干扰有所不同,但这点我们会再进一步阐述):
方式1:903ms
方式2:539ms
方式3:343ms
方式4:559ms
方式5:470ms
方式6:304ms
可以看到,每一步优化,都是提升明显的,尤其是移除.transform获取以及Vector3转换提升更是巨大,我们仅仅只是改变了对外导出的方式,并不需要付出很高成本,就已经可以节省66%的时间。
**
**
一些写的很好的学习资料
AssetBundle打包机制详解(4.x)
AssetBundle打包机制详解(5.x)
AssetBunlde内存管理机制
建议去看一下原始链接
简单写一下 推荐方案
对于需要常驻内存的Bundle文件来说,优先考虑减小内存占用,因此对于存放非Prefab资源(特别是纹理)的Bundle文件,可以考虑使用WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile加载,从而避免WebStream常驻内存;而对于存放较多Prefab资源的Bundle,则考虑使用new
WWW加载,因为这类Bundle用WWW.LoadFromCacheOrDownload加载时产生的SerializedFile可能会比new
WWW产生的WebStream更大。
对于加载完后即卸载的Bundle文件,则分两种情况:优先考虑速度(加载场景时)和优先考虑流畅度(游戏进行时)。
1)加载场景的情况下,需要注意的是避免WWW对象的逐个加载导致的CPU空闲,可以考虑使用加载速度较快的WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile,但需要避免后续大量地进行Load资源的操作,引起IO开销(可以尝试直接LoadAll)。
2) 游戏进行的情况下,则需要避免使用同步操作引起卡顿,因此可以考虑使用new WWW配合AssetBundle.LoadAsync来进行平滑的资源加载,但需要注意的是,对于Shader、较大的Texture等资源,其初始化操作通常很耗时,容易引起卡顿,因此建议将这类资源在加载场景时进行预加载。
只在Bundle需要加密的情况下,考虑使用CreateFromMemory,因为该接口加载速度较慢。
尽量避免在游戏进行中调用Resources.UnloadUnusedAssets(),因为该接口开销较大,容易引起卡顿,可尝试使用Resources.Unload(obj)来逐个进行卸载,以保证游戏的流畅度。
**
**
原文链接:http://www.xuanyusong.com/archives/3205
找一个合适的地方调用如下方法即可,这个方法在IOS和Android上都支持,但是经过测试IOS没必要使用,在android上还是很有必要使用的。
Screen.SetResolution(960,640,true);
大概原理就是: 强制把屏幕的分辨率指定成960X640, 然后取出宽高, 缩放. 然后安卓渲染的应该是 960 * 640 分辨率的. 提升应该挺大的. 有兴趣的可以去原文看看.会有几个常见的问题解决方案
**
**
重要:
书名:***<腾讯游戏开发精粹>***
如有能力请去支持原版书,本文只是做一些总结和摘录,如涉及到版权问题,请联系删除 谢谢
结果:
模型合并流程如下:
(1)合并头部、衣服、裤子、四肢模型顶点信息。
(2)因为后续还将合并贴图,所以这里需要重写四部分的顶点UV数据
贴图合并流程如下:
(1)衣服、裤子贴图与号码贴图合并为一张贴图。
(2)多个护具贴图与四肢皮肤贴图合并为一张贴图。
(3)衣服、裤子、四肢、头部贴图合并为一张贴图。
根据角色配置设置材质、身高和体型的流程如下:
(1)将材质赋予模型,将合并后的贴图赋予材质。
(2)缩放角色根骨骼,实现身高。
(3)缩放角色头部骨骼,调整头部比例。
(4)设置材质的肌肉数值和脂肪数值,实现体型。
// 示例代码,仅供逻辑学习参考,请勿生搬硬套
public class CAvatarCombine
{
// Is avatar mesh
// 是否为角色套装模型网格
private static bool IsAvatar(SkinnedMeshRenderer smr)
{
if (smr.name.Contains("body")
|| smr.name.Contains("head")
|| smr.name.Contains("armor"))
return true;
return false;
}

// Recalculate uv, because of merge texture
// 因为合并贴图,需要重新计算模型UV信息
private static Vector2[] CombinUV(Rect[] packs, List<Vector2[]> uvlist, int uvCount)
{
Vector2[] mergeUVs = new Vector2[uvCount];

int j = 0;
for(int i = 0; i < uvlist.Count; ++ i)
{
foreach (Vector2 uv in uvlist[i])
{
mergeUVs[j].x = Mathf.Lerp(packs[i].xMin, packs[i].xMax, uv.x);
mergeUVs[j].y = Mathf.Lerp(packs[i].yMin, packs[i].yMax, uv.y);
++j;
}
}
return mergeUVs;
}

private static Transform FindNode(Transform trans, string name)
{
if(trans.name == name)
{
return trans;
}

int count = trans.childCount;
for(int i = 0 ; i < count; ++i)
{
Transform t = FindNode(trans.GetChild(i), name);
if(t) return t;
}
return null;
}

private static void AddBonesList(List<Transform> list, Transform trans)
{
list.Add(trans);

int count = trans.childCount;
for(int i = 0; i < count; ++ i)
{
AddBonesList(list, trans.GetChild(i));
}
}

// combin mesh、texture、uv、bone、boneweight、bindpose
// 合并模型网格、贴图、UV、骨骼、骨骼权重、绑定姿势
public static void Combine(GameObject gb)
{
List<CombineInstance> cilist = new List<CombineInstance>();
List<Vector2[]> uvlist = new List<Vector2[]>();
List<Texture2D> texlist = new List<Texture2D>();
List<Transform> bonelist = new List<Transform>();
List<BoneWeight> boneWeightlist = new List<BoneWeight>();
List<Matrix4x4> matrixlist = new List<Matrix4x4>();
int uvCount = 0;

// build global bonehash from "bip001"
// 将“bip001”的骨骼信息存储到hash容器中
Hashtable bonesHash = new Hashtable();
Transform rootBone = FindNode(gb.transform, "Bip001");
List<Transform>transList = new List<Transform>();
AddBonesList(transList, rootBone);
Transform[] bones = transList.ToArray();

int boneIndex = 0;
foreach (Transform bone in bones)
{
bonelist.Add(bone);
bonesHash.Add(bone.name, boneIndex);
boneIndex++;
}

// bindposes, matrix
// 绑定姿势,矩阵
for (int b = 0; b < bonelist.Count; b++)
{
matrixlist.Add(bones[b].worldToLocalMatrix * gb.transform.worldToLocalMatrix);
}

SkinnedMeshRenderer[] smrs;
smrs = gb.GetComponentsInChildren<SkinnedMeshRenderer>();

foreach (SkinnedMeshRenderer smr in smrs)
{
if (! IsAvatar(smr))
continue;
if (smr.material.mainTexture == null)
continue;

CombineInstance ci = new CombineInstance();
ci.mesh = smr.sharedMesh;
ci.transform = smr.transform.localToWorldMatrix;
cilist.Add(ci);

// fill UV coordinate data
// 填充UV坐标数据
uvlist.Add(smr.sharedMesh.uv);
uvCount += smr.sharedMesh.uv.Length;

// fill texture data
// 填充贴图数据
texlist.Add(smr.material.mainTexture as Texture2D);

// fill boneweight
// 填充骨骼权重
BoneWeight[] weightArry = smr.sharedMesh.boneWeights;
Transform[] boneArry = smr.bones;
foreach (BoneWeight bw in weightArry)
{
BoneWeight bWeight = bw;
bWeight.boneIndex0 = (int)bonesHash[boneArry[bw.boneIndex0].name];
bWeight.boneIndex1 = (int)bonesHash[boneArry[bw.boneIndex1].name];
bWeight.boneIndex2 = (int)bonesHash[boneArry[bw.boneIndex2].name];
bWeight.boneIndex3 = (int)bonesHash[boneArry[bw.boneIndex3].name];

boneWeightlist.Add(bWeight);
}
Destroy(smr);
}

// CreateTexture
// 创建贴图
Texture2D mergeTex = new Texture2D(512, 512);
// MergeTexture
// 合并贴图
Rect[] packRect = mergeTex.PackTextures(texlist.ToArray(), 0);

// Combine
// 合并
SkinnedMeshRenderer r = gb.AddComponent<SkinnedMeshRenderer>();
r.sharedMesh = new Mesh();
r.sharedMesh.name = "Combine";
r.sharedMesh.CombineMeshes(cilist.ToArray());
// mergeUV
// 合并UV
r.sharedMesh.uv = CombinUV(packRect, uvlist, uvCount);
r.bones = bonelist.ToArray();
r.rootBone = bonelist[0];
r.sharedMesh.boneWeights = boneWeightlist.ToArray();
r.sharedMesh.bindposes = matrixlist.ToArray();

Material mat = Resources.Load("default_Mat") as Material;

// material
// 材质
r.sharedMaterial = new Material(mat);
r.sharedMaterial.mainTexture = mergeTex;
r.sharedMesh.RecalculateBounds();
}
}
GPU渲染流程如下:
(1)从顶点色Red和Green通道中获取记录肌肉、脂肪的权重。
(2)纠正资源制作时权重取反的计算。
(3)计算对应数值和法线方向,得到偏移向量,计算新的顶点位置。
顶点着色器代码:
// Vertex-Shader Stage
// 顶点着色阶段
// Get Vertex Color As Weight, and Invert Weight Value
// 获取顶点颜色作为权重值,并翻转权重值
float fMuscleWeight = 1.0- vertex.color.r;
float fFatWeight = 1.0- vertex.color.g;

// Get New Vertex Position
// 计算新的顶点坐标
v.vertex.xyz += (fMuscleValue * fMuscleWeight + fFatValue * fFatWeight) * v.normal.xyz;
优势:
劣势:
**
**
原文链接:http://www.manew.com/thread-141722-1-6.html
大概描述一下成果, 具体查找过程可以移步原文链接.
最终结果:
在发现Mono的增长部分其实是可以被GC的时候,逐个测试具体是哪部分的GC可以真正释放这块内存。前面已经列举了一次完整的GC所包含的东西,逐个去掉来进行测试,最终发现是Lua的GC调用影响最大。
这就说明,是由于Lua对于C#对象的引用,导致C#的GC机制无法释放掉对应的内存对象。
Lua自身是不会拿到C#的对象的,而是通过Tolua这个胶水层来处理。深入ToLua来看,会发现所有对象的引用都是由ObjectTranslator这个类来处理,其中使用了一个ObjectPool对C#对象进行存储,Lua层拿到的是一个int形式的Handler。对于Lua层拿到的对象,会重写其__gc函数,当Lua的GC执行的时候,会调用这一函数,从而释放掉ObjectTranslator这层缓存的C#对象。
为了验证这部分泄露的情况,同事又在ToLua层添加了对于对象的监控,通过log diff的形式来排查是哪些对象被泄露在了这一层。最终证明的确是那些在Lua层被访问过的对象,在不调用Lua GC的情况下会一直驻留在ObjectTranslator这一层。
我们来对整个逻辑做一下梳理和回顾:
解决方案:
总结:
这个内存泄露的问题困扰了我们大约一个多周的时间,这里记录的只是一些排查的关键步骤,对于中间的思考、讨论、对比等等细节无法完整地记录。由于项目临近上线,而合作方给予的测试用例也是一种比较极限的情况,所以最终线上的版本没有修复这个问题。正常进行游戏会有相对频繁的状态跳转,因此会有手动触发Lua GC的逻辑,可以让Mono内存不会累积到100多兆那么夸张的程度,因此对于玩家的影响不是很大。
**
**
Unity5.6 在Frame Debugger中新增了一项功能,能解释这些批次信息。
Frame Debugger是Unity 5.x推出的功能,你可以点菜单的Window > Frame Debugger 来打开Frame Debugger。它能显示游戏中所有的批处理信息,以及这些批处理的所有细节信息,包括着色器、贴图及批处理所用的大量信息等。
Unity 5.6中的Frame Debugger,这里说明为何Unity要发动批处理
导致批处理失败的原因
有时在编辑器中可以清楚地看到,一些本应被批处理的对象出于某些原因没有被批处理。首先,请检查Player Settings中是否启用批处理功能。这个步骤看似多余,但我们遇到太多的无法处理的原因都是因为忘记开启。
我们专门为此提供了展示项目来演示Unity在什么情况下必须发起新的批处理请求。首先下载项目并复制到Unity项目中。请注意,你需要安装Unity 5.6才能看到Frame Debugger中关于批处理状态的说明。
以下是展示项目(Unity 5.6)中导致无法进行批处理的原因
**
**
我忘记哪个有免费的次数了 可能有些是收费的
**
**
原文链接:https://blog.uwa4d.com/archives/LoadingPerformance_Mesh.html
简单描述一下成果 具体过程及原理 请移步原始链接
1、资源的数据量对加载性能影响较大,面片数越多,其加载越为耗时。设备性能越差,其耗时差别越为明显;
2、随着硬件设备性能的提升,其加载效率差异越来越不明显。
1、顶点属性的增加对内存和AssetBundle包体大小影响较大。与测试1中未引入Tangent顶点属性的网格数据相比,测试2中的网格数据在内存上均大幅度增加(增加量与网格顶点数有关),且AssetBundle大小同样有成倍(1~2)的增加。
2、顶点属性增加对于加载效率影响较大,且顶点数越多,影响越大。
注意事项:
模型常见的顶点属性主要有Position、UV、Normal、Tangent和Color。Color属性与Tangent属性一样,如果网格顶点拥有该属性,同样会对内存、物理体积和加载性能造成影响。
在使用Draw Call Batching时,**切忌将不同属性的网格模型拼合在一起。**举个例子 ,100个网格模型进行Static Batching,如果99个模型只有Position和UV两种属性,而剩下1个模型函数有Position、UV、Normal、Tangent和Color五种属性。那么引擎在进行拼合时,会将前99个模型的顶点属性补齐,然后再进行拼合。这样无形中会增加大量的内存占用,从而造成不必要的内存浪费。
1、关闭Read/Write功能会降低AssetBundle的物理大小,其降低量与资源本身数据量相关。同时,关闭Read/Write功能会大幅度降低网格资源的内存占用;
2、关闭Read/Write功能会略微提升该资源的加载效率。
通过以上测试和分析,我们对于网格资源的管理建议如下:
1、在保证视觉效果的前提下,尽可能采用“够用就好”的原则,即降低网格资源的顶点数量和面片数量;
2、研发团队对于顶点属性的使用需谨慎处理。通过以上分析可以看出,顶点属性越多,则内存占用越高,加载时间越长;
3、如果在项目运行过程中对网格资源数据不进行读写操作(比如Morphing动画等),那么建议将Read/Write功能关闭,既可以提升加载效率,又可以大幅度降低内存占用。
**
**
原文链接:https://blog.uwa4d.com/archives/LoadingPerformance_Shader.html
简单描述一下成果 具体过程及原理 请移步原始链接
测试1:不同种类的Shader资源加载效率测试
1、Shader资源的物理体积与内存占用虽然很小,但其加载耗时开销的CPU占用很高,这主要是因为Shader的解析CPU开销很高,成为了Shader资源加载的性能瓶颈;
2、Mobile/Particles Additive在解析方面的耗时远小于Mobile/Diffuse、Mobile/Bumped Diffsue甚至Mobile/VertexLit;
3、除Mobile/Particles Additive外,其他三个主流Shader在加载时均会造成明显的降帧,甚至卡顿。因此,研发团队应尽可能避免在非切换场景时刻进行Shader的加载操作;
4、随着硬件设备性能的提升,其解析效率差异越来越不明显。
测试2:Mobile Shader vs. Normal Shader
1、Mobile Shader较之同种Normal Shader在加载方面确实有一定的性能提升;
2、设备性能越低,性能差距越大,比如Mobile/Bumped Diffuse和Bumped
Diffuse的加载性能差距在红米2低端机上达到30ms+。
那么,问题来了,我们该如何优化它呢?
在优化之前,我们首先要做的是了解Shader解析时的真正耗时原因。一般情况下,Shader加载的CPU耗时与其Keyword数量有关,Keyword数量越多,则加载开销也越大。通过Unity 5.x的Inspector可以看到,Mobile/Bumped Diffuse的Keyword变量数量为39,Mobile/Diffuse的Keyword变量数量为27,Mobile/VertexLit的Keyword变量数量为15,Mobile/Particles Additive的Keyword变量数量为1。类似的,在Unity 4.x中,Mobile/Bumped Diffuse的Keyword变量数量为44,Mobile/Diffuse的Keyword变量数量为25,Mobile/VertexLit的Keyword变量数量为6,Mobile/Particles Additive的Keyword变量数量为0。这也是Mobile/Particles Additive解析开销如此之低的主要原因。
注意:Shader的Keyword数量是会随着场景设置的不同而变化的。在Unity 5.x中,Unity默认会根据场景设置、Shader Pass等来调整Shader的Keyword,比如如果存在Lightmap的使用,则会默认将对应的Keyword打开,而对于没有使用Fog的项目,则会直接将相关Keyword关闭。
方法一:
对于Unity 5.x项目,可通过skip_variants操作在Shader中直接去除相关Keyword。
该方法可以有效降低Keyword的数量,但该方法同样有一定的局限,一是目前skip_variants操作仅能在Unity 5.0以上版本中使用,二是该方法需要研发团队对Shader具备一定程度的了解,可根据项目实际情况有针对性对Shader进行修改。
方法二:
直接去除Shader中的Fallback选项。Fallback功能是对于无法使用当前Shader的硬件设备可以使用对硬件设备要求更低的Fallback Shader来进行渲染,以保证渲染的稳定性。但是,就目前的移动市场而言,不支持Mobile/Diffuse和Mobile/Bumped Diffuse的设备已经相当少(或者说,我们目前还没遇到不支持Mobile/Diffuse Shader的设备反馈)。
该方法不会像“方法一”那样完全去除“无用”的Keyword,但该方法简单易用,只需一步操作,因此,性价比很高。同时,该方法完全支持Unity 4.x引擎的项目。
测试3:开启/关闭Fallback功能的加载效率测试
为简单起见,我们直接关闭Mobile/Bumped Diffuse和Mobile/Diffuse的Fallback功能来制作一组对比数据。关闭Fallback后,这两个Shader的Keyword数量均为12,而原始Shader的Keyword为39和27。
通过上述测试可以看出,Keyword的降低确实可以大幅降低Shader的解析时间,进而提升加载效率。
加载方式
1、通过依赖关系打包,将项目中的所有Shader抽离并打成一个独立的AssetBundle文件,其他AssetBundle与其建立依赖;
2、Shader的AssetBundle文件在游戏启动后即进行加载并常驻内存,因为一款项目的Shader种类数量一般在50~100不等,且每个均很小,即便全部常驻内存,其内存总占用量也不会超过2MB;
3、后续Prefab加载和实例化后,Unity引擎会通过AssetBundle之间的依赖关系直接找到对应的Shader资源进行使用,而不会再进行加载和解析操作。
注意:对于Unity4.x版本,Shader的AssetBundle加载后只需LoadAll即可完成所有Shader的加载和解析,但对于Unity5.x版本,除执行LoadAllAssets操作外,还需要进行Shader.WarmupAllShaders操作,因为在Unity5.x版本中,Shader的解析和CreateGPUProgram操作是分离的。
注意:对于Unity5.x版本,如果可以通过AssetBundle来加载和解析Shader,则不建议通过ShaderVariantCollection来处理Shader的加载。在目前最新的Unity 5.3.5中,我们经过大量测试,发现ShaderVariantCollection在Shader的加载和管理中仍然存在一定的问题,我们暂时无法确定是否为引擎的问题,这已经不属于本篇文章的讨论范畴,在此不再赘述。
通过以上测试和分析,我们对于Shader资源的管理建议如下:
1、在保证渲染效果和项目需求的情况下,尽可能降低Shader的Keyword数量,以提升Shader的加载效率;
2、对于简单Shader,可尝试去除Fallback操作,该方法非常适合于目前正在大量使用的Mobile/Diffuse、Mobile/Bumped Diffuse等Built-in Shader;
3、尽可能对Shader进行单独、依赖关系打包并对其进行预加载,以降低后续不必要的加载开销。
**
**
原文链接:https://blog.uwa4d.com/archives/presentandsync.html
简单描述一下成果 具体过程及原理 请移步原始链接
大概成果: 那就是,忽略Gfx.WaitForPresent 和 Graphics.PresentAndSync这两个参数,优化其他你能优化的一切!
简单分析一下:
WaitForTargetFPS、Gfx.WaitForPresent 和 Graphics.PresentAndSync是我们经常会被问到的参数。想必正在读此文的你也经常在Profiler中遇到过这几项CPU开销过大的情况。对此,我们今天就来好好地聊一聊这几个参数的具体含义和触发规则。
WaitForTargetFPS
该参数一般出现在CPU开销过低,且通过设定了目标帧率的情况下(Application.targetFrameRate)。当上一帧低于目标帧率时,将会在本帧产生一个WaitForTargetFPS的空闲等待耗时,以维持目标帧率。
解析:该项在Unity引擎的主循环中其实是最早执行的,即引擎实际上是根据上一帧的CPU耗时,在当前帧中通过增补WaitForTargetFPS的方式来将运行FPS维持到目标值。比如,目标帧率为30帧/秒,上一帧耗时15ms,那么当前帧中WaitForTargetFPS将会是18(33-15)ms,但是这一帧中其他耗时为28ms,那么在Profiler中这一帧的总耗时就变成了46(18+28)ms。
因此,由该值造成了Profiler开销较高的现象,其实是耗时的“假象”,在优化过程中,你对它可以“视而不见”。
Gfx.WaitForPresent && Graphics.PresentAndSync
所以,如果你的项目中,Gfx.WaitForPresent或Graphics.PresentAndSync的CPU耗时非常高时,其实并不是它们自己做了什么神秘的操作,而是你当前的渲染任务太重,GPU负载过高所致。
同时,对于开启垂直同步的项目而言,Gfx.WaitForPresent 和 Graphics.PresentAndSync也会出现CPU占用较高的情况。
CPU端开销非常小,Present在很早即被执行,但此时VSync还没到,则会出现较高的等待时间,即Gfx.WaitForPresent 和 Graphics.PresentAndSync的CPU开销看上去很高。
PU端开销很高,使得Present执行时错过了VSync操作,这样,Present将不得不等待下一次VSync的到来,从而造成了Gfx.WaitForPresent 和 Graphics.PresentAndSync的CPU开销较高。这种情况在CPU端加载过量资源时特别容易发生,比如WWW加载较大的AssetBundle、Resource.Load加载大量的Texture等等。
造成这两个参数的CPU占用较高的原因主要有以下三种原因:
**
**
原文链接:http://blog.sina.com.cn/s/blog_15ff4f4c80102whyg.html
(挺简单的插件 有需要 请去原始链接下载 如果失效 请找别的教程 插件名称: Mesh Baker)
工作原理:
工具会寻找当前场景中使用相同材质球的物体分类,然后把同一个Shader的物体给统一合并成尽量少的贴图和材质球和Mesh来提高运行效率。
**
**
原文链接:https://blog.uwa4d.com/archives/1919.html
大概原理就是 动态加载 + 缓存 + 导出自动拆分脚本 + 实战优化技巧
有兴趣 可以去原链接看看 学习一下也是蛮不错的
**
**
原文链接:https://blog.csdn.net/jxw167/article/details/82455746
一些插件: 包括 裁剪 剔除 批处理 动态加载 一些检测 的解决方案
针对密集型建筑使用的解决方案,该方法在PC端,移动端都是适用的,最后把代码奉上,参考案例代码可以将其应用到自己的项目开发中
如有需求 去原链接下载 也是蛮好玩的 可以去看看
**
**
原文链接:http://www.manew.com/thread-143268-1-4.html