Unity 面试题汇总(五)性能优化知识点相关

目录

Unity 面试题汇总(五)性能优化知识点相关

1、资源分离打包与加载

2、贴图透明通道分离,压缩格式设为ETC/PVRTC

3、关闭贴图的读写选项

4、减少场景中的GameObject数量

5、图集

6、降低贴图素材分辨率

7、界面的延迟加载和定时卸载策略

8、避免频繁调用GameObject.SetActive

9、移动端性能优化心得

10、逻辑代码方法

11、GPU Instancing

13、移动端——贴图使用

14、移动端——模型面数参考


1、资源分离打包与加载

  游戏中会有很多地方使用同一份资源。比如,有些界面共用同一份字体、同一张图集,有些场景共用同一张贴图,有些怪物使用同一个Animator,等等。在制作游戏安装包时将这些公用资源从其它资源中分离出来,单独打包。比如若资源A和B都引用了资源C,则将C分离出来单独打一个bundle。在游戏运行时,如果要加载A,则先加载C;之后如果要加载B,因为C的实例已经在内存,所以只要直接加载B,让B指向C即可。如果打包时不将C从A和B分离出来,那么A的包里会有一份C,B的包里也会有一份C,冗余的C会将安装包撑大;并且在运行时,如果A和B都加载进内存,内存里就会有两个C实例,增大了内存占用。

  资源分离打包与加载是最有效的减小安装包体积与运行时内存占用的手段。一般打包粒度越细,这两个指标就越小;而且当两个renderQueue相邻的DrawCall使用了相同的贴图、材质和shader实例时,这两个DrawCall就可以合并。但打包也并不是越细就越好。如果运行时要同时加载大量小bundle,那么加载速度将会非常慢——时间都浪费在协程之间的调度和多批次的小I/O上了;而且DrawCall合并不见得会提高性能,有时反而会降低性能,后文会提到。因此需要有策略地控制打包粒度。一般只字体和贴图这种体积较大的公用资源。

  可以用AssetDatabase.GetDependencies得知一份资源使用了哪些其它资源。

2、贴图透明通道分离,压缩格式设为ETC/PVRTC

  最初我们使用了DXT5作为贴图压缩格式,希望能减小贴图的内存占用,但很快发现移动平台的显卡是不支持的。因此对于一张1024x1024大小的RGBA32贴图,虽然DXT5可将它从4MB压缩到1MB,但系统将它送进显卡之前,会先用CPU在内存里将它解压成4MB的RGBA32格式(软件解压),然后再将这4MB送进显存。于是在这段时间里,这张贴图就占用了5MB内存和4MB显存;而移动平台往往没有独立显存,需要从内存里抠一块作为显存,于是原以为只占1MB内存的贴图实际却占了9MB!

所有不支持硬件解压的压缩格式都有这个问题。经过一番调研,我们发现安卓上硬件支持最广泛的格式是ETC,苹果上则是PVRTC。但这两种格式都是不带透明(Alpha)通道的。因此我们将每张原始贴图的透明通道都分离了出来,写进另一张贴图的红色通道里。这两张贴图都采用ETC/PVRTC压缩。渲染的时候,将两张贴图都送进显存。同时我们修改了NGUI的shader,在渲染时将第二张贴图的红色通道写到第一张贴图的透明通道里,恢复原来的颜色:

fixed4 frag (v2f i) : COLOR  fixed4 col;  col.rgb = tex2D(_MainTex, i.texcoord).rgb;  col.a = tex2D(_AlphaTex, i.texcoord).r;  return col * i.color;  fixed4 frag (v2f i) : COLOR{fixed4 col;col.rgb = tex2D(_MainTex, i.texcoord).rgb;col.a = tex2D(_AlphaTex, i.texcoord).r;return col * i.color;}

这样,一张4MB的1024x1024大小的RGBA32原始贴图,会被分离并压缩成两张0.5MB的ETC/PVRTC贴图(我们用的是ETC/PVRTC 4 bits)。它们渲染时的内存占用则是2x0.5+2x0.5=2MB。

3、关闭贴图的读写选项

Unity中导入的每张贴图都有一个启用可读可写(Read/Write Enabled)的开关,对应的程序参数是TextureImporter.isReadable。选中贴图后可在Import Setting选项卡中看到这个开关。只有打开这个开关,才可以对贴图使用Texture2D.GetPixel,读取或改写贴图资源的像素,但这就需要系统在内存里保留一份贴图的拷贝,以供CPU访问。一般游戏运行时不会有这样的需求,因此我们对所有贴图都关闭了这个开关,只在编辑中做贴图导入后处理(比如对原始贴图分离透明通道)时打开它。这样,上文提到的1024x1024大小的贴图,其运行时的2MB内存占用又可以少一半,减小到1MB。

4、减少场景中的GameObject数量

  有一次我们将场景中的GameObject数量减少了近2万个,游戏在iPhone 3S上的内存占用立马减了20MB。这些GameObject虽然基本是在隐藏状态(activeInHierarchy为false),但仍然会占用不少内存。这些GameObject身上还挂载了不少脚本,每个GameObject中的每个脚本都要实例化,又是一比不菲的内存占用。因此后来我们规定场景中的GameObject数量不得超过1万,并且将GameObject数量列为每周版本的性能监测指标。

