渲染合批 · UrhoX 官方 API / 引擎能力建议
@优化 — 面向 UrhoX / TapMaker 官方的引擎能力诉求:建议提供什么、为何需要提供。
依据:官方 API 索引、Minecraft 体素示例、DualGrid 类项目的常见实践,以及对 Unity / Godot / Cocos 等引擎合批能力的对标。
1. 背景与问题
1.1 典型性能瓶颈
在 2D 正交 / 伪 3D 地图 类游戏中,常见场景包括:大量序列帧精灵、瓦片地图、装饰物、弹丸与短生命周期特效、头顶血条等。此类项目往往 三角数不高,但 GPU batch(draw call)极高,导致帧率骤降。
根因通常不是几何过于复杂,而是:
- Drawable 数量过多(每个小物体一个 StaticModel 或等价物)
- Material 实例过多(每个物体 Material:new() 或 Clone,仅微调颜色 / UV)
- 贴图与 Technique 状态频繁切换(每帧换图、每对象不同 shader 参数)
- 透明物体排序与合批条件冲突
1.2 UrhoX 现有能力
引擎已具备合批相关的底层积木:
| 已有能力 | 说明 |
| CustomGeometry | Minecraft 示例将整块体素合并为少量几何提交,是迄今最明确的合批范例 |
| StaticModelGroup | API 索引已列出,但缺少 Lua 使用说明与示例 |
| Instancing 相关枚举 | GEOM_STATIC、GEOM_INSTANCED、GEOM_STATIC_NOINSTANCING 等 |
| 纹理图集实践 | Minecraft 示例中的 TextureAtlas 及 mipmap 注意事项 |
| DiffAlpha / 正交 Box sprite | 大量 2D 项目的基础渲染路径 |
1.3 缺口
| 缺口 | 后果 |
| 无 2D / Billboard 精灵专用批渲染组件 | 每个项目重复实现 Minecraft 式 CustomGeometry 合批,成本高、易错 |
| StaticModelGroup / Instancing 无 Lua 教程 | 开发者退回「一物体一 StaticModel」 |
| Material 合批边界未文档化 | 误以为共用 Technique 即可合批,忽略 Material 实例与 per-object shader 参数 |
| 无构建期静态合批、无受限动态合批 | 静态场景与遗留代码难以低成本优化 |
| 性能统计不完整 | 仅有 batch 总数,难以定位「材质爆炸」或 Drawable 过多 |
| 图集工具链缺失 | 序列帧、小贴图分散,迫使运行时频繁换纹理 |
结论:底层能合批,但 Lua 应用层缺少一等公民 API 与规范;示例与脚手架若继续强化「单物体 + 单材质」,会系统性放大 batch 问题。
2. 建议总览
| 优先级 | 建议能力 | 为何需要 |
| P0 | SpriteBatch(或等价 Quad 批渲染组件) | 解决大量动态 2D 精灵的核心瓶颈 |
| P0 | StaticModelGroup / GPU Instancing 的 Lua 文档与示例 | 释放已有底层能力,覆盖同款静态/半静态物体 |
| P0 | 共享材质约定与 MaterialCache | 遏制 Material 实例爆炸 |
| P0 | 纹理图集资源类型 + 打包工具 | 减少纹理切换,支撑精灵合批 |
| P1 | 瓦片 / 地块 Chunk 合批组件 | 地图层「一格一 Drawable」的结构性问题 |
| P1 | CustomGeometry 易用封装(如 MeshBuilder) | 降低手动合批门槛,延续 Minecraft 范例 |
| P1 | 构建期静态合批 | 不变场景的大幅度 batch 下降 |
| P2 | 可选运行时动态合批 | 遗留项目、小 mesh 的低成本兜底 |
| P2 | 扩展性能统计与调试告警 | 可观测、可回归 |
| P2 | 后处理分项耗时、NanoVG 绘制统计 | 区分渲染与后效、UI 的开销 |
3. P0 — 精灵批渲染组件(SpriteBatch)
3.1 建议提供什么
面向正交 / 伪 3D 场景的 批量四边形渲染组件(名称可为 SpriteBatch、QuadBatchRenderer 等),支持:
- 单次配置:Technique、主纹理(或纹理数组)、混合模式、过滤方式、实例上限
- 批量提交:世界变换、缩放、翻转、旋转(可选)
- 每实例:UV 矩形(图集帧)、颜色乘子(受击闪白、淡出)、排序键(Y-Sort 或显式 priority)
- 提交后由引擎完成:按渲染状态分桶、排序、合并 draw call
- 可选:距离剔除、视口剔除、与逻辑实体解耦(无 Node 亦可驱动显示)
并配套:
- 可运行示例:百级实例动画,对比「批渲染 vs 逐对象 StaticModel」的 batch 与帧率
- recipe 文档:合批条件、透明排序规则、与 RenderOrder / Octree 的关系
3.2 为什么要提供
| 原因 | 说明 |
| 需求普遍 | 幸存者、塔防、生态模拟、ARPG 小怪等均为「大量同类型 quad」 |
| 现有路径过重 | 逐对象 StaticModel + Material:new() 使 batch 下限等于对象数 |
| CustomGeometry 门槛高 | Minecraft 范例正确但 Lua 手写顶点成本高,中小团队不敢用 |
| 业界标配 | Godot MultiMeshInstance2D、Unity SpriteRenderer 批量路径、Cocos BatchNode 均已成熟 |
| 与现有管线契合 | DiffAlpha / DiffUnlitAlpha + Box 模型已是官方推荐 2D 路径,缺的是提交层 |
3.3 引擎实现选型(供官方内部参考)
可在文档中说明倾向,无需暴露给游戏方细节:
- 短期:CustomGeometry 内部封装 + GPU buffer 复用
- 长期:GPU Instancing(unit quad + per-instance UV/色/矩阵)
对 Lua 应暴露 同一套组件 API,避免实现更迭时迫使项目重写。
3.4 必须文档化的合批规则
- 同一 batch 内:相同 Technique、相同 Texture 绑定、相同 blend
- 透明物体:同一纹理内的 排序方向(按 Y 或按 priority)需有 唯一默认语义
- 不同 RenderOrder 是否拆 pass、是否可在 batch 内用 instance 字段表达
- 像素风图集:NEAREST 过滤、mipmap 禁用 的建议(Minecraft 示例已有经验)
4. P0 — StaticModelGroup / GPU Instancing
4.1 建议提供什么
- engine-docs/api/graphics.md 中 完整章节:创建、绑定单一 Model + Material、增删改实例变换
- per-instance 扩展数据(如 UV 矩形、颜色乘子)的说明与上限
- 可运行 Lua 示例:百个相同 Box.mdl(或简单道具模型),验证 batch ≈ 1
- 明确 不适用场景:骨骼动画、每实例不同 Model、每实例独立 Material
4.2 为什么要提供
| 原因 | 说明 |
| 能力已存在但未可达 | 枚举与类型已索引,开发者无法发现 |
| 适合高重复静态物 | 箭矢、石头、树木、建筑构件、草地 impostor |
| 与 SpriteBatch 分工 | 同款 网格资源 用 Instancing; 平面序列帧 用 SpriteBatch |
| 降低 CPU 提交压力 | 大量相同 transform 仅更新实例缓冲,而非场景图遍历 |
4.3 特别说明:序列帧动画
每帧 UV 不同的动画精灵,应通过 Instancing 的 per-instance UV 或 SpriteBatch,而非每帧一个 Drawable。官方文档应明确这一选型,避免误用。
5. P0 — 材质共享与 MaterialCache
5.1 建议提供什么
- 官方约定:何种情况可共享 Material,何种情况 Material:new() / Clone() 必然破坏合批
- 材质缓存 API(或推荐模式):
- 按资源路径获取单例材质
- 按 Technique + Texture 获取共享材质
- 纯色材质按量化颜色缓存(血条、调试线、简单 FX 常见需求)
- 开发构建下可选:单帧 Material 创建次数 统计或告警
5.2 为什么要提供
| 原因 | 说明 |
| 最隐蔽的 batch 杀手 | 相同 Technique、相同贴图,却因 Material 实例不同而无法合并 |
| 教程易误导 | 示例常为每对象 SetShaderParameter 配新 Material |
| 纯色物体极多 | HP 条、范围圈、Gizmo 等若各 new() 一个,数量线性膨胀 |
| 与 Instancing / SpriteBatch 互补 | 批渲染组件的前提是 材质与纹理状态可共享 |
5.3 建议写入文档的反模式
- 仅为改色而 Material:new()
- 多部件角色「一部件一材质一 Drawable」且无图集规划
- 混用 lit / unlit Technique 导致同屏无法合批
6. P0 — 纹理图集与 TextureAtlas 资源
6.1 建议提供什么
- 构建期工具(CLI 或编辑器):多张小图 → 单张图集 + UV 元数据(JSON 或引擎资源)
- 参数:padding、是否生成 mipmap、过滤模式
- 运行时 TextureAtlas 资源类型:按逻辑名查询 UV 矩形,供 SpriteBatch / Instancing 使用
- recipe:像素风禁用 mipmap、padding 防串色(Minecraft 示例经验应上升为通用规范)
6.2 为什么要提供
| 原因 | 说明 |
| 合批前提 | 不同小图若各绑一次 TU_DIFFUSE,batch 按贴图数分裂 |
| 序列帧常态 | 行走/攻击多帧若各为独立 PNG,不换图集则无法批提交 |
| 每个项目重复造轮子 | 自写打包脚本与 UV 表,易与资源管线脱节 |
| 对标 | Unity Sprite Atlas、Cocos Auto Atlas、Godot AtlasTexture |
7. P1 — 瓦片 / 地块 Chunk 合批
7.1 建议提供什么
- 地块合并组件(如 TileChunkRenderer):将 N×M 格瓦片合并为少量 CustomGeometry 或烘焙纹理
- 支持:图集瓦片、脏区标记与局部重建、与正交相机视口裁剪配合
- 文档说明:静态底色烘焙(整层一张纹理)与 动态过渡层 chunk 合批 的组合策略
7.2 为什么要提供
| 原因 | 说明 |
| 瓦片游戏普遍 | 双网格、RPG 大地图、编辑器地形 |
| viewport pool 仍不够 | 可见区内「一格一 Drawable」在格数多时 batch 仍高 |
| 已有成功模式 | 底色烘焙至 1 draw call 证明 CPU 烘焙可行;缺的是官方抽象 |
| 对标 | Unity Tilemap、Cocos TiledLayer 合批 |
8. P1 — CustomGeometry 易用封装
8.1 建议提供什么
在保留底层 API 的同时,提供高层辅助(如 MeshBuilder):
- 声明图元类型(三角列表等)
- 追加标准 quad / tri,自带 UV、顶点色、法线
- 提交到 CustomGeometry 并 复用 GPU 缓冲
- 支持 预分配容量、脏区局部更新、上一帧顶点数统计
8.2 为什么要提供
| 原因 | 说明 |
| Minecraft 范例过底层 | 手写 DefineVertex 易错,阻碍推广 |
| 程序化内容常见 | 地形、条带、扇形、简单 UI 几何 |
| 过渡方案 | 在 SpriteBatch 未就绪前,仍可较低成本合批 |
9. P1 — 构建期静态合批
9.1 建议提供什么
- 构建或导入管线步骤:将 同 Technique、同 Material、同纹理状态 的多个静态 StaticModel 合并为单一网格或 StaticModelGroup 资产
- 可配置:按场景、按区域、按材质分组
- 碰撞与逻辑节点与渲染 mesh 分离,避免合并破坏玩法
9.2 为什么要提供
| 原因 | 说明 |
| 运行时零成本 | 合并一次,永久减 batch |
| 适合不变内容 | 建筑、边界装饰、固定地图道具 |
| 对标 | Unity Static Batching、Unreal Merge Actors |
| 与运行时合批分工 | 静态用构建期;动态用 SpriteBatch / 动态合批 |
9.3 不适用说明(文档应写明)
频繁移动、频繁换贴图、骨骼动画对象 不应 依赖静态合批。
10. P2 — 可选运行时动态合批
10.1 建议提供什么
- Renderer 级 opt-in 开关(默认关闭)
- 可配置:单 batch 最大顶点/索引数、允许的 Technique 白名单、是否禁止 per-object 唯一 shader 参数
- 开发模式:输出 本帧拒绝合批原因(材质不同、顶点超限等)
10.2 为什么要提供
| 原因 | 说明 |
| 遗留项目兜底 | 大量小 mesh 已存在,短期无法改为 SpriteBatch |
| 约束需透明 | Unity 等引擎动态合批规则复杂,文档化可避免错误预期 |
| 不应替代 SpriteBatch | 动态合批 CPU 成本与限制多,仅作补充 |
11. P2 — 性能统计与调试
11.1 建议提供什么
扩展帧统计接口,除 batch、三角数外,建议包含:
| 统计项 | 用途 |
| Drawable 总数 | 判断对象是否过多 |
| Material 实例数(或唯一 Material 数) | 定位材质爆炸 |
| 按 Technique / Texture 分组的 batch TOP N | 快速找热点 |
| StaticModel / CustomGeometry / Instancing 数量分项 | 判断架构是否合理 |
| 后处理各 pass 耗时(若可行) | 区分几何与 Bloom/FXAA 开销 |
| 3D 与 UI 渲染阶段的 batch 分界 | 与现有帧末统计对齐 |
开发构建可选告警:单帧 Material 创建过多、Drawable 超阈值、batch 高且 FPS 低。
11.2 为什么要提供
| 原因 | 说明 |
| 优化需可度量 | 无分项统计则只能猜 |
| 合批回归难发现 | 新功能加一个 Material:new() 即可打爆 batch |
| 降低支持成本 | 官方与项目方沟通时有共同指标 |
12. P2 — 场景图与剔除
12.1 建议提供什么
- 文档:「逻辑实体 + 批渲染」 范式——视觉不必一一对应 Node
- Batch 组件:视距 / 视锥剔除、可选逻辑 ID 与包围盒代理(供点击检测 broadphase)
- SpriteBatch 内 排序键 与 RenderOrder 的优先级说明,避免为 Y-Sort 强行拆 pass
12.2 为什么要提供
| 原因 | 说明 |
| Node 开销 | 百级对象 × 多层子节点,即使合批后场景图仍重 |
| 离屏仍在更新 | 无剔除时 CPU 浪费在不可见物体 |
| 2D Y-Sort 与合批冲突 | 需官方给出默认策略,否则各项目行为不一致 |
13. NanoVG / UI 与合批
13.1 建议提供什么
- 将现有 bloom 指南中的性能结论 纳入 UI 入门:每层 bloom 可能增加 draw call;单层渐变最高效
- 静态矢量 UI 烘焙为纹理 的官方推荐路径(若引擎可提供辅助 API 更佳)
- 若可统计:NanoVG 路径数 / 绘制调用数,纳入调试面板
13.2 为什么要提供
| 原因 | 说明 |
| HUD 与特效常混用 NanoVG | 几何 batch 优化后 UI 仍可能成为瓶颈 |
| 与 3D batch 分离困难 | 需帧内阶段统计才能判断 |
14. 业界对标(能力映射)
| 引擎 | 机制 | UrhoX 建议对应 |
| Unity | Static Batching | 构建期静态合批 |
| Unity | Dynamic Batching | opt-in 运行时动态合批 |
| Unity | GPU Instancing / SRP Batcher | StaticModelGroup + 共享 Material 规范 |
| Unity | Sprite Atlas | TextureAtlas + 打包工具 |
| Godot 4 | MultiMeshInstance2D | SpriteBatch |
| Cocos | Auto Atlas + BatchNode | 图集 CLI + SpriteBatch |
| Bevy | TextureAtlas + Sprite | TextureAtlas 资源 |
| Unreal | HISM / ISM | Instancing 文档与示例 |
| Unreal | Merge Actors | 构建期静态合批 |
UrhoX 优势在于 已有 CustomGeometry 与正交 sprite 管线;建议将 Minecraft 范例 升格为引擎级服务,而非留在每个项目中复制。
15. 建议交付路线图
M1(优先 — 解决最大共性痛点)
| 交付物 | 验收要点 |
| SpriteBatch(或等价)组件 + Lua 绑定 | 百级实例 batch 个位数 |
| 示例与 recipes/sprite-batching.md | 含反例、图集、mipmap、排序 |
| MaterialCache 或明确共享材质规范 | 文档 + 可选 API |
| renderer 扩展统计(Drawable / Material 计数) | 可对接调试工具 |
M2(图集与 Instancing)
| 交付物 |
| atlas-pack 工具 + TextureAtlas 资源类型 |
| StaticModelGroup 完整 Lua 章节与示例 |
| 瓦片 Chunk 合批组件或官方推荐 chunk 尺寸指南 |
M3(管线与高级)
| 交付物 |
| 构建期 batch-static 管线步骤 |
| opt-in 动态合批 |
| MeshBuilder 封装 |
| 后处理分项耗时 |
16. 待官方澄清的问题
- StaticModelGroup 在 Lua 中是否已完整绑定?若已绑定,计划何时提供示例?
- GEOM_INSTANCED 是否支持 per-instance UV、颜色?与 DiffAlpha 系列 Technique 的兼容性?
- CustomGeometry:Commit() 是否复用 GPU 缓冲?是否有性能剖析数据?
- 同一共享 Material 上调用 SetShaderParameter 是否会导致后续 draw 无法合批?请文档化 SRB / 常量缓冲行为。
- graphics:GetNumBatches() 与 renderer:GetNumBatches() 的语义差异及推荐使用场景?
- 是否考虑 2D 正交专用渲染路径(与通用 3D Octree 解耦)?
- 构建期是否可提供 场景 batch 预算报告(导入后预估 draw call)?
17. 相关参考(官方现有资料)
| 资料 | 内容 |
| examples/07-minecraft-voxel-world/world/ChunkMeshBuilder.lua | CustomGeometry 按 chunk 合并 |
| examples/07-minecraft-voxel-world/rendering/TextureAtlas.lua | 图集与 mipmap 注意 |
| engine-docs/api/graphics.md | StaticModelGroup、CustomGeometry 索引 |
| engine-docs/api/enums.md | Instancing 相关枚举 |
| engine-docs/recipes/nanovg_bloom_glow_guide.md | NanoVG bloom 与 draw call |
版本:v3.0 · 2026-07-02 · 面向官方:仅保留能力建议与动机,不含项目实现细节