1. 首页菜单

Unity3D技术之移动开发优化详解

2015年03月15日 16:57 0 点赞 0 评论 更新于 2025-11-21 13:29

移动开发优化

和PC一样,iOS和安卓(Android)等移动平台也存在各种不同性能级别的设备。很容易就能找到一部渲染效果比其他手机好10倍的手机。以下是一些缩放方法:

  1. 确保基准配置正常运行:保证应用程序在基准配置的设备上能够正常运行。
  2. 高性能配置使用更多特效:在高性能配置的设备上,可以使用更多特效,如更高的分辨率、后处理、多重采样抗锯齿、各向异性、复杂的着色器以及更高的特效/粒子密度等。

注重GPU

图形性能与填充率、分辨率和几何体复杂度(顶点数)密切相关。若能找到方法剔除更多渲染内容,这三个要素的值都会降低,此时遮挡剔除就能发挥有效作用。Unity会自动剔除视锥外的对象。

在移动平台上,基本上会受到填充率的约束(填充率 = 屏幕像素 着色器复杂度 透支度),着色器过于复杂是导致问题出现的最常见原因。因此,建议使用Unity自带的移动着色器,或者自己设计尽可能简单的着色器。如果可行,将代码移动到顶点着色器中,以简化像素着色器。

如果降低质量设置(Quality Settings)中的纹理质量(Texture Quality)后游戏运行速度加快,那么很可能是受内存带宽所限。此时可采取压缩纹理、使用贴图细化、降低纹理大小等措施。

LOD(细节等级)可以使对象在远处变得更简单,甚至完全消除,其主要目的是减少绘制调用的次数。

出色实践

移动GPU在热量产生、电量使用、尺寸大小和噪音方面都有严格限制。与台式机部件相比,移动GPU的带宽较小、运算器(ALU)性能和纹理功率也较低。其架构也进行了调整,以尽量使用最小带宽和功率。

Unity针对OpenGL ES 2.0进行了优化,使用GLSL ES(类似于高级着色语言HLSL)着色语言。内置着色器大多使用高级着色器语言(即HLSL,也称之为Cg)编写,针对移动平台,这些着色器会被交叉编译成GLSL ES。如果您想直接编写GLSL,会限制您访问OpenGL平台(如移动 + Mac),因为目前还没有GLSL->HLSL转换工具。在HLSL中使用浮点/半浮点/固定(float/half/fixed)类型时,它们在GLSL ES中会以highp/mediump/lowp精度限定符结尾。

以下是一些出色实践:

  1. 减少材质数目:尽量使材质数目减至最低,便于Unity轻松进行批处理。
  2. 使用纹理精灵:使用纹理精灵(大贴图包含多个子贴图),而非许多单个纹理,这样可以使加载速度更快,状态转换更少,批处理更方便。
  3. 使用共享材质:如果使用纹理精灵和共享材质,请用Renderer.sharedMaterial代替Renderer.material。
  4. 优化灯光:向前渲染像素灯比较耗性能,尽量使用光照贴图代替实时灯光;在质量设置中调整像素灯数目,基本上只有方向灯为逐像素,其他都为逐顶点,当然这取决于游戏本身;在质量设置(Quality Settings)中尝试调整灯光的渲染模式(Render Mode of Lights),以获得正确优先级。
  5. 避免使用特定着色器:避免使用抠图(透明度测试)着色器,除非真正有必要。
  6. 控制透明覆盖范围:将透明(alpha混合)屏幕的覆盖范围保持在最小。
  7. 避免多灯光照亮同一对象:努力避免多个灯光照亮任何给定对象的情况。
  8. 降低着色器通道总数:努力降低着色器通道(阴影、像素灯、反射)的总数目。
  9. 注意渲染顺序:渲染顺序很关键,一般情况下,先渲染从前往后完全不透明的对象,接着大致从前往后渲染经alpha测试的对象,然后是天空盒,最后是alpha混合对象(若需要,从后往前)。
  10. 谨慎使用后处理:在移动平台上进行后处理性能消耗大,请谨慎使用。
  11. 优化粒子:粒子方面,减少透支,尽量使用最简单的着色器。
  12. 使用双缓存网格:每一帧都要修改的网格使用双缓存,示例代码如下:
    void Update (){
    // flip between meshes
    bufferMesh = on ? meshA : meshB;
    on = !on;
    bufferMesh.vertices = vertices; // modification to mesh
    meshFilter.sharedMesh = bufferMesh;
    }
    