5、图集

  整理图集的主要目的是节省运行时内存(虽然有时也能起到合并DrawCall的作用)。从这个角度讲,显示一个界面时送进显存的图集尺寸之和是越小越好。一般有如下方法可以帮助我们做到这点:

  1)在界面设计上,尽量让美术将控件设计为可以做九宫格拉伸,即UISprite的类型为Sliced。这样美术就可以只切出一张小图,我们在Unity中将它拉大。当然,一个控件做九宫格也就意味着其顶点数量从4个增加到至少16个(九宫格的中心格子采用Tiled做平铺类型的话,顶点数会更多),构建DrawCall的开销会更大(见第6点),但一般只要DrawCall安排合理(同样见第6点)就不会有问题。

  2)同样是在界面设计上,尽量让美术将图案设计成对称的形式。这样切图的时候,美术就可以只切一部分,我们在Unity中将完整的图案拼出来。比如对一个圆形图案,美术可以只切出四分之一;对一张脸,美术可以只切出一半。不过,与第1)点类似,这个方法同样有其它性能代价——一个图案所对应的顶点数和GameObject数量都增多了。第4点已经提到,GameObject数量的增多有时也会显著占用更多内存。因此一般只对尺寸较大的图案采用这个方法。

  3)确保不要让不必要的贴图素材驻留内存,更不要在渲染时将无关的贴图素材送进显存。为此需要将图集按照界面分开,一般一张图集只放一个界面的素材,一个界面中的UISprite也不要使用别的界面的图集。假设界面A和界面B上都有一个小小的一模一样的金币图标,不要因为在制作时贪图方便,就让界面A的UISprite直接引用界面B中的金币素材;否则界面A显示的时候,会将整个界面B的图集也送进显存,而且只要A还在内存中,B的图集也会驻留内存。对于这种情况,应该在A和B的图集中各放一个一模一样的金币图标,A中的UISprite只使用A的图集,B中的UISprite只使用B的图集。

  不过,如果两个界面之间存在大量相同的素材,那么这两个界面就可以共用同一张图集。这样可以减少所有界面的总内存占用量。具体操作时需要根据美术的设计进行权衡。一般界面之间相同的通用的素材越多,程序的内存负担就越小。但界面之间相同的东西太多的话,美术效果可能就不生动,这是美术和程序之间又一个需要寻求平衡的地方。

  另外,数量庞大的图标资源(如物品图标)不要做在图集里,而应该采用UITexture。

4)减少图集中的空白地方。图集中完全透明的像素和不透名的像素所占的内存空间其实是一样的。因此在素材量不变的情况下,要尽量减少图集中的空白。有时一张1024x1024的图集中,素材所占的面积还没超过一半,这时可以考虑将这张图集切成两张512x512的图集。(有人会问为什么不能做成一张1024x512的图集,这是因为iOS平台似乎要求送进显存的贴图一定是方形。)当然,两张不同图集的DrawCall是无法合并的,但这并不是什么问题(见第6点)。

  应该说,图集的整理在具体操作时并没有一成不变的标准,很多时候需要权衡利弊来最终决定如何整理,因为不管哪种措施都会有别的性能代价。

6、降低贴图素材分辨率

  这一招说白了其实就是减小贴图素材的尺寸。比如对一张在原画里尺寸是100x80的,我们将它导入Unity后会把它缩小到50x40,即缩小两倍。游戏实际使用的是缩小后的贴图。不过这一招是必然会显著降低美术品质的,美术立马会发现画面变得更模糊,因此一般不到程序撑不住的时候不会采用。

7、界面的延迟加载和定时卸载策略

  如果一些界面的重要性较低,并且不常被使用,可以等到界面需要打开显示的时候才从bundle加载资源,并且在关闭时将卸载出内存,或者等过一段时间再卸载。不过这个方法有两个代价:一是会影响体验,玩家要求打开界面时,界面的显示会有延迟;二是更容易出bug,上层写逻辑时要考虑异步情况,当程序员要访问一个界面时,这个界面未必会在内存里。因此目前为止我们仍未实施该方案。目前只是进入一个新场景时,卸载上一个场景用到但新场景不会用到的界面。

  以上的9个方法中,4、5、6需要在一定程度上从策划和美术的角度考虑问题,并且需要持续保持监控以维护优化状态(因为在设计上总是会有新界面的需求或改动老界面的需求);其它都是一劳永逸的解决方案,只要实施稳定后,就不需要再在上面花费精力。不过2和8都是会降低美术品质的方法,尤其是8。如果美术对品质的降低程度实在忍不了的话,也可能不会允许采用这两个方法。

8、避免频繁调用GameObject.SetActive

我们游戏的某些逻辑会在一帧内频繁调用GameObject.SetActive,显示或隐藏一些对象,数量达到一百多次之多。这类操作的CPU开销很大(尤其是NGUI的UIWidget在激活的时候会做很多初始化工作),而且会触发大量GC。后来我们改变了显示和隐藏对象的方法——让对象一直保持激活状态(activeInHierarchy为true),而原来的SetActive(false)改为将对象移到屏幕外,SetActive(true)改为将对象移回屏幕内。这样性能就好多了。

9、移动端性能优化心得

