UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技
type: channel
link: https://blog.uwa4d.com/
description: UWA博客，UWA旗下官方博客。针对游戏及VR应用性能优化等内容记录、交流，以及案例分享。博客主要分为：厚积薄发、万象更新、博观约取、充电一刻、博物纳新等几个模块。
items: 流式加载后，为什么GPU仍在裁剪大量三角形
items: 内存数据不再逐页翻！GOT Online开启“精准降本增效”模式
items: 动画示例项目旋转浅析 - GASP Rotation
items: 隐藏视频变“常驻刺客”，VideoPlayer与“N/A”纹理该怎么查
items: 大R玩家从不抱怨，他们只是消失了
items: 想优化哪段就量哪段｜GPM自定义区间精准观测任意游戏流程
items: 1个额外相机、400个DrawCall：简单画面为何不简单
items: 我以前只能告诉你“哪个函数卡”，现在我能带你去看“玩家在哪卡”
items: 切场景卡顿200ms？深挖Resources.UnloadUnusedAssets底层优化全方案
items: 角色拉远切低模后，渲染线程为何还是很高

流式加载后，为什么GPU仍在裁剪大量三角形
type: item
date: 2026-08-12T05:09:00+03:00
description: 1）流式加载后，为什么GPU仍在裁剪大量三角形 2）开启级联阴影后，三角形数量为什么明显增加 这是第488篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q1：大地图已经做了流式加载，只加载角色附近区域，但GPU里仍有大量三角形被裁剪。为什么还会出现这种情况？ A：可以先看UWA GOT Online报告中GPU Input Primitive的数据，继续拆分Culled Primitives的具体构成。 以报告中第830帧为例，Culled Primitives约95.8万，其中Facing & XY Plane Culling Primitives约43.2万，Z Plane Culling Primitives约21万，Coverage Culling Primitives约31.6万。 Facing & XY Plane Culling里包含背面剔除，这本身属于正常的渲染流程，而且目前无法继续区分其中有多少来自背面剔除、多少来自XY方向超出范围，所以不能直接把这部分都当成无效开销。 Z Plane Culling可以优先检查场景里是否有覆盖范围比较大的Mesh。比如一块Mesh横跨较大的空间，其中一部分几何已经超出摄像机视锥的前后范围，但整块模型仍然被提交给GPU，这种情况下可以评估大块Mesh是否需要适当拆分。 Coverage Culling则可以重点检查LOD。如果模型已经离相机很远，但仍保持近距离时的高面数，三角形投射到屏幕后会非常小，其中一部分甚至覆盖不到有效采样点，最后被GPU裁掉。可以检查远距离LOD是否降得足够。 Q2：项目里的角色支持换装，如果给不同衣服都做LOD成本比较高，这种情况下角色LOD还需要做吗？ A：可以先确认角色对当前三角形数量的影响有多大，再决定是否值得做。比较简单的方式是在相同场景、相同视角下做一组开关测试，一组正常显示角色，一组把角色关闭，再对比Primitive相关数据。 角色关闭后三角形数量和裁剪数据明显下降，说明角色对当前几何压力的贡献较大，可以进一步评估角色LOD；数据变化不明显，则优先把精力放在场景模型上，继续检查大块Mesh和场景LOD。 实战案例 Q：高画质开启级联阴影后，三角形数量从几十万涨到100万以上，这部分增长和级联阴影有关吗？ A：有关系。级联阴影会让部分物体重复参与阴影绘制，同一个物体除了正常渲染，还可能需要参与额外的阴影绘制，所以GPU需要处理的几何量也会增加。 高画质下Triangle数量 中画质下Triangle数量 从GOT Online报告数据来看，两档画质下的几何量差异比较明显。中画质下大部分场景维持在几十万，峰值约62.1万；高画质下则多次接近或超过100万，峰值约130.6万。 不过，高画质下可能还同时调整了其他渲染配置，所以不能把这部分增长全部归到级联阴影上。可以固定场景和摄像机视角，只调整级联阴影相关配置，再对比Triangle或Input Primitive的变化。 中画质GPU Fragment Shaded 除了三角形数量，也可以结合GPU Fragment Shaded看GPU侧的压力。从报告中可以看到，Fragment shaded均值约332.2万，峰值约966.1万，说明除了几何量以外，还可以继续关注片元侧的处理量。 高画质下GPU压力增加时，不建议只看三角形数量。可以结合Triangle、Input Primitive和Fragment Shaded等数据一起判断。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

