Unity性能优化系列渲染篇 - 移动端渲染优化 性能优化系列 · 渲染篇。一句话摘要先把相机、可见集和 Pass 管住再谈合批开发期用 DrawCall、Batches、SetPass Calls 盯提交量真机用 CPU Time、GPU Time 和带宽做验收——动态合批和后处理按瓶颈开不要当默认开关。上一篇把 URP 的 Pipeline Asset、阴影、附加光、Render Scale、后处理栈按档位定了下来。管线开关对齐之后剩下真正把帧时间打满的通常已经不在 Asset 面板里而在场景里多出来的相机、拆开的批次、叠在屏幕中央的半透明、以及低档机仍在跑的全屏 Pass。为什么从场景渲染开始设置篇解决的是“同一套管线别让低配机跑高配参数”。渲染篇解决的是另一件事同一档管线里这个场景为什么还卡。原因同样很实际移动端同一份场景不同机型卡的地方不一样。有的卡在 CPU 提交有的卡在 GPU fill有的卡在 UI 重建。只压某一个数字换一台机器就失效Editor 的Stats可以实时看见 DrawCall、Batches、SetPass Calls适合当开发期的弦但它不是玩家体感。玩家感到的是真机上的帧时间、带宽和发热SRP Batcher、GPU Instancing、动态/静态合批分别适用于不同情况多相机和后处理用于实现画面或交互需求也各有额外开销。选用它们不是为了让某个统计数字好看而是看目标机型上的 CPU / GPU 帧时间、带宽和发热是否真正改善。所以这篇的前提是先看这一帧卡在哪一侧再选手段开发期用提交量挡住失控上线前用真机时间和带宽验收。移动端的约束动手之前先承认几条硬约束后面的每个决策都是从这几条推出来的GPU 是 tile-based带宽和片元比桌面贵得多。多一台相机、多一张全屏 RT、多一层近透明都是按像素计费不是按物体个数计费多一台 Camera 往往比多几十个物体更贵。Unity 官方测过主线程的相机处理时间和相机数量直接相关即便新相机什么都不画剔除和提交准备照样走一遍。URP 的 Overlay / Camera Stack 在手机上还可能多一次全屏解析并打乱单相机内部的 Overdraw 优化SetPass 经常比同材质多几次 Draw 更伤。切 shader、切混合状态、切关键字才是渲染线程上真正贵的那一段。UWA 很早就指出只盯 DrawCall 会误判Batches 和 SetPass 要一起看动态合批本身有 CPU 成本。CPU 要把小 mesh 变换到世界空间再拼起来现代移动 API 上这笔开销经常比少一次 draw 还贵散热决定持续性能。平均 60 帧没有意义1015 分钟后的 95/99 分位和是否掉频才是玩家体感。几个指标的含义先理解这些指标分别表示什么才能知道该从哪一类问题开始排查。DrawCallCPU 向图形 API 发出的一次绘制请求。Profiler Rendering 模块里能看到。BatchesUnity 统计口径里“实际提交的批”。和 DrawCall 有时接近但不等价——静态合批之后多个 Draw 可能被计成一个 Batch。Verts / Tris这一帧提交的顶点数 / 三角面数用来观察几何量。两者高时先查可见集、LOD、远景和模型复杂度但它们不直接等于 GPU 压力透明 Overdraw 和全屏 Pass 仍可能更贵。SetPass Calls切换 shader pass / 渲染状态的次数。同材质连续画SetPass 可以很低DrawCall 仍可以高。Overdraw同一像素被画了几次。这是 GPU fill 压力不是提交压力。CPU Frame Time / GPU Frame Time最终体验的主导指标。带宽、PSS、温度是配套验收不能被单指标绑架。读数时用这条判断不要上来就合批CPU 在等 GPU先查 Overdraw、半透明、后处理、阴影、全屏拷贝少几次 DrawCall 救不了SetPass 高材质、shader、关键字切得太勤。先共享材质、收变体URP 下让 SRP Batcher 吃到同一 variantBatches 高、SetPass 不高同 Pass 下物体被拆开了才轮到 Instancing、静态合批、减物体Batches 已经很低CPU 仍紧反过来怀疑动态合批、UI 重建、多相机剔除把 CPU 吃掉了。开发期对着 Editor 的 DrawCall、Batches、SetPass Calls上线前对着真机的 CPU Time、GPU Time、带宽。两层标准开发基线真机金标准研发过程里不可能每次改材质、加特效都打一包上真机。所以要先给场景订一条看得见、能天天对的弦再承认它不是最终判决。开发基线Editor 里就能盯的提交量给关键场景写死一组上限例如大厅、对局、结算各自「DrawCall / Batches / SetPass 大概该在多少以下」。Game 视图Stats和 Editor Profiler 可以实时对照用来防止开发中途悄悄涨上去。订基线时可以先参考公开数据再收成自己的场景预算UWA《2024-2025 年度 Unity 手游性能蓝皮书》截至本文更新时 UWA 公开发布的最新年度蓝皮书包含渲染模块的 DrawCall、Batches、同屏三角面数等主体范围并讨论 URP 的 SRP Batcher 效果。下面这组数值是本文采用的开发期预警线面向中轻量级移动端 URP 场景用来尽早发现提交量上涨不是行业标准也不是最终验收线。DrawCall、Batches、SetPass、顶点数和三角面数要一起看前 3 项偏提交成本后 2 项偏几何成本。场景与设备档位DrawCallBatchesSetPass Calls顶点数三角面数大厅 · 低档≤ 120≤ 80≤ 25≤ 100k≤ 75k大厅 · 中档≤ 180≤ 120≤ 35≤ 200k≤ 150k大厅 · 高档≤ 240≤ 160≤ 45≤ 330k≤ 250k常规对局 · 低档≤ 200≤ 140≤ 35≤ 200k≤ 150k常规对局 · 中档≤ 300≤ 220≤ 50≤ 400k≤ 300k常规对局 · 高档≤ 400≤ 300≤ 65≤ 650k≤ 500k特效峰值 · 低档≤ 280≤ 200≤ 45≤ 300k≤ 220k特效峰值 · 中档≤ 400≤ 300≤ 60≤ 500k≤ 400k特效峰值 · 高档≤ 520≤ 400≤ 75≤ 800k≤ 650k这些值从低到高档逐级放宽是为了给更高档设备保留画面空间不代表高档机可以无限堆叠。实际项目需要用目标档位的代表机型做真机 A/B如果某档位在表内仍然 GPU 超时、带宽过高或持续发热就继续收紧透明、阴影、后处理和相机数量如果真机长期有余量再按场景逐项放宽。用法是按场景和设备档位各留一张 Editor 对照表写清观察机位和操作路径日常开发只看预警线有没有被拉断——超了先查是材质拆批、透明层还是多了 Pass预警线用来减少无效的真机回归不是用来宣布优化完成。Editor 的渲染路径、资源加载、脚本开销都和真机不一样最终仍以不同档位代表机型上的帧时间、带宽和发热为准。金标准真机上的时间和带宽最终只认真机、尽量接近正式包上的CPU Frame Time、GPU Frame Time以及 95% / 99% 分位不只看平均 FPSGPU 模块拆开看 Shadows、Transparent、PostProcessing带宽和填充贴图尺寸、压缩、Overdraw、全屏拷贝、后处理内存与发热PSS、GC、连续玩 1015 分钟是否掉频。DrawCall 基线过了、真机 GPU 仍抖优先查 Overdraw 和后处理。真机 CPU 紧、Batches 却很低反而要怀疑动态合批或 UI 重建。UWA / UPR 的区间是开发期的弦金标准永远是目标机型上的时间和带宽。按这个顺序做先数相机、先减可见集——不该画的不要进管线共享材质、收变体默认让 SRP Batcher 生效大量重复网格再用真机 A/B 决定是否改走 Instancing动态合批最后考虑半透明和后处理按面积、按档位管开发期盯 DrawCall / Batches / SetPass真机用时间和带宽验收。1. 少加相机排查时先数相机再数 Batches。URP 里每多一个 Camera含 Stack 里的 Overlay通常要再做一轮剔除再走一套不透明 / 透明 / 后处理相关 Pass。落地约定场景里默认只留一台 Base Camera屏幕 UI 用 Screen Space Overlay不要为 UI 再挂透视 Overlay 相机更不要用多相机给 Canvas 排序——排序是 Canvas 自己的事角色预览、拍立得、小地图能短时用低分辨率 RenderTexture 就不要常驻第二台全屏相机自定义描边、分层效果按需求使用 Renderer Feature常规后处理要确认 Renderer 已挂后处理数据并由 Volume 控制不要「一层效果一台相机」弹窗盖住 3D 时优先关底层相机或停渲染而不是再叠一台遮罩相机。能少一台就少一台。少相机省的是整条提交和全屏带宽合批省的只是同一条路上的物体数。2. 先问物体该不该被画提交优化之前先减可见集视锥剔除默认就有。网格合得太大远处一整坨剔不掉反而更亏。静态合批主要降低提交开销但会抬 VBO 与内存DrawCall 已经很低时可以适当拆开换取内存和带宽遮挡剔除能少画被挡住的网格但要烘焙、占内存动态物体收益有限。室内、遮挡多的场景再开空旷大厅别当标配LOD / 远景 Impostor 降的是面数和采样不是 Batches。低端机同屏面数往往比多 20 次 DrawCall 更敏感UWA 简谱把低端机面数单独设预算就是这个原因蒙皮网格、粒子默认不走动态合批。同材质小人很多时Instancing / 合并网格要单独评估不要指望自动合批。窗口已经挡住大世界时优化对象经常不是合批而是底层还该不该继续画。3. 合批每次绘制选一条提交路径对同一个 Renderer 在同一次绘制中SRP Batcher、GPU Instancing 和动态合批不会叠加要在三者之间选一条主路径。静态合批是另一类场景资源策略要单独核算内存和加载成本别把它和前三种当成同一个开关。共享材质、少 shader variant比先勾哪个开关更要紧。不同材质几乎合不上关键字不一致时看起来是同一个 shader实际却是不同 variantSRP Batcher 也会拆开。提交路径默认优先级与适用情况主要优点主要代价SRP BatcherURP 的默认选择大量常规物体使用同一 shader variant、材质可以不同降低每次提交的 CPU 状态设置成本不一定减少 DrawCallMaterialPropertyBlock和不兼容 shader 会使其失效GPU Instancing只给大量相同网格、相同材质的候选组用真机 A/B 确认后再选一次实例化绘制可合并重复物体的提交与 SRP Batcher 互斥变体分散、实例频繁增删会增加 CPU 组批成本动态合批大量不同 mesh 共享同一材质且每个 mesh 都很小可合并这些小网格的提交每帧 CPU 变换并拼接顶点限制严格适用范围很小SRP Batcher主要让同一 variant 下的多次 Draw 变便宜不保证把 300 次 DrawCall 变成 30。启用后看SetPass、Render Thread/ CPU 时间是否下降并在 Frame Debugger 里确认出现SRP Batch。不兼容的材质太多时先治材质规范再谈开关。GPU Instancing的前提是重复物体的网格和材质完全一致。它不是全局替代 SRP Batcher 的“更高版本”而是重复组的另一条提交路径越复杂的材质、越频繁的实例增删收益越可能被 CPU 端组列表成本吃掉。开关只是一半另一半是目标机型上的渲染线程和 GPU 帧时间。动态合批现在的适用范围很小但并非完全没有空间当场景里有大量不同 mesh、它们却共享同一材质并且每个 mesh 的顶点 / 三角面数都很少时才值得作为候选方案。它每帧仍要在 CPU 上变换并拼接小 mesh限制也很严格单个 mesh 需满足顶点属性限制上限 900 个顶点属性简单的 Position、Normal、UV 网格才可能接近 300 顶点并使用单 Pass shader。CPU 已经是瓶颈时可能更差透明物体还要先按从后往前排序实际成功率更低。开启前后看真机CPU Frame Time不能只看 Batches 变少。静态合批能减少提交也会抬内存和 VBO加载弹性变差。把它当局部场景收益项用真机 A/B 决定留不留。少数重复组让 Instancing 有目的地接管 SRP Batcher当某一组物体数量很多、网格和材质完全一致时SRP Batcher 的多次 Draw 可能反而不如一次 Instancing 绘制划算。Unity 也明确说明SRP Batcher 与 GPU Instancing 不兼容这种场景可以有意让 shader 不支持 SRP Batcher再启用 Instancing。这里的结论只对通过同机位、同档位真机 A/B 验证的重复组成立不能因此全局关闭 SRP Batcher。一种可维护的做法是保留原来的 SRP Batcher 兼容 shader再为确认受益的重复组维护一个 Instancing 专用 shader 变体先在 Frame Debugger 和真机 Profiler 中确认候选组确实是大量相同网格、相同材质比较 SRP 路径与 Instancing 路径的 CPU、Render Thread、GPU 帧时间和内存而不只比较 Batches在手写 HLSL shader 的Properties中新增一个材质属性但不要把它声明进UnityPerMaterial的 CBUFFER。按 Unity 的兼容规则该 shader 就会被排除在 SRP Batcher 之外该属性不需要参与实际计算在这个专用 shader 的绘制 pass 保留#pragma multi_compile_instancing并在对应材质上启用Enable GPU Instancing如果项目使用自定义绘制也可改用Graphics.RenderMeshInstanced不要把这项改动施加到通用 shader 或所有材质上。只让已验证受益的重复组引用专用变体Frame Debugger 应确认它不再走SRP Batch并按预期形成 Instancing 批次Shader Graph 需要走这条路时可按官方方式导出编译结果并维护独立 shader每次 Graph 重新编译后都要重新应用该改动。因此长期维护通常优先为这类少数对象保留可控的手写 HLSL 变体。这个选择不是“谁更先进”而是让不同物体走适合自己的路径常规物体优先 SRP Batcher大量完全相同的重复物体才把 Instancing 作为可验证的例外动态合批始终最后评估。具体兼容条件和排除方式可对照 Unity 的 SRP Batcher 文档 与 有意排除 SRP Batcher 的官方方法。MaterialPropertyBlock改色方便但会把物体踢出 SRP Batcher。要变色优先走 Instancing 的 per-instance 属性、图集采样或可控的材质变体不要为了改个 tint 把整批合批拆掉。运行时renderer.material复制同理等于每物体一份材质实例。4. 什么在拆批比“开哪个开关”更值得查Frame Debugger 里相邻两次 draw 材质一变合批就断。高频拆批源每物体一份材质实例或运行时renderer.materialMaterialPropertyBlockShader 变体 / Keyword 不一致半透明穿插中间插进别的材质就断能合的半透明尽量同材质、少穿插光照贴图不在同一图集区域、镜像缩放、多 pass 灯光开了Opaque Texture/Depth Texture例如 SSAO 等深度效果、软粒子、折射或扭曲可能多出一到两张全屏图带宽会先涨一截。两个开关要分别确认具体使用者不能因为某个效果需要其中一个就把两者都打开。查法Stats 里 Batches 高、SetPass 不高多半是同 Pass 下物体被拆开SetPass 也高才是 shader / 关键字 / 相机 / 后处理在切状态。5. 阴影从便宜方案开始选阴影经常是“看得见一点、成本很高”的项。按画面需求从低成本方案往上试不满足再升级贴图阴影先用预制阴影贴图、Blob Shadow 等方式适合只需要稳定接地感的对象烘焙阴影静态场景优先烘焙并用 Light Probe 支持动态物体接受环境光照平面阴影对象会移动、但投影形状可以简化时使用实时阴影前三种都无法满足表现要求时才保留并按档位压Shadow Distance、级联、分辨率和投射者数量。无论最终选哪一种都要看阴影的实际消耗在同一镜头、同一机型上对比Profiler的 Shadows 模块、SetPass、CPU / GPU 帧时间和 95/99% 分位。关闭主光实时阴影时软阴影和附加光阴影也应随之关闭它们不再带来画面收益还会留下额外设置和变体。档位怎么切设置篇已经写过这里不重复配 Asset。6. 后处理单独排查按档位打开Bloom、DOF、SSAO、Motion Blur、全屏模糊都是按像素计费。GPU 已经吃紧时它经常比多几十次 DrawCall 更伤帧。Editor 里往往“看起来还行”低端真机上一次全屏拷贝就能把 fill 打满。UWA 对移动端抗锯齿的建议也很克制低档关高档最多 2x MSAA。排查顺序Frame Debugger 里后处理 Pass 是否真的在跑常规后处理要同时确认 Renderer 已挂后处理数据、Volume 开启了对应效果不能只看 Volume 挂着真机对同一镜头做“全关 / 只留 Bloom / 全开”三档 A/B看GPU Frame Time和 95/99% 分位确认Opaque Texture/Depth Texture分别由哪个效果使用Depth 常见于 SSAO 等深度效果Opaque 常见于折射或扭曲不能笼统归因给后处理。落地原则低档先关中档最多留一项可见收益高的常见是 Bloom高档再逐项加。Bloom、Tonemapping 等依赖 HDR 画面链路的效果要和 HDR 一起按档位评估不能只孤立保留一个开关。不要全机型共用一套 Volume。每加一个全屏 Feature 至少多一个 Pass要算总账。7. 半透明、粒子和 Overdraw 单独看DrawCall 和 Overdraw 是两条成本路径。粒子、扫光、Mask、Glow 叠在屏幕中央时DrawCall 可能只有几十GPU 已经满了。移动 GPU 上 alpha clip / 镂空还会伤部分机型的 Early-Z。处理顺序先减覆盖面积和层数再谈合批。大块无效透明像素比少一次 draw 更值钱。看 Overdraw 的流程可以固定Scene 视图打开 OverdrawFrame Debugger 看同层透明叠加真机对同一场景做“开/关关键透明层”A/B对比 GPU 时间看帧率稳定性不只看平均帧。8. UI合批和重建大部分 UI 抖动来自 Canvas 重建范围过大不是缺一张图集。Unity 的 Canvas 本身已经做了重建隔离子 Canvas 独立维护几何和批次某个 Canvas 变脏时不需要重建父 Canvas 与兄弟 Canvas。因此可以按刷新频率做动静分离大面积静态内容留在主 Canvas倒计时、进度、持续动效等会一起频繁刷新的元素放进同一个子 Canvas。但不要机械地“一个控件一个 Canvas”。Canvas 之间不会自动合成批次拆得太细会增加批次、DrawCall、排序和输入管理成本。只有下面两种情况同时成立才拆这组元素刷新明显更频繁且能作为稳定的一组一起刷新。偶尔改变一次的小图标不值得单独建 Canvas。两点容易漏静态或不接收输入的子 Canvas 不要挂Graphic Raycaster元素移出屏幕后 DrawCall 没降说明 CPU 仍在提交网格。UWA 测过看不见不等于没提交只是 GPU 不怎么填像素子 Canvas 能隔离脏更新但拆分不是免费午餐。改完一起看Canvas.BuildBatch、SetPass 和 DrawCall再决定是否保留。Unity 的说明和例子见 Optimization tips for Unity UI它明确建议把静态 UI 与同频刷新的动态 UI 分到不同 Canvas同时提醒每个 Canvas 都有独立的几何和批次。图集仍然重要同一界面内高频复用的图标、按钮和装饰图尽量进入同一 Sprite Atlas让 Image 尽可能共享贴图和材质减少 UI 的材质切换。TextMeshPro 也要统一 Font Asset 和材质回退字体、不同描边 / 字体材质会形成新的批次。还要同时看层级和遮挡关系。Hierarchy 决定 UI 的视觉前后关系重叠元素必须按这个关系显示但 Canvas 建批时还会分析元素深度、边界框重叠和材质。两个元素不重叠时Unity 可以按可合批性安排提交不必机械地逐个遵循 Hierarchy有遮挡关系时为保证画面前后正确中间不同材质的元素就会成为不能跨越的“中间层”。因此文本和图片的材质交替穿插不一定必然拆批关键在它们是否形成遮挡。例如“文字 A → 图片 B → 文字 C”中若 B 的边界框与 A、C 有重叠A 与 C 即使同用 TMP 字体材质也不能跨过 B 合批TextMeshPro 的字符区域外虽然透明文本的矩形边界仍可能与附近图片相交导致看不见的“中间层”拆批。能调整视觉层级或位置时尽量让同材质元素连续且不被不同材质遮挡不能调整时在 Frame Debugger 中确认这笔拆批是否值得保留。Unity 对 UI 建批、重叠与 Child Order 有更完整的说明基础绘制顺序可对照 Canvas 手册。9. 大窗口挡住 3D 时停渲染或截一帧这类做法并不少见。工程上不要等窗口打开后再猜它覆盖了多少画面而是在窗口定义中预先标记渲染策略KeepWorld保持世界渲染、HideWorld隐藏世界和SnapshotWorld截图冻结背景。窗口系统据此统一调度相机和截图资源。策略可以按窗口是否遮满世界画面分流全屏页底部 3D 已经完全不可见直接隐藏并停掉底层相机即可不需要截图非全屏弹窗仍要露出底部画面、且弹窗会停留一段时间时才截取一帧作为静态背景再停掉底层相机。这样弹窗停留期间背景不再提交渲染若世界逻辑仍在推进关闭弹窗时画面会从截图直接跳到当前状态小提示或短时浮层先看它自身的绘制成本和停留时长。它通常不需要冻结背景截图的帧末读取与纹理分配反而可能更贵此时归为KeepWorld即可。所以决策不只取决于“是不是全屏”还要依次问玩家是否需要看见底层内容弹窗本身和底层世界谁更贵它是否会停留到足以摊平一次截图的成本以《大富翁 GO》为例打开某些非全屏弹窗后底下的 3D 角色看起来完全不动过一会儿关闭弹窗原本在某处的角色会直接跳到此时的位置。仅从这一画面行为推测它很可能采用了“静态截图覆盖 底层继续更新或状态推进”的做法。这个观察案例也说明非全屏弹窗并不一定要让底层 3D 持续渲染。下面是一个独立的 Unity C# 示例。它只演示“全屏页直接停相机、非全屏弹窗截图后停相机最后一个窗口关闭后恢复”的资源所有权窗口如何创建、显示和销毁由上层 UI 系统负责。usingSystem.Collections;usingUnityEngine;usingUnityEngine.UI;publicenumWorldRenderPolicy{KeepWorld,HideWorld,SnapshotWorld}publicsealedclassWorldRenderCover:MonoBehaviour{[SerializeField]privateCameraworldCamera;[SerializeField]privateRawImagesnapshotImage;privateintcoverCount;privateintcaptureToken;privateTexture2Dsnapshot;publicvoidOpen(WorldRenderPolicypolicy){if(policyWorldRenderPolicy.KeepWorld)return;if(coverCount!1)return;if(policyWorldRenderPolicy.HideWorld)worldCamera.enabledfalse;elseStartCoroutine(CaptureThenPause(captureToken));}publicvoidClose(WorldRenderPolicypolicy){if(policyWorldRenderPolicy.KeepWorld)return;if(coverCount0||--coverCount!0)return;captureToken;// 关闭早于帧末截图时阻止旧协程再次关相机。snapshotImage.enabledfalse;snapshotImage.texturenull;Destroy(snapshot);snapshotnull;worldCamera.enabledtrue;}privateIEnumeratorCaptureThenPause(inttoken){yieldreturnnewWaitForEndOfFrame();if(thisnull||token!captureToken||coverCount0)yieldbreak;snapshotScreenCapture.CaptureScreenshotAsTexture();snapshotImage.texturesnapshot;snapshotImage.enabledtrue;worldCamera.enabledfalse;}privatevoidOnDestroy()Destroy(snapshot);}截图只在停留足够久的窗口触发离开后立刻释放截图纹理。多个窗口叠加要用计数避免一个窗口关掉就把底层渲染错误恢复。不是所有弹窗都适合截图一闪而过的提示截图当帧的ReadPixels和内存峰值可能比继续画 3D 更亏。UI 也能沿用同一思路当上层窗口完全遮住、且不再需要显示或交互底层 UI 时可以停掉底层 Canvas仍需露出或交互的部分则保留。但 UI 长期多层重叠会同时增加 DrawCall、Overdraw 和输入管理成本默认应从交互与界面设计上避免堆叠过多窗口而不是依赖隐藏策略兜底。从异常数字回到具体对象Stats只能告诉你“这一帧变贵了”不能告诉你是谁让它变贵。排查时不要一上来把所有合批开关轮流切一遍用同一帧的 Frame Debugger 把数字落到具体的 Camera、Pass、材质和 Renderer路径会短很多。工具的选择、真机抓帧方式以及 UPR / UWA 的报告与付费边界见《Unity 性能优化工具与数据采集》。本篇只讨论渲染问题本身如何定位和处理。先固定复现镜头。选一个能稳定复现的操作点记录相机位置、质量档、窗口状态和特效状态。没有固定镜头前后两次的可见集不同数字没有可比性。按渲染阶段找异常段。在 Frame Debugger 里先区分是不透明、透明、阴影还是后处理段突然变长如果多了一整段全屏 Pass先回查相机栈、Renderer 的后处理数据、Renderer Feature、Volume以及深度/不透明纹理的实际使用者而不是先看小物件。比较相邻 draw 的状态。在预期能连续合批的位置逐项比对材质、shader variant、关键字、贴图、Lightmap、排序和每物体参数。第一个不同项通常就是拆批原因如果状态相同仍没合上再确认该 shader 是否真的兼容 SRP Batcher 或 Instancing。只做一个可逆修改再复测。例如把一组材质改为共享材质、关闭一个 Volume 覆盖项或暂时隐藏一层粒子。先在 Frame Debugger 证明异常 Pass 或拆批消失再到目标真机确认 CPU / GPU 帧时间的收益只有两个证据都成立才把修改固化为资源或场景约定。这个顺序也能防止“看起来省了 Batches实际只是把画面或可见集一起改没了”。每次记录至少保留复现步骤、改动项、Editor 的 DrawCall / Batches / SetPass以及真机的 CPU / GPU 帧时间和测试时长不需要写入具体业务名称或资源名称。验证与回归方法同一场景、同一操作路径、同一设备做 A/B一次只改一类动作少一台相机、关一项后处理、开/关动态合批Editor 用 Frame Debugger 和 Stats 采集 DrawCall / Batches / SetPass确认 Pass 和合批行为符合预期真机采集 CPU / GPU Frame Time、95/99 分位必要时拆 GPU 模块动态合批、后处理这类“可能更贵”的开关目标档位机型上各做一次开/关用同一条关卡流向覆盖场景加载 → 普通游走 → UI 高交互 → 特效叠加 → 全屏弹窗。只在静止场景里测出来的优化上线后经常不成立连续跑 1015 分钟看温度和掉频。只录开头 30 秒平均帧率会骗人。其他应该注意的点没有场景提交量基线开发期完全不看 EditorStats等问题堆到打包才发现 DrawCall 涨了一截把 UWA / UPR 的区间当成项目 KPI或反过来只认 Editor 数字、不上真机看时间和带宽只看 DrawCall忽略 SetPass 或 GPU fill。Batches 降了、半透明层还在低端机照样烫动态合批盲开小物体场景之外 Batches 降了CPU 更忙SRP Batcher 迷信材质不兼容、变体一堆开关开了 Frame Debugger 里根本没有SRP BatchMaterialPropertyBlock改色把能走 SRP Batcher 的物体整批拆掉多挂 Overlay 相机做 UI / 预览Batches 没降剔除和全屏 Pass 先翻倍空相机也有成本Unity 官方测过“什么都不画”仍然吃Camera.Render全机型同一套后处理低档机被全屏 Pass 打满Opaque Texture或Depth Texture被打开却没有明确使用者网格合得太大视锥和遮挡都剔不掉远处仍在画VBO 先涨UI 只做图集没控制 Canvas 重建范围移出屏幕的元素还在提交截图优化没释放 RT弹窗路径上出现隐性内存峰值。总结可迁移的原则就三条先看真机卡在 CPU 还是 GPU再动手。开发期用 DrawCall、Batches、SetPass Calls 挡住提交量上涨金标准是目标机型上的 CPU Time、GPU Time 和带宽。UWA / UPR 给的是行业水位不是项目 KPI先减要画的再减提交。少加相机、收缩可见集、共享材质常规物体优先 SRP Batcher大量完全相同的重复组再 A/B Instancing动态合批最后考虑。静态合批单独核算内存和加载成本DrawCall 和 Overdraw 分开看一次只改一类动作。Frame Debugger 用来证实合批和 Pass 真的按预期发生结论以真机 Release 包为准连续玩一段时间后的分位和温度也要进验收。