unity性能优化方案整理 & 一些思路 & 一些技巧(持续更新 2019-09-12)

2023-10-26

原文链接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

为了防止原文被删&自己查找方便 总结一份 如有需求 请去原链接支持

文章包括 一些思路 和 一些技巧(实战中总结的一些技巧和插件):

  • cpu相关
  • gpu相关
  • 内存相关
  • 网络相关
  • 内存泄漏 相关
  • c#代码相关的一些小技巧
  • 数据传输 lua与c#交互优化
  • 代码热更(TODO)
  • AssetBunlde一些详解
  • Android的硬件缩放技术优化执行效率 (小技巧)
  • ------------------一些使用技巧-----------------
  • 基于看书讲解游戏<最强nba>的一些性能思路
  • Unity Profiler工具检测内存(TODO 待总结 其实蛮简单的 可以看cpu耗时 && 内存 等)
  • unity + lua ---- 内存泄露的一些误解 & 问题定位过程
  • Draw Call未被批处理?在Unity 5.6中如何找出原因
  • 一些性能检测工具
  • 一些对于网格的测试分析 & 建议
  • 一些对于shader的测试分析 & 建议
  • 分析一下Profiler中几项CPU开销过大的情况 & 一些误解
  • ------------------一些优秀插件-----------------
  • 优秀插件 Mesh Baker 提高场景运行帧率
  • ------------------一些实战技巧-----------------
  • 世界动态加载 实践
  • Unity大密度建筑场景加载解决方案
  • 大牛分享:100条Unity基础小贴士

**

一些基本概念

**

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

**

CUP相关

**

  • 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这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。

  • 复杂的脚本或者物理模拟
    避免过度复杂的脚本 过度重复的物理模拟

  • 一些CPU高占用函数
    在这里插入图片描述

**

GPU相关

**

  • 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分配它所需要的内存。同时,这个实例对象背后的所代表的引擎资源所需要的内存也需要被分配。

    一个WWW实例背后的资源:
    1 . 压缩的文件
    2 . 解压缩所需的缓存
    3 . 解压缩之后的文件
    在这里插入图片描述

  • 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自身的内存进行监控

**

c#代码相关的一些小技巧

