前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >移动平台 Unity3D 应用性能优化(下)

移动平台 Unity3D 应用性能优化(下)

原创
作者头像
WeTest质量开放平台团队
修改2017-06-30 16:58:41
2.1K0
修改2017-06-30 16:58:41
举报

作者:陈星百,腾讯移动客户端开发 工程师

商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。

原文链接:http://wetest.qq.com/lab/view/315.html

WeTest 导读

做了大概半年多VR应用了,VR由于双眼double渲染的原因,对性能的优化要求比较高,在项目的进展过程中,总结了一些关于移动平台上Unity3D的性能优化经验,供分享。(在《移动平台Unity3D 应用性能优化(上)》中介绍了一部分内容,本文将补充接下来部分)

一、Unity3D应用性能优化之GPU

一般人说DC的优化占了unity3D软件优化的三分天下,那么GPU的优化也占了三分天下。在了解GPU优化都有哪些着手点之前,我们先了解一下GPU在3D软件渲染中做了啥事:

顶点着色器

GPU接收顶点数据作为输入传递给顶点着色器。顶点着色器的处理单元是顶点,输入进来的每个顶点都会调用一次顶点着色器。(顶点着色器本身不可以创建或销毁任何顶点,并无法得到顶点与顶点之间的关系)。顶点着色器是完全可编程的,它主要完成的工作有:坐标变换和逐顶点光照。 坐标变换:就是对顶点的坐标进行某种变换—把顶点坐标从模型空间转换到齐次裁剪空间。顶点的多少直接决定了三角形面的多少,也直接决定了GPU的渲染流水线的工作量,所以减少顶点数是一个比较重要的优化点。那么减少顶点怎么操作呢,又有哪些途径? 1、优化基本几何体 3D软件都是从模型制作开始,在设计师建模的时候就要想到应该尽可能地减少顶点数,一些对于模型没有影响、或是肉眼非常难察觉到区别的顶点都要尽可能去掉。比如在项目中,对于用户背后的环境模型,一些树木和石头,视频背面永远无法看见的神庙,能削减的都已经削减了。

2、使用LOD(Level of detail)技术 LOD技术有点类似于Mipmap技术,不同的是,LOD是对模型建立了一个模型金字塔,根据摄像机距离对象的远近,选择使用不同精度的模型。它的好处是可以在适当的时候大量减少需要绘制的顶点数目。它的缺点同样是需要占用更多的内存,而且如果没有调整好距离的话,可能会造成模拟的突变。

3、使用遮挡剔除(Occlusion culling)技术 遮挡剔除是用来消除躲在其他物件后面看不到的物件,这代表资源不会浪费在计算那些看不到的顶点上,进而提升性能。刚才神庙后面的剔除就属于手动的遮挡剔除。

遮挡剔除是一个PRO版才有的功能, 当一个物体被其他物体遮挡住而不在摄像机的可视范围内时不对其进行渲染。遮挡剔除在3D图形计算中并不是自动进行的。因为在绝大多数情况下离 camera 最远的物体首先被渲染,靠近摄像机的物体后渲染并覆盖先前渲染的物体(这被称为重复渲染"overdraw"). 遮挡剔除不同于视锥体剔除. 视锥体剔除只是不渲染摄像机视角范围外的物体而对于被其他物体遮挡但依然在视角范围内的物体,则不会被剔除. 注意当你使用遮挡剔除时,视锥体剔除(Frustum Culling)依然有效。

中间操作

1、曲面细分着色器:是一个可选的着色器,主要用于细分图元。

2、几何着色器:是一个可选的着色器,可用于执行逐图元的着色操作,或者被用于产生更多的图元。

3、裁剪:这一阶段是可配置的。目的是把那些不在视野内的顶点裁剪掉,并剔除某些三角形图元的面片。部分在视野内的图元需要做裁剪处理,在裁剪边缘产生新的顶点和三角形进行处理。

4、屏幕映射:这一阶段是可配置和编程的,负责把每个图元的坐标(三维坐标系)转换成屏幕坐标(二维坐标系)。

