UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - 厚积薄发
type: channel
link: https://blog.uwa4d.com/category/technology/
description: UWA旨在诊断与优化游戏和VR项目开发过程中潜在的运行性能问题，帮助项研发团队快速检测项目运行时的性能瓶颈，高效定位问题根源，及时反馈针对瓶颈问题的深度分析和解决方法。
items: HPWater水体散射模型
items: GPU Clocks正常但带宽高怎么办
items: Spine动画更新耗时问题治理
items: 系统层显存 vs 引擎层显存的差值从哪来
items: UE5 Chaos物理｜创建物理世界对象
items: 小游戏的Spine数量过多开销大
items: Unity移动端游戏性能优化简谱之 功耗、发热和降频在软件层面的优化
items: Unity移动端游戏性能优化简谱之 以模块思维划分的CPU耗时调优（下）
items: 如何兼顾大量小怪渲染时的合批性能和层次感
items: Unity移动端游戏性能优化简谱之 分门别类地控制运行时内存（下）

HPWater水体散射模型
type: item
date: 2026-04-22T10:27:00+03:00
description: 【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ HPWater BSDF - 稍微基于物理的水体散射模型 实时水体渲染BSDF模型，支持体积散射、薄层SSS和背光透射。 本模型的散射模拟 直射光5°恒定厚度 直射光15°恒定厚度 直射光30°恒定厚度 第一张（厚度近似）后三张（不同直射光角度） 海浪厚度近似（法线）这个近似并不准确，但将就着用 可以看到不同厚度下，薄层SSS和透射经过不同厚度介质时，前向散射的分散性导致的散射形状 不同光向下的厚度近似模拟 与GGX BRDF的类比 本模型的结构设计参考了经典的GGX微表面BRDF，两者都将光照分解为几个物理项的乘积： GGX镜面公式： 水体散射公式： 两者的共同点： 都用几何项G表示入射光的有效投影面积 都用菲涅尔项F/T处理界面的能量分配 都将复杂光照分解为可独立计算的物理项相乘 主要区别： GGX处理的是表面微观几何的反射 水体BSDF处理的是体积介质的散射和透射 水体需要区分反射方向（diffR）和透射方向（diffT） 输入参数 相比GGX BRDF（只需要roughness和fresnel0），水体BSDF需要额外的体积散射参数。 材质参数（BSDFData）： 体积散射参数（WaterLightLoopData）： 派生量： 消光系数：μt = μa + μs（absorptionColor + scatterColor） 散射反照率：ω = μs / μt（决定吸收/散射比例） 光学深度：γ = μt · d（决定介质的“不透明度”） 整体概述 水体的光照与普通材质不同。光进入水中后会被吸收和散射，部分光在水体内部多次弹射后从表面出射回到摄像机。这个过程涉及：入射折射、体积散射、出射透射三个阶段。 本模型将水体散射分解为两个输出通道（遵循HDRP的CBSDF结构）。 关于diffR/diffT的命名： 在HDRP的CBSDF中，R = Reflection（反射方向），T = Transmission（透射方向）： diffR：光从表面的“同一侧”出射（入射和出射在法线同侧） diffT：光从表面的“另一侧”穿过来（入射和出射在法线两侧） 对于水体： diffR（宏观体积散射）：光从正面入射 → 水下散射 → 正面出射。光路在法线同侧，属于“反射方向”。适用于深水区域，通过Ray Marching累加散射。 diffT（薄层散射）：光从背面/侧面穿过薄层出射。光路穿过表面，属于“透射方向”。包含： thinLayerSSS：近似深水散射在薄层区域的延续，侧面和背面可见 backlitTransmission：光从背面直接穿透薄水层，看向光源时产生辉光 三个组件通过入射几何项自然分离，避免能量重复计算。 // ============================================================================ // 水体BSDF模型概述 // ============================================================================ // // 【物理场景：圆锥形薄层 + 深水区域】 // // ☀ 光源 // | // 波峰（薄层） thickness ≈ 0 // /\ // / \ ← 薄层：透射率高，fallback到深水 // / \ // / \ thickness渐变 // /________\ ← 圆锥底部，thickness = 1（薄层与深水边界） // | | // | 深水区 | ← ray marching 区域（macroScattering） // |__________| // ↓ // 水底 // // 【散射模型分解】 // // 1. 宏观体积散射 (diffR)： // - 正面入射 → 水下 ray marching → 正面出射 // - 处理深水区域的体积散射 // // 2. 薄层散射 (diffT)： // - 薄层 SSS：近似深水散射在薄层区域的延续 // - 背光透射：光直接穿透薄层，强前向散射 // - 处理波峰、浪花等薄水区域 // // 【薄层与深水的过渡】 // // 薄层（thickness 小）→ 透射率高 → 直接使用深水散射颜色（S_volume） // 厚层（thickness 大）→ 透射率低 → 使用薄层 SSS（近似深水累积） // // 通过 sss_transmittance 自动混合两者 // // 【出射菲涅尔】 // // T_exit = 1 - F(NdotV) 在 Po…

