UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - mono
type: channel
link: https://blog.uwa4d.com/tag/mono/
items: 对象销毁了，为什么材质还留在内存里
items: 为什么驻留对象没增涨，而Used Mono却一直在增涨
items: 为什么Mono堆内存总是居高不下
items: ProtoBuf-net Serializer.Serialize产生大量的GC
items: 子线程GC导致主线程函数耗时较高的问题
items: DrawInstance和完全不做合批情况下的性能差异
items: 多个相机分别实现各自屏幕后处理的问题
items: 开启Allow unsafe code的影响
items: 如何远程更新Addressable随包打进的游戏资源
items: 关于Texture2D Crunched压缩格式

对象销毁了，为什么材质还留在内存里
type: item
date: 2026-08-18T05:29:00+03:00
description: 1）Material Instance为什么持续增长 2）动态生成Mesh后，为什么Mono内存变高了 这是第489篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：项目运行一段时间后，Material Instance数量持续增长，从开始的100多个增加到2000多个，这是什么原因？应该如何排查？ A：这种情况可以先排查运行过程中是否不断实例化了新的Material。报告中可以看到，某类技能材质（Instance）从最开始的100多个逐渐增加到2000多个，而且持续出现新的实例。这类现象通常需要结合技能、角色、特效对象的生命周期一起看，确认运行过程中是否不断产生新的Material Instance。 Unity中访问Renderer.material时，如果当前Renderer使用的是共享材质，Unity会为它实例化一份独立的Material。这里不只是调用SetColor、SetFloat等修改操作需要注意，仅仅访问Renderer.material本身，就可能触发实例化。如果只是读取或继续使用共享材质，可以检查是否应该使用Renderer.sharedMaterial。 真正容易被忽略的是材质和GameObject的生命周期并不是完全绑定的。通过Renderer.material等方式在运行时实例化出来的Material，不会因为对应GameObject被Destroy就自动完成资源释放，需要对这类动态材质单独管理。 比如技能释放时创建了临时特效对象，同时实例化了一份材质。技能结束后GameObject已经销毁，但这份Material没有被显式销毁，下一次释放技能又生成新的实例，长期运行后就可能看到Material Instance数量从几百一路涨到几千。未被使用的材质也可能在Resources.UnloadUnusedAssets()等资源清理过程中被回收，但不应该把它当成高频动态材质的主要生命周期管理方式。 排查时可以从持续增长的材质名称入手，再对应技能释放、角色生成、特效播放等操作做复现。如果每执行一次操作，某类Material Instance数量都会增加，而对应对象销毁后实例仍然保留，就需要继续检查这条材质创建和销毁链路。 优化时可以重点关注两个方向： 减少不必要的Material Instance创建。 检查是否存在只为了读取材质却访问Renderer.material的情况；如果只是修改颜色、Vector等每个Renderer不同的属性，也可以评估MaterialPropertyBlock是否适用，减少独立Material实例。需要切换Shader或Shader Keyword时，则不能简单用MPB替代。 显式管理动态材质生命周期。 如果确实需要实例化Material，需要保留对应引用，并在对象生命周期结束时显式Destroy。对于高频创建的技能、角色和特效，也可以进一步检查是否能够复用材质，而不是不断创建新的实例。 实战案例 Q：项目地图区域原本直接使用MeshRenderer，但动态加载、卸载时会有卡顿，后来改为在脚本中保存顶点等数据，运行时再动态生成Mesh。UWA GOT Online报告显示Mono内存占用较高，如何判断是否和这部分数据有关？ A：可以先通过Memory Profiler确认Mono堆中的主要占用。如果其中存在大量顶点、矩阵等Mesh相关数据，再结合引用关系查看这些数据是否由地图区域相关脚本对象持有，就能进一步判断Mono占用是否和这套动态Mesh实现有关。 这里需要关注的并不是“动态生成Mesh”本身，而是为了生成Mesh，脚本侧长期保存了多少网格数据。例如C#侧保存了大量Vector3[]、索引数组或矩阵数据，这些内容会占用Managed Heap，也就是这里看到的Mono内存。随后再把这些顶点、索引数据提交给Mesh，Mesh自身还需要在Native侧维护对应的网格数据。 因此，如果Mesh已经生成，而C#侧的原始顶点、索引等数据仍然长期保留，就可能出现同一批网格信息同时占用Managed和Native两侧内存的情况。这里不能简单理解为固定的“双倍内存”，但确实需要同时关注两边的数据占用，而不能只看Mono。 排查时可以重点看： 确认Mono中的Mesh相关数据占了多少。 查看顶点数组、索引、矩阵等数据的数量和实际占用，再通过…