内存数据不再逐页翻！GOT Online开启“精准降本增效”模式
type: item
date: 2026-08-06T09:36:00+03:00
description: 对很多项目组来说，“内存”永远是性能优化里避不开的硬骨头。但每次分析报告时，寻找各种内存指标、整理不同岗位的内存数据，往往耗费了大量的无用时间。GOT Online此次更新，主打的就是把核心内存数据集中呈现，并全面增强了对内存细分资源的定位能力。 除了针对内存分析的重磅升级，本次还同步上线了函数Top10耗时点和函数组数据一键导出等实用功能。 聚合常用指标：按需定制专属面板 做性能排查时，程序、美术、测试打开的是同一份报告，但关注点完全不同。哪怕都在美术团队，场景、角色、特效需要盯着的数据也差了十万八千里。 结果就是每次打开报告，都得重新翻页、找指标、做对照。数据明明都在，时间却花在了“去哪里找”上。好的性能工具，从不强求全员看同一份大表，而是让每个人打开报告的第一眼，都能直奔自己的“主战场”。 GOT Online本来就支持自定义模板，这次进一步把内存分析模块中的指标也放进了面板。以后经常看的内存数据，直接集中搬到自定义面板里，大家按自己的工作内容建立模板，搞场景的看场景，搞特效的看特效。关注的数据一眼就能拿到，不用再在大大小小的页签里绕圈子。 结合同步更新的场景性能表，还能进一步看清各个场景区间的细分指标。像 DrawCall 均值/峰值、不透明与半透明 DrawCall 分布等数据一目了然，甚至超标项还会直接高亮预警。看到哪个场景区间有问题，点击对应行的“查看场景数据”就能秒级跳转深挖，排查效率直接拉满。 深挖Unreal资源：三步锁定内存大户 自定义面板解决了“内存数据怎么看更快”的问题。但一旦发现内存异常飙升，接下来还是得回答那个最现实的问题：到底是哪类资源占得多？最后又落到了哪个具体的资产上？性能排查最怕凭空盲猜，缩短排查路径的本质，就是尽可能减少这种无效猜想的过程。 GOT Online之前已经支持Unity项目的资源内存分析，这次把这项能力也补齐给了Unreal项目。现在Unreal项目也可以直观查看资源内存趋势、数量变化和不同资源的占用分布。纹理、网格、动画、音频、材质、Shader、RenderTexture和粒子系统等，都能分类查。 以前看到内存涨了，大家第一反应只能是猜：“是不是纹理爆了？还是RenderTexture没释放？”现在可以先看类型分布，纹理占用明显抬高，就先查纹理；RenderTexture数量不对，就继续盯RenderTexture。 看清类型后，点进具体资源列表，资源名称、内存占用峰值、数量、生命周期直接展现。如果是纹理，还能结合尺寸、格式、Mipmap和Streaming等属性继续判断。这样排查路径就彻底顺了：先看内存怎么变 ➔ 再看是哪一类 ➔ 最后追到具体资源。报告不会替项目直接下结论，但至少不用对着一条上涨曲线干猜了。 标出耗时Top10：快速定位最慢帧 一条耗时曲线上可能有成千上万个数据点，真正值得关注的，通常只是少数几个卡顿峰值。以前排查偶发问题，沿着曲线来回翻，大部分时间看到的都是正常数据。但性能优化的黄金法则，永远是先干掉最显著的瓶颈。别把有限的精力浪费在那些平稳正常的帧上。 现在，只需点击按钮，就会直接在曲线上标出Top10最慢的点。哪些位置拖了后腿，一眼就能挑出来，再结合对应帧、线程和上下文去深挖。当然，Top10只是耗时最高的位置，不代表它一定就是卡顿根因。但它至少把范围缩小了。与其从头到尾逐帧找，不如先盯住这10个最可疑的位置，排查效率显然高得多。 导出一键完成：告别手动搬运数据 很多团队都会按照项目特点建立“函数组”，把长期关注的函数整理在一起，用来做版本复测、设备对比或问题回归。麻烦的是，报告一多，这些函数组和对应耗时数据也会散在不同地方。后面想做对比，或者交给其他同事继续分析，往往还得手动重新整理一遍。 性能优化本身就足够消耗脑力了，完全没必要再把时间消耗在重复的数据搬运上。GOT Online现在支持一键导出所有函数组以及对应耗时数据。导出结果既有整体汇总（总耗时均值、自身耗时均值、耗时占比等），也有组内每个函数的详细耗时和调用数据。 同时，这项数据也已经同步支持OpenAPI拉取。不仅可以手动导出Excel，还能直接接进团队内部的自动化平台或对比工具。版本复测时，把不同报告的导出结果拿来一比，或者用接口自动跑对比脚本就行，不用再傻傻地逐个函数组去抄数据了。 场景横向对比：一眼找出表现异常 长时间运行的测试报告里，往往穿插了多个场景。真正开始深挖之前，第一步通常不是直接查某个具体函数，而是先找出“哪个场景表现不太对”。 GOT Online针对Unity项目重做了场景概览页，把不同场景的关键指标（FPS、CPU、GPU、内存、耗电和温度）拉到一起横向对比。哪个场景FPS突然掉底，哪个场景内存数据异常，通过图表和列表一眼就能看出来。先把差异摆出来，告诉你哪…