**

  • 我们在写程序的时候,经常会用List。其实很多不需要随机访问的情况下我们可以使用Queue或者Stack来代替List。前者O(1)的复杂度会比后者好很多。
  • 尽量使用泛型容器避免不必要的装箱拆箱对于性能来说也会有所提升。对于频繁且大量的访问操作,装箱与拆箱会带来巨大的性能差距。
  • 在Dictionary中进行查找的时候,我们最好去查找已经重载了比较操作的自定义类型或者是基本类型,当使用基本类型时使用较小的类似int、枚举等类型而非string。由于C#自带的比较等操作的实现效率并不高,所以在较为敏感的场景下使用自定义类做索引需要注意。
  • 为容器初始化容量:当容量不足时扩容的代价是非常昂贵的
  • 平时要注意常用集合操作的复杂度,在使用时避免不必要的操作。RemoveAt也有O(n)复杂度且n=Count-index。建议从尾部移除。批量移除时使用RemoveRange提高移除效率。
  • 减少多余的访问,例如Dictionary的使用中我们常常会先用一次ContainsKey再进行访问,相当于访问了两次。使用TryGet替代会更好。
  • 减少创建冗余的集合.例如利用OrderBy后我们会获得一个新的集合,但是我们将这个集合创建成一个新的List或者别的集合时会有一定的消耗,例如ToList、ToDictionary同理。
  • 考虑重写struct中自带的函数。就像前面所说的,系统自带的函数实际上性能非常低下,很多地方甚至使用了反射,对于性能敏感的代码来说是不能容忍的。
  • 考虑使用Cache
  • 不要滥用静态对象, 当不再需要的时候,将静态大对象置空,保证GC能够正常进行。
  • 避免在循环或者是Update中进行字符串操作
  • 可以使用for就不使用foreach,foreach产生的迭代器可以产生GC以影响性能
  • 使用尾递归而非其他的递归,尾递归的性能好于头递归
  • 避免频繁地SetActive操作,由于SetActive本身也有一定消耗,而且一些特殊的组件类似于:Text、MaskGraphic等,在OnEnable与OnDisable时有较为明显的消耗,建议在频繁进行SetActive的操作时采用先移出屏幕等待一段时间之后再将物体隐藏,保证不过度频繁地将物体重复Active或者Inactive。而在一些不适用于移出屏幕的物体,类似于UI,考虑减少该类操作,或者使用将Text设为空或者透明度设为0来避免调用OnEnable与OnDisable操作。
  • Transform的子类型过多时避免频繁地进行Transform操作,大量的子物体会带来大量的操作
  • 在设置需要频繁使用的材质属性时,尝试将字符串转换为数字并且保存下来,调用时使用数字进行查找属性,也是减少字符串索引的方法。
  • 支持分级Log(自定义logger),避免大量且频繁的Log,在构建时屏蔽log。
  • 避免频繁地Find、GetComponent。
  • 避免创建大量不必要的碰撞盒。
  • 使用gameObject.CompareTag(“XXX”)而非gameObject.tag,后者会产生额外的内存与性能消耗。
  • 使用消耗更小的运算:例如1/5使用1*0.2来代替、用位运算代替简单乘除。(不过在性能并不是非常非常敏感的地方可以忽略位运算这一条,毕竟可读性还是要的。
  • 使用内建的常量,例如Vector3.zero等等,避免频繁创建相同的对象。
  • 自己可以写一个工具类,使用Update替代简单的协程,例如等待若干秒等等,可以消除创建协程的消耗。
  • 在UI中使用池时考虑使用分级机制:在池中越不频繁出现的UI就应该更快地被销毁以释放内存,而频繁出现的UI则等待更长的时间。
  • 使用延迟加载的方式,一些不常用的资源在第一次使用的时候再进行加载。
  • 在使用池进行内存管理时特别要注意,当一个物体你不再需要的时候,请将其置为null。例如你封装了一个数组,其中装入了许多的对象,当你移除一个对象的时候或许并没有将其真正置空,而是移动了目前指向的位置,那么你本应移除的对象就泄漏了出去。
  • 不要主动调用GC。而是通过良好的代码,即时去除不需要的对象的引用可以更好地让我们使用GC来回收垃圾。

**

数据传输 lua与c#交互优化

**
原文链接:https://blog.uwa4d.com/archives/USparkle_Lua.html
有需求 可以去原文支持一下 , 我只是做一遍总结

大部分结论都是基于uLua+CsToLua的测试得出来的,sLua都是基于其源码来分析,但没有做过深入测试,如有问题的话欢迎交流。
(我们使用的是tolua, 文章的优化效果 还是挺明显的)

既然是Lua+Unity,那性能好不好,基本上要看两大点:

  1. Lua跟C#交互时的性能如何
  2. 纯Lua代码本身的性能如何

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,整个流程如下:

    1. C#中拿到Vector3的x、y、z三个值;
    2. Push这3个float给Lua栈;
    3. 然后构造一个表,将表的x,y,z赋值;
    4. 将这个表push到返回值里。

    一个简单的传参就要完成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。这带来几个好处:

    1. 函数调用的性能更好;
    2. 明确地管理这些Object的生命周期,避免让ULua自动管理这些对象的引用,如果在Lua中错误地引用了这些对象会导致对象无法释放,从而内存泄露;
    3. C#Object返回到Lua中,如果Lua没有引用,又会很容易马上GC,并且删除ObjectTranslator对Object的引用。自行管理这个引用关系,就不会频繁发生这样的GC行为和分配行为。
  • 合理利用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%的时间。

**

AssetBunlde一些详解

**

一些写的很好的学习资料

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)来逐个进行卸载,以保证游戏的流畅度。

**

Android的硬件缩放技术优化执行效率

**
原文链接:http://www.xuanyusong.com/archives/3205

找一个合适的地方调用如下方法即可,这个方法在IOS和Android上都支持,但是经过测试IOS没必要使用,在android上还是很有必要使用的。

Screen.SetResolution(960,640,true);

大概原理就是: 强制把屏幕的分辨率指定成960X640, 然后取出宽高, 缩放. 然后安卓渲染的应该是 960 * 640 分辨率的. 提升应该挺大的. 有兴趣的可以去原文看看.会有几个常见的问题解决方案

**

基于看书讲解游戏<最强nba>的一些性能思路

**
重要:
书名:***<腾讯游戏开发精粹>***
如有能力请去支持原版书,本文只是做一些总结和摘录,如涉及到版权问题,请联系删除 谢谢

  • 通过对角色头部,衣服,裤子,四肢.身高,体型肤色等元素的差异化整理,制作一套通用的资源和专属资源
  • 复用模型和贴图,减少包体和内存消耗
  • 标准模型共用骨骼
  • 运行时合并贴图 合并网格 解决拆分造成的
  • 通过修改顶点着色器 实现胖瘦
  • 缩放骨骼根节点 实现 不同高度的身高

