# 前言

关于 Unity3D 中的各种优化方式,相信早已是个老生常谈的问题了,不少人都开过坑。
为了避免让人觉得不开坑的话,就好像不知道这些优化措施一样,索性我也来这么一次。

说到对游戏的优化,有过相关了解的人,首先应该知道公认的主要有三个方面:CPU、GPU、内存相关优化。
这三个方面,也即是关乎整个游戏运行效率的三个 “根” 节点。

好了,那么下面有请在无数示例中都有所贡献的 “Window 绘图工具”—— 我很想这样说,不过由于最后画出来发现因为太复杂,导致很难看,所以决定直接用文字排版算了 (我会注意按照相应顺序进行排版的)。

# 优化

# CPU 优化

1.DrawCall

  • Dynamic Batching
    动态批处理,所有操作都已经由引擎自动完成了,不需要我们做任何事儿。
    需要注意的只有 “是否满足要求”:

    • Mesh 顶点小于 900 (若 Shader 使用过 Normnal、UV、Position 等,顶点支持小于 300)
    • 材质不同,不支持
    • 拥有 lightmap 的物件不能批处理
    • 多通道的 Shader 会妨碍批处理操作,接受实时阴影的物件无法批处理
    • 满足上诉条件,但是缩放不同也不能支持
      所以都说,动态批处理限制很多,最好用静态批处理。
  • Static Batching
    静态批处理,相比动态批处理来说,限制更小 (缩放、顶点等没啥影响)。在不需要移动的物体上勾选 Static 即可。当勾选 Static 之后,在场景时就会将其合并至 “Combined Mesh” 中,相当于该 GameObject 已经失去了 “自我”,所以也无法再行移动。
    如图:
    Static Batching
    这种方式可以将使用了相同材质的任意大小物体进行合并,以减少 Drawcall。
    PS:材质不同 DrawCall 是降低不了的,这点类似动态批处理,所以不要以为勾选了 Static 就完事儿了!因此再分细一点的话,还需将各个 GameObject 使用的小的贴图合并成大的图集,并采用相同材质进行优化。
    另外,再来一点:对于 StaticBatching 还可以使用代码 StaticBatchingUtility.Combine() 进行 StaticBatching 处理。

  • 遮挡剔除
    Unity 自带了 Occlusion Culling 烘培工具,可进行遮挡剔除的烘培处理。

  • 视椎体剔除
    可以通过调整相机的 Near、Far 剪裁平面距离进行一定优化。

  • 摄像机分层距离剔除 (Per-Layer Cull Distances)
    关于这个,可以在主相机 Camera.main.layerCullDistances 进行设置。

  • 光照
    实时光照 + 阴影会极大地增加 Drawcall,因为带有光源计算的 Shader 因为使用额外 Pass 计算阴影造成 Drawcall 增加。 一般情况下,最好使用烘焙光照贴图 + LightProp 等优化。
    (PS: 实时光照阴影真的很耗,没昼夜切换的要求就算了吧,就算有,若没有实时光照改变的需求,也可以通过烘培多张 LightMap 来 Trick。)

  • 骨骼

  • 没有动画的物体,在导入时务必确保删除了动画组件 (有时候模型没动画也会自动被添加)

2. 物理

  • 碰撞器
    各个碰撞器性能 (由小至大):
    SphereCollider->CapsuleCollider->BoxCollider->MeshCollider
    其中 MeshCollider 完全是 “能不用就千万别用” 的典范。
    平时可以根据上面排行,进行选择吧。
  • 物理查询
    尽量少用,并且不要每帧查询。(比如检测地面之类的)

3.GC

GC 是.Net 的垃圾回收机制,虽然主要是处理内存,但是在进行内存回收的时候,会极大地增加 CPU 的负担,所以... 也是需要注意的一点,尽量少触发 GC。
比如:

  • 链接字符串
  • 使用 For 代替 Foreach
  • 减少创建新对象的频率,如使用对象池技术
  • 能使用 Struct 的时候就不要用 Class
  • 等...

4. 代码
代码优化涉及到的东西就多了,而且相信各有各的经验,就不多提了。
(其中部分内容可参考下文的 “代码内存” 优化)

# GPU 优化

对于 GPU 方面的优化,主要有下面几个:

  • 顶点数量

    • 让美术优化模型 (比如使用法线、视差贴图代替高模渲染)
    • 使用 LOD
    • 若模型并未使用法线贴图,可以在导入的时候关闭 Normals&Tangents 导入。
    • 遮挡剔除 (上面 CPU 优化也有提到)
    • 视椎体剔除 (上面 CPU 优化也有提到)
    • 摄像机分层距离剔除 (Per-Layer Cull Distances)(上面 CPU 优化也有提到)
  • 像素填充率

    • 尽量少使用透明物体
      为什么呢?据说是因为现代显卡大多采用了 Early-Z 技术,进行透明物体处理将会与其发生冲突,导致更多的像素需要被处理。
    • 实时光与阴影
  • 显存带宽

    • 压缩纹理贴图
    • 使用 Mipmap
      Mipmap 可根据需要,向 GPU 发送更小细节的图片进行渲染。
  • Shader

    • 可以的情况下,尽量使用 Fixed、Half 代替 Float 进行计算。
    • 在 Shader 的 Fragment 中进行更少的计算,可能的话,移至 Vertex 程序中计算,并由 GPU 插值。
    • 尽量不使用 If 语句
    • 手机游戏就不要使用 Standard Shader 了,若有需要,可以使用 Mobile Shader 或者自己写个 V&F Shader。(Mobile Shader 使用的是 Surface Shader,多了很多冗余代码)

# 内存优化

  • 代码内存 (.Net 托管内存)
    • 链接大量字符串使用 StringBuilder
    • For 代替 Foreach
    • 使用 Struct (值类型) 代替 Class (引用类型),另外,若使用 Struct,一般可配合 Ref 关键字避免大量拷贝。
    • 减少局部变量 (或使用成员变量代替)
    • 使用对象池技术,减少创建新对象的频率
    • 等 (待补充...)
  • Unity 内存
    • 对动态加载的资源 (Resources.load 或者 AssetBundle) 使用完毕后,使用相应接口进行释放。
      (Resources.UnloadUnusedAssets()、assetBundle.Unload(false))
    • 纹理压缩
    • 图集
    • 等 (待补充...)

# 结语

目前总结如此,简单罗列了下自己所了解到的内容,后续会根据我自己的了解,进行对应的补充。

以上。