UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - 纹理
type: channel
link: https://blog.uwa4d.com/tag/纹理/
items: 粒子系统优化：Mesh模式下的优化策略
items: 如何渲染低分辨率的DepthTexture？
items: 如何更深入地掌握研发项目的物理性能？
items: Unity图片优化神器 - Dither算法进阶方案
items: 关于Unity中的资源管理，你可能遇到这些问题

粒子系统优化：Mesh模式下的优化策略
type: item
date: 2020-11-25T04:17:00+02:00
description: 在上一期《动画优化：关于AnimationClip的三两事》中，我们针对动画优化中的“AnimationClip的压缩方式”以及“AnimatorController挂载的AnimationState”做了解释。这些看似细小的知识点，很容易在大家的开发和学习过程中被疏忽，而长期的问题积累最终都会反映到项目的性能表现上。为此，我们将这些规则列出，并且以一个个知识点的形式向大家逐一解读。 本期，我们将面向粒子系统，讲解UWA本地资源检测中的规则：“网格发射数超过5的粒子系统”、“检测粒子系统使用的网格Read/Write选项”、“引用网格面片数超过500的粒子系统”和“引用纹理数超过5的粒子系统”。我们将力图以浅显易懂的表达，让职场萌新或优化萌新深入理解。 1、网格发射数超过5的粒子系统 关于粒子系统的定义，我们在此不再赘述。我们重点来看一下粒子系统的渲染模式。 粒子的渲染模式可分为两大类：2D的Billboard（公告牌）图形模式和Mesh模式。Billboard是3D游戏中用的非常多的一种技术，使用该模式，粒子会以一个平面的形式存在，并始终以一定的角度对着我们的镜头。举个例子来讲，场景中有一棵树，是实体存在的；但在各种Billboard模式下，你只需要看到这棵树的一个“面”。在展示效果上表现其实一样，但渲染一个“面”和渲染一整个实体，前者在消耗的内存和运算量上会更有优势。 Billboard模式，粒子会始终面向摄像机； Stretched Billboard模式下，粒子在面向摄像机的同时，会在速度的方向上进行拉伸，可以设置粒子的各种缩放和拉伸效果； Horizontal Billboard模式下，粒子平面会和XZ面平行； Vertical Billboard模式下，粒子平面在面向摄像机的同时，会在Y轴方向上直立（垂直于XZ平面）； 而“Mesh”模式就是我们今天的主题。这个模式允许粒子系统发射3D网格而非2D的Billboard，以此来实现更为复杂和贴合需求的粒子效果。 3D网格性能开销显然是高于2D的Billboard的。如果粒子系统的发射的网格数量过大，会带来较高的CPU计算开销、GPU渲染压力甚至较高的堆内存分配。所以我们对使用Mesh进行渲染的粒子系统的“最大粒子数”要求更为严苛。一般的粒子系统中，粒子发射数上限建议不超过30（我们在之前的文章中有进行讨论，详见《【性能黑榜】掌握了这些规则，你已经战胜了80%的对手！》）。经由UWA的测试结果表明：渲染模式为Mesh、网格发射数在不超过5的情况下，能够维持粒子效果和性能消耗上的一个相对平衡。 所以开发团队在本条规则执行后，需要对筛选出来的粒子系统进行显示效果和性能消耗上的平衡考量，并对相应的粒子系统进行修改。 2、所使用的网格未开启Read/Write选项的粒子系统 由于粒子系统的渲染对象从传统的粒子变成了3D网格，所以引用的网格对象本身的属性也会对整个粒子系统产生影响。在这种情况下，很多针对网格的优化建议也同样适用于Mesh模式下的粒子系统，只不过针对特定应用环境，部分优化建议需要“反着看”。 在之前的文章《网格优化：溃堤之穴，一个也不能放过》中，我们对于网格的Read/Write选项进行过一定的说明。仅仅针对网格而言，我们是不建议开启相关的读写权限。 但是在粒子系统应用中，往往需要去实现各种各样的动态效果。所以当渲染模式为Mesh时，网格就需要开启Read/Write选项以实现对3D网格的动态修改，来更好地实现相应的渲染展示效果。 Unity官方文档告诉我们，网格必须设置为Read/Write Enabled才能在粒子系统上工作。若不开启，在某些设备上运行时，可能会出现模型丢失的情况。可见问答：https://answer.uwa4d.com/question/5cf732fed27511377098284e。因此我们将该类异常的资源检测出来，希望研发团队进行规范，防止出现意外的显示结果。 3、引用网格面片数超过500的粒子系统 和上一条规则不同，这条便是网格优化在粒子系统中的正向应用。在以前文章《那些年给性能埋过的坑，你跳了吗？》中，我们对于网格的面片数有过相关的简单讲解。对3D网格而言，过多的面片数会带来内存，加载和渲染上的额外消耗。在粒子系统中也是同样如此。 在Mesh模式下，发射的3D网格对面片数的要求也是同样严苛的。所以开发团队需要在本条规则下，去检查相关粒子系统引用的网格是否存在面片数过多的情况，并视实际的表现效果和性能消耗去决定是否需要做出相关的修改。 4、引用纹理数超过5的粒子系统 在之前的文章《材质优化：如何正确处理纹理和材质的关系》中，我们简单讲了下材质的Shader中引用了过多纹理会在内存和计算上带来的影响。 同样的，在粒子系统中，如果在材质内…