gpu中实现体型思路

  • 模型顶点的法线为我们提供了最合适的向量方向,正是体型所需要的模型表面向外凸起或者向内收缩的方向。
  • 对于向量的长度,需要根据每个角色来定制,我们可以根据角色配置不同的数值。
  • 对于顶点的权重,有较多的选择,贴图、顶点的切线、UV2、顶点色等都可以供我们存储权重,而且都可以存储多维向量,方便我们区分肌肉和脂肪。使用贴图存储,绘制方便,可以在着色器顶点程序中对贴图进行采样获得权重,但顶点纹理拾取(Vertex
    Texture Fetch)需要Shader Model 3.0,我们希望可以在更低端的Shader Model
    2.0下运行。如果使用顶点的UV2、切线信息来存储,则避免了使用Shader Model 3.0和对纹理的采样,但是制作时无法使用DCC(Digital Content Creation)工具直接绘制,需要另外开发工具支持。因此,我们最终选择适应配置要求低、性能消耗小又方便绘制的顶点色来存储权重。
  • 这样,我们就可以通过法线向量权重配置的数值,来得到所期望的顶点移动向量,实现体型

结果:

  • 我们制作了一套标准的1.98m的角色模型,使用同一套标准的骨骼,所有动作资源可以在不同角色间通用。
  • 通过组合不同头部以及通用衣服、裤子和鞋模型,可以得到所有差异化的角色模型。
  • 通过组合球衣、球裤贴图和号码贴图,可以得到所有角色所需的球衣、球裤贴图(其他服饰等制作相同,除没有号码贴图合并以外)。
  • 通过选取一套通用肤色贴图,搭配专属的头部贴图,可以得到所有角色所需的贴图。
  • 通过根骨骼的缩放,可以得到正确的身高,再通过头部骨骼的缩放,得到正确的头部比例。

cpu整体思路

模型合并流程如下:
(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();
     }
    }

gup整体思路

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;

优势& 劣势

优势:

  • 同一套骨骼以及大量动画可以适应不同体型的角色。
  • 减少了Drawcall,可以提升性能。
  • 兼容低端移动硬件。

劣势:

  • 在运行时进行角色模型和贴图合并,会带来一定的性能和时间消耗,延长了进入正式游戏前的等待时间。
  • 贴图的合并,带来了内存消耗的增大。随着角色数量的增加,内存占用量也会呈线性增长。
  • 整个角色使用同一种材质,美术效果有所降低。

**

unity + lua ---- 内存泄露的一些误解 & 问题定位过程

**

原文链接: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这一层。

我们来对整个逻辑做一下梳理和回顾:

  1. 在没有UI缓存的情况下,每创建一个ui,都会去初始化对应的prefab,并且Lua层会获取自己需要设置的那些GameObject以及Component,这时候这些对象都会在ObjectTranslator这层有记录;
  2. 当UI关闭的时候,会调用GameObject.Destroy函数,将对应的C# GameObject销毁;
  3. 这时候,Lua中那些对于C#对象的应用并不会销毁,因为没有调用Lua的GC,于是出现了ObjectTranslator这层依然保存着这些对象的引用的情况;
  4. 由于Lua的内存增长比较慢,所以对于GC的触发非常不频繁;
  5. C#部分GC的时候,对于这些在ObjectTranslator层记录的对象,虽然它们在Unity眼中已经不再被使用了,与null的相等判定结果是true,但是作为System.Object对象,它们实际上并不是null,而且在被ObjectTranslator对象引用,无法释放占用的内存空间,这就导致了内存的增长,即使触发了C#的GC逻辑,也无法进行释放;
  6. 当Lua的GC被调用过一次之后,下次C#的GC就可以释放掉这部分的对象。