GPU Clocks正常但带宽高怎么办
type: item
date: 2026-04-21T05:17:00+03:00
description: 1）GPU Clocks正常但带宽高怎么办 2）FixedUpdate一帧调两次，这个"追帧"真的有必要吗 这是第472篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：在我们的项目中，用UWA GOT Online测了一个新坐骑场景用例的报告，单看GPU Clocks数据信息，表现还可以，基本没碰红线，按理说不太像是典型的GPU算力瓶颈。但问题是，带宽这边还是有点高。像这种情况，后面到底该先排查什么？是继续盯Fragment、三角形这些指标，还是该往别的方向看？ A：这个问题其实挺典型的，一般都会下意识觉得：GPU指标有波动，那是不是面数高了、Shader重了、像素太多了。就比如说Clocks，它是像素数量乘以Shader复杂度，只看像素数量，其实不一定能反映Clocks是特别高还是特别低，如果Clocks本身已经没超，那说明至少从“GPU算力被打满”这个角度看，当前这个场景在这档设备上问题不算大，Fragment Shader影响就比较小了，可以先不用关注。真往下查，还是要回到带宽到底是被谁拉高了的角度去查。 带宽压力一般会去从下面这两个角度进行考虑，一个是纹理，另一个是RT（顶点也可能有一定影响，不过当前报告中顶点的影响比较小）。 1. 纹理方面： 第一个纹理，一般是考虑纹理的压缩，比如说是ASTC 4×4、6×6或者8×8，原理就是你压得越厉害，带宽就肯定更小。 再一个要考虑3D纹理是不是开了Mipmap？假设用来做3D渲染的，如果不开，影响会比较大。而开启Mipmap的影响会比压缩大很多，因为不开Mipmap，它很容易Cache Miss，而打开，它就有缓存，可以从很大程度上减少带宽的开销。 还有一个就是各向异性。各向异性只会在那种视角很小的情况下，比如一个很长的地形，镜头拉得很远，看到比较远的地方会有一些毛刺，开各向异性才比较有收益。大部分情况下开各向异性其实没有什么好处，它只会增加带宽。 2. RT方面： 对于RT来说，影响带宽的主要因素是分辨率。RT分辨率越大，Fragment Shader数量就越多，每多画一个像素就要做纹理读取、Shader计算、寄存器存取等步骤，带宽就会越多。 再有，RT分辨率还会影响Load/Store次数。分辨率越大，要Load和Store的像素内容就会越多。如果能把分辨率降一下，带宽收益会非常高，顺带着Clocks也会更低。 再回到我们GOT Online的报告上来，从报告来看，分辨率影响最大。因为看起来RT的格式是比较正常的，穿插的也不是特别多，看上去只有一个Bloom的后期处理。所以还是分辨率的影响最大，如果用这个原始分辨率去做渲染，确实渲染的像素数量会非常多，切RT的时候，或者说Load/Store的时候也会有比较大的影响。 UWA以往优化的常规做法是把RT分辨率控制在1920×1080这个面积，约200万像素，而格式选32位而非64位。 实战案例 Q1：项目是帧同步，主要逻辑放在FixedUpdate里。UWA GOT Online报告显示，有些帧会出现FixedUpdate调用两次的情况，而且一旦跑两次，耗时明显就上去了。这个“两次调用”到底有没有必要？ A：一般追帧是为了保证物理模拟的准确性，比如子弹速度很快，如果不追帧，可能中间穿过去的墙就挡不住它了。但像这个项目的情况，应该不太会需要非常高速的移动或高速判定，所以理论上来说，触发两次是没有太多意义的，就需要做一下限制，可以在参数上做调整，就比如说Time里面的两个参数，一个是每一帧的上限，还有一个是每间隔多少时间触发一次，我们可以把每一帧的上限，调得更小一点，或者说把间隔调得更大一点。 Q2：但项目中是逻辑帧，间隔调得更大点可能就不合适了。 A：如果参数不好调，可以在代码里加一个硬核的限制。比如给它一个变量，判断这一帧有没有更新过，然后把更新过的变量变成True，第二次调用的时候因为变量是True就Return。这样不论上限设多少次，对这个游戏来说，FixedUpdate最多只调一次。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面…