如何渲染低分辨率的DepthTexture？
type: item
date: 2018-04-02T13:12:00+03:00
description: 本期聚集话题：材质（贴图）之间的性能影响、粒子系统不断修改发射点的性能最优解、如何渲染一个低分辨率的DepthTexture、Unity中物体实时光照的性能开销... 这是第105篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间15分钟，认真读完必有收获。文末，我们的互动话题是：.net 4.6是否值得尝鲜？ UWA 问答社区：answer.uwa4d.com UWA QQ群：793972859（仅限技术交流） 制作 Q1：请教一个问题，我们项目中有一种粒子系统需要不断地修改发射点，用worldspace模式确实可以达到一定的效果，但是目前需要在一帧里从若干个位置同时发射粒子，用多个相同的Particle System确实可以达到目的，但总觉得有些浪费，容易造成频繁的创建和销毁，如果都缓存的话总数可能就会很大了，不清楚有没有可能用一个Particle System来实现这个效果？ 可以参考这个文档: https://docs.unity3d.com/ScriptReference/ParticleSystem.Emit.html 通过调用ParticleSystem的Emit方法来发射粒子，可以在一帧里用不同的EmitParams进行多次调用Emit。这样的话，粒子系统本身就不用移动了，只是每次Emit的位置参数不同。 感谢zZ@UWA社区提供了回答，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5abde6cd425802635474fbb1 资源管理 Q2：情景1：一张512x256的纹理。情景2：两张256x256的纹理。请问以上哪个情景消耗会更大呢？ 进一步再说，一个同样的角色，情景1：一个材质球，一张512x256的纹理，情景2：两个材质球，各256x256的纹理，这种情况下又是哪个情景消耗更大呢？判断依据和原理是什么呢？ UWA：就内存方面，设置相同的情况下，二者应该是一致的（某些需要长宽相等的压缩格式除外）。 在CPU开销方面，那么二者基本上也可能是差异很小的。如果是一个材质，一张和两张的区别在于采样次数。一张纹理会减少一次纹理的绑定开销，理论上减小的耗时开销是在ns级别的，所以在绝大多数实际情况下CPU端的差异都可以忽略不计；如果是两个材质，影响的因素就会多了，可能会造成更多的Draw Call、更多的RenderState切换等等，这就是为什么要采用Atlas Packing的优化原因。 至于GPU上的开销，多一张纹理的Shader采样肯定要多一些计算，所以Shader Instruction和Cycles肯定也会更多一些。但是否真的是会带来更大的实际开销呢？不一定，因为GPU压力不光是要看一个Shader的Instruction，同时还要看Runtime到底有多少像素是通过这个Shader来渲染的，如果本身占比很小，那么多一次采样和少一次采样，在实际情况下没啥区别。 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5abdca59910222635366dc77 渲染 Q3：我想渲染一个低分辨率的DepthTexture，有什么比较方便的做法吗？ 一种方式是自己创建一个低分辨率的RenderTexture，然后通过Shader Replacement来渲染一个DepthTexture（Shader可以参考Unity的ShadowCaster的实现）。 还有一种是通过_LastCameraDepthTexture来访问其他的低分辨率相机（如果有的话）渲染出来的DepthTexture。 感谢zZ@UWA社区提供了回答，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5abde736910222635366dc78 渲染 Q4：我想了解下Unity中物体接受灯光、产生阴影、接受阴影的性能开销情况。之所以这么问，是想看看Unity中的以上三种的性能开销。另外，在移动端是否有开启以上三种，或者部分的可能性，谢谢。 UWA：题主的“接受灯光”是指接受Real time光照吗？如果是的话，那么这种性能开销是没法预估的，因为它跟你的渲染场景复杂程度、接受光照物体的多少等都有很大关系，并不能简单理解成“开启就耗时、绝对不可以开启”。建议看看这篇文章：【求知探新】独立游戏《Abi》场景耗时分析 “产生阴影”和“接受阴影”是实时阴影功能的两个选项而已，建议在UWA性能报告中关注Shadows.RenderShadowMap和Shadows.PrepareShadowMap两项，从而来看到你项目的具体性能…