动画示例项目旋转浅析 - GASP Rotation
type: item
date: 2026-08-05T05:01:00+03:00
description: 【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 前言 UE：5.7.1 GASP：5.7 本文是以Mover版本角色来进行浅析GASP中旋转的实现，包括Strafe移动，AimOffset，TurnInPlace。 同时也会结合分析Lyra和ALS的一些实现方案。 1. Debug与基础RotationMode 1.1 Debug 为了更方便查看旋转相关的Debug信息，我们需要打开Widget中的Draw Shapes，就会看到下图这样很多的箭头，其中有意义的部分为： 圈内黑色大箭头：Root根骨骼朝向 圈外黑色小箭头：角色（碰撞体）朝向 亮粉色箭头：TargetOrientation旋转偏移前的目标朝向 暗粉色箭头：TargetOrientation+RotationOffset旋转偏移后的目标朝向 橙色箭头：MovementIntent，角色的期望运动方向 绿色弧线：当前MovementDirection移动朝向的范围 1.2 RotationMode 在GASP中，旋转模式分为OrientToMovement、Strafe和Aim，如果你是用过ALS的，那么它们是与VelocityDirection、LookingDirection和Aiming一一对应的。 OrientToMovement：角色将旋转至面向移动方向 Strafe：角色在移动时将大体面向瞄准方向旋转 Aim：角色将始终以最小偏移量精确面向控制器旋转方向，也算是Strafe模式 RotationMode主要是由玩家的按键输入以及游戏模式决定，在GASP中可以通过鼠标中间切换Strafe和Movement两种模式，并且可以按住右键进入Aiming模式。在常见的开放世界游戏中大多都是默认使用OrientToMovement模式，而当玩家拿起弓箭或者枪械瞄准时则会进入Strafe之中。 其中OrientToMovement比较简单，角色朝哪边移动就转向哪边即可，就不过多介绍。 2. Strafe基础 2.1 BlendSpace做法存在的问题 记得我最早接触Strafe移动还是在跟着一些UE4新手教程做战斗系统的时候，当做到了弓箭瞄准，或者类似只狼锁定目标时，就需要出现玩家始终面朝某个方向，却向着其他方向移动的情况。但是新手教程中的做法往往很简单，例如下图这种，用一个Direction来表示离面朝方向偏差多少角度，然后一个1D BlendTree混合。也有稍微好一点点的会使用2D BlendTree，使用LR/FB来进行混合。 1D因为不是环形的，在遇到从左后切换到右后时，需要经过-180度到180度把中间状态全都走一遍的情况，表现十分奇怪。而2D无论是四向在融合的地方都可能出现预料之外的表现。对于一些融合表现不好的地方可以选择补全八向动画。 若使用RootMotion动画，混合后会出现移速衰减问题，例如前后、左右单方向移动速度均为100，在往左前走的时候速度就只有50 √2 ，但是我们的期望肯定是斜向移动也是100，最简单的做法通过调整动画播放速度，来调整移动速度，但是会比较鬼畜。也可以考虑用程序补偿，缩放一下，或者直接用程序位移。 2.2 程序化多方向动画 二维的BlendTree也并不是要固定四个方向的动画，可以根据需要灵活调整为8向，10向都是OK的，GASP则是用到了四个动画F\B\LL\RL，但GASP并不是通过BlendTree混合出来的，使用BlendTree融合在没有动画的角度融合的角色往往会不尽人意，其使用的是OrientationWarping，同样是程序化修改在小范围内效果还是可以的。Lyra更是暴力地只用一个动画就OrientationWarping了360°出来。 OrientationWarping能使脚根据运动方向向不同方向移动。让下半身运动匹配移动方向，而上半身保持不变，以此实现向不同角度进行Strafe。他的实现逻辑是先旋转Root，再反向依次旋转Spine，例如下图中就是使用向正右方移动，但是被Warp了45度到了右前方。 2.3 腿部穿插 无论是BlendSpace还是OrientationWarping这些方法都无法解决脚步交叉的问题。而脚步打绕常出现于交叉脚的时候去往另一个Pose融合，但是切换过去的脚前后相反，直接融合过去就会穿插，比如下图本来是向右走的左脚在前，交叉时切回向后的右脚在前，腿就穿了。 为了解决这个问题，一个比较常见的做法就是分扇区，将前向和后向分别作为不同的BlendTree，在每个扇区中都让同一只脚保持在前方，同时保证同样播放进度时脚都是同一的前后位置，这样在扇区内Blend的时候就不会出现前腿要放到后方的…

隐藏视频变“常驻刺客”，VideoPlayer与“N/A”纹理该怎么查
type: item
date: 2026-08-04T05:24:00+03:00
description: 1）隐藏视频为何持续占资源 2）“N/A”纹理如何定位 这是第487篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：一段操作提示视频只有在玩家连续5秒未进行指定操作时才会出现，但UWA GOT Online报告显示，用于播放视频的RenderTexture一直常驻，VideoPlayer相关函数也存在明显耗时。视频大多数时间并没有显示，为什么仍然会影响性能？ A：视频没有显示，不代表相关资源和逻辑已经停止运行。GOT Online报告显示，这段提示视频使用了一张全屏分辨率的RenderTexture，同时带有32位颜色格式和32位深度，并且在整个测试过程中一直常驻。也就是说，视频虽然只在特定条件下出现，但为它准备的RenderTexture平时也一直存在。 这张RenderTexture只是用来显示一段短暂出现的提示视频，却使用了全屏分辨率和较高的颜色、深度规格。这里需要确认三个问题：当前分辨率是否有必要；颜色和深度格式是否过高；以及这张RenderTexture是否必须在整段玩法中一直保留。 除了内存占用，VideoPlayer本身也有CPU开销。报告中的平均耗时为1.47ms，但它并不是每一帧都会执行。只看实际产生耗时的区段，单帧开销接近3ms。 项目目标帧率为30FPS，每帧的时间预算约为33ms。接近3ms已经占用了比较明显的一部分，而这段视频大多数时间并没有显示。 排查时可以先确认两个问题：视频隐藏后，VideoPlayer是否还在更新；对应的RenderTexture是否仍然需要常驻。如果它只是一个低频提示，也可以重新评估是否必须使用视频，以及当前资源规格是否过高。 实战案例 Q：项目在内存分析中发现，大量纹理资源的名称显示为“N/A”。这些纹理基本都是RGBA32格式，分辨率不同，有些长期驻留，有些只在特定流程中出现。这些“N/A”纹理是否代表发生了资源泄漏？应该如何判断并定位来源？ A：先别急着凭“N/A”就断定是内存泄漏。观察报告里这批纹理的总内存趋势，它并没有随时间出现阶梯式的持续上涨，而是稳定维持在一个固定值。所以眼下的核心问题不是产生了内存泄漏，而是这批资源丢了名称信息，导致没法直接从报告里看出用途。 报告中可以看到，这些纹理均为未经压缩的RGBA32格式，分辨率大小不一，整体占用也较为可观。继续查看生命周期后，还可以发现其中一些资源一直常驻，另一些资源只在局部流程中出现。通过纹理的分辨率、格式、占用大小和出现时间进行综合分析，可以进一步缩小资源来源范围，判断这些纹理出现的场景和使用方式。 这种无名称纹理，在实际项目里通常出在两个地方：第一类是代码在运行时动态new出来的临时纹理或RenderTexture。比如项目为了做屏幕截图、UI高斯模糊、画中画或者某些材质特效，在C# 侧写了new Texture2D() 或new RenderTexture()，但创建时顺手漏掉了.name的赋值。这部分资源丢进内存快照后，就会统一被标记为“N/A”。 排查时需要把项目里所有动态创建纹理的代码翻一遍，核对它们的创建时机、规格参数和生命周期。如果相关业务已经关闭，需要确认有没有调用Destroy或对应的释放接口。如果每次进出界面这种纹理都会变多，那才是真正的创建与回收没对齐。 第二类是第三方SDK或插件内部自己生成的纹理。比如视频解码插件、WebView渲染、广告SDK，或者是某些动态字库/图集系统，它们在运行时产生的纹理，很多原厂就没有写赋值name的逻辑，报告里自然也就全显示为“N/A”。 插件里的资源没法直接根据名字追代码，必须拿它们出现的时间点去对业务流程。例如某批“N/A”纹理刚好是在放视频、调网页或者打开特定插件界面时弹出来的，就可以直接锁定到对应的SDK上，检查它的内部释放和缓存机制；如果功能关了资源还不卸载，就要看是不是SDK没调Dispose，或者内部保留了Cache。 当然，上面这两类情况只是排查无名纹理时最常遇到的切入点，在没有拿到确凿的代码调用栈和生命周期日志之前，切忌拍脑袋认定是某段逻辑或某个插件干的。对于只在局部流程里出现的纹理，最好拉出对应时间段的测试录像和操作日志，看看当时到底在跑什么功能；对于全程常驻的纹理，则要重新评估它们常驻的必要性。 建议在项目里规范起来：所有运行时动态创建的纹理，应当为name属性赋值。虽然起个名字不省显存…