5、三角形设置:开始进入光栅化阶段,不再是数学上点了,而会把所有的点都映射到屏幕的具体像素坐标上,计算每条边上的像素坐标而得到三角形边界的表示方式即为三角形设置。

6、三角形遍历:这一阶段会检查每个像素是否被一个三角风格所覆盖。如果覆盖的话,就会生成一个片元(一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。

这些状态包括了屏幕坐标、深度信息,及从几何阶段输出的顶点信息,如法线和纹理坐标等。),这样一个查找哪些像素被三角形覆盖的过程就是三角形遍历。

片元着色器

片元着色器的输入就是上一阶段对顶点信息插值得到的结果,更具体点说,是根据从顶点着色器中输出的数据插值得到的。而这一阶段的输出是一个或者多个颜色值。这一阶段可以完成很多重要的渲染技术,如纹理采样,但是它的局限在于,它仅可以影响单个片元。片元着色器是比较花时间的,因为它是最终颜色的计算者,在某些情况下,例如复杂灯光环境下,片元着色器会出现GPU流水线主要的拖后腿的存在。为了让片元着色器的计算更加快,我们需要从很多方面进行提前的优化: 1、尽量减少overdraw 片元着色器最容易拖后腿的情况就是,overdraw!和Android app的开发一样,就是同一个像素点绘制了多次,某些情况会造成计算力的浪费,增加耗电量。前面提到的遮挡剔除有减少overdraw非常有用。在PC上,资源无限,为了得到最准确的渲染结果,绘制顺序可能是从后往前绘制不透明物体,然后再绘制透明物体进行混合。但是在移动平台上,对于不透明物体,我们可以设置从前往后绘制,对于有透明通道的物体(很多UI纹理就是含有透明通道的),再设置从后往前绘制。unity中shader设置为“Geometry” 队列的对象总是从前往后绘制的,而其他固定队(如“Transparent”“Overla”等)的物体,则都是从后往前绘制的。这意味这,我们可以尽量把物体的队列设置为“Geometry” 。对于GUI,尤其要注意和设计师商量,能用不透明的设计就用不透明的,对于粒子效果,也要注意不要引入透明值,多半情况下,移动平台的粒子效果透明值没有作用。

2、减少实时光照 移动平台的最大敌人。一个场景里如果包含了三个逐像素的点光源,而且使用了逐像素的shader,那么很有可能将Draw Calls提高了三倍,同时也会增加overdraws。这是因为,对于逐像素的光源来说,被这些光源照亮的物体要被再渲染一次。更糟糕的是,无论是动态批处理还是动态批处理(其实文档中只提到了对动态批处理的影响,但不知道为什么实验结果对静态批处理也没有用),对于这种逐像素的pass都无法进行批处理,也就是说,它们会中断批处理。

所以当你需要光照效果时,可以使用Lightmaps,提前烘焙好,提前把场景中的光照信息存储在一张光照纹理中,然后在运行时刻只需要根据纹理采样得到光照信息即可。当你需要金属性强(镜面)的效果,可以使用Light Probes。当你需要一束光的时候,可以使用体积光去模拟这个效果。

3、不要使用动态阴影 动态阴影很酷,但是对于片元着色器来说是灾难,阴影计算是三角投影计算,非常耗性能。如果想要阴影,可以使用 a、简单的使用一个带阴影的贴图 b、烘焙场景,拿到lightmaps c、创建投影生成器的方法 d、使用ShadowMap的方法(目前还没有研究)。

4、尽量使用简单的shader a、建议尽量实用Unity自带mobile版本的(built-in)Shader,这些大大提高了顶点处理的性能。当然也会有一些限制。 b、自己写的shader请注意复杂操作符计算,类似pow,exp,log,cos,sin,tan等都是很耗时的计算,最多只用一次在每个像素点的计算,还有有些除法运算尽量该能乘法运算等。 c、避免透明度测试着色器,因为这个非常耗时,使用透明度混合的版本来代替。 d、浮点类型运算:精度越低的浮点计算越快。 e、不要在Shader中添加不必要的Pass.