Spine动画更新耗时问题治理
type: item
date: 2026-04-14T05:06:00+03:00
description: 1）Spine动画更新耗时问题治理 2）项目卡顿与GC问题优化方案 这是第471篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q1：我们是一款三消游戏，进到游戏场景后SkeletonGraphic.Update和SkeletonGraphic.LateUpdate的耗时比较高，该怎么优化？ A：从GOT Online的报告中可以看到，进入游戏场景后Update和LateUpdate的调用次数达到50个以上，它其实反映了场景中激活状态的Spine组件数量，首先我们要定位下具体的来源，检查有没有预期之外的。 经讨论后确认，这些格子里的小图标特效是Spine做的，像这种变化幅度不是特别大的、没有像骨骼比较精细的动画，从我们经验上看确实没有太大必要用Spine来做，这种小的简单动画可以尝试改成其它动画方案实现。 另外了解到画面上方的一些角色也都是Spine做的，但是它们在屏幕外时没有做相关处理，仍然在更新。这种情况可以利用Graphic组件上的Freeze功能，也可以通过代码进行控制，当这个对象在视域体外的时候就把它冻结，这样它就会停止Spine相关逻辑的更新，等到快要回到视域体中时再把Freeze状态解除。 Q2：那如果我在这些角色滑出屏幕后直接disable隐藏掉，和这个Freeze效果是一样的吗？ A：理论上是一样的，但还是更推荐Freeze，因为SkeletonGraphic也是属于UI组件之一，然后看画面本身也是比较复杂的UI元素，如果直接调用激活隐藏，可能对UI的性能会有一些负面的影响，触发网格重建导致额外的开销。 实战案例 Q：我们目前比较关心项目的卡顿和GC相关的情况，想问下该怎么优化？ A：从GOT Online的性能测评报告来看，主要有两方面的问题。 首先是GarbageCollectAssetsProfile这个节点，也就是在调用Resource.UnloadUnusedAssets时触发垃圾回收的开销，在这个类似主城的场景里它的调用频率非常高，每隔几百帧就会触发一次，这是不太合理的。Unity中本身有一个机制，在LoadScene的时候会自动调用Asset垃圾回收，所以一般不需要手动去触发。不过也有一些像这种没有去做LoadScene切场景，不会有自动的调用，有的项目会选择用定时器的方式去手动调用，一般控制在5~10分钟调一次，还有的项目会放在触发内存报警Low Memory的时候主动调用，或者还有在场景切换前后这种明确的节点，主动调用。其他时候尽量还是要避免频繁调用，不然会造成非常多的卡顿。 另外还看到在三消的场景中，GC.Collect的调用频率比较高，从Mono报告能看到有个函数会以非常高的频率进行分配，且每次分配1MB以上的堆内存，主要是Json相关的数据写入序列化等。 总之从这个表现来看还是分配的频率太高，而导致GC的频率也比较高，建议排查一下这种操作的单次分配量和频率能否降低一些。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

系统层显存 vs 引擎层显存的差值从哪来
type: item
date: 2026-04-10T08:50:00+03:00
description: 1）系统层显存 vs 引擎层显存的差值从哪来 2）如何定位String的异常增长 这是第470篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：我们项目中通过系统层统计到的显存有1.5GB，但是通过引擎层采集到的纹理、网格、RT这些加起来也只有400+MB，为什么会这样，其它的部分是什么呢？ A：这个其实是一种比较少见的情况，根据以往的经验来说可能有以下几种可能性： Shader占用本身是属于引擎层Native部分的，在运行中会执行Shader.CreateGPUProgram把它编译成GPU可以理解使用的一个Program，生成的这个GPU Program在一些项目中可能造成相当可观的高占用，我们在过往部分极端测试中也发现，它会同时导致系统层Native内存与GFX显存出现显著上升。 测试发现一些RT除了在Memory Profiler中统计到的部分外，也会对显存造成一些额外的开销。 在其它项目中还见过使用Compute Buffer，或者在引擎设置里面开辟一些额外的Buffer，通过C++进行加载的，也会对显存造成额外开销。 这些内存的具体来源是什么无法直接通过Unity引擎的分析工具进行分类和定位，建议可以做一些对比测试，比如去拆分一些渲染策略或场景，如果观察到开启某个渲染策略、进入某个渲染场景时显存出现明显的飙升，那么基本就能做更精准的定位了。 参考文章：《Unity移动游戏工业级性能优化指南 3.0》 实战案例 Q：我用Memory Profiler看到托管堆内存部分中String驻留了50+MB、45万多条，这个现象是正常的吗？ A：这一部分占用还是比较高的，从下图右边的Details信息看很多来自TMP图集用的文本，这种大量字符串理论上不应该驻留在运行时了，我们生成了TMP的图集纹理后像这个文本应该就不需要了，可以看看是不是可以及时卸载。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