CPU端性能优化

  • 逻辑和表现尽可能分离开,这样逻辑层的更新频率可以适当降低些.
  • 对于一些热点函数,如mmo的实体更新、实例化,使用分帧处理,分摊单帧时间消耗.
  • 做好同屏实体数量、特效数量、距离显隐等优化.
  • 完善日志输出,避免没必要的日志输出,同时警惕日志字符串拼接.
  • 使用骨骼烘焙 + GPUSkinning + Instance 降低CPU蒙皮骨骼消耗和drawcall.
  • 开启模型的Optimize GameObjects减少节点数量和蒙皮更新消耗.
  • UI拼预制做好动静分离,对于像血条名字这种频繁变动的ui,做好适当的分组.
  • 减少C#和lua的频繁交互,尽量精简两者传递的参数结构.
  • 使用stringbuilder优化字符串拼接的gc问题.
  • 删除非必要的脚本功能函数,特别是Update/LateUpdate类高频执行函数,因为会产生C++到C#层的调用开销. 对于Update里需要用到的组件、节点等提前Cache好.
  • 场景里频繁使用的资源或数据结构做好资源复用和对象池.
  • 对于频繁显示隐藏的UI,可以先移出到屏幕外,如果长时间不显示再进行Deactive.
  • 合理拆分UI图集,区分共用图集和非共用图集,共用图集可以常驻内存,非共用图集优先按功能分类,避免资源冗余.
  • 使用IL2CPP, 编译成C++版本能极大的提升整体性能.
  • 避免直接使用Material.Setxxx/Getxxx 等调用,这些调用会触发材质实例化消耗,可以考虑使用 SharedMaterial / MaterialPropertyBlock代替.
  • 合并Shader里的Uniform变量.

GPU端性能优

  • 合理规划好渲染顺序,避免不必要的overdraw,如:地形(容易被其他物件遮挡)、天空盒放到较后渲染.
  • 分辨率缩放,对于填充率出现瓶颈时,这个是最简单高效的.
  • 避免使用GrabPass抓屏,不是所有硬件都支持,加之数据回拷和没法控制分辨率性能很差,可考虑使用CommandBuffer.blit去优化.
  • 控制好地形的Blend层数,控制在4层以内,考虑到地形一般屏占面积大、贴图采样次数多,对于中低画质考虑不用normalmap.
  • 做好物件、树、角色的LOD.
  • 避免使用RenderWithShader类方式来定制DepthTexture,可以考虑Camera的 public void SetTargetBuffers(RenderBuffer colorBuffer, RenderBuffer depthBuffer);进行优化.
  • 检查Shader的VertexInput 和 VertexOutput是否存在冗余数据.如:顶点色、多套UV.
  • 警惕项目里非必要的双面材质,对于需要局部双面的地方通过加面解决.
  • Shader里使用fixed、half代替float,理论上除position、uv、一些涉及depth相关计算使用float外,其他都应该使用fixed(主要是颜色值)、half.
  • 对于角色皮肤这种不是特别明显的效果,考虑使用预积分这种低成本的方案.
  • 对于frag里的计算过程,如果可以抽出来放到CPU应用层、顶点阶段的优先放这里计算. 需要注意放到顶点阶段引起的平滑过渡问题. 如: eyeVec导致高光过渡问题.
  • 镜面反射类效果避免使用反射相机+RT的实现,考虑使用SSR、CubeMap类实现.
  • 避免使用实时阴影,如若使用要合理控制下分辨率和阴影距离. 考虑使用Projector.
  • 使用统一的后处理框架代替多个Image Effect,可以共用模糊函数,减少blit操作. 另外Unity自带的Postprocessing V2 支持Volume,性能还是不错的.
  • Shader里避免使用分支、循环,sin、tan、pow、log等复杂数学运算.
  • Unity自带的遮挡剔除因为CPU消耗和内存占用较高,加之不能Instancing,不太适合移动平台,可以考虑静态预计算(缺点是不支持动态物体)、Hi-Z等优化方案.
  • 减少alpha test材质的使用,如若使用注意减小面积、控制渲染顺序.

内存优化

  • 警惕配置表的内存占用.
  • 检查ShaderLab内存占用:
  • 避免使用Standard材质,做好相应的variant skip.
  • 排查项目冗余的Shader.
  • 使用shader_feature替代multi_compile,这样只会收集项目里真正使用的变体组合,避免变体翻倍.
  • 检查纹理资源的尺寸、格式、压缩方式、mipmap、Read & Write选项使用是否合理.
  • 检查Mesh资源的Read & Write选项、顶点属性使用是否合理.
  • 代码级别的检查,如Cache预分配空间、容器的Capacity、GC等.
  • 使用Profiler定位下GC,特别是Update类函数里的. 如:字符串拼接、滥用容器等.
  • 合理控制RenderTexture的尺寸.
  • 优化动画Animation的压缩方式、浮点精度、去除里面的Scale曲线数据.
  • 减少场景GameObject节点的数量,最好支持工具监控.

10、逻辑代码方法

下面列举一些容易产生堆内存的函数

Unity API:

  • Debug.Log
  • AssetBundle.LoadAsset
  • Object.Instantiate/GameObejct.SetActive
  • Object.name
  • GameObject.AddComponent
  • ParticleSystem.Play/Stop/...(不指定某个粒子系统调用,就是相当于GetComponentInChildrens差不多)
  • Physics.Raycast

Plugins:

  • UIPanel.LateUpdate
  • LuaInterface.LuaDLL.lua_tostring
  • Protobuff.Serializer.Deserialize

System:

  • System.Delegate.Combine
  • Foreach
  • string.Concat/Split/ToLower

11、GPU Instancing

参考网址:Unity - Manual: GPU instancing

Use GPU Instancing to draw (or render) multiple copies of the same Mesh
at once, using a small number of draw calls. This is useful for drawing objects such as buildings, trees, grass, or other things that appear repeatedly in a Scene
.

GPU Instancing only renders identical Meshes with each draw call, but each instance can have different parameters (for example, color or scale) to add variation and reduce the appearance of repetition.

GPU Instancing can reduce the number of draw calls used per Scene. This significantly improves the rendering performance of your project.

使用 GPU Instancing 绘制(或渲染)多个副本 一次,使用少量的绘制调用。这对于绘制建筑物、树木、草地或其他重复出现的物体等物体很有用场景
.