为什么驻留对象没增涨，而Used Mono却一直在增涨
type: item
date: 2026-06-23T06:18:00+03:00
description: 1）为什么驻留对象没增涨，而Used Mono却一直在增涨 2）为什么粒子内存一直在增涨，场景里却没新增特效 这是第481篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：项目近期在排查Mono内存问题，从已跑测的UWA GOT Online Mono模式报告可以观测到：托管堆内存碎片持续上涨，但Managed Object总量几乎无增长，想咨询一下这类现象一般是什么原因造成的？ A：我们可以去GOT Online Mono模式报告的「堆内存对象快照」看一眼碎片占比的情况。需要注意的是，Mono内存上涨并不一定意味着驻留对象增长，也可能是堆内存碎片增长。在Mono模式报告里，Used Mono=Managed Object（实际对象）+堆内存碎片。这两块在「堆内存对象快照」里会分开显示，如下图所示，粉色是Managed Object，蓝色是内存碎片。 从这份报告可以看到，在15000～20000帧之间的Used Mono呈持续上涨趋势。按照常规思路，第一步通常会查看驻留对象，排查是否存在内存泄漏。但进一步分析发现，驻留对象并没有出现与上涨趋势相匹配的增长，继续拆解Used Mono的组成可以看到，真正上涨的并不是Managed Object，而是堆内存碎片。 判断依据很简单： 如果Managed Object曲线基本稳定，而Used Mono持续上涨，同时碎片占比不断扩大，那么问题大概率不是内存泄漏，而是频繁分配导致的堆碎片增长。这是Mono堆的典型特征之一：Mono堆通常按需扩容，而已经申请的堆空间一般不会随着对象释放而立即归还给操作系统。 当项目中频繁发生小对象的“分配 — 释放”行为时，即使Managed Object总量变化不大，也会在堆中留下大量无法连续复用的空闲区域。随着碎片不断累积，Mono堆会持续扩容，最终推高Used Mono。 在部分项目中，内存碎片甚至可能接近Managed Object的1:1水平，例如实际托管对象占用约200MB，而Used Mono最终可能被推高到接近400MB。 这种情况下，需要重点排查的不是“谁没有释放”，而是“谁在频繁分配”。LINQ、字符串拼接、装箱拆箱、协程闭包、临时List、频繁创建委托等，都是最常见的碎片来源。因此，当Mono内存持续上涨时，不要急着下结论认为发生了泄漏。先拆解Used Mono的组成，区分清楚究竟是对象增长，还是碎片增长，再决定下一步的优化方向。 实战案例 Q：从UWA GOT Online报告中可以看到，粒子系统内存持续上涨，总占用已超过100MB，但对应场景中并没有新增大量特效，运行过程中也未观察到明显的粒子播放行为。这种情况下，应该如何进一步排查？ A：很多项目会采用粒子系统预热策略：进入战斗时批量Instantiate特效实例，按粒子类型预生成缓存池，等到需要时再直接激活。这种方案的优势是能够规避运行时实例化带来的卡顿。但代价同样存在：如果缓存池缺少合理的回收机制，这些粒子系统及其关联资源就会长期驻留内存，即使运行期间从未真正发射过粒子。 GOT Online报告提供了一个非常实用的过滤条件：「Playing组件数峰值为0的粒子系统资源」。勾选后，可以快速筛选出当前仍占用内存、但Playing组件数峰值为0的粒子系统。换句话说，就是那些预热完成后，到当前采样时刻为止从未真正触发过的特效。 从报告中可以看到，粒子系统总占用超过100MB。开启过滤后发现，其中相当一部分都属于Playing组件数峰值为0的粒子系统 —— 它们被提前预热进缓存池，却始终没有被实际使用。 排查思路建议分三步： 第一步：看粒子系统内存走势。 如果内存呈现明显的阶梯式上涨，而非随场景变化平滑波动，大概率说明粒子系统存在分批预热行为。 第二步：统计Playing组件数峰值为0的粒子系统的占比。 如果占比过高，说明当前预热策略可能过于激进。 第三步：结合资源路径进行归类分析。 GOT Online报告支持向上追溯5个层级的资源路径。可以按技能特效、场景特效、UI特效等维度进行分类，找出那些从未实际播放过的粒子系统，再决定是改为按需加载，还是直接从预热列表中移除。 需要注意的是，Playing组件数峰值为0并不意味着这些特效永远不会被使用，而是代表截至当前采样时刻尚未触发过。因此，在调整预热策略前，还需要结合实际触发频率和业务场景综…