大R玩家从不抱怨，他们只是消失了
type: item
date: 2026-08-03T06:46:00+03:00
description: 大家好，今天小Q聊聊「核心玩家守护」，给最重要的那批玩家，每人一份体验档案。 入职自我介绍那天，我说过一句让不少运营同学沉默的话：大R玩家从不抱怨。体验不好，他们不发帖、不投诉，直接消失 —— 等周报里的流失数字出来，人早走远了。 那天我只给了半句承诺：给重点玩家算流失风险、提供高风险名单。今天，我想把这件事的完整答案摆出来。它在GPM左边栏里有个自己的位置，叫“核心玩家守护”。 为什么大盘看板，护不住最重要的那批人 先说清楚这个功能为什么必须存在，不然你会觉得“我已经有性能大盘了啊”。 你现在的监控，几乎全是“面”的：大盘崩溃率、平均帧率、整体卡顿。这些面数据护的是“产品”。可每个项目都有那么一小群人 —— 大R、主播、公会核心 —— 他们是要按“人”来护的。这群人有三个特点： 一是人少。少到什么程度？少到出了事，大盘根本看不见。几十个人的帧率集体崩了，摊到几万DAU的均值里，小数点后两位都不带动一下。 二是要紧。这不用我展开，你们的流水结构自己最清楚。 三是沉默。普通玩家卡了会骂，骂了你还有机会修；这群人不骂。他们只是登录得越来越晚，在线越来越短，然后某一天，不来了。 所以核心玩家守护做的事，一句话：把“面”的监控，翻译成“人”的守护 —— 先把“该重点关照谁”变成一份名单，再把这份名单上每个人的性能、告警、流失风险，变成你看得见、追得上的东西。 大盘均值护得住产品，护不住具体的人。 第一步：把“重点关照谁”变成名单 打开核心玩家守护，第一件事是建分组。不想从零想，我们备了几套现成模板：鲸鱼用户（大R）、中等付费用户（中R）、主播/KOL、公会/战队核心、新进高潜用户 —— 勾选即建。 分组里的每个成员，还能再分三档角色：Core（核心大R）、High（高付费）、Normal（普通VIP）。别小看这三个标签，它们不是装饰 —— 后面你会看到，告警定级、流失率分层、风险评分加权，全都吃这三档。同一个“出事了”，出在Core身上和出在Normal身上，在这套系统里的动静是不一样的。 成员管理：一人一行，Core/High/Normal三档角色，谁命中“内存高、CPU/GPU温度高”的系统标签，名单上直接看得见 名单建好，分组概览页就是你每天的第一眼：每个分组一张卡，未关闭告警按P0/P1/P2摆着，受影响成员按Core/High/Normal分层数着。以我们演示环境里的「重点观察鲸鱼用户（大R）」分组为例：1条P0告警，影响分层Core 3、High 5、Normal 2 —— 一眼就知道今天该先看谁。 分组概览：每组一张卡，P0/P1/P2告警 + Core/High/Normal影响分层，先看谁一目了然 这里的P0不是谁拍脑袋定的。判级规则明明白白：核心成员受影响≥1人、或受影响总数≥3人，就是P0；高付费加核心≥3人、或总数≥8人，是P1；其余P2。这套线是默认值，每个分组都能自己改。告警发出去走飞书、钉钉、企业微信、邮件；处置动作（确认、静默、关闭）就在列表上，最近7天的平均确认时长（MTTA）、平均恢复时长（MTTR）也一并统计 —— 告警不只要发得出去，还要看得出“有没有人接”。 告警处置：P0/P1/P2一目了然，确认/静默/关闭就在行内，MTTA/MTTR顺手帮你记着账 点进告警详情，证据链摆在明面上：命中了哪条规则（“核心用户≥1，当前3”这样连当前值一起给你）、数据窗口是哪段、系统标签几点算的、Top归因是什么、受影响的每一个VIP是谁 —— 每一环都能核，核完直接跳去下钻。 P0不是我拍的，是你画的线算出来的。 告警详情：命中规则带着当前值、Top归因、受影响VIP名单，一条P0从判定到证据全程可查 性能分布看板：这17个人，此刻玩得舒服吗 名单有了，第二个问题就来了：这批人此刻的真实体验，到底什么样？ 注意，这个问题问的不是“平均帧率多少”。平均值在人少的时候尤其会骗人 —— 17个人里3个卡成幻灯片，均值看起来照样体面。所以性能分布看板给你的不是均值，是分布：把这个分组一段时间内的所有会话，按你选的指标切成一个个区间，告诉你每个区间里落了多少。 还是拿「重点观察鲸鱼用户（大R）」举例。最近7天，这组17个人产生了41次会话。FPS分布图上一眼扫过去：七成会话落在45~60帧，舒服；27%落在30~45，将就；但有一小截落在0~15帧 —— 这一截在任何大盘上都不会有存在感，在这里，它就是你今天要查的事。 性能分布看板：41次会话按帧率切区间，掉队的那一小截，在这里藏不住 指标不止帧率。FPS、卡顿率、内存、CPU/GPU/电池温度、功耗、电流，九个维度随你切；统计口径可以按会话数，也可以按人头（Active玩家）算；粒度按小时或按天；还能拉一条基线窗（前一个等长窗口、前7天、前30天或自定义）做对比，…