GPU 实例化仅在每次绘制调用时渲染相同的网格,但每个实例可以具有不同的参数(例如,颜色或比例)以增加变化并减少重复的出现。

GPU 实例化可以减少每个场景使用的绘制调用数量。这显着提高了项目的渲染性能。

Adding instancing to your Materials (将实例添加到您的材质中 )

要在材质上启用 GPU 实例化,请在 项目窗口,并且在 Inspector,勾选启用实例化复选框。

当您使用 GPU 实例化时,以下限制适用:

  • Unity 自动选择 MeshRenderer 组件并Graphics.DrawMesh调用实例化。请注意,不支持SkinnedMeshRenderer。

  • Unity 仅对在单个 GPU 实例化绘制调用中共享相同网格和相同材质的游戏对象进行批处理。使用少量网格和材料以获得更好的实例化效率。要创建变体,请修改您的着色器脚本
     添加每个实例的数据(请参阅下一节以了解更多信息)。

您还可以使用调用Graphics.DrawMeshInstanced和Graphics.DrawMeshInstancedIndirect从脚本执行 GPU 实例化。

GPU 实例可在以下平台和 API 上使用:

  • Windows 上的DirectX 11DirectX 12

  • Windows、macOS、Linux 和 Android 上的OpenGL Core 4.1+/ES3.0+

  • macOS 和 iOS 上的Metal

  • Windows、Linux 和 Android 上的Vulkan

  • PlayStation 4Xbox One

  • WebGL
     (需要 WebGL 2.0 API)

13、移动端——贴图使用

(一)贴图的最大能多大

为啥要说这个呢,是因为,最近公司另一个团队所制作的游戏(一款手机篮球游戏,全部是UI,ngui制作,简称《N》),遇到上线之后的大面积崩溃问题。后来经过询问得知,此游戏开发者竟然使用了2048*2048大小的贴图。而且好多张这样的贴图。我和我的小伙伴当时就震惊了。

尽管U3D和安卓是支持这么大的贴图的使用的。但是我们在安卓的开发中,往往会遇到很多低端的设备。这些设备在各级缓存以及内存上都是着(zhuo)急的。当我们要加载和显示这么大的贴图的时候,往往会需要向内存或者缓存申请2048*2048这么大的连续空余空间。但是,当机器在运转一段时间后,随着内存碎片的增多,这时候操作系统就很郁闷了。因为他找不到这么大的连续空间。最后就是各种命中失败,甚至申请失败。所以就被系统KILL掉啦。

所以呢,为了避免这种情况。我们应该尽量的减每张贴图的大小。尽量做到1024*1024以内。甚至最好能使用512*512以内的。因为即使系统为应用分配这么大的空间。也有可能会查找好几次才查到地址。或者系统发现自己hold不住,而主动触发系统GC,大家知道GC是好卡的~~~。所以,我们要尽量使用小贴图,以保证系统内存命中的几率。但是又不能全是非常小哦。这样碎片又多了。空间还是利用不起来。因此适当的使用atlas(一种合并小贴图的方式),让每一张的贴图资源大小都控制在512左右,是非常好的。我们自己的项目《忍》这点上做得非常好。基本都是很固定大小的。所以即使是真3D,即使在非常低配置的安卓手机(512M内存,1G CPU)上,也很少(只能说很少,因为内存导致崩溃原因很多啦)出现内存型崩溃。

(二)贴图的格式

一般我们使用的是PNG,或者TGA格式。UI使用PNG,模型使用TGA。直接使用的贴图大小要是2的幂次方。以提高加载和解析速度。

Generate Mip Maps这个选项。我们要慎重。本身就功能来说。用于UI的贴图基本是不需要MIPMAP的。原因就不解释拉。而用于3D显示的贴图的确是需要这个选项的,为了避免远离摄像机对象的贴图闪烁问题。但是呢,不得不说一声但是,据我的经验。我们使用了MIPMAP之后,在某些国产的安卓手机上,点名说下比如盛大手机。它们在长时间玩游戏,比如2个小时候之后,会出现贴图MIPMAP等级选择错误的现象。举例来说就是举例摄像机很近的贴图也会使用Mipmap中最小的贴图。这个问题,我找不到解决方案纳(哪位大佬可以解释下呢?)。因此我关闭了所有贴图的这个选项。我们游戏不会出现Z范围特别大的跳跃,所以问题不大啦。

Filter Mode 过滤方式当然是要选线性,双线性就够啦。原因我就不解释拉。Point在某些细节显示的地方会很着急,比如字体。

Max Size 这个很有用拉,在不用修改资源的前提下,直接制作高清版和高速mini版。各位试试就知道拉。

Format:这个要注意了。很多人喜欢使用Automatic Compressed格式的。的确可以缩小包大小。但是效果我个人是觉得惨不忍睹的,特别是IOS啊,千万不要用拉。个人经验觉得使用16位色可以减少很大的尺寸,而且效果要比这个压缩好很多。亲们可以试试。Automatic 16bit。

(三)UI的资源布局

《忍》的ui资源布局的原则是:大量使用公共资源。这些UI资源具有整个游戏明显的风格特征。比如对话框地板,大UI地板等等。按钮中的“确定”,“取消” 等资源,都放到公共资源组里面,这些公共资源组内的贴图资源,可以不卸载掉,这样每一个UI打开的时候只需要单独去加载此UI特有的特殊性资源,一般都比较小,这样可以加快资源的加载速度。并且,由于大量的公用性,资源的利用率很高,会减少资源包大小。