为什么Mono堆内存总是居高不下
type: item
date: 2026-05-12T05:42:00+03:00
description: 1）持续性字符串拼接：每帧5KB从何而来？ 2）碎片占比超50%：Mono内存碎片化怎么避免？ 这是第475篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q1：GOT Online Mono模式报告显示，我们项目Mono堆内存中存在持续的字符串拼接现象，这是什么原因导致的，该怎么定位和优化？ A：看到在UIPanel.LateUpdate中全程出现持续的String.Concat的分配，出现这种现象常见的原因包括：UI元素的name赋值；Panel取名等逻辑里存在字符串拼接；业务打点日志中频繁做字符串拼接；Shader查找流程中存在名称拼接操作等。优化原则是避免每帧进行字符串拼接操作，将拼接结果缓存复用，减少Mono堆内存临时分配，字符串拼接逻辑本身也不应放在游戏核心循环的热路径中。 从当前报告数据来看，仅仅是持续字符串拼接每帧新增约5KB临时垃圾分配，再加上NGUI中一些其它分配，累计每万帧分配量超90MB。UWA推荐Mono报告中Top10函数平均每10k帧分配堆内存总和小于50MB，目前仅一个UIPanel.LateUpdate就已超出推荐值范围，因此要对以UIPanel.LateUpdate为首的多个分配较高的函数进行排查。 排查方法是在Mono模式报告中，按"分配视角"查看函数堆栈，定位累积分配最多的叶子函数，追溯其调用路径。另外还可以利用报告里的倒序调用分析模块，它能直接定位高分配的底层问题函数，帮我们省去逐层排查的繁琐操作，例如上述的String.Concat问题就能直接定位到。 Q2：另外我们还在报告中发现Mono堆内存出现了一个52MB的大数组，但查不到具体来源，该怎么排查？ A：我们可以从这份Mono模式报告看到，这个byte数组从测试开始就存在，但无法获取其分配堆栈。这是因为在UWA性能采集启动前（游戏初始化阶段），该数组就已经完成分配了，所以追踪不到来源。 需要我们采集数据的时候，在SDK面板上点击Direct Mode进行采集。点击Direct Mode后会在开始采集时杀掉进程并重新启动游戏，从游戏启动开始，Mono分配的整个生命周期就会被采集，这样就可以追踪到之前无法获取堆栈的大数组来源了。 这类大数组常见来源多为大型配置表、场景标记数据等。 若确认是配置表导致，可优化两点： 将配置表数据下沉至Native层存储，不占用Mono堆内存，规避GC压力与回收耗时； 改用更紧凑的数据结构，例如用bit替代int存储标记信息，进一步缩减内存占用。 实战案例 Q：Mono内存碎片化比较严重，怎么避免，有什么优化建议？ A：从报告来看，碎片占比高达112MB/219MB（约51%），在堆内存分配释放较为频繁的项目中，UWA也常观察到对象实际占用的部分和碎片会逐渐趋于相等。Unity的Boehm GC为非压缩回收器，不会主动整理Mono堆碎片，频繁产生临时内存分配，是造成碎片化的主要原因；即便GC回收空闲内存，已形成的碎片也无法合并成连续大块，只能被小对象复用，因此要尽量减缓碎片占比逐渐上升的趋势。 我们可以从以下三个方面进行优化： 第一，减少临时分配，避免在热路径上进行字符串拼接、容器扩容等操作； 第二，对频繁创建销毁的对象使用对象池复用； 第三，对可预期的容器预分配足够容量，避免运行时扩容。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

ProtoBuf-net Serializer.Serialize产生大量的GC
type: item
date: 2022-03-09T03:48:00+02:00
description: 1）ProtoBuf-net Serializer.Serialize产生大量的GC ​2）如何快速在工程代码中每个函数注入自己想要插入的代码 3）Unity设置IL2CPP导出的Mac平台的Xcode工程中，如何通过Object-C代码反调Unity侧的C#代码 4）ShaderGraph生成的Shader在UGUI上使用的问题 这是第289篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Mono Q：在我们游戏中有使用到ProtoBuf的序列化，在查找性能问题的时候看到序列化的函数产生了大量的GC，如下图： A：可以参考《Unity3D游戏GC优化总结---protobuf-net无GC版本优化实践》。 感谢lanyt@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/621f2a12d83cff4676f5b924 Script Q：如何快速地在工程代码中的每个函数注入自己想要插入的代码？有说借助Mono Ceil的，还有一些库没有开源，所以想请问有没有其他方面的资料可以参考？ A：可以参考《使用Mono.Cecil实现IL代码注入》。 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6220e17380139e0c1f157aae Script Q：在iOS平台下，IL2CPP导出的Xcode工程中Object-C调用Unity方法是通过SendMessage实现的： 请问在Mac平台下IL2CPP方式导出的Mac工程，如何通过Object-C代码反调Unity侧的C#代码？也是通过SendMessage的方式吗？但是我没找到相关的接口。 A：用SendMessage是可以实现的，但是效率不好。可以参考我这个Object-C回调Unity。把你需要的接口，写成函数指针，在Object-C里注册，需要时做回调。 可以参考《Unity与Object-C交互》。 感谢廖武兴@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6215fcede7172c2775f76e56 Shader Q：我用URP 10.4.0和Shader Graph10.4.0建立了一个用于UGUI Image的Shader，它能在PC上正确运行，但是在手机上UV就错误了，如下图： 如下图，这样直接连一个简单的Tex2D就会出现，ShaderGraph也如下图连接： 我使用Unity 2022 URP13.1.6 SG13.1.6再次建立同样的ShaderGraph Shader在手机上是正确的，但是我暂时没有办法更换Unity版本。 A：可以参考这个视频的做法，应该是不能直接把ShaderGraph生成的Shader直接用到UGUI上的，需要做一些调整： https://www.youtube.com/watch?v=RxW7_qgkXPo 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6221ec0d80139e0c1f16e5e2 封面图来源于：Protobuf-net Optimization Protobuf-net无GC优化版本，另外还提供了一些无GC自定义数据结构，以及对Unity 3D中C#代码的各种无GC使用方式测试。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）