UE5 Chaos物理｜创建物理世界对象
type: item
date: 2026-04-08T06:02:00+03:00
description: 【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 物理系统与物理世界 物理系统是每个游戏引擎必备的功能，可以让游戏模拟真实世界的物理规则。而现代游戏引擎中的“物理”，应该称为刚体动力学。刚体（Rigidbody）是理想化、无限坚硬、不变形的固体物理。动力学（Dynamics）是一个过程，计算刚体怎样在力（Force）的影响下随时间移动及相互作用。 在游戏世界背后，有一个镜像的物理世界，其中不关心Actor的视觉表现，只关注Actor的形状、物理材质等信息。简单使用场景下，只要把所有Actor当成刚体处理即可。 以常见的StaticMeshActor为例。下图左侧是Editor窗口，右侧是Chaos Visual Debugger看到的物理世界。 UE5的物理引擎已经从PhysX改成了Chaos，可能物理查询速度会慢些，但更适合做物理破坏效果，而且数据结构和游戏引擎统一，不需要再做转换。 物理世界类 UWorld是代表由Actor构成的整个游戏世界，它持有一个FPhysScene来代表物理世界，可以类比为渲染中的FScene，物理世界的步进（Advance）初始也由UWorld::Tick()发起。UWorld与物理相关的功能一般在PhysLevel.cpp中实现。FPhysScene等价于FPhysScene_Chaos，继承自FChaosScene，是Chaos的首要入口。碰撞事件的注册分发，物理网络同步相关的内容也由其处理，物理模拟的步进也在该类开始（StartFrame）。 示例：创建一个StaticMeshComponent StaticMesh的物理配置 编辑器中，可以给StaticMesh设置碰撞，这里直接使用和模型大小一样的Box碰撞即可。还有其他很多选项，如Sphere、Capsule、凸包等，面对复杂模型时会用多种基础形状拼出一个碰撞。 UBodySetup 这些碰撞配置，最终会存储到UBodySetup类里，作为资源的一部分，它会是StaticMesh的一个配置项。对于例子中添加的简单几何体碰撞，存储在其AggGeom变量中。 class UBodySetup : public UBodySetupCore { //... /** Simplified collision representation of this */ UPROPERTY(EditAnywhere, Category = BodySetup, meta=(DisplayName = "Primitives", NoResetToDefault)) struct FKAggregateGeom AggGeom; //... }; AggGeom里是一大串几何体的数据，Box就位于BoxElems数组里。 struct FKAggregateGeom { GENERATED_USTRUCT_BODY() UPROPERTY(EditAnywhere, editfixedsize, Category = "Aggregate Geometry", meta = (DisplayName = "Spheres", TitleProperty = "Name")) TArray<FKSphereElem> SphereElems; UPROPERTY(EditAnywhere, editfixedsize, Category = "Aggregate Geometry", meta = (DisplayName = "Boxes", TitleProperty = "Name")) TArray<FKBoxElem> BoxElems; UPROPERTY(EditAnywhere, editfixedsize, Category = "Aggregate Geometry", meta = (DisplayName = "Capsules", TitleProperty = "Name")) TArray<FKSphylElem> SphylElems; UPROPERTY(EditAnywhere, editfixedsize, Category = "Aggregate Geometry", meta = (DisplayName = "Convex Elements", TitleProperty = "Name")) TArray<FKConvexElem> ConvexElems; UPROPERTY(EditAnywhere, editfixedsize, Category = "Aggregate Geometry", meta = (Dis…

小游戏的Spine数量过多开销大
type: item
date: 2026-04-01T05:09:00+03:00
description: 1）小游戏的Spine数量过多开销大，怎么办 2）关于在AssetBundle中同时看到SpriteAtlas图集纹理和小图是否合理 这是第469篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From 问答社区 Q1：我们小游戏上Spine耗时有点高，平均每帧耗时8.04ms，该怎么优化？ A：从函数调用次数来看，每帧有200多次调用，这个数量级下单个Spine的开销其实已经相对较低了。其中还看到SkeletonAnimation.FixedUpdate会有3~4ms的开销，理论上进入FixedUpdate后会直接Return，但由于调用数量太高（物理调用次数最大17，导致SkeletonAnimation.FixedUpdate的调用次数达到3000+），还是把这里的开销堆上去了。可以考虑禁用Skeleton Animation组件，专门写一个Manager主动触发Spine的Update和LateUpdate，应该能减少大量Spine对象导致跨语言调用开销。 Q2：是不是要重写一个Skeleton Animation组件呢？ A：在Manager里面注册Skeleton Animation到List里面，统一把SkeletonAnimation.enabled=false就好，然后用Manager统一调用Update和LateUpdate，不用重写Skeleton Animation。 Q3：是在Manager里面的Update直接getComponent到Skeleton Animation点出Update执行吗？ A：最好一开始就注册到某个List里面，动态维护这个List，在Update里面遍历这个数组，每个成员都执行Update。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69c9ec21abed2e338a7dac6d From 问答社区 Q：有个打AssetBundle的情况想咨询下， 我们把一个Unity SpriteAtlas图集打成一个包，这个包里理论上应该只有合并后的图集一个资源， 现在用AssetStudio解包看除了图集还有单个散图，是正常的吗，会不会导致运行时同时加载出来图集和小图而冗余？ A：下图是只打了一个SpriteAtlas图集的AssetBundle在AssetStudio中看到的情况。AssetBundle打包了sactx的Texture2D大图的同时，也打包了很多Sprite；只要看到小图的Type不是Texture2D，就是正常的。 Assets: - Assets/CommonAssets/AssetBundle Test/Texture_common/SpriteAtlas/TestAtlas.spriteatlasv2 Dependencies: [] 运行时还是仅加载一张大图，然后找到其中小图所在的UV位置和长宽，就渲染出来了。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69c9e6feabed2e338a7dac6c 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

Unity移动端游戏性能优化简谱之 功耗、发热和降频在软件层面的优化
type: item
date: 2026-03-24T11:11:00+02:00
description: 《Unity移动端游戏性能优化简谱》共分为五个部分，今天向大家介绍文章的最后一部分：功耗、发热和降频在软件层面的优化，包含了移动端高功耗与发热产生机制、功耗与发热的定量数据分析、使用Perfetto的功耗调优实践等多项常见的游戏画面表现讲解。（全文长约3200字，预计阅读时间约15分钟） 前四部分可点击以下对应文章查看： 一、《Unity移动端游戏性能优化简谱之 前言》 二、《Unity移动端游戏性能优化简谱之 分门别类地控制运行时内存（上）》 《Unity移动端游戏性能优化简谱之 分门别类地控制运行时内存（下）》 三、《Unity移动端游戏性能优化简谱之 以模块思维划分的CPU耗时调优（上）》 《Unity移动端游戏性能优化简谱之 以模块思维划分的CPU耗时调优（下）》 四、《Unity移动端游戏性能优化简谱之 达到GPU压力与画面表现间的平衡》 1. 功耗、发热与降频基本原理 1.1 移动端高功耗与发热产生机制 手游运行时功耗由核心计算功耗（CPU/GPU/NPU）、访存功耗、外设功耗（屏幕/射频/传感器/存储）、系统调度开销四部分构成，其中对于手游来说尤其常见和需要关注的情况有： 高负载场景下（战斗、大世界渲染、物理模拟、网络同步），CPU/GPU持续高频运行，功耗可能持续过高； 手机机身散热面积有限，热量无法快速传导至外部，形成热堆积，且往往出现部分硬件模块压力尤其大，发热严重并传导到附近其他模块，使得这些模块也出现性能问题； 功耗与性能并非线性关系，超过能效拐点后，性能小幅提升会带来功耗大幅上涨，发热急剧恶化。 当SoC温度达到温控阈值，Android内核与固件会启动多级热防护机制，强制降低性能以控制发热，多数情况下体现为CPU/GPU频率降低、限制大核调度、减少并发线程数。 降频同时导致了CPU/GPU的算力下降，常直接导致游戏运行过程中帧率暴跌。尤其相对于其他前文讨论的性能问题，该问题反而在一些玩家的高端设备上更加显著，对于一些算力强劲的CPU/GPU，可能计算压力并不足以直接导致掉帧，但发热降频却导致了这些玩家有手机发烫、低帧卡顿等负面体验。 1.2 功耗与发热的定量数据分析 作为开发者和性能优化执行者，观察问题的视角显然还是要与玩家区分。因此，要精准定位功耗与发热的核心诱因，应避免依赖 “手感发烫”、“续航缩短”等主观感受，必须通过定量数据分析建立客观判断标准。正如上文讨论的，移动端游戏的功耗发热本质是硬件模块（CPU/GPU/存储/射频等）持续高负载运行的结果。所以定量分析的核心目标是：明确各硬件模块的功耗占比、定位高功耗场景的时间分布。 不过定量分析功耗发热问题的核心困难是，该问题其实已经脱离了游戏引擎可掌控的范畴，属于非常硬件层的问题。这就导致了不同芯片厂商、不同SoC厂商、不同手机厂商，其散热设计、保护机制等因素的不同都会导致发热和降频的情况互不相同；且他们提供的数据采集接口即便看起来单位一致，其实表示的实际开销也互不相同；在测试过程中，也会受到环境的干扰，比如冬夏气温、插电测试都会导致测试结果受到影响。 所以，应当尽量在固定设备使用可靠的工具进行测试，将数据进行按时间的、优化调试前后的纵向测试，而不是不同硬件、不同工具之间的横向测试。 1.3 使用Perfetto的功耗调优实践 1.3.1 市面上功耗测试工具评测 功耗优化的第一步是精准采集数据，市面上各类功耗测试工具各有优劣，需结合实际测试需求、设备适配性择优选用，以下是主流工具的实操测评与选型建议： （1）GOT Online作为常用性能监测工具，可实时采集帧率、帧耗时、CPU核心频率、内存占用、功耗峰值等核心数据，支持设备截屏、数据导出与对比分析，能直观呈现游戏运行时的功耗波动，基础监测能力在线，但针对细分硬件功耗的拆解能力有限。 （2）Trepn Profiler实测实用性较低，数据采集精度与稳定性难以满足专业调优需求；adb shell dumpsys batterystats指令虽能获取电池消耗数据，但数据维度单一、参考价值不大，仅能作为辅助参考，无法支撑深度功耗分析。 （3）Android Studio自带的Profiler工具，搭载On Device Power Rails Monitor（ODPM）模块，可监测CPU大小核、GPU、内存、屏幕、WiFi等硬件组件的功耗，但缺陷十分突出：不仅需要Debuggable包体，还仅适配Pixel系列机型，且测试时长不宜过长，通用性极差，难以适配主流安卓机型的规模化测试。 （4）Perfetto是移动端功耗测试的最优选择之一，无需Debuggable包体，支持USB/WebSocket连接实时录制。只不过在采集各硬件功耗轨道数据时，谷歌官方只在自己旗下的Pixel品牌手机中进行了专门的插桩，从…

Unity移动端游戏性能优化简谱之 以模块思维划分的CPU耗时调优（下）
type: item
date: 2026-03-24T04:54:00+02:00
description: 《Unity移动端游戏性能优化简谱》从Unity移动端游戏优化的一些基础讨论出发，例举和分析了近几年基于Unity开发的移动端游戏项目中最为常见的部分性能问题，并展示了如何使用UWA的性能检测工具确定和解决这些问题。内容包括了性能优化的基本逻辑、UWA性能检测工具和常见性能问题，希望能提供给Unity开发者更多高效的研发方法和实战经验。 今天向大家介绍文章第三部分：以引擎模块为划分的CPU耗时调优，共10小节，包含了渲染模块、UI模块、物理模块、动画模块、粒子系统、加载模块、逻辑代码、Lua等多个模块等常见的游戏CPU耗时调优讲解。 本章包含6-10小节，1-5小节请点击《Unity移动端游戏性能优化简谱之 以模块思维划分的CPU耗时调优（上）》。 （全文长约14115字，预计阅读时间约30分钟） 文章第一部分《Unity移动端游戏性能优化简谱之 前言》、第二部分《Unity移动端游戏性能优化简谱之 分门别类地控制运行时内存（上）》、《Unity移动端游戏性能优化简谱之 分门别类地控制运行时内存（下）》可戳此回顾，完整内容可前往UWA学堂查看。 6. 粒子系统 围绕粒子系统相关优化更全面的内容可以参考《粒子系统优化——如何优化你的技能特效》。 6.1 Playing粒子系统数量 UWA统计了粒子系统数量和Playing状态的粒子系统数量。前者是指内存中所有的ParticleSystem的总数量，包含正在播放的和处于缓存池中的；后者指的是正在播放的ParticleSystem组件的数量，这个包含了屏幕内和屏幕外的，我们建议在一帧中出现的数量峰值不超过50（1GB机型）。 针对这两个数值，我们一方面关注粒子系统数量峰值是否偏高，可选中某一峰值帧查看到底是哪些粒子系统缓存着、是否都合理、是否有过度缓存的现象；另一方面关注Playing数量峰值是否偏高，可选中某一峰值帧查看到底是哪些粒子系统在播放、是否都合理。 前文提到的控制同种粒子数量上限也是常见方案。 分级策略也是粒子系统优化过程中必不可少的手段。比如对于一个有12345，五种子粒子的火焰特效Prefab，其中345可能是美术同学用来做火焰燃烧时飞溅的火星、飘散的烟尘、空气的波动的，只有12是火焰本体。则低端机上应该考虑只保留12。 虽然我们在讨论粒子系统对耗时的影响时，多数时候还是在讨论粒子系统的渲染开销，无论是CPU端还是GPU端的。但对于粒子系统使用非常重度、数量非常多的项目来说，Playing的粒子过多也会对其本身在CPU端进行更新计算的ParticleSystem.Update()耗时有所影响。 6.2 Culling Mode 为粒子系统设置合理的Culling Mode也是一种有效降低粒子系统CPU端更新耗时的方式。 相关的关键设置为如上图的Simulation Space和Culling Mode。 其中，Simulation Space可选项有Local，World，Custom；CullingMode可选项为Automatic，Pause And Catch-up，Pause，Always Simulate。通常创建的粒子系统的默认选项是Local和Automatic。 我们显然希望当粒子在屏幕外看不到时停止相关运算、减少其耗时开销。因此我们测试在不同设置组合下粒子系统在屏幕外时的ParticleSystem.Update()函数耗时，结果如下： 在Local模式下，只有Culling Mode为Always Simulate有例子更新耗时（没有开启额外的Module）。 在Local模式下，如果粒子系统开启了Collision等Module，那么Culling Mode为Automatic时也会产生耗时。 在Local模式下，如果是Culling Mode为Pause或者Pause and Catch-up，就算有Collision Module也没有耗时。 在World模式下，Culling Mode为Automatic也会有耗时（没有开启额外的Module）。 在World模式下，Culling Mode为Pause，就算有Collision，也没有耗时。 综上，我们可以看到Culling Mode的设置对不同设置的粒子系统开销的限制能力是合理递进的，这说明我们在设置粒子系统时应尽量至少设为Automatic；应慎用Simulation Space为World的粒子；应慎用Collision、Trigger、Noise、Trails这些颇为常见的Module；对于确实要使用Module的粒子，也应考虑进一步调整设置为Pause等以节省耗时。 6.3 Prewarm ParticleSystem.Prewarm作为粒子系统的核心初始化选项，其设计初衷是为了…

如何兼顾大量小怪渲染时的合批性能和层次感
type: item
date: 2026-03-24T03:55:00+02:00
description: 1）如何兼顾大量小怪渲染时的合批性能和层次感 2）Lua堆内存是怎么触发GC的 这是第468篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From 问答社区 Q：我们有个类似吸血鬼幸存者的小游戏，画面中有很多怪物，大概有4~5种不同的类型，目前是已经转成了GPU Skinning的方式做渲染，但如果不对怪物做排序，会因为穿插导致无法合批，渲染耗时就很高；而如果做了排序，是能够进行合批，渲染耗时也降下去了，怪物之间却没有了层次感，表现效果也不好，请问该怎么处理？ A：建议做成Texture2DArray版本的材质球，应该纹理顺序就不影响合批了。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69c0f55a92894f1c4f0c2356 From UWA AI 问答 Q：Lua堆内存是怎么触发GC的？ A：Lua垃圾回收（GC）采用增量式策略，触发机制不依赖真实时间，而是由虚拟机内存分配行为驱动。只有当虚拟机分配新内存时，GC才会推进。 一、触发阈值控制 GC启动由内存使用量倍数决定，通过参数LUA_GCSETPAUSE调节。 默认配置：C API传入值为200。 对应倍率：200%即2倍。 计算逻辑：触发阈值 = 上次GC后内存量*(参数值/100)。 单位校验：若上次GC后内存为100MB，参数值200。 计算过程：100MB*(200/100) = 200MB。 结果：当内存增长至200MB时触发GC。 优化建议：调低该值（如150）可降低内存峰值，但会增加GC频率与CPU开销。 二、回收速度控制 GC推进速度由参数LUA_GCSETSTEPMUL控制。 默认配置：C API传入值为200。 对应倍率：2倍。 作用：表示GC清理速度目标是新增内存速度的两倍，防止内存无限膨胀。 优化建议：数值过低可能导致回收赶不上分配；过高则导致单帧耗时波动增大。 三、监控与优化建议 建议通过性能分析工具观察堆内存曲线。堆内存显著下降意味着发生GC。高频GC降低峰值但增加耗时。若内存持续上涨无下降，需排查泄漏。 优化策略： 减少运行时动态创建Table和闭包，尽量复用对象。 合理预分配内存，避免频繁分配驱动GC。 在加载场景间隙手动调用收集接口，避开帧渲染高峰。 定位高分配热点函数，从代码源头减少内存分配。 调节参数需结合项目内存预算与CPU性能，平衡触发频率与回收速度，实现最佳折衷。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=dc49a77a-7262-4ca1-98b8-6063b9a13d70 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

Unity移动端游戏性能优化简谱之 分门别类地控制运行时内存（下）
type: item
date: 2026-03-20T12:10:00+02:00
description: 《Unity移动端游戏性能优化简谱》从Unity移动端游戏优化的一些基础讨论出发，例举和分析了近几年基于Unity开发的移动端游戏项目中最为常见的部分性能问题，并展示了如何使用UWA的性能检测工具确定和解决这些问题。内容包括了性能优化的基本逻辑、UWA性能检测工具和常见性能问题，希望能提供给Unity开发者更多高效的研发方法和实战经验。 今天向大家介绍文章第二部分：资源内存、Mono堆内存等常见游戏内存控制，共13小节，包含了纹理资源、网格资源、动画资源、音频资源、材质资源等多个资源内存以及Mono堆内存等常见的游戏内存控制讲解。 本章包含8-13小节，1-7小节请点击《Unity移动端游戏性能优化简谱之 分门别类地控制运行时内存（上）》。 （全文长约11400字，预计阅读时间约20分钟） 文章第一部分《Unity移动端游戏性能优化简谱之 前言》可戳此回顾，完整内容可前往UWA学堂查看。 8. Render Texture 8.1 渲染分辨率 资源列表中的一些RT资源能反映项目当前的渲染分辨率。对于GPU和渲染模块压力较大的项目，在中低端机型上降低其渲染分辨率是非常直观有效的分级策略。但相关调整比起说影响内存，还是更多地影响GPU片元计算压力和带宽，后文GPU章节会进行更加详细的讨论。 如果一些其他的RT资源分辨率过高也应引起注意，尤其是2048*2048以上的资源。应当排查是否有必要用到如此精细的RT，在低端机上考虑采用更低分辨率的效果。 8.2 抗锯齿 资源列表中展示了RT资源的AA倍数。开启多倍AA会使RT占用内存成倍上升，并对GPU造成压力。建议排查是否有必要开启AA，尤其在中低端机上，可以考虑关闭此效果。 特别的是，在华为部分机型上2倍的AA会失效。即已经造成了性能消耗但没有实际起到抗锯齿效果。 8.3 后处理 一些常见的后处理相关的RT（如Bloom、Blur）是从1/2渲染分辨率开始采样，可以考虑改从1/4开始采样、并减少下采样次数，从而节省内存并降低后处理对渲染的压力。 站在性能优化的角度，在中低端机型上甚至最好分级地关闭各类后处理。围绕一些常见后处理效果的讨论会在下文GPU部分中进一步展开。 8.4 URP下的RT 使用URP时，内存中会多出_CameraColorTexture和_CameraDepthAttachment两份RT资源作为渲染目标，而开启URP相机的CopyDepth和CopyColor设置时会额外产生_CameraDepthTexture和_CameraOpaqueTexture作为中间RT。当资源列表中出现这两种RT时，需要排查确实是否用到CopyDepth和CopyColor，否则应予以关闭以避免不必要的浪费。 9. Shader资源 9.1 ShaderLab Unity 2019.4.20是Shader内存统计方法的一个转折点。在此之前，Shader的内存主要统计在ShaderLab中，而之后则主要统计在Shader资源自身身上。 对于Unity 2019.4.20之前的版本的项目，查看ShaderLab的内存使用Memory Profiler截帧查看。而无论是Shader资源本体还是ShaderLab内存占用过高，都要着手于控制Shader的数量、变体数量和Shader复杂度。 9.2 变体数和复杂度 对于单个Shader资源而言，其内存占用高本质在于其编译后的行数高。因此也可以视为，怎么控制变体的数量和每个变体的平均行数（也就是Shader的复杂度），这两个乘数的问题。 其中，变体数过多是造成一个Shader资源内存占用过大、占用包体过大的主要原因，也是相对比较好和排查和修改的部分。在项目迭代中可能会出现已经被弃用或者没有被实际使用到的关键字，导致变体成倍上升；又或者Shader写的比较复杂，其中一些关键字组合永远不会被用到，从而导致很多变体是多余的。UWA的本地资源检测中提供了Shader检测功能，可以看到变体数量，定位变体数过多的Shader资源。 针对上述情况，Unity提供了回调函数，在项目打AssetBundle包或者Build时线剔除用不到的关键字或关键字组合相关的变体。剔除Shader变体的方法可以参考《Stripping scriptable shader variants》。 而Shader的复杂度同时还涉及到GPU性能相关的优化。具体在后文GPU部分有更加详细的讨论。 9.3 冗余 Shader冗余尤其需要予以关注，Shader的冗余不光导致内存上升，还可能造成重复解析，即运行时不必要的Shader.Parse和Shader.CreateGPUProgram API调用耗时。 一般的做法是，首先使用ShaderVariantCollection这一Unity…