尽量拼满一张Atlas。这不是说没有需要也要找些资源来拼满,而是说如果你的资源比512*256大一点点,但是合并之后就会变成512*512大小的贴图,其余空间都浪费了。所以这个时候,可以将你的贴图每一个小sprite缩小下,这样就变成512*256拉。还有,如果你有一张大贴图是512*800的,这样这张贴图被打成atlas之后必定会变成512*1024以上。所以你可以跟你的美术商量,将这张512*800的贴图压缩成512*512的贴图,Y轴上压缩,用的时候再在Y轴上拉伸。就可以啦,轻轻松松,节省1倍。

14、移动端——模型面数参考

1)武器面数
武器模型控制在150个三角面,187个顶点以内。贴图控制在1张,最大256*256,最好128*128。

2)场景面数
场景(地形、建筑、其他装饰物)面数控制在10000面左右,贴图控制在最多2张512*512。

3)主角面数
主角模型控制在900-1300个三角面(以最终导入unity3d中的三角面数量为准)。贴图控制在1张,最好256×256,最多512×512。

3)小怪面数
小怪模型控制在600-900个三角面(以最终导入unity3d中的三角面数量为准)。贴图控制在1张,最大256×256。

注意:所有单个模型不准出现超过20000三角面的情况,否则导出时可能会出错。

包体大小优化

游戏的安装包体大小对于游戏开发而言非常重要,因为发布后这些包都会上传到渠道或商店让用户下载,包体越小用户下载的时间就越少,如果一个包体过大,可能导致好不容易推广的app,由于下载很久而直接被用户取消。包体大小优化技巧这里总结一下:

  • 图片能用jpg的尽量使用jpg, 用压缩率更大的算法。
  • Jpg, png图片在满足游戏清晰度精度的情况下可以通过调节参数,压缩文件体积。
  • 声音文件可以通过降低码率,采样率,多声道变单声道等方式降低体积大小。改变声音压缩算法,如.wav变成ogg来减少声音文件体积。
  • 字体文件,可以通过去掉不用的字模来减少字库的体积。
  • 3D模型文件体积, 可以通过减少模型的面数等来进行优化,通过细节增强技术减少模型面数达到同样的效果。
  • 没有用的资源不要打包到包体里面,使用Resources, StreammingAssets时这些资源无论如何都会被打包进去,所以在这些文件夹中一些没有用的资源要移除或删除。
  • 减少代码的体积,把不用的一些代码不编译打包进去。
  • 可以把包体的全部或部分资源部署到服务器上,这样可以减少包体的体积大小。

内存大小/内存性能优化

游戏开发中运行的内存过大,是经常我们遇到的问题,内存包含了运行时候的内存以及资源所占的显存。如何优化内存所占的大小,我们也可以从几个方面去考虑。

内存大小与数据量是成直接关系的,平常写代码的时候,尽量的要用好内存,少用内存。
    游戏开发中资源所在的内存占比是比较大的,资源所在的内存包括了模型顶点数据,模型纹理数据,UI纹理数据,声音数据,预制体数据,ab包数据等。这些和上面的文件体积不同,比如 1920x1080的jpg与png, 不论是哪个文件加载到显存的大小都为 1920x1080 * 每个像素的字节数(4字节)。所以我们要减低运行时的内存就是要即使的将不用的资源从内存中释放掉。有些资源的内存格式显卡时支持要锁的,比如Android支持ETC, IOS支持纹理压缩格式PVRTC。
    写代码的时候尽量少产生垃圾对象,避免性能问题与内存碎片。比如字符串拼接,尽量用字符串的格式化来替代字符串加法。因为你很难说清楚

string a = “hello” + b + “hello” + 4;

这个过程中到底又多少个string对象产生出来。在update里面尽量不要写那些每帧都会去new 的代码,避免产生内存碎片。同时我们为了性能,把同时大量重复使用的对象做成对象节点池缓存起来,杜绝反复创建避免内存碎片。

所以写代码的时候要考虑到这些因素,写出内存占用低性能比较好的代码。
写代码影响帧率的一些不良习惯

接下来我们来列举一些写代码会影响帧率的不良习惯,希望能帮助大家避免掉一些帧率上不去的坑。

  • 不要在update里面每帧的时候去调用查找组件这些函数,最好在初始化的时候先查好保存起来。
  • 不要在游戏运行中同步的去加载一个资源,导致代码卡在了在原加载的函数上,比如加载一个资源要0.5s, 如果运行时同步去加载资源就会卡0.5秒,那么在0.5秒内游戏就卡住了,帧率就降到为1~2。
  • 不要在游戏运行中做IO操作,比如游戏运行中同步的读写文件,这样就会导致main thread 去等IOS,导致帧率下降。
  • 不要把耗时的纯算法的操作放main thread中,比如要计算某个NPC的一个结果,这个计算可能需要花1秒钟,但是1秒内其它的NPC或角色都可以正常的处理,我们不要在主线程上来计算这个算法,而是通过线程的方式,在其它线程计算不卡主线程影响其它的逻辑。等其它线程计算好后通知主线程。这样游戏帧率不会下降,同时又不会影响游戏,比如寻路算法等都可以用这种方式来做。
  • 误以为协程不会帧率,很多开发者在写代码的时候把一些操作放协程,认为不会降低帧率,其实协程也在主线程执行,会影响帧率。

渲染相关的优化技巧