如何更深入地掌握研发项目的物理性能？
type: item
date: 2018-03-26T13:15:00+03:00
description: 本期我们聚集了这几个话题：如何提高Unity物理引擎Physx的性能、如何禁止Unity自动编译脚本、使用Texture2DArray时开启Mipmap效果... 这是第104篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间15分钟，认真读完必有收获。文末，我们的互动话题是，Mono和IL2CPP，哪个更受你的青睐，我们期待你的灼见！ UWA 问答社区：answer.uwa4d.com UWA QQ群：793972859（仅限技术交流） 封面图片来自Google 物理 Q1：如何提高Unity物理引擎Physx的性能？我在场景中加入100个具有物理刚体的小人，并充分碰撞，发现低端机的帧率只能跑到10帧以下。 UWA：如果需要充分进行物理计算，这些拥挤在一起的小人很可能会造成大量的物理碰撞，进而造成很高的物理耗时。 在优化之前，题主需要先了解一些基础物理数值推荐，并不是所有设备都能流畅跑起100个自由制作的蒙皮模型的。所以，建议先看这篇文章：https://blog.uwa4d.com/archives/Simple_PA_Physics.html 另外，题主还需要看一下真机上的具体耗时瓶颈是否真正耗时在物理上，100个角色，如果Mesh量很大，渲染耗时也是不可忽视的，所以建议通过Unity Profiler来先确定一下真机上性能瓶颈。 最后，是否每个小人都需要精确的物理计算，是否可以通过一些射线求交、范围判断等性价比高的判断方法来代替纯物理碰撞？这需要题主从设计的角度上来进行权衡。 你并没有什么办法直接调用PhysX的API，所以所谓优化物理引擎的性能，倒不如说优化相关组件的性能。首先优先使用CharacterController而非RigidBody。然后尽量避免使用MeshCollider，用BoxCollider、SphereCollider和CapsuleCollider代替，如果不可避免，那么钩上Convex。当然，最好的优化就是不要用物理碰撞。射线能解决很多问题，但是射线也不是免费的，它的消耗跟场景里的面数正相关，当然设置射线的maxDistance和LayerMask能节省掉不必要的消耗。最好就是用简单的物理模型来描述问题，自己写代码实现。 感谢凯奥斯提供了回答 欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab47292d35eb22c10a0a403 编辑器 Q2：有什么方法可以禁止Unity自动编译脚本呢？ UWA：可以把Preferences > General > Auto Refresh的勾去掉，不过如果这样，如果资源在外部被修改，也不会自动重新导入了，会有些影响。 另外，也可以试试这两个函数EditorApplication.LockReloadAssemblies/ UnlockReloadAssemblies，参考文档：https://docs.unity3d.com/ScriptReference/EditorApplication.LockReloadAssemblies.html 该问题由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab8a76dd35eb22c10a0a41f 渲染 Q3：我使用Texture2DArray的时候发现没有Mipmap效果，想问下怎么让Mipmap生效？原始的纹理都是开启Mipmap的。 UWA：在创建Texture2DArray时需要注意倒数第二个参数，设为true就表示开启Mipmap，参考文档： https://docs.unity3d.com/ScriptReference/Texture2DArray-ctor.html 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab7afd60b827e2c0bfdd18f 制作 Q4：现在有一张外部生成的Texture可以作为mainTex渲染，但希望获取它的内容保存下来，但是GetPixels是Texture2D的函数，所以想问问这样的Texture的内容该如何获取？ UWA：可以先通过Graphics.Blit把内容画到Render Texture里面，然后通过ReadPixels从RenderTexture生成一张Texture2D，假设sourceTex是原始的Texture，tex2D是最终生成的Texture2D，则代码如下： RenderTexture renderTexture = new RenderTexture(sourceTex.wid…

