UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - gpu
1）如何统计DrawMeshInstancedIndirect绘制物体的Triangle数据 2）如何量化骨骼数量对功耗的影响 3）Sprite Atlas和单独的Sprite的RW设置分别如何影响内存 4）Playable的Animator.WriteTransform一直执行在一个Job线程里 这是第444篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 GPU Q：我们项目的人物渲染从Mesh Render渲染改成了GPU Driven，调用DrawMeshInstancedIndirect渲染，然后看起来人物的面数在Unity Profiler的Triangle数据中统计不到了。有没有别的方法统计监控这部分Triangle数据？ A：之前我们也遇到过用这种方式绘制物体的项目，可以试试GPU底层获取的Primitive，应该是准的。比如UWA Gears或GOT Online GPU模式的参数。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/68be4643682c7e5cd61bfb18 Animation Q：我们项目角色骨骼数量现在大概是200多，怪物会少一些，但应该也属于比较多的，一直想让美术把规格降下来。现在在Profiler里看到蒙皮网格主要是在子线程有消耗，但是不影响帧率。担心对功耗有影响，但是没法量化，不确定美术这边优化到什么水平比较合理，有没有什么好的办法或建议？ A：一个模型200骨骼对移动项目来说肯定是比较多的，不过具体优化到多少也要结合同屏角色和怪物数量看。 功耗的角度上，Perfetto工具可以量化功耗的影响，但是需要Pixel设备。可以参考今年UWA DAY《移动端Unity游戏功耗调优的破局实践》话题的讨论。 还有个方法，出个关闭动画的包，对比下功耗的差异。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/68be7a93244ce21ce9ec0914 Memory Q：Sprite开启Read/Write选项，会导致额外的内存占用。如果禁用Sprite Atlas的Read/Write，单独的Sprite不禁用，内存占用会比都禁用高吗？ A：内存占用量应该以最终进包进内存的图集的设置为准。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/68be4547682c7e5cd61bfb17 Animation Q：Playable的Animator.WriteTransform一直执行在一个Job线程里，如何让其分散在各个Job里执行？ A：在场景里平铺，不要有共同的父节点。 感谢littlesome@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/68917164682c7e5cd61bfad2 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
1）Sprite纹理被材质引用导致冗余问题 2）为什么在Android端发现Vertices shaded次数远大于Unity场景中的模型顶点数 3）物体使用Unlit的材质，为何会被点光源打断合批 4）微信小游戏2D动画方案如何选择 这是第439篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 Memory Q1：纹理设置成Sprite后，如果再被材质引用，会被收集两次造成冗余，即使将纹理设成dependAsset进行收集也没用，这个有办法解决吗？ A：这个是Unity引擎一直都有的问题，需要复制一份，一个设置成Default，一份设置成Sprite2D。 Q2：那这样和不处理有什么区别？ A：还是有区别的，如果有多个UI和普通材质球使用这个纹理，就会冗余多份；如果复制了一份，那么就会只有1个Sprite和1个Default纹理。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/687e04fc3d306f3e9d594f1d GPU Q：在Android端发现Vertices shaded次数远大于Unity场景中的模型顶点数，这是什么原因导致的呢？已经排除了一个模型多个Pass导致的原因了，可以确定只有一个Pass。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/6874cfc53d306f3e9d594f13 Rendering Q：项目中发现有的物体使用的是Unlit的材质，但仍会被点光源打断合批（因为关闭点光源或把点光源的Culling Mask调整为不包含这些物体的Layer），可能是什么原因？ A：Unity有过类似的Bug，不排除仍是Bug的影响。 参考： https://unity.cn/release-notes/full/2021/2021.3.7f1 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/687dbf973d306f3e9d594f1b Animation Q：关于微信小游戏2D动画这一块，Unity的2DAnimation性能表现如何，建议使用吗？或者有其它比较推荐的方案吗？ 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/687dfeda682c7e5cd61bfac8 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
1）UnityEngine.Object在失去引用后用什么方法销毁或回收 2）关于纹理采样方式对带宽的影响原理的问题 3）微信小游戏缓存catalog.json文件的方案 4）微信小游戏的帧率设置用哪个更好 这是第438篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 Memory Q：UnityEngine.Object在失去引用后，是一定要调用Destroy进行销毁么？不会被GC自动回收么？ A：UnityEngine.Object是一种基类，题主的问题比较大。常见的有三种情况： C#的托管堆内存在失去引用后，会在GC后被自动回收； 从AssetBundle中被加载出来的资源，需要用AssetBundle.Unload(True)这类的API定向卸载，或在失去引用后被Resources.UnloadUnusedAssets标记并卸载； 还有一种动态创建的对象，如通过new、instantiate生成的资源，则需要手动Destroy，或在失去引用后被Resources.UnloadUnusedAssets标记并卸载。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/686b545d682c7e5cd61bfaba GPU Q1：想问下关于纹理采样的消耗这块，有没有一些相关的经验可以分享？ 从Mali的文档上看，Cache Hit的情况下是1cycle采样1纹素，Cache Miss的情况下需要100-200cycle。 那么如果触发Cache Miss，是从当前采样UV位置的附近的2*2读取到Tex Cache上，还是直接读取L1大小的数据上去。比如Adreno有1KB的L1？ A：从Onchip到L1不是固定的2*2，也不是整个L1；根据GPU型号不同，有一个固定的每次取的大小，纹理压缩率越低、单个像素信息量越大，每次能取的像素数量就会更少，Cache Miss率就更高，此时就会往更远的L1、L2去取。 Q2：那是按1维去取n个像素，还是二维去取m*m个像素？想了解下怎么样摆放数据能Cache友好些。 A：应该是m*m。一般只要开启Mipmap，交由GPU自动选择合适的层级，就能使像素的采集过渡平滑友好，Cache Miss率较低了。此外就是尽量压缩纹理格式、避免使用各向异性这种大幅增加采样次数的做法，也能有效降低带宽。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/687476f2682c7e5cd61bfabf Memory Q：现在我们不能缓存catalog，每次需要临时下载还挺麻烦的。APP的Addressables热更的方案就是先去线上请求catalog.hash文件，然后判断本地的catalog文件是不是旧的，旧的就删除掉本地的catalog文件，然后下载新的。但是在微信小游戏会一直请求catalog，所以我们每次进入游戏都要下载catalog文件。请问微信小游戏该如何缓存catalog.json文件呢？ A：我的方案是自定义了catalog1000、settings1000文件，加上版本号，相当于是自己改了settings文件里面的catalog URL，然后改了catalog文件名，也改了Addressables的源码，取到版本号后去读取指定的settings文件，这样settings文件也可以缓存了。 我的方式是catalog和settings都自定义成了下面这样： 然后settings文件的这一句话改成了我指定的catalog1000： "m_InternalId":"{UnityEngine.AddressableAssets.Addressables.RuntimePath}/catalog1000.bundle" 再改了个Addressables的源码，Addressables在初始化的时候会去找settings文件： 然后就是走微信小游戏自己的规则了，即本地有就读取本地，本地没有下载缓存。catalog和settings都加了版本号，也不怕settings同名或cdn缓存问题。 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/687482d6682c7e5cd61bfac0 Platform Q：微信小游戏现在都是推荐用Unity的Application.targetFrameRate了吗？现在我们用的这个WX.SetPreferredFramesPersecond，会产生什么问题吗？没Unity那个性能好？ A：帧率设置可能失效。现在微信小程序已对Application.targetFrameRate做了较好的适配。 更推荐用这个： https://github.com/wechat-miniprogram/minigame-unity-webgl-transform/issues/713 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/6874a85b3d306f3e9d594f11 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
1）关于il2cpp.so裁剪的问题 2）如何在UE5的渲染管线里多加一个颜色 3）请问iOS用MallocBinned2的效果如何 4）XCode显示的带宽为什么这么高 这是第414篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 Script Q：我们最近在关注包体问题，发现il2cpp.so里有1/4的代码是Generics GenericMethods，请问有什么方法能减少这些代码生成的同时，运行不报错呢？ 另外对于APP，有没有人测试过IL2CPP Code Generation的两个选项OptimizeSize和OptimizeSpeed对于运行时影响有多大呢？ 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/675f9b483d306f3e9d594dc9 Rendering Q：UE5的Movie Render Queue Additional Render Pass的功能，将来有没有计划做一个Runtime版的？或者说UE5将来会不会做一个Runtime的Render Graph功能？ 我主要想在整个渲染管线里多加一个颜色。就是在RGB之外再加一个颜色红外IR。请问有什么办法吗？ A：在整个管线里都要多加一个通道的话，单独加一个Render Pass也不解决问题。目前没有纯数据驱动的Runtime Render Graph的计划。但是Runtime在管线特定环节注入自己的GlobalPass甚至MeshPass都是可以的，也可以自己注入自己的结构Patch到SceneUB上。 感谢Abyss@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/675fe1e7682c7e5cd61bf9c9 Memory Q：请问iOS用MallocBinned2的效果如何？我们遇到疑似MallocBinned导致的内存访问和系统调用方面的性能问题，在评估移植MallocBinned2对iOS的支持的收益，有人处理过吗？ 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/675fe0ff3d306f3e9d594dcc GPU Q：请问XCode显示的这个带宽是指什么？为什么1s会有26G，而且vs那么高呢？ 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/675fe466682c7e5cd61bf9ca 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
为什么Unity里的变体数和UWA工具测出来的不一样
1）为什么Unity里的变体数和UWA工具测出来的不一样 2）使用TextureArray为什么会导致L1 Cache Miss率变高 3）Gfx.PresentFrame耗时异常高 4）AO方案中哪个更适合移动端 这是第412篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 Shader Q：为什么Unity里的变体数和UWA工具测出来的变体数不一样呢？ A：shader_feature在编辑器中预览只会保留第一个关键字的变体，所以会少显示很多；但实际进入包体的是UWA AssetBundle工具显示的量。 顺便一提，通过变体收集的方式可以让引擎在打包的时候自动去掉一部分shader_feature关键字导致的变体组合，节省部分内存。 该回答由UWA提供，欢迎大家转至社区交流： https://answer.uwa4d.com/question/674d1de4682c7e5cd61bf9b3 GPU Q：做地形的时候用的ID图的方案，将所有的地表贴图做成了TextureArray，Xcode测试性能。发现TextureArray导致Texture L1缓存的Cache Miss率高达77%，且不用就不高了，设备是iPad Air 5。 请问是相邻像素的TextureArray的ID不同导致Cache Miss吗？ 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/674d2240682c7e5cd61bf9b6 Rendering Q：部分安卓机型上，某个东西显示出来的时候，Gfx.PresentFrame耗时非常高。 以下是真机测试、多线程渲染下的截图： 目前还没找到根本原因，但实验发现： 1. 挂掉出问题的Shader的Clip函数，就不会有问题。 2. 删掉工程内的变体收集器，也不会有问题（新版本和老版本相关的区别就是新收集了一波变体，然后打进了Bundle里）。 目前解决方案就是把变体收集器删了，因为现在就没做Shader预热，所以收集器其实没用到。 请问其本质原因是什么？ 针对以上问题，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/674ab5a93d306f3e9d594db6 Rendering Q：请问以下AO方案中，哪些适合移动端呢？ SSAO、SSDO、HBAO+、GTAO、MXAO、LSAO、VXAO、DeepAO、以及上一年Meta的PSAO。 A：一般动态物体就是烘培AO，也就是单个物体有，静态物体就是LightmapAO也是烘培的，移动端从性能考虑大多还是走烘焙。 只有那些大世界的场景实在是太大才会想各种办法去设计一个GI效果，比如SSAO去凑整个环境的AO效果。 感谢南瓜@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/674d1ff2682c7e5cd61bf9b4 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
1）高通芯片GPU是否有类似于HSR的功能 ​2）UGUI上的RT动图会不会导致UI更新 3）UI经常迭代外观，如何尽量少改代码 4）开发过程中该使用AssetBundle包模式，还是模拟模式？ 这是第279篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） GPU Q：高通芯片GPU是否有类似于HSR功能？望解答，谢谢。 A：参考如下： 测试案例1：小米9 点击按钮添加全屏不透明的Quad，使用Standard Shader，离相机越远，RenderQueue越小。 结果：从FPS上看，到100层，帧率也都是60帧。说明小米9上确实有类似于HSR的功能，就是不透明对象不需要手动调整渲染队列从而让靠近相机的先渲染。 测试案例2：小米5X 点击按钮添加全屏不透明的Quad，使用Standard Shader，离相机越远，RenderQueue越小。 结果：从FPS上看，1层的时候43帧，2层就掉到25帧，3层掉到17帧。说明小米5X上没有类似于HSR的功能。 测试案例3：小米5X 点击按钮添加全屏不透明的Quad，使用Standard Shader，离相机越远，不调整RenderQueue。 结果：从FPS上看，1层的时候43帧，5层的时候依旧43帧。不调整RenderQueue后，因为Unity的不透明是按照由近到远绘制的，由于Early-Z的关系，导致被遮挡的物体不会进行Fragment Shader计算了，所以帧率比较稳定。 总结：对于像地形这样的大面积且Shader复杂度通常比较高的对象，还是需要调整渲染队列到比较靠后的，因为对于低端机型，并没有HSR这样的功能。所以可以通过调整地形的渲染队列靠后，从而让Early-Z对于大地形生效，从而减少地形的渲染像素。 对于高端机型，如小米9，因为其GPU硬件支持HSR功能，所以对于不透明物体来说，渲染队列没有关系。 总体来说，还是建议调整大地形的渲染队列，对低端机型有好处。 感谢小苗子@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61b6e84cd8413e18eb47e0f4 UGUI Q：在UGUI组件上使用RenderTexture动图会不会导致UI更新？ A：对于这个问题进行了简单的实验：实验设置了一个相机拍摄不停在运动两个物体，然后得到的RenderTexture赋给UGUI上的100个RawImage。在真机上运行，此时UI更新耗时几乎为0。 理论上，这些RawImage的顶点属性没有发生变化，确实不会导致UI更新。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61b6e8a0d8413e18eb47e1c4 UGUI Q：问题：之前的项目是用Lua做UI开发的。一个ToLua，一个XLua。策划或美术经常要调已经做好的UI的外观，一般需要变布局或节点层级。最早，代码里访问某个节点都是用GameObject.Find找到节点，层级关系一变，就需要程序配合改，很麻烦。怎么做到调整UI，不用改代码呢？ 思路：想到一个命名规范方案：程序拿到UI的Prefab绑上UI关联Lua的脚本后，把所有程序需要访问的节点，改名成rd_xxxxx，脚本控制下的子节点rd_开头的不能重复，如果有列表的子节点，上面再挂一个辅助的脚本（比如UItemHelper）。 Lua第一次加载时遍历所有节点，缓存所有rd_xxx的节点关系到脚本里（Lua里），这样，Lua代码里直接用self.rd_xxxxx就能得到对象，也不怕Prefab层级调整，也不用多次调用GameObject.Find。 规定其他人员调整UI Prefab时，不能改掉rd_xxxxx，如果需要改则要程序配合。 参考代码：ToLua项目的一个参考修改: 关联Lua脚本对象的LuaXXX.cs里（比如叫LuaMono.cs）初始化，增加： …… if (m_params.Count == 0) { //lua关联对象自动收集 SetRDObjectRef(luaClass, this.gameObject, true); } else { //节点整理期间的兼容(复用UI也走这里，避免反复搜集) foreach (ParamItem pi in m_params) { luaClass[pi.name] = pi.value; } } …… /// /// 把节点下所有节点查一下，如果名字是"rd_"开头的则是程序需要 /// /// /// void SetRDObjectRef(LuaTable luaClass,GameObject go,bool is_root = false) { if(go) { if(MyExtensions.StringStartsWith(go.name,"rd_")) { #if UNITY_EDITOR //检查是否有重复 if(luaClass[go.name] != null && !luaClass[go.name].Equals(null)) { GameFramework.Log.Error("{0} LuaClass already have GO key:{1} {2}", LuaScript, go.name, luaClass[go.name]); } #endif luaClass[go.name] = go; ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Add(item); } if(is_root || (go.GetComponent<LuaMono>() == null && go.GetComponent<UItemHelper>() == null)) { //遍历所有子节点 for(int i=0;i<go.transform.childCount;++i) { SetRDObjectRef(luaClass, go.transform.GetChild(i).gameObject); } } } } UItemHelper.cs using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; public class UItemHelper : MonoBehaviour { #if UNITY_EDITOR public class ParamItem { public string name; public UnityEngine.Object value; } #endif Dictionary<string, GameObject> m_nodes = new Dictionary<string, GameObject>(); bool init = false; #if UNITY_EDITOR [SerializeField] protected List<ParamItem> m_params = new List<ParamItem>(); #endif void Awake() { Init(); } public void Init() { if(!init) { m_nodes.Clear(); SetRDObjectRef(this.gameObject, true); init = true; } } /// /// 把节点下所有节点查一下，如果名字是"rd_"开头的则是程序需要 /// /// /// void SetRDObjectRef(GameObject go, bool is_root = false) { if (go) { if (MyExtensions.StringStartsWith(go.name, "rd_")) { #if UNITY_EDITOR //检查是否有重复 if (m_nodes.ContainsKey(go.name)) { GameFramework.Log.Error("sub item node {0} already have GO key:{1} {2}", this.name, go.name, m_nodes[go.name]); } else #endif { m_nodes.Add(go.name, go); #if UNITY_EDITOR ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Add(item); #endif } } if (is_root || (go.GetComponent<LuaMono>() == null && go.GetComponent<UItemHelper>() == null)) { int childCount = go.transform.childCount; Transform t = go.transform; //遍历所有子节点 for (int i = 0; i < childCount; ++i) { SetRDObjectRef(t.GetChild(i).gameObject); } } } } public GameObject GetNode(string name) { if(m_nodes.ContainsKey(name)) { return m_nodes[name]; } return null; } public Image GetNodeImage(string name) { if (m_nodes.ContainsKey(name)) { GameObject go = m_nodes[name]; if (go != null) { Image img = go.GetComponent<Image>(); return img; } } return null; } public Text GetNodeText(string name) { if (m_nodes.ContainsKey(name)) { GameObject go = m_nodes[name]; if (go != null) { Text txt = go.GetComponent<Text>(); return txt; } } return null; } public Button GetNodeButton(string name) { if (m_nodes.ContainsKey(name)) { GameObject go = m_nodes[name]; if (go != null) { Button btn = go.GetComponent (); return btn; } } return null; } // 后期添加的节点 public void AddNode(GameObject go) { #if UNITY_EDITOR //检查是否有重复 if (m_nodes.ContainsKey(go.name)) { GameFramework.Log.Error("sub item node {0} already have GO key:{1} {2}", this.name, go.name, m_nodes[go.name]); } else #endif { m_nodes.Add(go.name, go); #if UNITY_EDITOR ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Add(item); #endif } } // 删除后期添加的节点 public void RemoveNode(GameObject go) { #if UNITY_EDITOR if (m_nodes.ContainsKey(go.name)) #endif { m_nodes.Remove(go.name); #if UNITY_EDITOR ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Remove(item); #endif } } public Dictionary<string, GameObject> GetAllRdGameObject() { return m_nodes; } } 其他：当然各个项目情况不一样，需要各自实现。不知大家项目里遇到类似问题，用的是什么方案？ 比如有的方案不希望一开始初始化，可以在创建关联Lua时，设置元表，重写_index，当self.rd_XXX这样访问时，取到索引的前缀是rd_，则去判定缓存，没有则去找对应节点关联。 A1：基于路径的UI控件查找还是比较脆弱，直接使用Unity序列化引用更好，可以随意修改结构改名字不用担心丢失。 这里有一个不错的方案：UIControlBinding 感谢张迪@UWA问答社区提供了回答 A2：别用GameObject.Find方法，编辑器序列化直接绑定到Lua。和使用原生CS绑定一样。 感谢1 9 7 3-311135@UWA问答社区提供了回答 A3：把UI控件直接序列化到C#脚本上，运行时不要用Find和GetComponent，UI面板的Prefab实例化后，直接把这些对象塞到一个Lua Table里传给Lua。 感谢唐崇@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61af21abd8413e18eb3d51b1 Asset Q：使用AssetBundle包，能够较大程度保证资源的正确性，但一点资源的修改就要打包，比较浪费时间，特别是美术、策划同学对它比较抵制；使用模拟模式，大概只是开发效率高一些，效果错误、循环引用等问题要延后到打包后才能发现（当然有很多其他方式可以提早发现，但属于附加手段）。 最近项目中发现，即使预加载了资源，Play过程中还是会出现非常严重的卡顿（1s+），这么长时间的卡帧导致表现效果直接不对了：比如，1s从起点移动到终点的过程，直接变成闪现到终点，成了Bug。 卡顿原因查下来是：AssetDatabase.Load出来的Prefab并不会将其依赖的Texture一起加载进内存，Prefab第一次渲染的时候Texture才会加载。我们这一个很复杂的（未优化）技能特效，用了40MB+的Texture，把UploadTexture卡了。 以这样一个特殊的问题为契机，准备重新考虑下要实行的方案，希望大家能多给些建议，可以是二者的对比选择，也可以是其他更好的做法或实现。 A：把Unity项目放在高速SSD中应该能缓解不少。 之前见到一个方案是在预加载时，创建一个玩家看不到的相机，把技能特效的Animator、ParticleSystem和音频组件等都执行一下，同时调用Camere.Render，强制把各类资源都WarmUp好，这样进入游戏后基本没有IO操作，应该能大大缓解题主描述的问题。 针对一些特效是被Timeline的轨道管理的，然后Timeline本身是一个独立模块的，可以根据技能配置找到特效（技能配置关联Timeline，Timeline关联特效），然后对特效预渲染就可以了，Timeline只是逻辑流程控制器。当然如果Timeline本身卡顿，还是要预执行一遍更好。 感谢张迪@UWA问答社区提供了回答，欢迎大家转至社区交流：： https://answer.uwa4d.com/question/61af045cd8413e18eb3d22c6 20211213 更多精彩问题等你回答～ Unity增量打包AssetBundle没变化的资源也会被重新打包 在模型有UV2的情况下开启Generate Lightmap UVs 如何实现AAB包的增量更新 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
本期聚集话题：PBR在手游开发中的适用性、如何设计渲染等级（进阶版）、如何判断硬件支持GPU Instancing、25个你需要规避的DrawCall增长的问题... 这是第109篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间15分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群：793972859（仅限技术交流） 渲染 Q1：我们是MMO游戏，现在想给主角（包括其它玩家）使用PBR材质（Untiy自带的Standard），其它角色和场景不使用。不知这种情况下性能是否能抗住，同时我也想了解下目前手游中对于PBR材质的使用情况。 UWA：目前手游中使用PBR材质的项目明显增多，特别是MMO、RPG等项目中，主要的角色、NPC和大Boss等都开始往PBR方面走了。 如果PBR仅是在以上情况下使用时，Unity引擎自带的PBR是没有太大性能问题的，因为一般角色占据的屏幕面积都会比较小，而过剧情时，虽然屏幕面积占据较大，但时间一般都很短，所以给总体性能造成的压力不大。 但是UWA暂时还不建议大家在地形等常常在屏幕中占据较大面积区域的物体上使用PBR（尤其是中低端设备），因为它确实会造成较高的GPU压力，我们在UWA Day 2018上也对此进行了定量分析（类似下图），这点需要研发团队在使用时注意。但是，一切应该以自身项目测试为主，具体查看项目在自己设定的低配机器上到底性能如何。 最后，在使用Standard Shader时，除了关注性能外，还要关注下它对于内存的影响，可以参考这篇文章：【求知探新】Unity中ShaderLab内存优化 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ae2f40132493548fbd32bbc 渲染 Q2：UWA DAY 2018大会中说有25种造成DrawCall增长的原因，我想知道是哪25种呢？ 导致批处理失败的原因如下： 1、Additional Vertex Streams — 对象使用MeshRenderer.additionalVertexStreams设定了额外的顶点信息流。 2、Deferred Objects on Different Lighting Layers — 该物件位于另一不同的光照层中。 3、Deferred Objects Split by Shadow Distance — 两个物体中有一个在阴影距离范围内而另一个不是。 4、Different Combined Meshes — 该对象属于另一个已合并的静态网格。 5、Different Custom Properties — 该对象设定了不同的MaterialProperyBlock。 6、Different Lights — 该物件受不同的前向光照（Forward Light）影响。 7、Different Materials — 该对象使用不同的材质。 8、Different Reflection Probes — 该对象受不同的反射探头（Reflection Probe）影响。 9、Different Shadow Caster Hash — 该对象使用其他的阴影投射着色器，或是设定了不同的着色器参数/关键词，而这些参数/关键词会影响阴影投射Pass的输出。 10、Different Shadow Receiving Settings — 该对象设定了不同的“Receive Shadows”参数，或是一些对象在阴影距离内，而另一些在距离之外。 11、Different Static Batching Flags — 该对象使用不同的静态批处理设定。 12、Dynamic Batching Disabled to Avoid Z-Fighting — Player Settings中关闭了动态批处理，或在当前环境中为避免深度冲突而被临时关闭。 13、Instancing Different Geometries — 使用GPU Instancing渲染不同的网格或子网格。 14、Lightmapped Objects — 对象使用了不同的光照贴图，或在相同的光照贴图中有不同的光照贴图UV转换关系。 15、Lightprobe Affected Objects — 对象受其他光照探头（Light Probe）影响。 16、Mixed Sided Mode Shadow Casters — 对象的“Cast Shadows”设定不同。 17、Multipass — 对象使用了带多个Pass的着色器。 18、Multiple Forward Lights — 该物件受多个前向光渲染影响。 19、Non-instanceable Property Set — 为instanced着色器设定来non-instanced属性。 20、Odd Negative Scaling — 该对象的缩放为很奇怪的负值，例如(1,-1,1)。 21、Shader Disables Batching — 着色器使用“DisableBatching”标签显式关闭了批处理。 22、Too Many Indices in Dynamic Batch — 动态批处理索引过多（超过32k）。 23、Too Many Indices in Static Batch — 静态批处理中的组合网格索引过多。对于OpenGL ES来说是48k，OSX是32k，其他平台是64k。 24、Too Many Vertex Attributes for Dynamic Batching — 欲进行动态批处理的子网格拥有超过900个顶点属性。 25、Too Many Vertices for Dynamic Batching — 欲进行动态批处理的子网格顶点数量超过300个。 感谢果果爸@UWA问答社区提供了回答，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ae1eef932493548fbd32ba1 GPU Q3：我在国产安卓机上发现不少据说支持ES 3.0、但是实际上却不支持GPU Instancing功能的情况。那么现在大家都是如何判断硬件支持GPU Instancing的？ UWA：建议使用SystemInfo.supportsInstancing，链接： https://docs.unity3d.com/ScriptReference/SystemInfo-supportsInstancing.html 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ae714b00956834900752685 渲染 Q4：接我们上期的问答《如何设计项目中的渲染等级》推出后，又有两位行业大咖分享了他们的灼见： 王亮：我们的项目还在开发中，也经历了同样的过程，最后也形成了类似的文档。这个过程中我的体会是，虽然感觉最后形成的是一个表，会根据这个表提供机型的预设配置。但是实际上针对自己的项目，高中低主力机型，FPS的目标，渲染效果的要求不同，结果可能会大不一样。 抛砖引玉分享一下我的思路： 1、首先是确定测试用例 我们分别选取主城镇、不同战斗类型的最高面数场景作为场景用例 2、角色方面选择不同战斗类型的最高面数角色 3、接下来配置测试关卡，编写采样脚本 本次测试数据采用脚本记录帧数功能 测试方法集成到了Cheat上，用于测试指定时间的帧数范围和平均帧数粗略地描述性能 满怪渲染：主角绕着小怪 把测试的所有小怪聚拢 保持同屏全部渲染 满怪释放技能：尽量保证打中尽可能多的小怪 验证粒子压力 4、请测试同学跑测试组合用例，并且采样数据 我选取第2次测试报告作展示如下： 其实中间经过了几次迭代。表格里的最后一列是针对帧数目标，经过反复调整配置形成的数据。 5、最后根据测试数据，调整分级表的配置 最终形成的这次配置如下，红色是本次测试，调整和新增的内容： 以及本次测试用发现的问题： 感谢王亮@UWA问答社区提供了回答 文雅：正好我也整理过项目的LOD规则，可能不限于题主描述的渲染等级。一套好的LOD系统需要美术在资源上做大量的资源分级工作，需要程序搭建完善的LOD框架和辅助工具，每增加一个功能模块都需要把LOD考虑进来。 一、如何确定项目的LOD分档和性能标准 LOD（Level of Detail），这里的D代表Detail而不是Distance，也就是说，所有游戏画面和游戏功能的细节都可以进行分级，不必受限于距离。 1、如何制定性能标准？ 在分级之前，需要确定目标（省电、正常的游戏体验、噱头），我们要在哪一种机型上面运行哪些表现效果和功能，达到怎样的性能目标（帧数FPS、内存占用、Drawcall、同屏三角面数等）。 ◆ 经验总结： 1）画质的表现力与性能开销是相互矛盾的 2）需要持续维护，相关功能系统的设计都需要考虑到不同画质 3）极简画质牺牲了可玩性，同时会增加LOD框架的复杂性，使用最简单暴力的方式去处理 ◆ 兼容性问题： 1）低配画质需要考虑的适配问题 • 可能不支持Shader定义8张以上的贴图纹理 • 可能不支持ETC2格式的贴图纹理 • 可能不支持OpenGL ES3.0 2）高配画质需要考虑的问题 • 支持PBR、线性空间 • 奇怪的Bugs 3）需要解决各个画质下的兼容性问题，建立黑名单和白名单。 2、如何找出可以进行分级的细节？ ◆ 优先找出开销大的点 • 后处理效果Bloom、HDR、ToneMapping、MotionBlur、DOF等 • 实时光照和阴影、水面实时反射 • PBR物理光照 • Ragdoll、DydamicBones等物理系统 • 日夜循环、特效天气系统 ◆ 细节模块的LOD功能考虑 • 场景 / 角色 / 特效 / 摄像机相关 / 其他系统模块 二、LOD模块细分 1、场景相关 ◆ Shader LOD • shader.globalMaximumLOD来指定不同画质的LOD值 • Shader内部定义多个SubShader，逐个降低计算和纹理采样 使用Shader LOD存在一个问题，在Properties中定义的贴图，在低级别的SubShader中虽然不被采样和计算，但是仍会有内存占用。建议和角色一样，设计LOD框架时考虑两份prefab，支持场景转一份低配场景文件给低配使用。 ◆ 制作时合理使用高低模 ◆场景物件分级Level_1、Level_2、Level_3 • 含场景特效、场景动画物件等，在不同的级别显示/隐藏 最早期我们采用距离来控制场景特效的显示。 缺点： 1、实时监控距离的开销 2、每个场景需要单独配置合理的距离值 使用层级来控制更为直接，在制作LOD时最优先考虑最简单暴力的方式，这样美术规范也不会太复杂，后期也不需要过多的维护。 ◆ 场景光照切换 • Light/Light_High 涉及实时灯光对角色、场景阴影绘制的控制 • 场景光照图切换 ◆ 物件的Layer层决定阴影 • 场景文件中配置的Cast Shadows和Receive Shadows设置给烘焙使用，不方便存储下来 • 设计不同的Layer来决定物件是否产生阴影和接受阴影 ◆ 角色产生阴影 • 高配时玩家和Npc角色的Layer修改为Shadow，低配时修改为Player/Npc • 中配时玩家自身和Boss角色的Layer修改为Shadow，其他玩家为Player • Shadow层绘制实时阴影，Player/Npc会使用圆片来绘制脚底阴影 ◆ 角色接收阴影 • 仅在高配画质开启接收阴影 ◆ 裁剪距离、雾效距离 • 设计裁剪距离与雾效距离按照百分比减小的算法，防止数值异常 ◆ 后期处理效果 • 全局后期处理 通过全局后期处理管理后期的开启、关闭，控制自定义的选项。 • 场景后期处理 场景配置高、低两种后期处理方案，低配时只使用ColorGrading • UI和剧情动画中的后期处理只在高配时开启 比如：UI中使用的Bloom、剧情中使用的RadialBlur运动模糊等 ◆ 单个场景的特效等级 通过配置表定义场景的类型和同屏可显示的特效数量和等级 2、角色相关 ◆ 角色材质、shader、贴图 • 通过配置表，在不同画质调用不同的prefab • A.prefab和A_Low.prefab使用A.mat和A_Low.mat • A_Low.mat使用的Shader降低计算和贴图纹理采样 • A_Low.mat使用的_MainTex替换低精度贴图纹理 ◆ LODGroup • 角色的模型面数标准提升后，制作高模、低模两档，通过LODGroup功能按距离降档 ◆ SubShader • 高配和中配的光照模型切换，PBR切换Blinn-Phong ◆ 角色部件 • 部件在配置表中可配置为空，在低配时可以不显示背部挂件 ◆ 角色骨骼Skin Count • Skin支持的最大骨骼数降级2-Bones 1-Bones • DynamicBone基于物理系统的动态骨骼效果的开关 • Npc死亡动作基于物理系统的表现的开关 ◆ 同屏角色数量 • 不同画质设置不同的同屏显示角色上限 • 无逻辑任务的客户端NPC开关 3、特效相关 ◆ 天气系统 • 制作不同数量级的粒子系统 ◆ 脚步特效 • 开启和关闭脚步特效，可对玩家自身和其他玩家分开控制 ◆ 掉落特效 • 复杂特效可以制作简化版本 以上三种特效都可以通过简单的配置表功能完成。 ◆ 技能特效 • 通过配置表，在不同画质调用不同的Prefab ◆ 技能特效的标准 • 制作阶段控制特效的性能开销 • 工具辅助生成*_Low.Prefab • 严格控制*_Low.Prefab的性能开销 4、其他模块 ◆ 渲染分辨率 • 使用不同的渲染分辨率，且限制最高分辨率为1080p ◆ 开放视角 • 视角上下左右旋转和 • 摄像机最远最近距离 ◆ 脚本控制 特殊情况下需要制作可以在四种画质下进行切换的配置，通过脚本控制： • Prefab切换 • Material切换 5、不适合LOD的系统模块 ◆ UI • UI上的图素和特效不适合做显隐和优化 • UI场景不适合做烘焙和实时光的切换 • UI角色可以根据界面的重要程度选择使用高级或低级 • 管理好加载和卸载后，可以考虑替换高低配UI资源 ◆ 剧情 感谢文雅@UWA问答社区提供了回答 欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5acc208b425802635474fc7d 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流） 本文封面图来源： https://www.artstation.com/artwork/nWlL6
本期聚集话题：Texture2DArray、远距离物体的物理模拟、粒子系统的CPU/GPU开销、如何读取StreamingAssets目录下的压缩包... 新年伊始，随着大家紧锣密鼓地开始了工作，UWA每周推送的知识型栏目《厚积薄发 | 技术分享》在节后的首个工作日迎来了第100篇！今天，我们继续为大家精选了5个和开发、优化相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 粒子系统 Q1：请问粒子系统的消耗如何区分呢？哪部分是在CPU哪部分在GPU呢？一个空粒子（禁用所有模块，包括Render）会有消耗吗？如果有消耗，会体现在哪部分呢？ 就目前的Unity引擎的原生功能而言，粒子系统中的状态更新（粒子的位置、朝向、触发事件等）均为CPU端开销，具体表现在ParticleSystem.Update和ParticleSystem.EndUpdateAll等函数中。具体可以查看UWA性能报告中的这两张图： 相关链接 相关链接 同时，粒子系统在渲染时也需要在CPU端进行准备，比如粒子系统的Culling和Draw Call操作等等，这些也都属于CPU端开销，如下图所示。 而GPU端的耗时则与其他Mesh渲染无差，主要和ALU、Bandwidth和Overdraw相关。但与其他一般场景渲染不同的是，粒子系统在GPU端的压力往往在Overdraw中进行体现，如下图所示： 上图来自《极无双》的性能详细报告，大家可以参考完整数据来加深这部分的理解： 一个空粒子其自身开销理论上很小，但UWA这边暂时没有做过测试，题主可自行做一些测试，绝知此事要躬行。 此问答来自于UWA 问答社区，如您对该问题仍有疑问，欢迎转至社区进行进一步交流。 资源管理 Q2：我用的是Unity 5.5 版本，由于项目采用LZ4格式的AssetBundle，如果直接放进StreamingAssets下，包就太大了。现在想把所有的AssetBundle打成一个压缩包，再放进StreamingAssets，让玩家在第一次安装时，拷贝到PersistentDataPath目录下再解压。但目前不知道怎么读取Android平台下的StreamingAssets目录下的压缩包，希望能得到指导，感谢！ 我们是直接使用的C实现的unzip库，启动游戏的时候就常驻打开Apk，后面都直接通过unzip库解需要的文件到缓存目录来读取，读完了，不用就定期做一些清理。 感谢Lujian提供了以上回答 在Unity的API里应该就只有WWW（新版本里对应UnityWebRequest）能访问非Bundle文件了，但这个接口是异步的，另外.bytes操作会产生较高的堆内存分配，容易撑高堆内存峰值。所以，有的做法是通过Android的Java API来读取，可以参考下雨松MOMO的这篇文章里用的Java接口：https://www.tuicool.com/articles/fAnYJ3I 此问答来自于UWA 问答社区，如您对该问题仍有疑问，欢迎转至社区进行进一步交流。 资源管理 Q3：我用的是Unity 2017.2f版本，我们Prefab里Image元素直接引用的是Sprite Atlas文件夹里的图片，现在把Prefab和Sprite Atlas分别打到两个不同的AssetBundle里，并且去掉了Sprite Atlas的include build的选项。拿工具查看Prefab和Sprite Atlas的Bundle发现，Prefab所在的Bundle会把Image元素引用的那张Sprite图也打进来，而Sprite Atlas所在的Bundle里不但包括了这个Atlas图，并且原图都在这个Bundle里。这是为什么呢？ UWA测试发现Unity 2017.2和2017.3都存在题主说的问题，不过也都能通过一些操作绕过这个问题。先说一下个人认为正常的现象： include in build不应该影响Bundle的打包行为，且Prefab的Bundle中始终不应该有SpriteAtlas存在。但是如果不开启include in build，正常加载完两个Bundle，且实例化Prefab后，Sprite不会显示，因为需要做一下late binding。 接下来说一下碰到的这个Bug： 如果在第一次（打之前清空Bundle目录）打Bundle时，include in build开启了，那么atlas会被打包进prefab的bundle中（即题主的问题）； 但如果在第一次打Bundle时，include in build没开启，那么就是正常现象（Atlas不在prefab的Bundle中）； 有意思的是，在第二次打Bundle时，不论include in build有没有开，结果都和上一次打包一样。所以，绕过去的方法是： 先清空Bundle目录，关闭include in build打一次Bundle；再开启include in build打第二次。得到的结果就是Atlas不在Prefab的Bundle里，同时Atlas的include in build是开启状态的。 题主可以按这个步骤做个尝试。 此问答来自于UWA 问答社区，如您对该问题仍有疑问，欢迎转至社区进行进一步交流。 渲染 Q4：我从文档上来看，GLES3 Metal 已经可以支持了。最容易想到的就是地形的splat层，如果是四层splat，那么就可以用一个Texture2DArray来代替，好处是减少了bind消耗。相关的知乎链接，其中根据龚大的意思来看，似乎还可以减少splat采样次数，但根据自己的测试和理解来看并不行，因为Texture2DArray 的slice之间并不会 blending，不知是不是我理解问题。 我使用一个Texture2DArray来代替4层的Splat地形，从XCode上看开销，并没有任何的减小，那目前来看这么做地形的意义就不是很大了。AssetStore上有一个插件叫MegaSplat就是使用Texture2DArray来达到很多层混合。目前想到的Texture2DArray还有一个可以利用的地方是场景贴图。比如场景用到了多张1024的贴图，或者多张lightmap，原来由于贴图不同导致StaticBatching无法合并，现在就可以使用Texture2DArray来做了。还有比如UI上的icon之类的，可以合并到一个Texture2DArray来达到DrawCall的合并。 目前Texture2DArray最不利的地方是：只能通过代码来创建，没有编辑器的支持。如果要离线制作Texture2DArray，就需要为不同的平台准备多份资源了。请问大家有没有Texture2DArray的经验可以分享呢? UWA研究了Texture2DArray，看起来它是将多个2D的Texture组合起来变成一个对象，所以在使用的时候只需要绑定一次，就可以采样多个2D Texture。的确如题主所说，采样的时候还是一次只能采样一个指定的slice，blend也需要另外的Shader代码来完成。 题主说了：其中根据龚大的意思来看，似乎还可以减少splat采样次数，但根据自己的测试和理解来看并不行 我感觉他的意思可能是：并不是每次采样必须把所有的slice都采了，而是可以只采其中一部分。估计原本想表达的是一次采样只采一个slice。 我使用一个Texture2DArray来代替4层的Splat地形，从xcode上看开销，并没有任何的减小，那目前来看这么做地形的意义就不是很大了。 如果单从性能上看，Texture2DArray比Texture2D的确只是减少纹理绑定的开销，然后在游戏引擎中可能会对合批产生影响。其他的做法跟普通Texture2D是一样的。Texture2DArray比Texture3D在LOD处理上不同，Texture3D会减少slice，这并不是渲染Terrain时想要的。然后Texture2DArray在filter的时候只会在U,V上做，而Texture3D还会在d上做，所以这部分Texture2DArray也比Texture3D性能更好。综合这些因素可能是推荐渲染Terrain使用Texture2DArray的原因吧。题主通过实验说明Texture2DArray渲染地形没有减少开销，也有可能是因为一般一个场景就一个地形，从绑4张纹理变成绑1张就少了几毫秒，而且也不是每帧都绑，所以从整体效果上看不大出来。 UWA做了个简单实验验证了一下，下图分别是用Texture2D和Texture2DArray渲染地形结果和GLES API调用。实验设备为三星S6。 Texture2D： Texture2DArray： 其中，GLES API调用图中红色框表示纹理绑定的API调用，绿色框表示渲染API调用。WT表示了该API调用的耗时，单位为纳秒。从Texture2D图中可知一共有5次纹理绑定，分别对应于材质中的5张纹理绑定，而Texture2DArray图中只有两次调用，分别是splat纹理和四层混合纹理的绑定。从图中可知一次glBindTexture的耗时大约为1000~10000ns，即最多0.01ms。因此，三次glBindTexture加上三次glActiveTexture也最多0.06ms。所以看不出来。 此问答来自于UWA 问答社区，如您对该问题仍有疑问，欢迎转至社区进行进一步交流。 物理 Q5：我们正在开发一款超大地形的MMO游戏，场景中的角色或者怪物较多，请问Unity引擎是否会对远处（500m或1km外）的GameObject进行物理模拟？是否有设置可以降低这部分GameObject的物理模拟开销？ Unity引擎确实会对远处的GameObject进行物理计算，只要该物体是Active、挂载Rigidbody（Character Controller）组件并且没有Sleeping的。Unity引擎并没有原生功能可以根据距离的远近来调整物理系统的计算，不过题主可以自行来进行控制，比如通过Culling Group或者自行计算GameObject的远近来控制Rigidbody组件的Active和Deactive，从而降低整体的物理系统耗时。 此问答来自于UWA 问答社区，如您对该问题仍有疑问，欢迎转至社区进行进一步交流。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
Hello, 各位Unity开发者，无论您是正用着UWA的工具、还是阅读着UWA的相关技术文章亦或是在UWA博客中寻找一个问题的解答，我们都倍感荣幸能和你在此相遇。本篇可以说是UWA产品服务的向导，认真读完它，您会了解UWA究竟是做什么的，并且如何利用UWA提供的资源和工具。在与UWA的日益接触中，您会发现它不仅能为你们项目的性能保驾护航，还能少走很多弯路，缩短优化的时间。 Part 1. 优化工具 Q：UWA提供哪些可以快速提升性能的工具？ 这个要从UWA的定义说起，UWA由侑虎科技开发的游戏/VR应用性能优化平台，目前提供 1）线上性能测评 2）UWA GOT 3）UWA GOT Online 三大工具。下面我们将分别介绍。 一、线上性能测评 1、性能检测与优化 通过真人真机对项目测试，提供针对性的性能报告和优化方案，既能从宏观上分析项目运行时的整体趋势，包括总体CPU性能、GC调用频率和引擎各模块性能等，也能从微观上深入剖析内存管理、资源使用以及逻辑代码在运行时的效率瓶颈。研发团队通过对报告提供的方案进行针对性的优化，能在短期内提升项目的运行效率。 入手方法：该服务主要通过在UWA官网上提交项目的测试包（apk、ipa等，无需工程源码）来完成。由UWA工作人员测试分析后，会提供针对该项目的性能报告和分析建议，可以在此参考报告demo。 该工具分为免费版和收费版，免费版一个月可以使用一次。强烈推荐大家来试试免费版哦！ 2、资源检测与分析 反馈资源的打包情况，包括具体包含的资源情况、详细的依赖关系和资源的冗余程度等。用户通过优化报告指出的冗余问题来实现减小包体、减小内存并提升加载速度的目的。 入手指南：用户在UWA官网上传AssetBundle包（无需游戏包），一旦上传，UWA将自动开启检测并返回结果，一般时长在30分钟以内（视AB包体量而定），可以在此参考报告demo。 该工具永久免费使用，无使用次数限制。 二、本地性能测试工具：UWA GOT UWA GOT 全称 Game Optimization Toolkit，是由侑虎科技研发的本地性能测评工具。它既能方便研发团队在本地直接进行真机测试，也支持在Unity Editor中搭建本地服务器来接收和分析性能数据。整个测试过程中，研发团队能自定义测试时间和流程，几分钟后即可查看性能数据，同时还提供深度的数据分析，如脚本中的主要函数耗时、堆内存的具体分配堆栈等高级分析功能，从此为大家的项目性能又增添一份安全堡垒！ 具体功能说明：戳此传送门 入手指南：戳此传送门 价格：RMB399，包括维护和更新 三、UWA GOT Online UWA GOT Online是基于其本地测评模式上的线上同步功能，研发团队可以将本地测评的数据快速同步到UWA官网，生成在线测评报告。除了“本地化”和“快”这两大天然优势，我们目前提供了四大性能检测模式：总体性能检测、Mono堆内存分析、运行时资源检测和Lua性能分析来满足大家的优化需求。 具体功能说明：戳此传送门 入手指南：戳此传送门 Part 2. 干货大全 一、引擎把握篇 性能卡顿、内存泄露、资源冗余...这些开发中遇到的疑难杂症大部分都能在这里找到解决方法，它们无不通过大量的项目深度优化和周而反复的测试验证。如果你想避开一些不必要的坑，请务必啃下这些硬货！ 1.1 Unity性能优化进无止境——CPU篇 从渲染、UI、加载等几个重要维度分析了目前大部分项目中存在的CPU性能问题和解决方案 1.2 Unity性能优化进无止境之内存篇（基础篇） 谈优化必谈内存，该文深入浅出地分析了Unity项目的内存占用 1.3 Unity性能优化进无止境之内存篇（进阶篇） 内存泄露、Mono无效堆内存、资源冗余...你关注的内存问题都在这里！ 1.4 AssetBundle打包机制详解（5.x） （4.x 版本的API接口有所差别，请看这里） 1.5 AssetBundle内存管理机制 AssetBundle真是个绝妙的设计，但是想说爱你不容易！ 1.6 Profiler中常见的高CPU耗时函数 1.7 代码热更新（dll反射） 亘古不变的话题了 1.8 加载模块详解（纹理篇） 加载龟速？是时候来点加速度了！ 1.9 加载模块详解（网格篇） 1.10 加载模块详解（Shader篇） 1.11 加载模块详解（动画资源） 二、提升：工欲善其事，必先利其器 在这里不仅有效果拔群、性能出众的插件帮助大家省心省力，也有卧虎藏龙的民间高手抛钻引玉给大家分享自己的优化技巧。 1、UI相关 2.1.1 UGUI 降低填充率技巧两则 2.1.2 如何优化UGUI的ScrollRect 2.1.3 UGUI表情系统解决方案 2.1.4 UGUI 文本多语言方案 2、制作相关 2.2.1 毛发及眼球的渲染技术 2.2.2 游戏中的角色渲染技术之皮肤篇 2.2.3 卡通渲染及其相关技术总结 2.2.4 Unity移动端动态阴影总结 2.2.5 合并Shader系列 | 如何合并渲染状态 2.2.6 如何打造高逼格耀斑效果 2.2.7 Unity线性空间下移动设备上烘焙变暗问题处理 2.2.8 Unity 5烘培场景带来的“爱”与“愁” 2.2.9 GPU Skinning 加速骨骼动画 2.2.10 如何精简Unity中使用的字体文件 2.2.11 使用MaterialPropertyBlock来替换Material属性操作 2.2.12 Unity图片优化神器 - Dither算法进阶方案 2.2.13 布料系统原理浅析和在Unity手游中的应用 2.2.14 聊聊Unity里的嵌套Prefab 2.2.15 如何实现更真实的软阴影效果 2.2.16 开启HDR时，半透明渲染画面异常的解决方案 2.2.17 使用Fast Shadow Receiver优化渲染效率 2.2.18 Unity+ iPhone X适配方案 2.2.19 Unity手游开发札记——使用Shader进行UGUI的优化 2.2.20 iOS下拆分Unity图集的透明通道（不用TP） 2.2.21 Journey中的沙丘渲染及其Shader实现 2.2.22 扩展Unity模型编辑器 2.2.23 如何在Unity中实现Stochastic Screen Space Reflection效果 2.2.24 高效的iPhone X适配技术方案（UGUI和NGUI） 2.2.25 如何大幅优化NGUI的堆内存分配 2.2.26 如何在Unity中实现Stochastic Screen Space Reflection效果 2.2.27 基于Unity引擎的Bent Normal实现 2.2.28 Unity移动平台下的烘焙使用及优化 2.2.29 基于2D SDF的体积字实现 2.2.30 Unity如何实现投影阴影效果 2.2.31 Unity引擎后处理性能优化方案解析 2.2.32 利用多进程并行化加速Unity资源构建 2.2.33 Unity引擎iOS增量编译方法解析 3、插件&工具相关 2.3.1 插件 | 那些你不得不用的好插件！——工具篇 2.3.2 插件 | 你的项目和大作只差一个插件的距离！——美术效果篇 2.3.3 插件 | 好插件让你事半功倍！——资源篇 2.3.4 技巧 | Unity中Avatar换装实现 2.3.5 插件 | Unity一键图集生成工具，附源码 (基于NGUI和TexturePacker) 2.3.6 插件 | Unity游戏界面解决方案: PSD For UGUI 2.3.7 插件 | ShaderForge插件分享 2.3.8 技巧 | ScreenSpaceShadowMask Blur推荐 2.3.9 插件 | USequencer系列 |初识 2.3.10 插件 | USequencer 准确显示瞬时事件和持续事件 2.3.11 插件 | USequencer 缤纷事件、事件间吸附 2.3.12 屏幕特效插件分析 | FxPro & Beautify 2.3.13 屏幕后处理特效性能分析—运动模糊 2.3.14 Unity插件开发基础—浅谈序列化系统 2.3.15 如何高效定位Unity安卓开发中的闪退问题 2.3.16 ACT技能编辑器的制作经验分享 4、内存相关 2.4.1 Lua性能优化—Lua内存优化 2.4.2 用好Lua+Unity，让性能飞起来——Lua与C#交互篇 2.4.3 用好Lua+Unity，让性能飞起来—LuaJIT性能坑详解 2.4.4 Unity项目常见Lua解决方案性能比较 2.4.5 Lua配置表存储优化方案 2.4.6 Unity匿名函数的堆内存优化 2.4.7 给调皮的AssetBundle加上面向对象式加载调试管理 2.4.8 Unity 5.x AssetBundle零冗余解决方案 2.4.9 AssetBundle进阶内存优化（Unity 4.x） 2.4.10 巧用UWA资源检测，轻松剔除冗余资源！ 2.4.11 听说你的项目中仅这些资源就卡成了翔？看看他们怎么做！ 2.4.12 Lua的CPU开销性能优化 2.4.13 我所理解的委托和匿名函数 5、研发实录 2.5.1 我们用50次游戏性能的深度优化，总结出了五条“毒鸡汤” 2.5.2 Unity手游开发札记—大世界动态加载实战 2.5.3 Unity手游iOS内存分析和测试 2.5.4 Unity 协程运行时的监控和优化 2.5.5 Unity 游戏的String interning优化 2.5.6 Unity动画文件优化探究 2.5.7 动画重定向技术分析及其在Unity中的应用 2.5.8 VR交互动画短片《拾梦老人》的开发经历 2.5.9 独立游戏《Abi》场景耗时分析 2.5.10 Optimize GameObject选项对骨骼Transform的影响 2.5.11 《为谁而炼金》UI界面加载性能分析 2.5.12 Unity中ShaderLab内存优化 2.5.13 使用UWA GOT优化Unity性能和内存 2.5.14 客户端配置文件优化策略 2.5.15 Unity中字体名对依赖关系的影响 2.5.16 《小米超神》技术总监王啸予：重度MOBA的优化之路 2.5.17 基于团队的持续优化之道 2.5.18 《Exploring in UE4》多线程机制详解 6、行业参考 2.6.1 UWA发布 | 2017 Unity手游体检蓝皮书 — ARPG篇 2.6.2 UWA发布 | 2017 Unity手游体检蓝皮书 — MMORPG篇 2.6.3 UWA发布 | 2017 Unity手游体检蓝皮书 — MOBA篇 2.6.4 Unity版本使用情况统计报告 7、案例精讲 2.7.1 UWA助力独立游戏开发！《江湖X：汉家江湖》性能诊断精讲！ 2.7.2 一次MMO手游的性能诊断全纪录 2.7.3 RPG |《星辰奇缘》测评精讲 2.7.4 ARPG|《王者之剑2》性能数据精讲 2.7.5 RPG |《仙剑奇侠传3D回合》性能精讲 2.7.6 ACT |《艾尔战记》性能精讲 2.7.7 MMO |《六龙争霸》测评精讲 2.7.8 FPS |《全民枪战》测评精讲 2.7.10 FPS手游《战地先锋》性能案例精讲 2.7.11 MOBA手游《小米超神》案例精讲 2.7.12 UWA官方Demo新增《小米超神》，全面揭秘重度手游的性能表现！ 8、学术论文 2.8.1 基于功耗预算的实时绘制 2.8.2 微表面模型－PBR渲染管线的材质 2.8.3 基于物理的渲染—更精确的微表面分布函数GGX 2.8.4 基于物理的渲染—基于球面调和基的实时全局光照明 2.8.5 基于物理的渲染—基于Haar小波基的实时全局光照明 2.8.6 基于物理的渲染－用真实的环境光照亮物体 2.8.7 基于物理的渲染—HDR Tone Mapping 2.8.8 CG中的深度学习 |Siggraph 2017 相关论文总结 Part 3. UWA 问答社区 所有的已知，在未知面前不过是冰山一角。遇到问题怎么办，投石问路！正所谓不学不问，不问不知。一个高质量的提问是解决问题的一半。如果你真心想解决某个问题，不妨和大家一起来讨论下这些话题吧。 3.1 资源放到resource下面有什么优势和劣势 3.2 如何降低动画文件的浮点数精度 3.3 AssetBundle 粒度规划 3.4 数据模板表打包以及加载方式怎么做可以兼顾加载速度和热更新表结构 3.5 资源引用最后导致应用包体大小的疑惑 3.6 AssetBundle未加载完使用错误 3.7 项目初期如何确定美术规范 ... UWA问答社区地址：answer.uwa4d.com 关于我们的官方QQ群（465082844） 如果你也想做出高颜值的代码，如果你也期待能让项目走向性能巅峰，如果你本人也乐于分享从善如流，欢迎加入UWA群；与其闭门造车不如群策群力；比起形而上学，我们更乐于解决实际问题。 Part 4. UWA 自学素材 UWA不仅举办了多次技术优化日，也积极活跃在网络。我们尽己所能，将技术的触角传递到每个角落。以下就是我们留下的足迹： 4.1 8.10 直播回顾 | 一个小时内，我们用UWA GOT解决了这些问题... 4.2 UWA 六月直播季 | 6.29 Unity UI模块中的优化案例精讲 4.3 UWA 六月直播季 | 6.22 Unity中动画系统性能优化方案回顾 4.4 UWA 六月直播季 | 6.15 Unity引擎渲染效率全解析 4.5 UWA 六月直播季 | 6.8 移动游戏加载性能和内存管理全解析 4.6 5.4 UWA直播回顾：Unity游戏的代码堆内存优化 4.7 NGUI性能优化技巧（上） 4.8 NGUI性能优化技巧（下） 4.9 UGUI美术制作——如何实现次时代美术效果 4.10 UGUI性能优化技巧 4.11 请让我用一小时，将你的项目性能提升一个高度！ 4.12 UWA优化日厦门站回顾 | 资深TA教你如何将艺术概念转化为渲染代码 4.13 不喧哗，自有声—记UWA优化日深圳站（附完整技术PPT） 4.14 记一次冷雨寒风日的UWA优化日（内附技术PPT） 4.15 You should be here ！| 附UWA优化日技术PPT Part 5. 虚幻引擎学习之路 我们会将UWA内部学习过的Unreal相关技术文档进行总结和整理，并陆续发布，计划发布两大系列文章，第一个系列是功能篇，主要讲述我们学习过程中所了解到的Unreal引擎功能特色；第二个系列是性能篇，重点分析Unreal引擎在多个平台（特别是移动平台）上的性能效率。 5.1 虚幻引擎学习之路：渲染模块之光照系统 5.2 虚幻引擎学习之路：渲染模块之材质系统 5.3 虚幻引擎学习之路：相机图像后处理 5.4 虚幻引擎学习之路：渲染模块之全局光照明 5.5 虚幻引擎学习之路：动画模块之基础篇 5.6 虚幻引擎学习之路：动画模块进阶篇之动画融合 5.7 虚幻引擎学习之路：动画模块进阶篇（二） 5.8 虚幻引擎学习之路：粒子系统篇（一） 5.9 虚幻引擎学习之路：粒子系统篇（二） 5.10 虚幻引擎学习之路：新粒子系统Niagara 5.11 虚幻引擎学习之路：基于Unreal引擎的大地形加载研究 与其他世面文章不同的是，我们会在讲解中揉入和Unity引擎相似功能的对照说明，以方便大家更快地对其进行理解和掌握。这里，需要郑重说明的是，我们之所以这么做并不是为了比较Unreal引擎和Unity引擎谁更强大，二者都是当今世界上伟大的引擎。我们这么做是因为发现这可以极大地提高对于Unreal引擎的学习效率。 到这里便是UWA迄今为止所有在Blog以及微信上推送的技术博文汇总，我们将其梳理，是为了让一些经典好文能够重现，让知识能常流通，同时也是为了方便大家日后查找。本篇文章作为UWA Blog里的目录将随时更新，建议大家Mark起来，有空没空随手刷上个两篇，结合自身的研发项目多做测试加深理解，UWA相信一定事半功倍。 最后也要感谢所有向UWA供稿的开发者，感谢你们将这么棒的想法分享出来，从而帮助到更多的研发团队。当然如果你也有好的想法或者研发路上的心得体验，也可以分享给我们哦（support@uwa4d.com），知识的百科全书需要你我共筑。
本期我们聚集了这些话题：深度剖析Occlusion Culling、频繁开启界面对Mono堆内存的影响、如何获取详细的GPU信息... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 渲染 Q1：我们是手游项目，想对场景中的静态物件做一个遮挡剔除，不知道Unity自带的Occlusion Culling对性能优化的提升有多少呢？我们用Unity官方例子测试，启用Occlusion Culling后发现DrawCall、Triangle几乎没有变化。另外也想获知，Occlusion Culling的数据能否像NavMeshData那样可以动态加载呢？谢谢！ Occlusion Culling在使用后具体能提升多少，这个其实是没有明确数值的，甚至可能不升反降！这个只能题主在自己的项目中进行尝试。 一般来说，Occlusion Culling功能特别适合第一人称或第三人称跟随的平视角相机（比如传统意义上的MMO等类似游戏），且适用于在场景中存在大量的遮挡情况。因此，在城市街道漫游、峡谷漫游等特定场景中，比较推荐开启Occlusion Culling功能。 但是Occlusion Culling功能本身存在一定的性能开销，因为需要每帧均遍历烘焙的Cell来明确哪些物体需要或不需要渲染，所以场景中Cell越细，那么其查询开销也就越大。因此，如果场景中本身没有大量的遮挡关系，那么开启Occlusion Culling功能后，其节省下来的渲染耗时可能抵不上其Cell查询耗时来的大，这样就得不偿失了。 下图为我们优化过的一个案例，项目中开启了Occlusion Culling功能，由于场景过于复杂且Cell也烘焙较细，所以可以看到其Culling中的WaitingForJob耗时达到了33%。 因此，我们尝试将项目中的Occlusion Culling在运行时关闭，并在Profiler中直接查看关闭前后的对比结果（下图），可以看到，Rendering中的Draw Call、Triangles并没有变化，但其Culling开销却大幅下降。这说明，在该项目中，Occlusion Culling虽然开启但并没有起到效果，反而大幅增加了CPU端的Culling耗时。 综上所述，Occlusion Culling功能仅适合于特定项目、特定场景来进行使用，并且建议在使用前后，项目组应该多加对比，反复查看其加入后是否确实带来性能提升。 最后，Occlusion Culling的Data目前并不能动态加载，只能随场景来进行加载。因此，可以尝试创造一个拥有Occlusion Culling Data的“空场景”，然后通过LoadLevelAdditive方式来进行加载，从而来达到“动态”加载的效果。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/596c93da2832ee102d2ac18e 如您对该问题仍有疑问，可以转至社区进行进一步交流。 内存管理 Q2：通过Profiler.GetMonoHeapSize()获取到的堆内存，在操作UI的时候，打开某个节点比较多的UI界面，根据上面接口取到的堆内存会突然涨6~8MB，但是Profiler取不到这个数据，该怎么办呢？我不是固定打开某个界面，而是随机出现在某个界面上，这样的问题该如何入手呢？ Mono总体堆内存一次性涨6-8MB是比较正常的。Mono内存并不是随用随分配的，而是当其发现不够用时，一次性从系统中获取一段连续的内存。在游戏运行一段时间后，这个值一般是8MB。所以，题主遇到的情况很有可能是在连续操作UI界面时，Mono发现堆内存不够用了，于是就有了题主观察到的内存分配。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/596f3a85e7b53b286095384d 如您对该问题仍有疑问，可以转至社区进行进一步交流。 动画 Q3：我写了一个顶点动画的Shader，对物体顶点位置进行了修改，使其能够跟随相机移动，并总是出现在相机前面。当游戏运行后，开始时物体能够正常显示。相机在场景中移动一段距离后，物体便不再显示，但相机换一下朝向，物体又能显示。目前排除了面剔除的原因，在Shader中已经关闭了Cull（Cull Off），请问还可能是什么其他原因呢？ 消失的原因应当是Unity的Frustum Culling引起。因为顶点动画是在shader中修改的顶点位置，而Frustum Culling是根据顶点修改之前的Mesh的bounds进行的，因此随着相机移动，Mesh实际已经出了Frustum的范围。最简单的解决方法是直接将Mesh的bounds设置足够大，让Unity始终不对其进行Culling： mesh.bounds = new UnityEngine.Bounds(transform.position, new Vector3(float.MaxValue, float.MaxValue, float.MaxValue)); 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/596c8cb52832ee102d2ac18c 如您对该问题仍有疑问，可以转至社区进行进一步交流。 GPU Q4：我使用SystemInfo.graphicsDeviceName获取的GPU信息不太详细。比如ARM系列的Mali，T880 MP2与T880 MP12相差甚远，但是只能获取到T880，无法获取MP的信息。各位是否有办法？ 对于获取GPU名称信息，在Android的Java层中可以利用API：GLES20.glGetString(GLES20.GLRENDERER)获取显示设备名。可以尝试在Unity中利用AndroidJavaClass(https://docs.unity3d.com/ScriptReference/AndroidJavaClass.html)来调用该API，尝试获取更详细的型号。 如果获取GPU名称的目的是判断GPU性能，可以看下SystemInfo中其他graphicsDevice相关的GPU性能信息是否够用。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/59688ccda5c647ad018763cf 如您对该问题仍有疑问，可以转至社区进行进一步交流。 物理模块 Q5：两个挂了Collider的物体，一个物体挂了Rigidbody，去碰撞另一物体，为什么每次OnCollisionEnter这个函数在实际还没有碰撞的时候就调用了呢？如下图，这时OnCollsionEnter已经被调用，但从图上可以看到两物体实际还没有接触，这个函数调用之后才会真正碰撞在一起。 物理系统在FixedUpdate中触发，包括OnCollisionEnter。而FixedUpdate早于Frame Rendering，因此碰撞实际已经发生，只是画面还没更新。其实在运行时这点时间差几乎应该可以忽略。 此问答来自于UWA 问答社区，感谢 Saber的回答。 https://answer.uwa4d.com/question/5970192f8cbe6c92774e0507 如您对该问题仍有疑问，可以转至社区进行进一步交流。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）