如何提升游戏物体的渲染性能也是很多开发者摸不清楚的,在这里给大家总结一下常用的思路和技巧,给大家在做渲染优化的时候做一个参考指南。

  • 通过3D细节增强的技术(法线贴图,高度贴图等),来降低模型精度,让低模+细节增强来实现高模的效果。
  • 尽量让美术以少的模型面数来制作效果好的模型。
  • 做好场景的物体管理(四叉树场景管理),把那些不用绘制的物体隐藏起来,节约绘制的成本。
  • 基于反画家算法从后向前渲染,尽量减少透明物体,尽量把背景的大物体分成多个小物体,做遮挡剔除的时候会更好。
  • 可以考虑使用静态光照来做场景烘培, 代替实时光照。
  • 可以考虑使用贴图来做物体的阴影,代替实时阴影。
  • 如果要用实时光照,尽可能的用一种光源。
  • 如果要用实时光照,又不会只有一种光源,尽可能的使用延时渲染或其它自定义的渲染管线(轻量级渲染)来代替向前渲染。因为向前渲染每个光源都要绘制一次,性能影响很大。
  • 场景多光源的情况下,根据场景物体的位置,管理好有效光源,无效光源不参与物体计算,这里的策略可以通过定制渲染管线。
  • Shader 相关的优化,空间换时间, 多光源光照计算优化与阴影计算优化等。
  • 绘制同一批物体的时候,尽量用同一个shader, 减少Shader的切换, Shader切换又叫SetPassCall, SetPassCall非常消耗性能,同时把常用的Shader加载到内存常驻。

避免Shader不断重复交叉切换。

  • 尽量把一些物体合并到一个材质里面,比如玩家手里的枪,可能有很多支,但是我们把所有枪的纹理合并到一个纹理里面,就可以使用一个材质来渲染任意枪的类型,这样可以合并一些枪的drawcall。比如可以把地图里面的物体贴图合并到一个忒图里面用一个材质。
  • 可以考虑使用 静态合批,动态合批,GPU Instancing合批来节约DrawCall。UGUI可以通过做图集来节约DrawCall。
  • 通过预先采样将顶点保存起来优化动画播放组件。

物理引擎相关优化技巧

物理引擎也是性能开销很大的一个点,很多物理刚体比较多的游戏帧率下降的比较厉害,如何优化物理引擎可以从一下方面来考虑:

  • 尽可能的减少的刚体数目,刚体的数目决定了物理引擎的计算成本。
  • 尽可能的使用性能高的物理形状捧起,避免复杂的物理形状碰撞器。球型碰撞器的性能就要大于刚体碰撞器。
  • 调整一些物理参数,减少物体的迭代次数来提升物理引擎的性能。
  • 更换性能更好的物理引擎,或者更换某个方面更好的物理引擎。
  • 自己根据游戏的需求自己实现一个性能高的阉割版“物理引擎”。

1.渲染

  • 利用reflect probe代替反射、折射,尽量不用RTT、GrabPass、RenderWithShader、CommandBuffer.Blit (BuiltinRenderTextureType.CurrentActive...)
  • 建立统一后处理框架(bloom、hdr、DOF等)代替多后处理,可以共用模糊函数,减少多次blit;另外要注意RTT的尺寸。
  • 空气折射、热浪扭曲等使用GrabPass不是所有硬件都支持,改为RTT或者后处理来优化。
  • 建立统一shader材质代替单一shader,充分利用shader_feature、multi_compile,并将宏开关显示于界面。
  • 图像混合代替多通道纹理,阴影投射、阴影接收、MetaPass、forwardadd 等pass不需要时要剔除。
  • 少用alpha test、discard、clip、Alpha Converage等,因为会影响Early-Z Culling、HSR的优化。
  • 避免Alpha Blend穿透问题(权重混合、深度剥离等透明排序方法代价太大了)。
  • 光照贴图代替动态阴影、尽量不用实时光;阴影贴图、环境贴图用16位代替32位;利用projector+rtt或者光圈代替实时阴影。
  • 将环境参数(风、雨、太阳)等shader全局参数统一管理。
  • 非主角可以用matcap代替pbr、无金属不一定要用pbr,仔细选择物理渲染所用的FDG(F:schlick、cook-torrance、lerp、要求不高用4次方,D:blinn-phong、beckmann、GGX、GGX Anisotropic,G:neumann、cook-torrance、Kelemen、SmithGGX;standard shader要注意选择BRDF1-BRDF3),渲染要求不高时不用GGX;可以用LH来优化GGX。
  • 用fixed、half代替float,建立shader统一类型(fixed效率是float的4倍,half是float的2倍),小心选择shader变量的修饰(uniform、static、全局),选择Mobile或Unlit目录下shader
  • 使用高低配渲染,内存足够时可以考虑开启mipmap
  • 使用surface shader注意关掉不用的功能,比如:noshadow、noambient、novertexlights、nolightmap、nodynlightmap、nodirlightmap、nofog、nometa、noforwardadd等
  • standard shader的变体太多(3万多),导致编译时间较长,内存占用也很惊人(接近1G),如果使用要关掉没用的shader_feature,比如:_PARALLAXMAP、SHADOWS_SOFT、DIRLIGHTMAP_COMBINED DIRLIGHTMAP_SEPARATE、_DETAIL_MULX2、_ALPHAPREMULTIPLY_ON;另外要去掉多余的pass
  • shaderforge、Amplify Shader Editor生成的shader有多余代码要程序专门优化,Amplify Shader Editor功能更强大一些,而且开源,建议学习。
  • 不要用unity自带terrian,因为即使只用3张splat图,shader也是对应4个的,建议T4M或者转为mesh。
  • 模型和材质相同且数量巨大时用Instance来优化,比如草。
  • 利用查找纹理(LUT)来优化复杂的光照渲染,比如:皮肤、头发、喷漆等。
  • 尽量不要使用Procedural Sky,计算瑞丽散射和米氏散射效率比较低。
  • 尽量不要使用speedtree,改为模型加简单树叶动画,不过SpeedTreeWind.cginc里面的动画函数很丰富,TerrianEngine中的SmoothTriangleWave很好用。
  • 多用调试工具检查shader性能,常用工具有:FrameDebug、Nsight、RenderDoc 、AMD GPU ShaderAnalyzer / PVRShaderEditor、Adreno Profiler 、腾讯Cube、UWA等;另外可以内置GM界面,比如开关阴影,批量替换shader等方便真机调试。