Unity图片优化神器 - Dither算法进阶方案
type: item
date: 2017-08-22T11:02:00+03:00
description: 在Unity移动平台的游戏开发过程中，贴图资源是往往是占资源量最大的资源。如何在保证视觉效果的同时，尽可能地减少贴图资源，是开发团队会经常遇到的问题。通常来说，对于3D物体的纹理，是可以采用ETC/PVRTC等压缩比很大的算法处理，但是对于细节要求很高的UI纹理，这样处理造成的失真往往达不到质量要求。对于这类的贴图，我们可以考虑使用失真较小的16位贴图格式存储。 但是对于颜色数较高的纹理，Unity提供的默认转换方法会呈现明显的色阶。针对该问题，keijiro实现了一种Dither4444的改进算法。从下图1上可以看到，对于画面细节比较平滑的图片，该算法虽然消除了色阶现象，同时带来了肉眼可见的噪点。 <图1> 左：原始图 右：keijiro的 Dither4444示意图 笔者在keijiro的算法基础上进行了改进，提供了一个将RGB24 Bit图Dither之后转RGB565的方法，基本消除了肉眼可见的失真，实际效果见下图2。 <图2> 左：原始图 右：笔者的 Dither565示意图 实际在我的项目的应用中，对于不适合ETC/PVRTC压缩的图片，都采用了该文章中的RGB565或者RGB565+A8的方式。在肉眼基本无失真的基础上，节省了部分资源。 最后附上OnPostprocessTexture代码： void OnPostprocessTexture (Texture2D texture) { if(assetPath.Contains ("_dither565")) { var texw = texture.width; var texh = texture.height; var pixels = texture.GetPixels (); var offs = 0; var k1Per31 = 1.0f / 31.0f; var k1Per32 = 1.0f / 32.0f; var k5Per32 = 5.0f / 32.0f; var k11Per32 = 11.0f / 32.0f; var k15Per32 = 15.0f / 32.0f; var k1Per63 = 1.0f / 63.0f; var k3Per64 = 3.0f / 64.0f; var k11Per64 = 11.0f / 64.0f; var k21Per64 = 21.0f / 64.0f; var k29Per64 = 29.0f / 64.0f; var k_r = 32; //R&B压缩到5位，所以取2的5次方 var k_g = 64; //G压缩到6位，所以取2的6次方 for(var y = 0; y < texh; y++){ for(var x = 0; x < texw; x++){ float r = pixels [offs].r; float g = pixels [offs].g; float b = pixels [offs].b; var r2 = Mathf.Clamp01 (Mathf.Floor (r * k_r) * k1Per31); var g2 = Mathf.Clamp01 (Mathf.Floor (g * k_g) * k1Per63); var b2 = Mathf.Clamp01 (Mathf.Floor (b * k_r) * k1Per31); var re = r - r2; var ge = g - g2; var be = b - b2; var n1 = offs + 1; var n2 = offs + texw - 1; var n3 = offs + texw; var n4 = offs + texw + 1; if(x < texw - 1){ pixels [n1].r += re * k15Per32; pixels [n1].g += ge * k29Per64; pixels [n1].b += be * k15Per32; } if(y < texh - 1){ pixels [n3].r += re * k11Per32; pixels [n3].g += ge * k21Per64; pixels [n3].b += be * k11Per32; if(x > 0){ pixels [n2].r += re * k5Per32; pixels [n2].g += ge * k11Per64; pixels [n2].b += be * k5Per32; } if(x < texw - 1){ pixels [n4].r += re * k1Per32; pixels [n4].g += ge * k3Per64; pixels [n4].b …

关于Unity中的资源管理，你可能遇到这些问题
type: item
date: 2016-11-08T16:28:00+02:00
description: 关键字 AssetBundle 资源制作 纹理\网格\材质\Shader\音频\动画 Lightmap 一、AssetBundle 相关 Q1：Unity中的SerializedFile是怎么产生的？请问用Unload(false)可以清除吗？因为读取了Bundle里面的内容后已经赋值给其他物体了。而且我把图片都打成了Bundle，然后读取出来，图片的大小应该是超过了这个SerializedFile的大小的？ SerializedFile是AssetBundle加载时产生的序列化信息，一般为LoadFromCacheOrDownload、LoadFromFile和New WWW加载本地AssetBundle文件所致。如果AssetBundle中的资源已经加载，且后续没有依赖该AssetBundle的资源进行加载，那么可以通过Unload(false)将其删除。SerializedFile中记录的是AssetBundle的序列化信息，而不是其包含资源的内容，因此，其大小要小于或远小于资源的实际内存。 Q2：我们现在采取了2种资源管理方式， 我想了解下内存占用问题。 方式A：AssetBundle加载好以后在内存中保留， 如果需要创建对象就通过Instantiate来创建。方式B：AssetBundle加载好以后立刻通过Instantiate实例化一个对象， 然后Unload（false）这个AssetBundle，如果需要，创建对象通过clone来完成。对于方式A，在Profiler中发现 WebStream中有相关AssetBundle文件存在， 我们认为这份内存是多余的, 所以就采取了方式B， 方式B又面临资源卸载不干净的情况。想确认一个问题， 如果采取方式A： 一个 xxx.assetBundle原始文件大小是 1MB， 解压到Webstream内存中是2MB， 最终实际内存占用是2MB 还是3MB 呢？ 如果采取方式A：最终实际内存占用是2MB，而不是3MB，WebStream中已经包含原始AssetBundle的数据。对于没有依赖关系的AssetBundle文件，我们推荐方式B的形式对AssetBundle进行卸载，这样可以免除不必要的内存占用，对于这种方式加载出来的资源，可以通过Resources.UnloadAsset和Resources.UnloadUnusedAssets来进行卸载，如果无法卸载，则该资源一定被缓存了，研发团队可通过检测自己的缓存池/Constainer来进行检测。同时，如果是Unity 5.3以后版本，可尝试通过Memory Profiler来进一步查看。 Q3：打包时候AssetBundle的md5总变化(被打包的东西没变)，请问能怎么解决？有说法是加上DeterministicAssetBundle就可以，但是我尝试后发现md5依然变化。 该方法确实并不受用。对于Unity 4.x版本的AssetBundle文件，其md5值在某些情况下确实会前后不一致（哪怕是完全一样的内容进行打包）。对于该系列版本，仅能建议开发团队建立配置文件来对AssetBundle进行管理。 而对于Unity 5.x版本，则可以在打包时开启 AppendHashToAssetBundleName 选项，这样Unity引擎会在每个AssetBundle文件后生成一个唯一的HashID（显示地放在文件名后），开发团队可以通过该ID来判断对应的AssetBundle文件是否发生改变。 Q4：如果采用依赖打包的话，比如NGUI，图集A作为被依赖包，界面1、2、3作为独立包，分别依赖打包。当图集A重新打包的时候，是不是界面1、2、3也都要重新打包？目前我们是界面打包时清空全部的图集信息再打包，在客户端加载后再动态赋值回来。这样可以实现单独更新图集，但是代价就是加载的性能。请问有什么更好的解决方案呢？ “当图集A重新打包的时候，是不是界面1、2、3也都要重新打包”，这是不需要的，Unity 4.x的依赖打包的限制在于，在重新打一个包时需要将它依赖的包都重新打一次，但不需要重打依赖它的包。 Q5：请问内置的shader怎么打包？我用到了内置材质球，不只是Shader，这时候在Profiler中看到加载的结果中会出现多份，如下图所示 通常有两种方式对内置的Shader进行打包： 将其添加到Graphics Settings中的Always Included Shaders 中，此时添加后的内置Shader就不会被打入AssetBundle包中； 在http://unity3d.com/cn/get-unity/download/archive下载内置的 Shader，将其导入项目，并替换成非内置的材质球，从而可以直接通过脚本来控制其…