想优化哪段就量哪段｜GPM自定义区间精准观测任意游戏流程
type: item
date: 2026-07-29T05:36:00+03:00
description: 大家好，今天 小Q 来带你认识GPM新功能：自定义区间统计。 用GPM的，从来不只是做性能优化的同学 —— 主程盯着架构和线上风险，制作人盯着版本质量，运营盯着活动和玩家口碑，QA盯着这版有没有变差。岗位各不相同，但有一件事出奇地一致：你们真正在意的，几乎从来不是“整局的平均数”，而是某一段具体的时刻。 制作人问的是 “新版本上线首日，玩家进游戏那几步顺不顺”； 运营担心的是 “大活动开启那一下会不会卡崩”； 主程要确认的是 “那套新框架在真实战斗里稳不稳”； 做优化的，盯的是 “那段团战的帧率到底掉多少”。 你看，没有谁在优化“整局游戏” —— 大家心里装着的，都是一段一段的“具体现场”，而不是一个笼统的整体数字。 可你打开性能看板，能切的刻度就那么几个：版本、机型、场景。它们都很有用，但有一个共同的前提 —— 边界是系统替你定好的，未必对得上你心里那一段。于是你只能挑一个“最接近”的场景名将就着看，或者干脆翻原始报告、自己对时间戳，把那一段一点点拼出来。 将就是有代价的。你看的那条边界，和你真正在乎的那条边界，一旦错位，均值就会被你根本不关心的部分稀释：你以为在优化“战斗”，其实数字里掺进了一半的待机和跑图。修了半天，你都不确定有没有修在玩家真正难受的那一段上。 所以，做到一定深度的团队，迟早会撞上同一个诉求：能不能让我自己定义“要盯的是哪一段”，而不是被现成的维度框死？ 这正是GPM新上线的自定义区间统计要回答的问题。它做的事一句话：把“该量哪一段”的决定权，连同“在哪儿画线”的那支笔，一起交到你手上 —— 你在代码里圈出你真正在乎的那一段，剩下的读数它来给。（这是GPM平台上你自己动手用的功能，不是我替你跑的活；我这篇，是来帮你把“你为什么会需要它”讲明白。） 为什么场景这把尺子，圈不出你要的那一段 先把话说透，不然你会觉得“不就是再多切一个维度嘛”。 均值这东西，是有边界的。你按什么边界去平均，它就只能回答什么边界内的问题。而现成的两把尺子，边界都不由“你要优化的这一段”说了算。 全局均值，边界是整场会话的头到尾。它告诉你“这台设备这局整体流不流畅”，但你要优化的那3秒加载、那一次团战爆发，早被几十分钟的正常帧率稀释得看不见了。 场景均值，边界是“场景切换”的那一刻。场景比全局细得多，这条边界也不一定是引擎定死的 —— 你完全可以按自己的需要去规划场景怎么划。但场景有一条改不掉的铁律：它必须串行，任何两个场景不能重叠。玩家在任一时刻只能处在一个场景里，上一段结束、下一段才开始，像一条不回头的时间线。 就是这条“不能重叠”的铁律，卡死了一大类你真正关心的问题。最典型的是团本里两个同时在场的BOSS：BOSS A血量还没清、BOSS B就登场，两段战斗在时间上叠着，你想分开看“打A卡不卡、打B卡不卡”，场景却只能把这段时间整体归给某一个 —— 你想按BOSS拆，它天生拆不开。不止BOSS —— 一段网络请求正好压在一段渲染上、一个持续的Buff特效横跨了好几波小怪，凡是“两件事在同一段时间里并行发生、你却想拆开各看各的”，场景这把尺子都无能为力。 场景是一条不回头的时间线，一段接一段、绝不重叠；可你想量的东西，常常是能叠在一起的。能不能重叠，就是你为什么需要自定义区间的那道分水岭。 自定义区间做的，就是把这条限制拿掉，再把“画线的那支笔”交回你手上。你在自己的代码里，给一段流程打上一对开始/结束标记、起个名字（对应GPM收到的 custom_interval_begin/custom_interval_end 这对事件），这一段就成了一个能被独立统计的对象。它可以跨好几个场景，可以只是一个场景里的一小截，更可以和另一个区间彼此重叠 —— BOSS A的区间还没结束，BOSS B的区间照样开得起来，两条线各算各的、互不打架。边界怎么画，全你说了算。然后GPM把千百台真机上所有被你这样标过的同名区间，聚合成一张读数：这一段的FPS均值、抖动、低帧、Jank、帧时间超100ms的比例，以及它这一段里的内存、温度、功耗、CPU/GPU表现。 你截图里那几个 test/test001/test002/test1/test2/test3 ，就是有人已经这么标过了 —— 每一行，都是一段被单独框出来、单独量过的流程。 这把尺子，具体能接住你哪些活 我把落地场景挑了最典型的五个。第一个，就是场景这把尺子永远替不了的那种。 1. 两段在时间上重叠的流程 —— 比如两个同时在场的BOSS。 这是自定义区间最不可替代的场景，因为它是场景唯一做不到的那件事。团本里BOSS A血量没清、BOSS B已经进场，两段战斗在时间上叠着；你想知道的是“打A卡不卡、打B卡不卡”，得把这两段分开量。场景做不到 —— 同一时刻只归属一个场景，它俩会被压进同一条…