子线程GC导致主线程函数耗时较高的问题
type: item
date: 2021-11-10T04:18:00+02:00
description: 1）子线程GC导致主线程函数耗时较高的问题 ​2）升级Unity大版本后，Text颜色修改问题 3）清除增量式GC导致的Mono堆内存泄漏问题 4）多Pass合批优化问题 这是第274篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：在主线程中有非常多的卡顿，从UWA的报告中看到很多异常的高耗时，请问可能是什么原因造成的？ A：像上面这样的频繁卡顿，且卡顿函数种类非常多的情况，应该是子线程分配了非常多的堆内存，导致子线程GC，从而卡住了主线程。当GC的时候，主线程可能会处于各种阶段，因此对应阶段的函数耗时就会包括等待GC的耗时。可以从UWA的Mono报告中查看是否有子线程分配了大量的堆内存，通常是由这种（Thread）打头的子线程函数的分配导致的。 感谢han@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6180e0f28f8c834241fd6930 UGUI Q：从Unity 4.6.9f1升级到Unity 2020.3.2.f1c1。首次Unity升级之后UI的Text颜色修改是正常。当运行一次之后，所有的Text颜色都无法修改了。就算新创建一个新的Text也无法修改。 A：会有多余的UI-Default和UI-DefaultFont这两个Shader，删除它们，然后重启Unity就好了。 感谢芝麻青豆角@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6185085fd8413e18eb034520 Mono Q：最近在研究Mono堆内存时，发现一帧内分配多次较大内存，会导致内存无法被回收。 过程如下： 在同一帧内调用三次分配100MB内存的方法，分配内存的变量都是在各自的作用域，在这之后调用GC.Collect()。发现有较大几率出现100MB或者200MB无法被回收的Mono堆内存。经过排查之后发现取消勾选Incremental GC之后，内存就能完全被收回。 我推测是每次申请内存的时候会执行一次GC，清除上一次分配的内存，但是由于使用增量GC，无法在本帧完成回收工作。再下一次GC的时候，第二次GC的回收内容被重置了，导致第二次的GC需要被回收的内存就泄漏了。 不知道我这么理解是不是对的，希望大佬们解惑。同时希望大佬告知有没有办法清除这部分内存。 测试环境： Unity 2019.4.15c1 Unity 2020.4.15f2 测试平台： 安卓 Mono 以下为测试代码： using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; public class TestMonoMemory : MonoBehaviour { void Do() { CallBack01(); CallBack02(); CallBack03(); } public void CallBack01() { List<int> i = new List<int>(1024 * 1024 * 100 / 4); //CallBack02(); } public void CallBack02() { List<int> i = new List<int>(1024 * 1024 * 100 / 4); //CallBack03(); } public void CallBack03() { List<int> i = new List<int>(1024 * 1024 * 100 / 4); } public string text; private void OnGUI() { GUILayout.Label("Allocated Mono heap size :" + Profiler.GetMonoHeapSizeLong() / (1024 * 1024) + "MB"); GUILayout.Label("Mono used size :" + Profiler.GetMonoUsedSizeLong() / (1024 * 1024) + "MB"); GUILayout.Label("Total Reserved memory by Unity: " + Profiler.GetTotalReservedM…

DrawInstance和完全不做合批情况下的性能差异
type: item
date: 2021-01-25T10:25:00+02:00
description: 1）DrawInstance和完全不做合批情况下的性能差异 ​2）UWA报告中检测出工程没有的资源 3）精灵设置九宫后，如何不在界面中显示出来 4）关于AssetBundle资源的卸载问题 5）Total Mono突然上涨的原因 这是第236篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：我做了个测试，环境：Unity 2019.4.15 ，机型：小米5X，绘制1000个物体（顶点数>300，不动态合批），测试顺序是：Unity默认渲染（20 FPS）–>Update中通过Graphics.DrawInstance（16.5 FPS）–>通过CommandBuffer实现的DrawInstance（21.5 FPS），对于第二个测试DrawInstance性能反而降低了不太理解，有大佬帮忙解答吗？ 图片和测试工程可通过以下链接下载： 链接：https://share.weiyun.com/ZFOlW1of 密码：ip37rr A：1.Instancing耗时更高主要原因还是GPU压力比较大，见下面的测试案例： 测试1：1000个物体（顶点数88，关闭动态合批） Default：子线程Camera.Render（14.59 ms）、Gfx.PresentFrame（3.94 ms） Instancing：子线程Camera.Render（7.1 ms）、Gfx.PresentFrame（4.70 ms） 测试2：1000个物体（顶点数1278） Default：子线程Camera.Render（15.02 ms）、Gfx.PresentFrame（9.01 ms） Instancing：子线程Camera.Render（8.21 ms）、Gfx.PresentFrame（19.15 ms） Instancing是能够明显降低CPU渲染（Camera.Render）耗时的，但GPU调用DrawInstanced()比Draw()的性能似乎差一些，导致CPU端等待耗时较高。所以Instancing总体帧率有所下降主要是GPU的压力较大导致的。 2.如下图Graphics.DrawMeshInstanced在Update中每帧调用存在脚本耗时明显DrawInstancing更高（在Update中准备相关DrawInstance参数），Demo渲染的是静态物体，使用CommandBuffer实现DrawInstancing可优化这部分耗时，同时在Update中的逻辑也会有New XXX[]数组这样的Mono堆内存频繁的分配，也会有GC影响（这部分影响较小）。 结论：Instancing可以降低CPU渲染耗时，但GPU API性能会有所降低，帧耗时还是受到CPU渲染耗时及GPU耗时的综合影响，不同的机型可能表现的会有所差异。 Instancing的使用：推荐绘制大量静态物体时使用CommandBuffer的实现，例如草海之类的。 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/60095ef310a17c6c2b09dc04 Texture Q：请问一下：性能报告中，RGBA32的图片有一些在工程中没有，但是在报告中却体现，请教一下这些是哪里产生的？ A：截图中的Background、UIMask、UISprite是由UGUI中默认的UI元素引入的，如下图中的ScrollView： UnityNormalMap这个是由于Shader中的纹理使用Bump作为默认的值，但是在材质球中又没有给这个纹理赋值导致的。对于这种空纹理的资源，在UWA本地资源检测中是可以检测出来的。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/600a812910a17c6c2b09dc31 UGUI Q：导入一张171*41的图片，设置完九宫信息后创建UI图片。然后设置图片宽高为0，并设置图片模式为Sliced。但是还是能看到图片显示出来了。个人感觉上是UV计算错误了。如果切换到Simple则图片正常不显示。请问大佬这是什么原理？ 测试工程可在原问答下载（版本：Unity 2018.4.23） A：这个和九宫格图片的实现原理有关，可以看看下面这个图片： 当你选择Sliced模式，他有一个最小的绘制区域，也就是四个角：1、3、7和9，必须绘制出来。当你设置的Rect大于1、3、7、9四个角组成的区域时，那么…

多个相机分别实现各自屏幕后处理的问题
type: item
date: 2021-01-13T16:26:00+02:00
description: 1）多个相机分别实现各自屏幕后处理的问题 ​2）怎么查看子线程中的GC Alloc 3）Mesh组件的世界坐标 4）绘制的Mesh丢失了Unity自带的Keywords 5）AssetDatabase V2和V1的核心区别 这是第235篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：游戏中有多个相机，每个相机有自己的后处理效果。但是发现Depth越大的相机OnRenderImage传过来的图像越小于这个Depth所有相机的叠加图像，这样就没法实现各自的后处理效果，有什么方法可以解决吗？ A：经过测试，给Camera赋值RenderTarget，那这个相机上的OnRenderImage就只会对这个RT起作用。 使用这种方式，对不同的Camera加不同的OnRenderImage。最后需要一个额外的相机将这些RT合在一起，在这个额外的相机里面，可以渲染一个Canvas，里面用多个RawImage来将之前的RT叠在一起。 下图中，球和天空盒是场景相机渲染的，相机渲染到RT1，文本是UI相机渲染的，相机渲染到RT2。 第一个相机OnRenderImage里面颜色反一下，第二个相机的OnRenderImage里面变成灰度图。从下图可以看到这两个效果是相互独立的。 最后用CombineCamera画一个Canvas，里面有2个RawImage，RawImage里面的Texture分别使用那两个RT就可以了，如下图： 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5ffd0b2910a17c6c2b09d98c Mono Q：我把一些大计算量的东西放到了子线程中，现在想看下有没有GC Alloc，但是没找到像Profiler.BeginSample/Profiler.EndSample这样的方法，看不到子线程中的GC Alloc，只能看到时间开销。请问要怎么才能看到子线程中的GC Alloc呢？ A：目前新版本的GOT2.3版本已经支持了。 《多线程统计 | GOT Online新功能上线》 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5e575ef386a3954891983a38 UGUI Q：Mask组件的世界坐标传给了Shader，然后要把模型空间的Vertex转到世界空间进行对比。 把Mask的四个角的坐标通过GetWorldCorners这个接口获取到传给Shader， 然后将特效的Vertex转换到世界空间中作比较。但是效果看起来这个转换的值好像不太对，我的渲染模式是Overlay。 A：我把Render Mode改成Camera就行了，应该是在Overlay下GetWorldCorners获取到的值不正确。 感谢题主方星@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5ff9606310a17c6c2b09d94e Rendering Q：使用CommandBuffer.DrawRenderer绘制的Mesh会丢失Unity自带的Keywords，导致ShadeSH9计算的结果不对。（版本：Unity 2019.2.0f1） 正常渲染： CommandBuffer.DrawRenderer： A1：光照处理阶段，CommandBuffer的内容还未产生，所以也就不会有球谐和屏幕空间阴影的内容了。 感谢李星@UWA问答社区提供了回答 A2：测试后发现，可以使用EnableKeyword来强行开启Keyword。但是会遇到环境光渲染不出来的问题。 正常的渲染如下： 环境光渲染不出来，如下： 虽然Keywords是完整的，但是环境光还是没渲染出来，原因是SH的系数出问题了，具体解决方案可以参考： https://github.com/keijiro/LightProbeUtility 使用如下的代码可以让这个Cube和普通的渲染方式产生相同的渲染结果，但是需要使用CommandBuffer.DrawMesh的方式。 public class TestCommand : MonoBehaviour { public MeshRenderer rend; public Mesh mesh; CommandBuffer cb; MaterialPropertyBlock block; Materi…

开启Allow unsafe code的影响
type: item
date: 2020-10-19T07:14:00+03:00
description: 1）开启Allow unsafe code的影响 ​2）Unity线性空间下，PhotoShop制作半透明图片，制作带外发光、内发光的图层如何正确导出 3）Unity中实现完善的暂停机制 4）RenderPipelineManager.DoRenderLoop_Internal的堆内存优化 5）Unity 2020版本中ARBackgroundRenderer和ARRenderMode的相应替代类 这是第223篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Script Q：最近项目引进几个插件，以及学习别人的插件，代码中用到了unsafe关键字，需要再project settings中勾选Allow unsafe code，之前知道一点是不勾选最好，但是勾选以后到底哪里不好也忘了，也没有勾选之后上线来验证，所以问问大家，是否应该勾选，以及勾选之后有何影响？ A：勾了没什么实际上的坏处，只是由于.net当初设计的时候是不鼓励使用unsafe代码的，所以在编译器默认设置这个门槛，鼓励大家尽可能使用安全的代码（也就是不要直接操作原生内存地址）。 但是，很多高性能的C#代码为了最大限度地高效访问内存，不可避免要用unsafe，只要保证unsafe代码被限制在较小的范围并严格测试保证不会出内存访问错误，对整个工程来说就不是大问题。 感谢招文勇@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5f8820039424416784ef27cb Texture Q：大家好，因为项目是PBR项目，所以Unity使用线性空间，现在在制作UI的时候，带透明通道的图片，混合时候就会出错。 我们前后试了几种方法： 1. 改PhotoShop的工作空间为线性的，定义Gamma为1.0： 2. 单独改用灰度系数混合RGB颜色：1.00，这样大部分情况下，Unity中效果PhotoShop中效果就是一致的： 3. 改Unity渲染管线。 以上方法都有一些局限和问题。 1. 普通不透明图层有些渐变效果无法绘制，因为线性空间下的暗部颜色精度不够； 2. PhotoShop制作带外发光、内发光的图层合并之后会错误： 3. 因为用的还是内置Forward管线，影响太大，低端机效率低下，暂不考虑。 我们最终采用的是第二种，美术还是在Gamma工作空间作图，只是图层混合采样。 最后想问下大家，针对线性空间下的半透明图层如何导出？ 或者有没有什么办法能处理这种外发光、内发光图层问题？ A1：目前公司项目都采用Linear方式，美术直接修改PhotoShop设置，出图过后，在Unity中测试，能与原图是一个色彩，之前也尝试过其他办法，如Shader修正，但是都有一定的问题，目前采用的这种方案，暂未发现问题。 感谢范超@UWA问答社区提供了回答 A2：用下图的配置，在PhotoShop CC 2019里试了下，只要不合并图层，直接导出png，再放到Unity中就没有问题。另外如问题中的例子，只要从下向上合并图层（带着背景），最终结果就没问题。可能是PhotoShop的Bug。 就我和美术了解的情况，他们很少合并图层。一般导出就是把不需要的图层隐藏了。所以这种情况应该是可以避免的。 感谢张首峰@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5f8129c29424416784ef25a1 Script Q：目前使用TimeScale=0来暂停游戏，但是在UI上不想让特效和动画暂停，而在某些战斗场景，只希望暂停一部分逻辑。如果把TimeScale=0，就要处理ParticleSystem、TrailRenderer、Spine动画等组件，特别是TrailRenderer和Spine都需要修改源码。如果不用TimeScale=0， 是不是就要在框架层就处理好各模块的更新逻辑？ A1：常规的做法确实就如你说的，不用TimeScale=0，各个地方去实现暂停，然后统一管理。 感谢邓永健@UWA问答社区提供了回答 A2：可参考这篇文章：《Unity Chronos - Time Control 时间控制器》 感谢黄程@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5f812d769424416784ef25a4 Mono Q：项目采用了URP，用UWA进行了初步测试，发现RenderPipelineM…

如何远程更新Addressable随包打进的游戏资源
type: item
date: 2020-07-14T12:08:00+03:00
description: 1）如何远程更新Addressable随包打进的游戏资源 ​2）Unity内存机制与安卓内存机制的关系 3）UWA报告中的GPU渲染信息统计 4）如何采样不同纹理压缩格式的时间 5）如何去掉每帧的GC 这是第211篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Addressable Q：我们新项目想用Addressable Asset System。我这几天看了一下，有个问题想请教一下：AAS通过Build and Load Paths设置local/remote决定Group中的资源随包/远端下载。那么我想在远端更新local方式的资源（即随包打进StreamingAssets中的资源）该怎么操作？ A：Local打包方式的Group设置Cannot Change Post Release，两个属性bundlename设置成FileName，取消bundle的CRC，这样再Check/Build之后就可以做到增量更新了。Addressable会把改变的部分生成到一个新的Group中，这个Group会默认设置成remotebuild/load 。这样就做到增量更新了。 感谢题主柚子爱爸爸@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5ee9d9725d07d80acd26241c Memory Q：Unity内存机制与安卓自身的内存机制有什么关系？我目前的理解是安卓操作系统基于Linux内核，所以自己有本身的内存管理机制；而java VM是为了让一些应用可以运行在安卓平台上而建立的一种跨平台机制。相当于在安卓系统上开了一个虚拟机，应用运行在虚拟机的环境中。同理，Mono和IL2CPP也是另一种虚拟机，也会运行在安卓操作系统上，但是也有着自己本身虚拟机的内存管理机制，也就是说Unity的内存管理机制实际上就是Mono或者IL2CPP的内存管理机制。 想了解下我的理解对吗？虚拟机的管理机制和安卓系统的管理机制有什么样的关联呢？任何相关的回答都不胜感激，谢谢！ A：可以参考： 《浅谈Unity内存管理》——视频 https://www.bilibili.com/video/av79798486/ 《浅谈Unity内存管理》——猫冬夏的笔记 https://www.notion.so/Unity-f79bb1d4ccfc483fbd8f8eb859ae55fe 感谢俞顺捷@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5f02890dc6123f2e0df5b3ae Rendering Q：URP项目GPU渲染信息统计问题：看情况是统计到逻辑代码里了，这个后面会区分出来吗？ A：CPU耗时和GPU耗时是完全不同的两码事。虽然最终渲染是在GPU端进行，但是CPU端也需要做很多事情的，比如提交Draw Call。以不透明渲染来说，常见的是Render.Mesh,Batch.DrawStatic这些Draw Call的提交，再比如MeshSkinning.Render，Culling等，这些都算在Camera.Render的耗时里面。 在使用了SRP的情况下，与Camera.Render相对应的函数叫RenderPipelineManager.DoRenderLoop_Internal()，这里面的耗时也都是CPU端的耗时，建议题主通过UWA线上的真人真机测评服务来查看更为具体的耗时分配。 题主截图中的GPU耗时为0，可能是因为使用了Vulkan，也可能是因为某些GPU型号问题（一般出现在华为手机上），但这个跟CPU的耗时都没有关系的，也并不是GPU的耗时统计到CPU逻辑代码里面去了。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5f02e4a2ade17d6115427bb2 Texture Q：有什么方法可以知道采样不同纹理压缩格式的时间吗？想了解下对于ETC2 ETC、ASTC采样所消耗的时间，能够具体量化吗？ A：我理解只要是硬件原生支持的贴图格式，采样的时长应该没什么差别吧，毕竟都是硬件解压到显存中进行。 更可能产生差异的是大小不同导致的传输时间和命中率上的差异，比如ASTC通常优于RGBA的原因就是对于带宽占用小，Cache命中率高。 可能整体看更好，当然如果要去追究采样消耗，可能通过一些GPU Profile统计Samp…

关于Texture2D Crunched压缩格式
type: item
date: 2020-06-22T11:47:00+03:00
description: 1）关于Texture2D Crunched压缩格式 ​2）Unity自身GC和.NET的GC如何协调工作 3）关于环境光照球面坐标映射2D贴图UV的某算法的疑问 4）iOS审核dlopen、dlsym风险 5）Unity 2018.4在iOS上压缩贴图用的是哪个可执行文件 这是第208篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Texture Q1：关于Texture2D的压缩格式设置上，想问一下大家有没有Crunch ETC2 压缩格式的上线经验。效果如何？ A1：当时是使用的Unity 4.7，后来翻了5.6的源码移植。2017的算法有了更多的优化。为了解决包体大小的问题，曾经在某上线项目中，将Crunched ETC2功能移植到了Unity早期的版本中。 使用Crunched ETC2压缩后会降低包体的大小，但同时会增加运行时解压缩的时间消耗，总体来说效率影响是比较可控的。不同的贴图使用Crunched ETC2会有不同的压缩比，于是对于一类贴图批量控制贴图格式将达到更好的压缩性价比。 当时我们针对部分类型的帖图进行了Crunched ETC2的压缩，测试下来的效果是默认开启Crunched ETC2整体包体可以优化百分之十左右。其中在三星 Note3上，单张512开启10级Mipmap，解压时耗大约在2ms左右。 使用最新的Unity引擎启用Crunched后将获得更好的效率（Unity本身也持续有优化。貌似之前Crunched的作者已被官方收编）如果对包体大小要求较高，建议可以大胆尝试。 Q2：目前用Crunch ETC2算法压缩在安卓平台下一个2048*1536的图可以压到342KB。压缩比例很高，使用的版本用的Unity 2017还是更早？ A2：当时是使用的Unity 4.7后来翻了5.6的源码移植。2017的算法有了更多的优化。 感谢康斯特@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5eec64f9ea02f0538ce31716 Script Q：Unity有自己的GC，.NET也有自己的GC，两者在游戏运行的时候，是如何协调工作的？ A1：提出这个问题之前，先要搞清楚一个问题：Unity与Mono和.NET三个概念之间的的关系。 维基百科： .NET框架（英语：.NET Framework）是由微软开发，一个致力于敏捷软件开发（Agile Software Development）、快速应用开发（Rapid Application Development）、平台无关性和网络透明化的软件框架。 .NET Framework是一个软件框架，可以理解为对一系列结构和功能的期待。不是一个具体的某个软件。最初有一段时间，.NET框架只能在Windows平台上运行。 Mono是.NET运行时和开发库的实现版本，为C#脚本提供一个编译器和运行的虚拟机（CLR）。Mono的初衷是：(1)使微软.NET应用可以跨平台运行 (2)支持.NET在Linux上使用。Mono比.NET Core支持更多的平台（如：Linux、FreeBSD、Unix、Mac OS X和Solaris）。 Unity引擎对跨平台特性有很大的需求，于是使用Mono提供C#等符合CLI规范的脚本语言的运行环境。 直观的理解参考下图： 总的来说，Unity驱动Mono，为C#等提供编译和运行的环境。而C#等脚本的GC便是由Mono的虚拟机提供的。 因此，题主说的Unity自己的GC，和.NET的GC，应该是一回事，也就是Mono的GC。（如有错误之处，希望大神指出） Ref: https://www.cnblogs.com/shanyou/p/4295163.html https://www.cnblogs.com/zhujinghui/p/3369217.html https://zh.wikipedia.org/wiki/.NET框架 https://en.wikipedia.org/wiki/Mono_(software) https://www.cnblogs.com/u3ddjw/p/10909975.html 上面忽略了Native层的内存垃圾，不过Native的垃圾回收不存在像Mono一样的自动回收机制。需要主动调用Resources.UnloadUnusedAssets()来触发。 感谢Prince@UWA问答社区提供了回答 A2：Unity里面的 C# GC是针对代码层的。Unit…