解决方案:

  1. 比较理想的方式,其实是在C#触发GC的时候,先去调用一次Lua的GC,这样让两边的GC有一个同步的过程,可以多地释放掉无用的内存。但是这种方式不太好实现,貌似没找到方便监听系统触发GC的逻辑。
  2. 使用更高频率的Lua GC。我们之前Lua手动GC的方式是在状态改变的时候,这次针对ui开关的测试是无法触发到Lua的手动GC的,那么一种思路是按照一个间隔来手动触发Lua的GC,尽早释放掉内存。但是这个实际其实比较难找,做不好会造成莫名其妙的顿卡。
  3. Tolua的作者蒙哥建议在关闭ui这样的节点,手动做一下一个小Step的GC,这样可以保证释放掉一部分内存。这个Step的参数要自己调整好,过大会在关闭ui的时候造成顿卡,过小又没办法及时释放内存。
  4. 在Lua中确定不再需要C#对象的时候,手动使用System.Object的Destroy函数进行释放,这个Warp出来的函数Tolua做了特殊的处理,会调用Tolua.Destroy来进行释放。这种就相当于针对这些对象放弃了自动GC的逻辑,需要手动进行释放,好处是可以精准控制,但是坏处是很繁琐,需要对于代码做大量的重构。
  5. 在C#层,做一个tick逻辑,每帧检查ObjectTranslator中的objects中的一部分对象,如果是Unity.GameObject类型的,查看其是否等于null,如果作为Unity.GameObject对象是null,而作为System.Object对象不是null,说明这个对象已经被Unity标记为销毁了,Unity.GameObject重载的==运算符让游戏逻辑认为它是空的,这时候C#对象可以提前销毁掉,因为即便Lua层想访问它,也已经会报错了。

总结:

这个内存泄露的问题困扰了我们大约一个多周的时间,这里记录的只是一些排查的关键步骤,对于中间的思考、讨论、对比等等细节无法完整地记录。由于项目临近上线,而合作方给予的测试用例也是一种比较极限的情况,所以最终线上的版本没有修复这个问题。正常进行游戏会有相对频繁的状态跳转,因此会有手动触发Lua GC的逻辑,可以让Mono内存不会累积到100多兆那么夸张的程度,因此对于玩家的影响不是很大。

**

Draw Call未被批处理?在Unity 5.6中如何找出原因

**

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)中导致无法进行批处理的原因

    • AdditionalVertex Streams —
      对象使用MeshRenderer.additionalVertexStreams设定了额外的顶点信息流。
    • DeferredObjects on Different Lighting Layers — 该物件位于另一不同的光照层中。
    • DeferredObjects Split by Shadow Distance —
      两个物体中有一个在阴影距离范围内而另一个不是。
    • DifferentCombined Meshes — 该对象属于另一个已合并的静态网格。
    • DifferentCustom Properties — 该对象设定了不同的MaterialProperyBlock。
    • DifferentLights — 该物件受不同的前向光照(Forward Light)影响。
    • DifferentMaterials — 该对象使用不同的材质。
    • DifferentReflection Probes — 该对象受不同的反射探头(Reflection Probe)影响。
    • DifferentShadow Caster Hash —
      该对象使用其他的阴影投射着色器,或是设定了不同的着色器参数/关键词,而这些参数/关键词会影响阴影投射Pass的输出。
    • DifferentShadow Receiving Settings — 该对象设定了不同的“Receive
      Shadows”参数,或是一些对象在阴影距离内,而另一些在距离之外。
    • DifferentStatic Batching Flags — 该对象使用不同的静态批处理设定。
    • DynamicBatching Disabled to Avoid Z-Fighting — Player
      Settings中关闭了动态批处理,或在当前环境中为避免深度冲突而被临时关闭。
    • InstancingDifferent Geometries — 使用GPU Instancing渲染不同的网格或子网格。
    • LightmappedObjects — 对象使用了不同的光照贴图,或在相同的光照贴图中有不同的光照贴图UV转换关系。
    • LightprobeAffected Objects — 对象受其他光照探头(Light Probe)影响。
    • MixedSided Mode Shadow Casters — 对象的“Cast Shadows”设定不同。
    • Multipass — 对象使用了带多个Pass的着色器。
    • MultipleForward Lights — 该物件受多个前向光渲染影响。
    • Non-instanceableProperty Set — 为instanced着色器设定来non-instanced属性。
    • OddNegative Scaling — 该对象的缩放为很奇怪的负值,例如(1,-1,1)。
    • ShaderDisables Batching — 着色器使用“DisableBatching”标签显式关闭了批处理。
    • TooMany Indices in Dynamic Batch — 动态批处理索引过多(超过32k)。
    • TooMany Indices in Static Batch — 静态批处理中的组合网格索引过多。对于OpenGL
      ES来说是48k,OSX是32k,其他平台是64k。
    • TooMany Vertex Attributes for Dynamic Batching —
      欲进行动态批处理的子网格拥有超过900个顶点属性。
    • TooMany Vertices for Dynamic Batching — 欲进行动态批处理的子网格顶点数量超过300个。