着色器优化

检查是否受填充率绑定很简单:如果降低显示分辨率,游戏运行速度加快,那么就受到了填充率的限制。

可以尝试使用以下方法降低着色器的复杂度:

  1. 避免使用alpha测试着色器:避免使用alpha测试着色器,而是采用alpha混合版本。
  2. 使用简单优化的着色器代码:使用简单、已优化的着色器代码(如Unity附带的“移动(Mobile)”着色器)。
  3. 避免使用高消耗数学函数:避免在着色器代码中使用性能消耗大的数学函数(如指数表达式、指数、对数、余弦、正弦、正切等),考虑使用预计算的查找纹理代替。
  4. 选择低精度格式:为实现最佳性能,尽可能选择最低数值的精度格式(Cg中的浮点、半浮点和固定(float, half, fixed))。

注重CPU

游戏受到GPU像素处理限制的情况时有发生,这会导致最终出现CPU未充分使用的情况,多核移动CPU尤其如此。将GPU中的一些工作移出来,放到CPU中处理往往是比较明智的做法(Unity会处理所有这些操作),例如网格蒙皮、小对象的批处理、粒子几何体更新。

不过要慎用,不要盲目地将所有工作都放到CPU中处理。如果不受绘制调用限制,那么随着剔除效率的降低和更多对象受到灯光影响,批处理的实际性能会变差。

出色实践

  1. 控制绘制调用次数:在移动设备上每帧的绘制调用不要超过数百次。
  2. 慎用查找函数:FindObjectsOfType(以及Unity的一般getter属性)非常慢,慎用。
  3. 设置静态属性:设置非移动对象的静态(Static)属性,允许静态批处理等内部优化。
  4. 优化遮挡剔除和排序:消耗大量CPU周期进行遮挡剔除和更好的排序(利用Early Z - cull)。

物理(Physics)

物理(Physics)占用CPU的比重较大,可以通过编辑器(Editor)分析器对其进行分析。如果物理似乎占用了太多CPU时间,可以采取以下措施:

  1. 调整固定时间步长:将Time.fixedDeltaTime(位于工程设置(Project settings)-> 时间(Time))调至可接受的最高值。如果游戏运行缓慢,需要的固定更新次数很可能比快动作游戏少。快节奏游戏须更频繁地计算,这样一来,fixedDeltaTime就要降低,否则碰撞可能会失败。
  2. 调整求解迭代次数:调整Physics.solverIterationCount(物理管理器(Physics Manager))。
  3. 减少特定对象使用:尽量少使用布(Cloth)对象;必要时才使用刚体(Rigidbody)。
  4. 使用原始碰撞器:在首选网格碰撞器中使用原始碰撞器。
  5. 避免移动静态碰撞器:永远不要移动静态碰撞器(如不带刚体的碰撞器),否则可能对性能造成很大影响,在分析器(Profiler)中显示为“Static Collider.Move”,但真正的处理过程是在Physics.Simulate中进行。如有必要,添加一个刚体(RigidBody)并将isKinematic设为true。
  6. 使用分析工具:在Windows系统中,必要时可以使用NVidia’s AgPerfMon分析工具包获取更多细节信息。

GPU架构与优化

流行移动平台体系架构

以下是一些流行的移动平台体系架构,硬件供应商与PC/控制台空间不同,GPU架构与“常用”GPU相比也有很大差异:

  1. ImgTec PowerVR SGX:基于平铺延迟,在小单元内渲染所有内容(如16×16),只为可见像素着色。
  2. NVIDIA Tegra:经典架构,渲染所有内容。
  3. Qualcomm Adreno:平铺架构,在单元内渲染所有内容,在大单元内进行设计(如256k),Adreno 3xx可切换到传统模式。
  4. ARM Mali:平铺架构,在单元内渲染所有内容,在小单元内进行设计(如16×16)。