2.脚本

  • 减少GetComponent、find等查找函数在Update等循环函数中的调用、go.CompareTag代替go.tag 、
  • 减少SendMessage等同步函数调用;减少字符串连接;for代替foreach,5.5以后版本foreach已经优化过了;少用linq;
  • 大资源改为异步加载
  • 合理处理协程调用
  • 将AI、网络等放在单独线程
  • 发布优化:关闭log、剔除代码
  • 伪随机
  • 脚本挂载类改为Manager等全局类实现
  • lua中尽量不实现update、fixedupdate等循环函数,lua和csharp互调用的效率比较低。

3.内存管理

  • 池子管理粒子、float UI等小资源,频繁地GC会造成卡顿
  • 必要时主动调用GC.Collect()
  • 按照不同资源、不同设备管理资源生命周期,Resources.Load和Assetbundle统一接口,利用引用计数来管理生命周期,并打印和观察生命周期。保证资源随场景而卸载,不常驻内存,确定哪些是预加载,哪些泄漏。
  • 内存泄漏(减少驻留内存):Container内资源不remove掉用Resources.UnloadUnusedAssets是卸载不掉的;对于这种情况,建议直接通过Profiler Memory中的Take Sample来对其进行检测,通过直接查看WebStream或SerializedFile中的AssetBundle名称,即可判断是否存在“泄露”情况;通过Android PSS/iOS Instrument反馈的App线程内存来查看;
  • 堆内存过大:避免一次性堆内存的过大分配,Mono的堆内存一旦分配,就不会返还给系统,这意味着Mono的堆内存是只升不降的。常见:高频调用new;log输出;
  • CPU占用高:NGui的重建网格导致UIPanel.LateUpdate(按照静止、移动、高频移动来切分);NGUI锚点自身的更新逻辑也会消耗不少CPU开销。即使是在控件静止不动的情况下,控件的锚点也会每帧更新(见UIWidget.OnUpdate函数),而且它的更新是递归式的,使CPU占用率更高。因此我们修改了NGUI的内部代码,使锚点只在必要时更新。一般只在控件初始化和屏幕大小发生变化时更新即可。不过这个优化的代价是控件的顶点位置发生变化的时候(比如控件在运动,或控件大小改变等),上层逻辑需要自己负责更新锚点。 加载用协程; 控制同一个UIPanel中动态UI元素的数量,数量越多,所创建的Mesh越大,从而使得重构的开销显著增加。比如,战斗过程中的HUD血条可能会大量出现,此时,建议研发团队将运动血条分离成不同的UIPanel,每组UIPanel下5~10个动态UI为宜。这种做法,其本质是从概率上尽可能降低单帧中UIPanel的重建开销。
  • 资源冗余:AssetBundle打包打到多份中;动态修改资源导致的Instance拷贝多份(比如动态修改材质,Renderer.meterial,Animation.AddClip)。
  • 磁盘空间换内存:对于占用WebStream较大的AssetBundle文件(如UI Atlas相关的AssetBundle文件等),建议使用LoadFromCacheOrDownLoad或CreateFromFile来进行替换,即将解压后的AssetBundle数据存储于本地Cache中进行使用。这种做法非常适合于内存特别吃紧的项目,即通过本地的磁盘空间来换取内存空间

4.美术

  • 建立资源审查规范和审查工具:PBR材质贴图制作规范、场景制作资源控制规范、角色制作规范、特效制作规范;利用AssetPostprocessor建立审查工具。
  • 压缩纹理、优化精灵填充率、压缩动画、压缩声音、压缩UI(九宫格优于拉伸);严格控制模型面数、纹理数、角色骨骼数。
  • 粒子:录制动画代替粒子、减少粒子数量、粒子不要碰撞
  • 角色:启用Optimize Game Objects减少节点,使用(SimpleLOD、Cruncher)优化面数。
  • 模型:导入检查Read/Write only、Optimize Mesh、法线切线、color、禁用Mipmap
  • 压缩纹理问题:压缩可能导致色阶不足;无透明通道用ETC1,现在安卓不支持ETC2已不足5%,建议放弃分离通道办法。
  • UI:尽可能将动态UI元素和静态UI元素分离到不同的UIPanel中(UI的重建以UIPanel为单位),从而尽可能将因为变动的UI元素引起的重构控制在较小的范围内; 尽可能让动态UI元素按照同步性进行划分,即运动频率不同的UI元素尽可能分离放在不同的UIPanel中; 尽可能让动态UI元素按照同步性进行划分,即运动频率不同的UI元素尽可能分离放在不同的UIPanel中;
  • ugui:可以充分利用canvas来切分不同元素。
  • 大贴图会导致卡顿,可以切分为多个加载。
  • iOS使用mp3压缩、Android使用Vorbis压缩

5.批次

  • 开启static batch
  • 开启dynamic batch:要求模型小于900顶点,用法线小于300,用切线小于180,缩放不一致、使用lightmap、多通道材质等会使dynamic batch无效。
  • 减少GameObject,场景模型数量对fps影响巨大。
  • 批次不是越少越好,过大的渲染数据会给总线传输带来压力。