1个额外相机、400个DrawCall：简单画面为何不简单
type: item
date: 2026-07-28T05:07:00+03:00
description: 1）UI简单，DrawCall为何接近400 2）只显示一个模型的Camera，为何耗时仍然很高 这是第486篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：我们在测试过程中发现，部分玩法画面从视觉上看并不算复杂，但运行时的UI相关开销却比较高。画面中并没有直观可见的大量复杂内容，为什么UI相关开销还是比较高？这类问题应该从哪些方向继续定位？ A：这个问题的关键在于，UI画面的视觉复杂程度和实际渲染开销并不是完全对应的。开发者通常会根据画面中能看到多少图片、文字和控件来判断UI压力，但UGUI的渲染成本还会受到绘制批次数量、UI元素组织方式以及更新频率等因素影响。即使最终画面看起来并不拥挤，只要UGUI产生了大量独立的绘制批次，渲染提交开销同样会明显升高。 从UWA GOT Online报告可以看到，渲染模块中的Canvas.RenderSubBatch调用次数区间峰值达到394次。在这份报告的分析口径中，该节点的调用次数可以用来观察相应帧中的UGUI DrawCall数量变化。极端情况下，项目的UGUI DrawCall数量达到300～400的水平，说明UI侧需要提交的绘制批次已经处于较高水平。 这里需要注意，UGUI DrawCall较高，并不等于画面中一定存在大量可见控件。场景中的部分玩法表现可能通过UI组件实现，再叠加UI特效、TMP文本等内容后，即使画面看起来并不复杂，实际参与渲染和更新的UI对象仍可能很多。至于具体哪些元素破坏了合批，目前仅凭报告还无法直接确定，需要结合项目结构和渲染调试数据继续确认。 同时，该问题也反映在CPU侧的UI处理开销上。UI模块CPU耗时平均达到7.85ms，说明它不仅带来了较多渲染批次，同时也占用了较多CPU时间。对于目标帧率为60FPS的项目，一帧总预算约为16.67ms，UI模块长期占用接近8ms，会明显压缩逻辑、动画和其他渲染工作的执行空间。 从整体曲线来看，渲染模块和UGUI相关耗时的变化趋势非常接近。这种相关性说明，当前渲染开销的波动与UI侧存在较强关联，排查时不能只停留在渲染模块，还需要继续查看UI更新流程。 在UI模块中，主要耗时入口是UpdateBatches。该流程负责处理UI元素更新和渲染批次整理。当图片颜色、文本内容或其他UI属性发生变化时，相关Canvas需要更新对应的渲染数据。UI Particle、TMP等相对复杂且更新频繁的对象，也可能增加UI更新开销。 如果需要进一步定位具体是哪些UI元素频繁触发Canvas更新，可以在Canvas.SendWillRenderCanvases相关流程中进行插桩，在运行时记录触发更新的UI对象，从而找到持续触发Canvas刷新的元素。定位到具体对象后，再结合UI层级、更新频率和使用方式进行针对性优化。具体实现方式可以参考雨松MOMO对UGUI网格重建定位的分析方法：《UGUI研究院之找到具体某个引起了网格重建的UI元素(三十二)》。 如果动态UI和大量静态UI混合在同一个Canvas中，少量元素变化还可能扩大更新影响范围。因此，优化不能只停留在“删掉一些UI”，而应分别处理绘制批次和更新成本：一方面结合渲染调试数据检查各类UI元素的合批情况，确认是否存在不必要的批次拆分；另一方面定位高频变化的UI对象，将需要频繁更新、激活或隐藏的内容与静态UI适当拆分，缩小每次更新影响的范围。 实战案例 Q：项目中为了避免特定模型受到近裁剪面的影响，单独增加了一个Camera，用于控制该模型的显示。这个Camera只在特定玩法场景中启用，也只负责该模型的渲染。在一些近距离显示场景中，如果模型距离Camera过近，可能会受到近裁剪面（Near Clip Plane）的影响，导致模型部分区域被裁切或无法正常显示。在本项目中，为了解决这一显示问题，通过额外Camera调整裁剪范围，对该模型进行独立渲染。但从UWA GOT Online报告来看，这个Camera在整个测试过程中的平均耗时已经接近常驻的UI Camera。明明只渲染一个模型，为什么仍会产生这么高的开销？这种情况应该如何优化？ A：为了满足特殊显示需求，增加额外Camera是比较常见的做法，但Camera带来的成本并不只取决于它最终绘制了多少个物体。即使最终画面中只有少量内容，新增Camera仍需要单独完成渲染准备、可见性判断、渲染…