开发者应抽出一些时间了解不同渲染方法,并相应设计游戏,特别注意排序问题。在开发周期前期定义支持的最低终端设备,设计游戏时开启分析器对其进行测试。同时,使用针对特定平台的纹理压缩。

iOS平台GPU

iOS平台只考虑PowerVR架构(基于平铺延迟),如ImgTec PowerVR SGX,基于平铺延迟,渲染单元内所有内容,只对可见的像素着色;ImgTec.PowerVR MBX,基于平铺延迟,固定函数,适用于iPhone 4/iPad 1之前的设备。

这意味着:

  1. 贴图细化非必需:贴图细化并非必须。
  2. 抗锯齿和各向异性性能消耗低:反锯齿和反向异性耗费的性能很少,有些情况下iPad 3上并不需要。

缺点:

  1. 顶点数据缓存问题:如果每帧的顶点数据(顶点数 * 顶点着色器之后所需的存储空间)超过驱动器分配的内部缓存,那么场景须“分开”,这会耗费性能。之后,驱动器可能会分配更大缓存,或者您可能需要减少顶点数目。非常复杂的着色器上约有10万个顶点时,在iPad2 (iOS 4.3)上会趋于明显。
  2. 分析困难:TBDR须为平铺和延迟部分分配更多晶体管,理论上留给“原始性能(raw performance)”的晶体管就较少。在TBDR上获得绘制调用的GPU时间非常难(几乎不可行),使分析更加困难。

资源包管理

资源包操作

资源包可以在一定限度内保存缓存到设备上。

  • 创建:用编辑器(Editor)API进行创建。
  • 加载:使用WWW API,资源加载方法有AssetBundle.CreateFromMemory或AssetBundle.CreateFromFile。
  • 卸载
  • AssetBundle.Unload有一个选项供卸载资源包,但会隔开已加载的资源,即使是在场景中引用,也会关闭所有已加载资源。
  • Resources.UnloadUnusedAssets用于卸载场景中不再引用的所有资源,记住关闭不再需要的资源引用。
  • Resources.UnloadAsset用于从内存中卸载一个特定资源,需要时可以从磁盘中重新加载。

下载限制

iOS上对同时下载资源包的数目有限制,通过OS提供的不同步API实施下载,所以OS决定需要创建多少线程以供下载。多个并行下载时应注意可以支持的设备总带宽和空余内存百分比,每个并行下载分配自己的临时缓存,应注意这些缓存未占用完内存。

资源处理

资源需要被Unity识别,以放入发布版中。可以将.bytes文件扩展作为二进制数据添加到想要Unity识别的任何原始字节,将.txt文件扩展作为文本资源添加到想要Unity识别的任何文本文件中。发布时资源会转成平台适用的格式,使用Resources.Load()进行加载。

低级错误列表

  1. 纹理压缩问题:纹理未经过适当压缩,不同情况下使用不同的分辨率,除非肯定不要压缩,否则一律压缩纹理。ETC/RGBA16是安卓(android)默认设置,可根据GPU供应商进行调整,可行时使用ETC是最佳方法;alpha纹理可以使用两个ETC文件,其中一个通道用于alpha;PVRTC是iOS默认设置,适用于大多数情况。
  2. 纹理像素操作问题:纹理启用了Get/Set pixels会加倍封装,除非需要Get/Set,否则不要勾选。
  3. 纹理加载问题:运行时从JPEG/PNG中加载的纹理不压缩。
  4. 音频文件问题:大mp3文件在加载时标记为解压。
  5. 场景加载问题:附加场景加载后,未使用的资源在内存中保留为未清理,存在静态区域;非卸载的资源包也会占用内存。
  6. 崩溃问题:如果偶然崩溃,尝试软件开发工具包或带2 GB内存的设备(如Ipad 3)。有时控制台中未出现任何问题,仅为偶然性崩溃;快速脚本调用和剥离可能导致在iOS上偶然崩溃,试试没有这两项会如何。