**

一些性能检测工具

**

我忘记哪个有免费的次数了 可能有些是收费的

**

一些对于网格的测试分析 & 建议

**

原文链接:https://blog.uwa4d.com/archives/LoadingPerformance_Mesh.html

简单描述一下成果 具体过程及原理 请移步原始链接

  • 测试1:不同面片数的网格资源加载效率测试

1、资源的数据量对加载性能影响较大,面片数越多,其加载越为耗时。设备性能越差,其耗时差别越为明显;
2、随着硬件设备性能的提升,其加载效率差异越来越不明显。

  • 测试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个模型的顶点属性补齐,然后再进行拼合。这样无形中会增加大量的内存占用,从而造成不必要的内存浪费。

  • 测试3:开启/关闭Read/Write功能的加载效率测试

1、关闭Read/Write功能会降低AssetBundle的物理大小,其降低量与资源本身数据量相关。同时,关闭Read/Write功能会大幅度降低网格资源的内存占用;
2、关闭Read/Write功能会略微提升该资源的加载效率。

通过以上测试和分析,我们对于网格资源的管理建议如下:

1、在保证视觉效果的前提下,尽可能采用“够用就好”的原则,即降低网格资源的顶点数量和面片数量;
2、研发团队对于顶点属性的使用需谨慎处理。通过以上分析可以看出,顶点属性越多,则内存占用越高,加载时间越长;
3、如果在项目运行过程中对网格资源数据不进行读写操作(比如Morphing动画等),那么建议将Read/Write功能关闭,既可以提升加载效率,又可以大幅度降低内存占用。

**

一些对于shader的测试分析 & 建议

**

原文链接: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进行单独、依赖关系打包并对其进行预加载,以降低后续不必要的加载开销。

**

分析一下Profiler中几项CPU开销过大的情况 & 一些误解

**

原文链接: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

    • 这两个参数在Profiler中经常出现CPU占用较高的情况,且仅在发布版本中可以看到。究其原因,其实是CPU和GPU之间的垂直同步(VSync)导致的,之所以会有两种参数,主要是与项目是否开启多线程渲染有关。当项目开启多线程渲染时,你看到的则是Gfx.WaitForPresent;当项目未开启多线程渲染时,看到的则是Graphics.PresentAndSync。
    • Graphics.PresentAndSync 是指主线程进行Present时的等待时间和等待垂直同步的时间。Gfx.WaitForPresent其字面意思同样也是进行Present时需要等待的时间,但这里其实省略了很多的内容。其真实的意思应该是为了在渲染子线程(Rendering Thread)中进行Present,当前主线程(MainThread)需要等待的时间。听起来依然很拗口,下面,我们就来进行详细地解释。
    • 当项目开启多线程程渲染时,引擎会将Present等相关工作尽可能放到渲染线程去执行,即主线程只需通过指令调用渲染线程,并让其进行Present,从而来降低主线程的压力。但是,当CPU希望进行Present操作时,其需要等待GPU完成上一次的渲染。如果GPU渲染开销很大,则CPU的Present操作将一直处于等待操作,其等待时间,即为当前帧的Gfx.WaitForPresent时间,如下图所示。在这里插入图片描述
    • 同理,当项目未开启多线程渲染时,引擎会在主线程中进行Present(当前绝大多数的移动游戏均在使用该中操作),当然,Present操作同样需要等待GPU完成上一次的渲染。如果GPU渲染开销很大,则CPU的Present操作将一直处于等待操作,其等待时间,即为当前帧的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占用较高的原因主要有以下三种原因:

    • CPU开销非常低,所以CPU在等待GPU完成渲染工作或等待VSync的到来;
    • CPU开销很高,使Present错过了当前帧的VSync,即不得不等待下一次VSync的到来;
    • GPU开销很高,CPU的Present需要等待GPU上一帧渲染工作的完成。

**

优秀插件 Mesh Baker 提高场景运行帧率

**

原文链接:http://blog.sina.com.cn/s/blog_15ff4f4c80102whyg.html
(挺简单的插件 有需要 请去原始链接下载 如果失效 请找别的教程 插件名称: Mesh Baker)

工作原理:

工具会寻找当前场景中使用相同材质球的物体分类,然后把同一个Shader的物体给统一合并成尽量少的贴图和材质球和Mesh来提高运行效率。

**

世界动态加载

**

原文链接:https://blog.uwa4d.com/archives/1919.html

大概原理就是 动态加载 + 缓存 + 导出自动拆分脚本 + 实战优化技巧

有兴趣 可以去原链接看看 学习一下也是蛮不错的

**

Unity大密度建筑场景加载解决方案

**

原文链接:https://blog.csdn.net/jxw167/article/details/82455746

一些插件: 包括 裁剪 剔除 批处理 动态加载 一些检测 的解决方案

针对密集型建筑使用的解决方案,该方法在PC端,移动端都是适用的,最后把代码奉上,参考案例代码可以将其应用到自己的项目开发中

如有需求 去原链接下载 也是蛮好玩的 可以去看看

**

大牛分享:100条Unity基础小贴士

**

原文链接:http://www.manew.com/thread-143268-1-4.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

unity性能优化方案整理 & 一些思路 & 一些技巧(持续更新 2019-09-12) 的相关文章

  • 获取CPU温度

    我想知道CPU的温度 以下是我使用 C 和 WMI 所做的工作 我正在读取 MSAcpi ThermalZoneTemperature 但它始终相同 而且根本不是 CPU 温度 有没有办法不用写驱动就能获取CPU的真实温度 或者有什么我可以
  • 线程(在 Java 或 C++ 程序中)与 CPU 核心数之间有什么关系?

    有人可以解释一下吗 i7 处理器可以运行 8 个线程 但我很确定我们可以在 JAVA 或 C 程序中创建超过 8 个线程 但不确定 我有一个 i5 处理器 在研究并发性时我创建了 10 个线程用于分配 我只是想了解 CPU 的核心评级与线程
  • 用于查找 UNIX 计算机上 CPU 信息的命令

    您知道是否有一个 UNIX 命令可以告诉我 Sun OS UNIX 机器的 CPU 配置是什么 我也在尝试确定内存配置 有没有 UNIX 命令可以告诉我这一点 AFAIK 没有标准的 Unix 命令 我没有使用过Sun OS 但是在Linu
  • linux内存初始化时内核CPU使用率高

    在服务器上引导我的 java 应用程序时 我遇到了 Linux 内核 CPU 消耗高的问题 此问题仅发生在生产中 在开发服务器上一切都是光速 upd9 关于这个问题 有两个疑问 如何修复它 名义动物建议同步并删除所有内容 这确实有帮助 su
  • U3D游戏开发中摇杆的制作(UGUI版)

    在PC端模拟摇杆 实现玩家通过控制摇杆让玩家移动 以下是完整代码 using System Collections using System Collections Generic using UnityEngine using Unity
  • U3D游戏开发中摇杆的制作(NGUI版)

    在PC端模拟摇杆 实现控制摇杆让玩家或者物体移动 以下是完整代码 using System Collections using System Collections Generic using UnityEngine public clas
  • 游戏开发之常见操作梳理——武器装备商店系统(NGUI版)

    游戏开发中经常出现武器商店 接下来为你们带来武器装备商店系统的具体解决办法 后续出UGUI Json版本 敬请期待 武器道具的具体逻辑 using System Collections using System Collections Ge
  • 游戏开发常见操作梳理之角色选择一

    进入游戏后 我们经常会进入角色选择的界面 通常是左右两个按钮可以更改角色供玩家选择 对于这种界面我们通常使用数据持久化将角色信息存储起来 接下来的笔记中 我将使用自带的数据持久化系统对其进行操作 实现角色的选择页面 后续会更新xml系列的文
  • 游戏开发常见操作系列之敌人系统的开发一(U3D)

    在开发游戏的过程中 我们常常会出现一些敌人攻击我们玩家 并且实现掉血以及死亡的现象 敌人还会源源不断地生成 这是怎么制作的呢 接下来为大家提供方法 其中使用了NGUI 后续会更新其它方法 敬请期待 使用HUDText实现扣血时显示文本 直接
  • 使CPU的缓存失效

    当我的程序执行具有获取语义的加载操作 具有释放语义的存储操作或可能是完整栅栏时 它会使 CPU 的缓存无效 我的问题是 缓存的哪一部分实际上失效了 只有保存我使用的获取 释放变量的缓存行 或者整个缓存都失效了 L1 L2 L3 等等 当我使
  • 为什么CPU负载的变化不会超过百分之几?

    我正在运行这个命令 grep cpu proc stat awk usage 2 4 100 2 4 5 END print usage 但它只输出 0 99xxxx 之类的东西 如果我进行 apt get 升级或任何过程 我想它会超过 1
  • 如何使用 bash 命令创建 CPU 峰值

    我想在 Linux 机器上创建接近 100 的负载 它是四核系统 我希望所有核心都全速运行 理想情况下 CPU 负载将持续指定的时间 然后停止 我希望 bash 有一些技巧 我在想某种无限循环 I use stress http linux
  • 多处理和并行处理之间的比较

    有人能告诉我多处理和并行处理之间的确切区别吗 我有点困惑 感谢您的帮助 多重处理 多重处理是使用两个或多个中央处理单元 单个计算机系统中的 CPU 该术语还指 系统支持多个处理器和 或的能力 在他们之间分配任务的能力 并行处理 在计算机中
  • 如何让Java使用机器上的所有CPU资源?

    我有时用 Java 编写代码 我注意到有时它在多核机器上使用超过 100 的 CPU 我现在正在一台具有 33 个 CPU 亚马逊的 EC2 的多核机器上运行一些代码 我想让我的 Java 进程使用所有可用的 CPU 这样它将具有非常高的机
  • 为什么每个逻辑 CPU 在多线程情况下都有自己的 CR3 寄存器?

    当我们有一个支持某种形式的多线程的 CPU 时 每个逻辑 CPU 都有它自己的一组寄存器 至少 包括 CR3 寄存器 由于我们在执行不同线程时正在处理同一进程的虚拟地址空间 并且永远不会发生上下文切换 切换同一进程的线程时TLB缓存也不会失
  • java中获取某些进程的cpu使用率的正确命令行是什么

    给定进程 ID 在 Java 中从进程获取当前 cpu 使用情况的正确命令是什么 命令 typeperf Memory Available bytes processor total process time 不适用于特定进程 并且任何第
  • 当JVM执行Java应用程序时,操作系统的作用是什么?为什么我们需要操作系统?

    我在网上读过一些资料 有人说Java应用程序是由java虚拟机 JVM 执行的 执行 这个词让我有点困惑 据我所知 非Java应用程序 即 用C C 编写 可以由操作系统执行 在较低级别 这意味着操作系统将二进制程序加载到内存中 然后指示C
  • MIPS 中的影子寄存器是什么以及它们如何使用?

    当我了解 MIPS 架构时 我遇到了影子寄存器 据说它们是通用寄存器的副本 我无法理解以下内容 何时使用影子寄存器 MIPS 影子寄存器用于减少处理中断时的寄存器加载 存储开销 分配了影子寄存器组的中断不需要保存任何现有上下文来提供空闲寄存
  • Linux:如何对系统内存施加负载?

    我正在开发一个小功能 它可以让我的用户了解 CPU 的占用情况 我在用着cat proc loadavg 它返回众所周知的 3 个数字 我的问题是 当我正在开发时 CPU 目前没有做任何事情 有没有一种好方法可以在CPU上产生一些负载 我在
  • 哪种架构称为非均匀内存访问(NUMA)?

    根据wiki http en wikipedia org wiki Non uniform memory access 非均匀内存访问 NUMA 是一种用于多处理的计算机内存设计 其中内存访问时间取决于相对于处理器的内存位置 但尚不清楚它是