我以前只能告诉你“哪个函数卡”，现在我能带你去看“玩家在哪卡”
type: item
date: 2026-07-23T05:21:00+03:00
description: 大家好，今天 小Q 来聊聊我新学会的一手本事：卡顿分布。 你们递给我一个卡顿问题的时候，说实话，我以前只能做半个称职的同事。 我能告诉你是哪个函数在卡、调用栈长什么样、自身耗时多少毫秒 —— 这些对定位代码很要紧，我一直做得挺稳。可每次你顺口问一句“那……玩家是玩到哪儿才卡的？”，我就卡壳了。函数名答不了这个问题，因为同一个函数，会在无数个场景里被调用。 这半个答案，我自己都嫌它不称职。所以我去补了另一半。 为什么“半个答案”救不了你的排期会 先说清楚这半个答案为什么重要，不然你会觉得我小题大做。 调用栈是写给引擎看的坐标；可玩家的卡，是发生在场景里的。排期会上你真正要拍的那一下，从来不是“这个函数慢不慢”，而是 —— 这个卡，值不值得这个版本修？ 要回答它，你得知道：玩家是在核心战斗里卡，还是在一个没几个人去的角落界面卡？卡的那一刻屏幕上在发生什么？伤到了多少台设备、多少次会话？这些答案，过去得有人一份份翻报告、对时间戳、猜场景 —— 几乎没人做得动，于是这一层一直是空的。 我补的就是这块空。同一批卡顿帧，我换一套你能看懂的坐标，重新讲给你听。 ▶ 你要的从来不是“哪段代码慢”，是“这一下卡，该不该占用你这个版本的排期”。 现在，我能给你一份完整的卡顿现场 我把这手本事拆成五步，每一步，都是替你省掉的一次“本来得自己动手翻”。 第一步，我把每一次卡顿，还原成“玩家玩到哪里”。 我把这个问题下所有卡顿帧，按发生时间归属到玩家当时所处的场景，给你一张场景占比分布：哪个场景卡得最多、占多大比例、每次卡的均自身耗时、伤到了多少台设备和多少次会话。于是“函数卡了很多次”变成了“三分之二的卡都发生在重负载战斗场景”。你终于可以按“玩家在哪卡、多严重、伤多少人”来排优先级，而不是对着调用栈拍脑袋。这一步几乎不花额外成本 —— 我复用的是报告里现成的场景数据。 我给你的第一张脸：顶部先亮采样率/归属率/覆盖率，下面就是场景分布表 第二步，我把玩家当时看到的画面，摆到你面前。 Scene_HeavyLoad01是个什么画面？程序起的名字，你未必记得，策划更对不上号。所以我不只给你场景名，还给代表性的卡顿帧配上“卡顿时刻附近那张游戏截图” —— 玩家卡的时候，屏幕大致就是这样。要是几十张糊图堆给你，你一样看得累，所以我多做一步：把长得像的画面聚成“几类”，每类只留代表帧，再给每一类起个语义名、写句画面描述。你看到的不是一墙糊图，是“这个场景的卡，主要集中在这两类画面上”。 按相似画面聚类：每一类画面带耗时分布、样本数，还能一键回到最严重那一帧 第三步，我翻出卡顿前几秒，玩家到底做了什么。 一张卡顿时刻的截图能告诉你“玩家在哪”，答不出“为什么偏偏这一下卡”。有价值的线索，常藏在卡之前那几秒：玩家刚触发了什么操作？系统正好报了什么错？所以我为代表帧多采了一段 —— 卡顿发生前N秒（默认3秒）的自定义事件和错误，汇成清单摆在画面旁边。看画面、看行为、看报错，三条线索并排，卡顿归因才从“猜”变成“读”。 卡顿前的操作与报错：我把“卡之前发生了什么”也一并摊给你 第四步，我一键把你送回那一帧现场。 前面这些都还是“看”。你的下一步动作是“进去” —— 进到那一帧，看它的调用树到底卡在哪。所以我把这条路打通了：任意一张画面证据，点一下，我就在新标签页帮你打开那份异常报告，并自动高亮到对应的那一帧卡顿，右边同步显示它的帧截图和调用树。从“玩家在重负载场景卡得最多”到“这一帧的调用栈长这样”，中间不再断线。 点一下就回到现场：那一帧被高亮选中，截帧+调用树都在右边等你 第五步，我把这一切，收成一份带优先级的诊断。 信息够全了，可全也意味着要你一条条读、自己在脑子里拼结论。所以我留了个“AI汇总卡顿场景” —— 你点一下，我就把这些已经算好的证据一并接手：卡顿主要集中在哪些场景、按严重度怎么分层、可能的根因假设是什么、不同画面之间有什么差别。我读的不是几张缩略图，是这一整套结构化证据，所以给你的结论，和你眼睛看到的信息量是对齐的，不是一段套话。 ▶ 前四步，我把现场铺给你；第五步，我把现场读给你。你要动手的地方，只剩“改哪儿”。 卡顿从“一堆数据”变“一份结论”：我读完帮你划重点，还附上修复优先级 为什么这件事，值得你一版一版做下去 我想跟你算一笔账 —— 不是我的账，是你的账。 卡顿优化的回报，从来不是一次性的。你修好一个卡顿场景，换来的不只是“关掉一个ticket”，而是一批玩家从此在这里不再卡 —— 他们不会发帖道谢，只是安安静静地多留一会儿、多玩几局。这份回报，会一点点复利到你的留存里。 而卡顿分布要做的，是让你这笔投入尽量不打折： 它让你每一版的力气，都落在“最该修的那个场景”上。 过去按调用栈排期，容易被一堆零散的卡顿牵着走，修了半天，不确定有没有…

切场景卡顿200ms？深挖Resources.UnloadUnusedAssets底层优化全方案
type: item
date: 2026-07-22T10:18:00+03:00
description: 【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 前言 Resources.UnloadUnusedAssets是Unity项目里一个绕不开的卡顿源：每次切场景，它都会在主线程上同步跑完一整套Native资源回收，200-300ms的耗时几乎是常态，低端机甚至到秒级别。而它名称标注的“异步”并不能把任何工作真正挪出主线程。 下面分四步拆：执行机制、耗时构成、可优化的环节、具体的改造方法。最终给出两套互不冲突的优化方案，实测能把整体耗时减少70%左右。 一、Resources.UnloadUnusedAssets的作用 1.1 它要解决的问题 写过Unity的都遇到过这种代码： GetComponent<Renderer>().material.color = Color.red; 这一行看着无害，实际触发了一次Material Clone。Renderer.material的Getter不直接返回Shared Material，而是检查“这个Renderer是否已经持有自己的Material实例”，没有就Clone一份。Clone出来的实例，生命周期挂在Renderer 上。 问题来了：Renderer销毁了，Clone出来的Material谁来回收？C#这边没人引用它了，但它是个NativeObject，GC.Collect()管不到。 Resources.UnloadUnusedAssets就是来收这个尾的。它扫一遍所有还活着的NativeObject，找出那些“既不被C#持有、也不在场景里”的资源，挨个释放。 1.2 还在哪里被触发 主动调用是一种，但更隐蔽的是：每次SceneManager.LoadScene（非Additive模式），引擎自动调一次。这是写在PreloadManager里的固定流程，关不掉。也就是说，只要项目用Unity切场景，这个API就会规律性地造成固定卡顿开销。 1.3 规避的做法 其实上面两个例子，UnloadUnusedAssets的调用并非完全不可规避：Clone出来的Material可以自己持有引用手动销毁，切场景的那次触发也可以稍微改造引擎去掉。但实际项目有历史负担，也要控制内存峰值，在我面对的业务场景下回避不了，只能优化。 二、它做了什么：执行流程与异步真相 一次UnloadUnusedAssets耗时拆解 UnloadUnusedAssets的内部流程，可以打这么个比方：它像物业管理员搞一次小区普查，目的是腾出空房子。整个流程分五步，每一步对应一个具体阶段。 2.1 五个阶段 第一步：列住户名册：FindAllLiveObjects打开物业系统，把所有登记在册的住户（Object::IDToPointerMap里的所有对象）抄一遍，生成liveObjects[]。 第二步：确认门牌种子：MarkSceneRootsAndReduceLiveObjects找出哪些住户是“明确不能动的” —— 场景里挂着的GameObject、MonoBehaviour、AssetBundle等，以及MarkManagerRoots找出的全局Manager。这些标成Root，索引加入initialNeedsProcessing队列，作为追踪起点。 第三步：建索引：CreateObjectToIndexMappingFromNonRootObjects为剩下的对象建一张“InstanceID → 索引”的哈希表。后面要频繁通过InstanceID找对象，有这张表能在O(1)查到。 第四步：把C#那边的根也加进来，然后挨家敲门：MarkManagedStaticVariableRoots + MarkAllDependencies 这一步是两个连续动作，合起来占全流程接近九成： 4a. MarkManagedStaticVariableRoots（=GC.MarkStaticDependencies，~49%）：调IL2CPP的Liveness::FromStatics()，遍历所有C#静态字段、递归walk整个托管堆，把发现的Native对象加入needsProcessing队列。这一段全程在C#托管堆里跑，没碰Native图遍历。 4b. MarkAllDependencies（=GC.MarkNormalDependencies，~39%）：一次性消费两个队列 —— initialNeedsProcessing（场景根/Manager根）和needsProcessing（FromStatics新发现的根），递归MarkDependencies，把所有可达Native对象Marked=1。 第五步：封空房：Cle…