6.物理

  • 不需要移动的物体设为Static
  • 不要用Mesh碰撞,角色不用碰撞体
  • 触发器逻辑优化
  • 寻路频率、AI逻辑频率 、Fixed Timestep、降帧到30
  • 出现卡顿的复杂计算,例如寻路、大量资源加载 可以用分帧或者协成异步来处理

Unity 面试题汇总(五)性能优化知识点相关相关推荐

  1. Unity 面试题汇总(三)Unity 基础相关

    Unity 面试题汇总(三)Unity 基础相关 目录 Unity 面试题汇总(三)Unity 基础相关 0.FSM(状态机).HFSM(分层状态机).BT(行为树)的区别 1.什么是协同程序? 2. ...

  2. 【转】【Unity+Lua】实测如何性能优化(Lua和C#交互篇)

    [转][Unity+Lua]实测如何性能优化(Lua和C#交互篇) https://blog.csdn.net/swj524152416/article/details/71125478 posted ...

  3. PostgreSQL:十五. 性能优化

    十五. 性能优化 优化简介: PostgreSQL优化一方面是找出系统的瓶颈,提高PostgreSQL数据库整体的性能: 另一方面,需要合理的结构设计和参数调整,以提高用户操作响应的速度: 同时还要尽 ...

  4. 前端面试题-CSS选择器性能优化

    一.CSS选择符 CSS选择符由一些初始化参数组成,这些参数指明了要应用这个CSS规则的页面元素. 作为一个网站的前端开发工程师,应该避免编写一些常见的开销很大的CSS选择符模式,尽量编写高效的CSS ...

  5. 一文了解程序员必须要知道的JVM和性能优化知识点

    目录 JVM和性能优化 1.Java内存区域 虚拟机的历史 未来的Java技术一览 运行时数据区域 站在线程角度来看堆和栈 深入辨析堆和栈 方法的出入栈 虚拟机中的对象 堆参数设置和内存溢出实战 2. ...

  6. 实践+收藏版——Linux 性能优化知识点总结大全!

    Part1Linux性能优化 1性能优化 性能指标 高并发和响应快对应着性能优化的两个核心指标:吞吐和延时 图片来自: www.ctq6.cn 应用负载角度:直接影响了产品终端的用户体验 系统资源角度 ...

  7. Unity 对GPU的基础性能优化

    Unity GPU 性能优化 遇到的问题 分析工具 解决方法 1.不在代码层面的优化方法 2.部分在代码层面进行优化 特别说明 遇到的问题 因为项目中采用的主机为工控机,不带有独立显卡,CPU采用的是 ...

  8. 【面试题】前端性能优化

    参考文章: 前端性能优化 24 条建议 前端必会!四步带你吃透浏览器渲染基本原理 大公司里怎样开发和部署前端代码? 前端性能优化的七大手段 和面试官吹嘘一下我的Vue项目性能优化 优化原理 1.网络: ...

  9. Unity面试题汇总(第一部分)

    一.什么是渲染管道? 答:就是告诉GPU一些数据,经过一系列的操作,得到最终要显示的数据.渲染管道中的很多步骤,总的来说是将几何物体从一个坐标系变换到另一个坐标系中去. 主要步骤如下:  本地坐标系 ...

  10. 膜拜大牛!这份333页关于性能优化知识点的PDF你不能不看!Android篇

    前言 说起程序员人们的第一印象就是工资高.加班凶.话少钱多头发少.再加上现在科技互联网公司太吃香,bat.华为小米等公司程序员加班情况被广泛传播,程序员用生命在敲代码的印象刻在了很多人的心里. 与其它 ...

最新文章

  1. 图解C/C++中函数参数的值传递、指针传递与引用传递
  2. Windows进程与线程学习笔记(三)—— KPCR
  3. C Primer Plus(第五版)7
  4. Flutter AlertDialog弹框的基本使用、Flutter showDialog方法使用概述
  5. 计算机多了一个虚拟硬盘,移动硬盘中安装多个虚拟操作系统 -电脑资料
  6. (转)Android Bitmap 与 Drawable之间的转换
  7. mysql8.0 利用docker容器安装配置多主多从集群
  8. DMA—直接存储区访问
  9. 第二课 了解编程环境
  10. heic转换成jpg,学会这个方法就够了
  11. 辛格函数sinc(x)和抽样函数Sa(t)
  12. CentOs解压缩命令
  13. System.BadImageFormatException: 未能加载文件或程序集
  14. SSM框架练手项目【虎牙个人博客】手把手带你搭建自己的个人博客
  15. HD Tune结合硬盘再生器HDDREG快速修复硬盘错误
  16. 动态网站开发(应该怎么做啊)
  17. 聚类评价指标S_Dbw及其python实现
  18. html5页面可见xing,【 前端资源 网页插件 】全屏滚动效果H5FullscreenPage.js
  19. 对阿,为什么大学程序设计老师不去外面公司当程序员?
  20. img标签—三种图片格式使用

热门文章

  1. 对于自己(Android)秋招的一点总结(感慨)
  2. 协同OA产品要完全符合企业的办公模式吗?
  3. OA系统行业解析:中小型企业OA选型常见问题
  4. 【精度】概率论之概念解析:边缘化(Marginalisation)
  5. 剖析矩阵的本质及其意义
  6. 学习Vue3 第一章
  7. 《海瑞官场笔记》有感
  8. 大会回顾丨游戏用户体验优化如何实践,看大咖怎么说(附PPT下载)
  9. VMware 搭建私有云
  10. 【自我介绍】小白程序员的成神之路