二、Unity3D应用性能优化之内存

unity中有两类内存,一个是Mono托管的内存(相当于DVM的内存),一个是Unity3D使用的资源类类型的内存(Texture、Mesh这种)。

Mono内存

1、尽量不要动态的Instantiate和Destroy Object,使用Object Pool。

2、不要动态的产生字符串,使用字符串的直接拼接,使用System.Text.StringBuilder代替。

3、Cache一些东西,在update里面尽量避免search,如GameObject.FindWithTag("")、GetComponent这样的调用,可以在Start中预先存起来。

4、尽量减少函数调用栈,用x = (x > 0 ? x : -x);代替x = Mathf.Abs(x)。

5、定时重复处理用 InvokeRepeating 函数实现。

6、减少GetComponent的调用,使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(transform用的最多)。

7、使用内置数组,内置数组是非常快的。ArrayList或Array类很容易使用,你能轻易添加元件。但是他们有完全不同的速度。 内置数组有固定长度,并且大多时候你会事先知道最大长度然后填充它。内置数组最好的一点是他们直接嵌入结构数据类型在一个紧密的缓存里,而不需要任何额外 类型信息或其他开销。因此,在缓存中遍历它是非常容易的,因为每个元素都是对齐的。

Unity3D类的内存

这类内存包括 1、AssetBundle Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBundle,其实两者区别不大。 Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时 动态加载,可以指定路径和来源的。

AssetBundle运行时加载:

(1)来自文件就用CreateFromFile(注意这种方法只能用于standalone程序,就不提了)。

(2)也可以来自Memory,用CreateFromMemory(byte[]),这个byte[]可以来自文件读取的缓冲,www的下载或者其他可能的方式。其实WWW的assetBundle就是内部数据读取完后自动创建了一个assetBundle而已,Create完以后,等于把硬盘或者网络的一个文件读到内存一个区域,这时候只是个AssetBundle内存镜像数据块,还没有Assets的概念。

下图是AssetBundle的加载卸载示意图:

AssetBundle是如何加载的呢?用AssetBundle.Load(同Resources.Load) 这才会从AssetBundle的内存镜像里读取并创建一个Asset对象,创建Asset对象同时也会分配相应内存用于存放(反序列化)。异步读取用AssetBundle.LoadAsync,也可以一次读取多个用AssetBundle.LoadAll。

AssetBundle如何释放呢?

AssetBundle.Unload(flase)是释放AssetBundle文件的内存镜像,不包含Load创建的Asset内存对象。

AssetBundle.Unload(true)是释放那个AssetBundle文件内存镜像和并销毁所有用Load创建的Asset内存对象。

2、Texture 对于IOS选择使用 PVRTC压缩格式的,对于Android选择ETC压缩格式的,纹理可以节省大量内存和读取速度快,但是会有所降低图像的质量。

2D纹理如果没有必要不要使用mimap(会约增加33%的内存开销),曾经在IOS上吃过亏。3D模型的纹理一般是需要mimap的,但是如果确定了3D模型距离摄像机的距离,在GPU分析器上确定了unity使用的纹理,就可以保留,关闭mimap(比如项目中的avatar)。

3.Mesh 有Mesh合并和Mesh压缩(坑比较多,不建议使用)。

4.Particle 粒子效果只要记住使用之后及时释放销毁就行。

针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验。

目前功能还在免费开放中。,欢迎点击链接: http://wetest.qq.com/product/cube 使用。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原文链接:http://wetest.qq.com/lab/view/315.html
  • WeTest 导读
  • 一、Unity3D应用性能优化之GPU
    • 顶点着色器
      • 中间操作
        • 片元着色器
        • 二、Unity3D应用性能优化之内存
          • Mono内存
            • Unity3D类的内存
            相关产品与服务
            测试服务
            测试服务 WeTest 包括标准兼容测试、专家兼容测试、手游安全测试、远程调试等多款产品,服务于海量腾讯精品游戏,涵盖兼容测试、压力测试、性能测试、安全测试、远程调试等多个方向,立体化安全防护体系,保卫您的信息安全。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档