随机推荐

  • pdf格式文件下载不预览,云存储的跨域解决

    需求背景 后端接口中返回的是pdf文件路径比如 pdf文件路径 https wangzhendongsky oss cn beijing aliyuncs com wzd test pdf 前端适配是这样的 a href https wan
  • Java: Java学习笔记之 String的常见用法

    String的常见用法 String的常见用法 1 创建String 2 字符串比较 3 字符串查找 4 字符串替换 5 字符串拆分 6 字符串截取 7 转换功能的方法 8 其他操作方法 9 StringBuffer 和 StringBui
  • Mybatis基础全集适合新手(面试大全)

    Mybatis 简介及搭建 一 MyBatis简介 1 MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis 2010年6月这个项目由Apache Software Foundation迁移到了Google Cod
  • 保姆级--Ubuntu 安装Django并简单应用第一个项目

    一 虚拟机创建Ubuntu 准备 一台正常的电脑 已经安装的Oracle VM VirtualBox虚拟机软件 版本随意 已经下载好的Ubuntu系统镜像 这里演示最新版 按需下载 创建虚拟机 打开虚拟机软件Oracle VM Virtua
  • Ubuntu 18.04问题收集

    1 网络不通 1 时间溢出会导致网络不通 相关链接 https blog csdn net knico article details 82018715 2 Ubuntu关机慢 1 网上找了一下 说是安装了mysql 关机要10分钟 相关链
  • 用java实现杨辉三角

    杨辉三角 将第一行中间的数记为1 两边的数记为0 则第二行的数为其左上角的数加上右上角的数的和 如上图 代码如下 import java util Scanner public class demo1 杨辉三角 public static
  • vue中axios学习

    文章目录 axios实现get和post axios结合vue axios实现get和post then中的回调函数分别在请求 成功或失败 后触发
  • android 中 Drawable 和 ConstantState 的一些认识

    Drawable Drawable就是一个可绘制的对象 或者叫做可渲染的对象 里面保存的是可以绘制的数据 其可能是一张位图 BitmapDrawable 也可能是一个图形 ShapeDrawable 还可能只是一个颜色 ColorDrawa
  • 解决移进/规约冲突

    How to resolve shift reduce conflicts 如何解决移进规约冲突 比如下面的文法就有冲突 expr expr expr expr expr expr 对于输入 1 2 解析完1后 可以继续移进 或者根据规则
  • 数组(1)

    https note youdao com s coQY0U5bhttps note youdao com s coQY0U5b
  • UnityShader学习教程之<关于颜色的详解,与shader后期调色的实现>

    今天讲解shader中的颜色color 颜色是由rgb混合而成的 color float4 r g b a 这是颜色的公式 我们想要自己调色 只需要改变这些颜色的值 就可以实现颜色有自己控制了 首先我们可以输出我们的顶点颜色 看看当前的界面
  • JDK8新特性(一):Lambda表达式

    1 首先来个Demo public class LambdaDemo public static void main String args 开启一个线程 new Thread new Runnable Override public vo
  • 数据分析之T检验

    1 定义 t检验 亦称student t检验 Student s t test 主要用于样本含量较小 例如n lt 30 总体标准差 未知的正态分布 t检验是用t分布理论来推论差异发生的概率 从而比较两个平均数的差异是否显著 它与f检验 卡
  • UE4变量Config设置

    UE4将变量config设置 在UE4中可以将变量的设置放进config文件中 可以在打包后直接修改配置文件的方式修改变量的初值 例如 新建一个C Actor类 在类的UCLASS中加入Config宏 UCLASS Config MyAct
  • CubeMX STM32串口1DMA使用IDLE中断接收、串口2DMA接收DMX512信号(标准)

    CubeMX STM32串口1DMA使用IDLE中断接收 串口2DMA收发DMX512信号 标准 DMX512协议 CubeMX 代码部分 串口1 串口2 外部中断 定时器1 总结 DMX512协议 这是我第一次写文章 请大家多多指教 最近
  • 算法训练营第三十四天(8.23)

    目录 Leecode 1049 最后一块石头的重量II Leecode 494 目标和 Leecode 474 一和零 Leecode 1049 最后一块石头的重量II 题目地址 力扣 LeetCode 官网 全球极客挚爱的技术成长平台 题
  • Java中的数组互相赋值

    本文探讨Java中数组中的赋值问题 在探讨这个问题之前必须先弄懂一件事 Java中的数组到底是什么东西 是类 是对象 还是什么其他奇奇怪怪的东西 答案是 Java中的数组本质上是对象 但是这个对象不是通过某个类实例化来的 而是JVM创建的
  • 明显调用的表达式前的括号必须具有(指针)函数类型 编译器错误 C2064

    看到 明显调用的表达式前的括号必须具有 指针 函数类型 这句时我才发现我的语文水平有多烂 怎么看都看不懂 折腾了半天才知道是哪里出了问题 举个简单的例子 class CTest void CTest m pFun void CallFun
  • 数据库连接超时No operations allowed after conn

    com mysql jdbc exceptions jdbc4 MySQLNoTransientConnectionException No operations allowed after connection closed 分析 出现这
  • unity性能优化方案整理 & 一些思路 & 一些技巧(持续更新 2019-09-12)

    原文链接1 https www cnblogs com zhenlong p 4862869 html 原文链接2 http www xuanyusong com archives 3205 原文链接3 https www cnblogs