# UIEffect 降 DC 改动总结 > 提交:`fa5f0cdb 降低DC的操作`(2026-08-26) > 涉及目录:`unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/` ## 一、背景与目标 UI_Effect 插件原本每个 `UIEffect` 组件都会生成**独立的材质实例**(缓存 key 使用 `effectId`,即组件的 `GetInstanceID()`,每个实例唯一),导致: - 相同配置的特效无法共享材质 → 无法参与动态合批 → **Draw Call 偏高**; - 批量刷 UI 时材质创建开销大,伴随帧率波动。 目标:让**相同配置的 UIEffect 共享同一材质**,从而参与合批,降低 DC。 ## 二、方案选型 | 方案 | 说明 | 结论 | | --- | --- | --- | | UIEffectReplica + Preset | 业务代码逐个改为副本组件,指向共享 preset | 需要改动大量业务文件、创建 preset 资产,侵入性大 | | **configId 配置指纹(最终采用)** | 在插件内部按"配置内容哈希"共享材质 | **业务代码零改动**,同配置自动共享合批 | ## 三、具体改动 ### 1. `UIEffectContext.cs`(新增约 121 行) 核心:新增 `configId` 属性——特效配置的"稳定指纹"。 - **反射字段缓存**:`s_ConfigFields` 为 `static readonly FieldInfo[]`,`GetFields` 只在类型加载时执行一次,运行时无反射类型发现开销,避免批量刷新时的 GC 尖峰(这是此前"帧率不稳"问题的根因与修复点)。 - **内容哈希 `HashValue`**: - `null` → 0; - `UnityEngine.Object` → `GetInstanceID()`(引用同一资产即同哈希); - `Gradient` → `GradientHash` 按 colorKeys / alphaKeys 逐 key 做内容哈希(不同实例但内容相同的渐变得到相同指纹); - 基元/枚举 → `GetHashCode()`;嵌套值类型(Color、Vector、MinMax01 等)→ `HashFields` 逐字段展开(`s_FieldCache` 按类型缓存)。 - **雪崩混淆**:FNV-1a 混合后追加 MurmurHash3 终混,让相近配置散列开,降低哈希碰撞概率。 ### 2. `UIEffectBase.cs`(`GetModifiedMaterial`) 材质缓存 key 由 `effectId` 改为配置指纹: ```csharp var configId = context.configId; var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u; var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId); ``` 要点: - `rootId` **仅在特效需要视图矩阵时**(transition / gradation / detail / shiny)才参与哈希,普通灰度(ToneFilter)等不受 root 影响,可共享范围更大; - `Hash128(baseMaterial, configId, samplingScaleId, rootId)` 相同 → 命中 `MaterialRepository` 中同一材质实例 → 参与动态合批。 ### 3. `ShaderVariantRegistry.cs`(Profiler 采样修复) `RegisterVariant` / `GetVariant` 的**所有提前 return 分支**均补上 `Profiler.EndSample()`,保证 `BeginSample`/`EndSample` 严格配对,修复控制台的: ``` Non-matching Profiler.EndSample (Every EndSample call must have a preceding BeginSample call...) ``` ## 四、业务侧影响 - **业务文件零改动**:`EquipBuffItem`、`MapBlockItem`、`PetItem`、`V_RechargeItem`、`FightLinkHeroItem`、`AvatarItem` 等继续正常使用 `UIEffect` 控制灰度,无需迁移到 `UIEffectReplica`。 - 同屏所有**同配置**灰度特效(如大量 `ToneFilter.Grayscale` 的图标)自动共享一个材质并合批。 ## 五、效果 - N 个相同配置的 UIEffect:N 个材质实例 → **1 个共享材质**,可被 UGUI 动态合批; - DC 显著下降(受益程度取决于面板内同配置特效数量)。 ## 六、注意事项与局限 1. **配置不同的特效无法互相合批**——每种不同配置组合仍各占一个材质/DC,属预期行为; 2. `configId` 在画布重建时计算,字段遍历存在少量装箱开销,已通过静态缓存降到最低(普通 UI 不会每帧触发); 3. 新增 `UIEffectContext` 公共字段会自动纳入指纹(遍历 public instance 字段),**无需手动维护字段清单**; 4. 验证方式:打开 Frame Debugger / Profiler,确认同配置 UI 元素合并为同一批次;确认控制台不再出现 `Non-matching Profiler.EndSample`。