Skip to content

ADR-074: 动态 Cubemap RT 球面反射

状态: 🗑️ 已放弃(被 ADR-092 取代;2026-07-19,stash 内容已清理。Stash 中的 cubemap 代码与当前 HEAD 架构冲突——水反射已改用统一 PlanarReflection 引擎(ADR-092),model-loader 传参方式也不同(二进制 vs URL)。材料诊断工具等附属代码一并移除。后续若重新评估此方案,需基于当前 HEAD 重写。) 日期: 2026-07-19 关联: ADR-062(水面反射 RT 系统·P3 路线)、ADR-024(ReflectionProbe 环境反射)、ADR-026(环境系统增强) 前置: ADR-062 P1(planar RT)已实施(2026-07-10),P2(波浪 UV 偏移 + 泡沫衰减)待实施


一、背景

1.1 现状

ADR-062 P1 已实施(2026-07-10)planar RT 反射系统(env-water.ts),水面可反射场景几何体(PMX 模型等)。但存在两类反射盲区:

反射场景当前方案问题
金属球/柱/曲面ReflectionProbe 静态 cubemap(refreshRate=0仅反射环境(天空/地面),不含模型本体;模型移动/灯光变化后反射不更新
水面 planar RT2D RT + mirror camera仅适用于平面,曲面物体采样 2D 纹理会变形拉伸

1.2 目标

引入动态 cubemap RT,为金属/曲面物体提供实时环境反射(含模型本体),同时保持与现有 planar RT 和静态 ReflectionProbe 的共存。

1.3 与现有系统的关系

ReflectionProbe(renderer.ts)     → 静态 cubemap,仅环境(天空/地面),refreshRate=0
Planar RT(env-water.ts)          → 2D RT,水面平面反射,refreshRate=按需
Cubemap RT(P3 新增,本 ADR)      → 动态 cubemap,金属/曲面物体反射,refreshRate=可调

三者独立生命周期,不共享状态。水面仍用 planar RT(平面场景 planar 更准确),cubemap RT 服务于非平面材质。


二、技术方案

2.1 核心思路:Babylon ReflectionProbe 动态化

复用 Babylon.js ReflectionProbe 类(已封装 cubemap 6-face 渲染),通过调整 refreshRate 实现动态刷新:

typescript
// 现有静态模式(renderer.ts)
_probe = new ReflectionProbe('envProbe', 256, scene);
_probe.refreshRate = 0;  // 仅渲染一次

// P3 动态模式
_probe.refreshRate = 1;  // 每帧刷新(高画质)
_probe.refreshRate = 2;  // 每 2 帧刷新(中画质)
_probe.refreshRate = 4;  // 每 4 帧刷新(低画质)

选择路径 B(ReflectionProbe 动态化)而非路径 A(手写 6-face RT)的理由

  1. ReflectionProbe 已在 renderer.ts 中验证可用,动态化是参数调整
  2. Babylon 内部处理 6-face 渲染 + cubemap 合成 + seam 修复,手写需重做这些
  3. 手写 6-face 的收益仅在极端优化场景(如"仅更新水平 4 面"),初期不值得

2.2 与现有 ReflectionProbe 的区分

现有 _reflectionProbe(renderer.ts)是环境探针,服务于所有模型的材质反射。P3 新增的是场景探针,额外包含模型本体。

维度环境探针(现有)场景探针(P3 新增)
renderListsky / env / ground / water+ 模型 meshes
refreshRate0(静态)1-4(动态)
绑定目标所有 StandardMaterial含 sphere map / PBR metallic 的材质
显存256³ × 4B = 1MB256³ × 4B = 1MB(共用或独立)

决策:扩展现有 _reflectionProbe,不新建第二个 probe

理由:

  • 同一场景不需要两套 cubemap(环境探针和场景探针内容高度重叠)
  • 将 renderList 从"仅环境"扩展为"环境 + 模型",按需切换 refreshRate
  • 减少显存占用(复用同一 cubemap RT)

2.3 动态刷新策略

typescript
// renderer.ts 扩展
interface CubemapRefreshPolicy {
    mode: 'static' | 'dynamic';
    refreshRate: number;    // 1=每帧, 2=每2帧, 4=每4帧
    resolution: number;     // 单面分辨率: 64/128/256
}

let _cubemapPolicy: CubemapRefreshPolicy = {
    mode: 'static',
    refreshRate: 0,
    resolution: 256,
};

触发规则

  • 静态模式(mode=static):refreshRate=0,仅初始化时渲染一次(当前行为)
  • 动态模式(mode=dynamic):refreshRate=N,每 N 帧刷新 cubemap
  • 交互时升频:用户旋转/移动相机或模型时临时 refreshRate=1,静止后降回 refreshRate=4

2.4 renderList 扩展

现有 renderList 过滤规则:

typescript
// 当前:仅环境
_probe.renderList = scene.meshes.filter(m =>
    m.name.includes('sky') || m.name.includes('env') ||
    m.name.includes('ground') || m.name.includes('water')
);

P3 扩展为包含模型:

typescript
// P3:环境 + 模型
_probe.renderList = scene.meshes.filter(m =>
    m.isVisible && !m.name.startsWith('envWater')  // 排除水面自身
);

注意:模型 meshes 加入 renderList 后,每次 cubemap 刷新需额外渲染所有模型,GPU 负载随模型数量线性增长。需配合性能分级控制。


三、性能分级

3.1 Cubemap 专属分级

模式单面分辨率刷新率显存(6面)GPU 负载适用场景
256×256每帧6MB6× 场景渲染桌面端近景 + 多金属物体
128×128每 2 帧1.5MB3× 场景渲染桌面端远景 / 中端设备
64×64每 4 帧0.375MB1.5× 场景渲染移动端 / Android
00性能模式 / 无金属物体

3.2 与 Planar RT 分级的协调

水面 planar RT 和 cubemap RT 独立分级,互不干扰:

设备Planar RT(水面)Cubemap RT(金属)总额外显存
高端桌面512×512 每帧256×256 每帧5MB
中端桌面256×256 每 2 帧128×128 每 2 帧1.5MB
移动端128×128 每 4 帧64×64 每 4 帧0.5MB

四、材质绑定策略

4.1 自动检测

模型加载时检测材质属性,决定是否绑定 cubemap RT:

typescript
function _needsCubemapReflection(mat: Material): boolean {
    if (!(mat instanceof StandardMaterial)) return false;
    // 含 sphere map 的材质已有球面反射,cubemap 可增强
    if ((mat as MmdStandardMaterial).sphereTexture) return true;
    // PBR 材质的 metallic 通道需要环境反射
    if ('metallic' in mat && (mat as any).metallic > 0.3) return true;
    // 用户显式设置了 reflectionTexture 的材质
    if (mat.reflectionTexture) return true;
    return false;
}

4.2 与现有绑定逻辑的整合

复用 bindReflectionProbeToModel(renderer.ts:819),扩展过滤规则:

typescript
export function bindReflectionProbeToModel(meshes: Mesh[]): void {
    if (!_reflectionProbe) return;
    const rt = _reflectionProbe.cubeTexture;
    for (const mesh of meshes) {
        const m = mesh.material;
        if (m && _needsCubemapReflection(m)) {
            (m as any).reflectionTexture = rt;
        }
    }
}

4.3 与 Planar RT 的不冲突

材质类型反射源说明
水面 ShaderMaterialplanar RT(2D sampler)env-water.ts 独立管理
模型 StandardMaterial(含 sphere/PBR)cubemap RT(CubeTexture)renderer.ts 管理
模型 StandardMaterial(无反射需求)不绑定

两者采样不同纹理类型(2D vs Cube),shader 路径独立,无冲突。


五、实施计划

Step 1:POC 验证(0.5 天)

  • 将现有 _reflectionProbe.refreshRate 从 0 改为 1(动态模式)
  • 绑定到测试场景中的金属球材质
  • 验证项:
    • 模型移动时 cubemap 是否实时更新
    • 帧率影响(baseline vs 动态 cubemap)
    • cubemap seam 是否可见
    • 显存占用变化

Step 2:CubemapRefreshPolicy 模块(1-2 天)

  • renderer.ts 中新增 CubemapRefreshPolicy 接口和管理逻辑
  • 实现 setCubemapMode(mode, refreshRate, resolution) API
  • 扩展 renderList 过滤规则(环境 + 模型)
  • 确保 dispose 路径完整(关闭动态模式时恢复 refreshRate=0

Step 3:材质自动绑定(1 天)

  • 实现 _needsCubemapReflection 检测函数
  • 扩展 bindReflectionProbeToModel 逻辑
  • 模型加载/卸载时自动更新 cubemap renderList
  • 确保与 planar RT 无冲突

Step 4:性能分级 + UI(0.5 天)

  • scene-render-levels.tssettings-performance.ts 增加 cubemap 分级选项
  • 与现有 reflectionProbeEnabled 整合(复用开关,新增子选项)
  • 移动端默认低档或关闭
  • 交互时自动升频逻辑(可选,Phase 2)

Step 5:测试 + 收尾(0.5 天)

  • 补充单元测试(refreshRate 切换、dispose、分辨率变更、renderList 更新)
  • 更新 ADR-062 状态,标记 P3 已实施
  • 全平台验证(桌面 + Android)

六、风险与缓解

风险等级缓解
动态 cubemap 帧率暴跌默认 refreshRate=2(每 2 帧),仅交互时升为 1;复杂场景(10+ 模型)强制 refreshRate=4
6 面 RT 显存溢出(Android)移动端默认 64×64 或关闭;总显存预算硬限 2MB
ReflectionProbe 内部不支持动态POC 先验证;若不行,退回路径 A 手写 6-face RT
模型加入 renderList 后 cubemap 刷新变慢renderList 按距离裁剪(仅渲染摄像机 50 单位内的模型)
cubemap seam(面交界处接缝)Babylon ReflectionProbe 内部已处理;POC 目测验证
与 planar RT 双 RT 系统协调两者独立生命周期,不共享状态

七、决策对比

方案描述优点缺点
A. 手写 6-face RT创建 6 张 RT + 6 个方向相机 + 手动合成 CubeTexture完全可控,可按面单独优化实现量大(~200 行),需处理 seam/翻转
B. ReflectionProbe 动态化(本 ADR)复用 Babylon ReflectionProbe,改 refreshRate实现量小(~50 行),Babylon 内部处理 6-face控制粒度低,refreshRate=1 性能代价高
C. 暂不实施保持静态 cubemap,金属物体反射不更新零成本金属/曲面物体反射静态,视觉缺陷

选 B。理由:P3 的核心诉求是"金属物体反射随场景更新",ReflectionProbe 动态化以最小实现量满足需求。手写 6-face 留作后续优化路径(当需要面级控制时从 B 渐进到 A)。


八、与现有 ADR 的关系

  • ADR-062 P1(planar RT):已实施(2026-07-10),P3 不改动。水面仍用 planar RT。
  • ADR-062 P3(本 ADR):细化 cubemap RT 方案,填补 ADR-062 遗留的"球面反射"缺口。
  • ADR-024(ReflectionProbe):现有静态探针扩展为动态,renderList 从"仅环境"扩展为"环境 + 模型"。
  • ADR-055(AR 相机模式):AR 模式下 cubemap RT 可增强虚拟物体与真实环境的融合感(反射真实环境),但非 P3 范围。

九、验证方式

  1. POC:empty scene + 金属球 + 动态模型,旋转相机确认 cubemap 实时更新且无 seam
  2. 帧率:对比 baseline / 中档 / 低档的 FPS 差异,确认移动端可接受
  3. 显存:监控 gl.getParameter(gl.RENDERBUFFER_WIDTH) 或 Babylon engine._gl.getExtension('WEBGL_lose_context') 确认 RT 显存不超预算
  4. 回归npm run check && npm run test && npm run build 全绿