角色拉远切低模后，渲染线程为何还是很高
type: item
date: 2026-07-21T06:57:00+03:00
description: 1）低模后，Render为何还是很高 2）同一个模型，为什么近距离测试帧率下降 这是第485篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：角色拉远切换低模后，Render线程耗时为什么没有明显下降？重新拉近后，为什么又出现MeshSkinning耗时升高的情况？ A：Render线程耗时升高，并不一定就是渲染压力过大。很多情况下，看到Render线程占比较高，大家会优先去看模型复杂度、Shader或者GPU负载。但在实际项目中，Render线程也可能只是等待其他线程完成任务，并不代表自身执行了大量渲染工作。 这个项目中就遇到了类似情况，角色拉远后，会自动切换到低模，同时Shader复杂度也有所降低。按照正常预期，渲染开销应该有所下降，但实际测试中发现，切换低模后Render线程耗时并没有明显降低。 查看Profiler后可以看到，Render线程的耗时主要来自等待Job工作线程完成计算，而Job线程中的热点集中在MeshSkinning相关计算。 这里需要注意，并不是角色重新拉近后，单纯因为模型面数增加，导致MeshSkinning耗时升高。结合该项目业务逻辑可以看到，角色处于远距离状态时，项目侧会主动降低部分相关计算的更新频率，以减少性能消耗。当角色重新进入Near范围后，需要恢复这些计算，因此Job线程会出现一次明显的耗时峰值。 继续结合业务代码分析可以发现，Job线程热点集中在MeshSkinning相关计算，而这部分计算与衣物模拟相关逻辑存在关联，例如StartClothUpdate流程。 角色进入Near范围后，相关业务计算恢复更新，触发Skinning/变形相关Job执行，导致Job线程出现耗时峰值。而Render线程由于需要等待Job任务完成，最终表现为Render线程耗时升高。 因此，这里的问题并不是Render线程本身执行了过多渲染工作，而是等待其他线程任务完成导致的耗时增加。后续优化方向应该关注对应业务计算和更新策略，而不是单纯降低Render线程开销。 实战案例 Q：我们项目在测试过程中发现，模型处于远距离状态时帧率表现正常，但切换到近距离显示状态后，帧率出现明显下降。当前测试场景中，画面中看到的是一个Cube模型，但实际由多个模型叠加组成。在模型资源本身没有发生明显变化的情况下，如何确认这种情况下帧率下降的原因，以及应该如何进一步定位瓶颈？ A：遇到这种情况时，不能只根据模型资源是否发生变化判断性能消耗，还需要结合运行时数据确认具体瓶颈。 从UWA GOT Online报告可以看到，Near+Cube+Original场景下持续出现：Gfx.WaitForPresentOnGfxThread耗时。 该函数通常表示CPU侧正在等待GPU完成当前帧相关工作，可能与GPU侧压力或同步等待有关，因此需要进一步结合GPU指标判断。 为了进一步确认GPU状态，我们使用支持GPU参数采集的OPPO R17设备进行了补测。由于Original材质在该设备测试过程中存在稳定闪退问题，无法直接采集对应GPU数据，因此采用Empty材质场景进行测试。 需要注意的是，出现Gfx.WaitForPresentOnGfxThread耗时的场景为Original材质，而GPU数据采集来自Empty材质，两者并非完全相同工况。Empty场景的数据可以辅助判断Near状态下存在较高GPU压力，但不能完全等同于Original场景的GPU表现。 测试结果显示，在Near+Cube+Empty场景下，GPU Clocks和Fragment Shaded指标均出现明显升高。 其中，Fragment Shaded明显增加，说明GPU需要处理更多像素相关计算。模型距离摄像机变化后，屏幕占比也会发生变化，即使模型资源本身没有变化，近距离状态下也可能因为覆盖更多屏幕区域导致Fragment计算量增加。 需要注意的是，虽然画面中看起来只是一个Cube，但实际测试场景中存在多个模型叠加的情况，因此不能只根据单个模型的外观判断实际渲染开销。如果当前GPU压力高于预期，还需要结合模型叠加数量、材质类型以及渲染方式进一步排查。例如半透明材质、多层覆盖等情况，可能导致Overdraw增加，从而进一步提升Fragment计算量。 在实际项目分析中，可以结合UWA GOT Online GPU模式下的热力图查看高消…