Appearance
反射的统一
背景:水面反射和地面反射用两套互斥机制,双双失效。ADR-092 决策统一为单一纹理工厂 + 单一平面反射引擎。 过程:定义 TextureFactory 类 → 统一 canvas/外部贴图创建 → 统一 RenderTargetTexture 反射 → 消除互斥逻辑。
序
七月十二日,晚上。
Riku 站在屏幕前,看着环境子系统的代码。
水面反射:RenderTargetTexture + 手写 _mirrorCam(_worldMatrix 黑魔法)。
地面反射:MirrorTexture(靠 mirrorPlane 推导)。
两套机制,互斥——地面启用反射时销毁水面反射,但关闭地面反射时不恢复水面。
"两套反射,双双失效," Riku 对自己说。
问题
ADR-092 的文档打开着,标题是「贴图与反射统一 —— 单一纹理工厂 + 单一平面反射引擎」。
问题清单:
| # | 问题 | 证据 |
|---|---|---|
| 1 | 贴图创建散点(11 处) | 无统一工厂,重复模式 + 泄漏风险 |
| 2 | 地面 MirrorPlane 未经验证 | 符号/朝向可能错误 |
| 3 | 地面双重驱动 | 引擎自动渲染 + 手动 .render() → 反射错乱 |
| 4 | 水面互斥单向 | 地面反射销毁水面反射,但关闭地面反射不恢复水面 |
| 5 | 水面 _mirrorCam 脆弱 | 主相机切换/FOV 变化时不更新 |
"11 处贴图创建," Jieling 看着屏幕说,"没有统一管理?"
"对," Riku 说,"每处都是 document.createElement('canvas') → getContext('2d') → draw → toDataURL → new Texture。重复模式,没有统一释放。"
纹理工厂
Riku 在 src/scene/env/ 下新建了 texture-factory.ts。
typescript
class TextureFactory {
private textures: Map<string, Texture>;
constructor(scene: Scene) {
this.scene = scene;
this.textures = new Map();
}
createCanvasTexture(
key: string,
width: number,
height: number,
drawFn: (ctx: CanvasRenderingContext2D) => void
): Texture {
// 复用已有纹理
if (this.textures.has(key)) {
return this.textures.get(key)!;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d')!;
drawFn(ctx);
const texture = new Texture(canvas.toDataURL(), this.scene);
this.textures.set(key, texture);
return texture;
}
dispose(key: string): void {
const texture = this.textures.get(key);
if (texture) {
texture.dispose();
this.textures.delete(key);
}
}
disposeAll(): void {
for (const texture of this.textures.values()) {
texture.dispose();
}
this.textures.clear();
}
}"统一创建,统一释放," Riku 说,"不再有散点。"
"释放登记呢?" Jieling 问。
"在 disposeAll() 里," Riku 说,"场景销毁时调用 textureFactory.disposeAll(),确保所有纹理被释放。"
反射引擎
统一反射引擎更复杂。
Riku 的方案:水面和地面都用 RenderTargetTexture + 同一个 _mirrorCam。
typescript
class PlanarReflectionEngine {
private renderTarget: RenderTargetTexture;
private mirrorCam: FreeCamera;
constructor(scene: Scene, mirrorPlane: Plane) {
this.mirrorCam = new FreeCamera('mirrorCam', Vector3.Zero(), scene);
this.renderTarget = new RenderTargetTexture('reflection', 1024, scene);
// 镜像相机设置
this.mirrorCam.position = mirrorPlane.normal.scale(-1);
this.mirrorCam.setTarget(Vector3.Zero());
}
update(mainCamera: Camera): void {
// 同步主相机到镜像相机
this.mirrorCam.position = mainCamera.position.clone();
this.mirrorCam.setTarget(mainCamera.getTarget());
// 镜像变换
this.mirrorCam.position.y = -this.mirrorCam.position.y;
}
render(): void {
this.renderTarget.render();
}
}"一套引擎," Riku 说,"水面和地面共用。不再互斥。"
"互斥逻辑删了?" Jieling 问。
"删了," Riku 说,"现在水面和地面可以同时有反射。"
尾声
Riku 提交了代码。
feat: 纹理工厂(texture-factory.ts)— 统一贴图创建与释放
feat: 平面反射引擎统一(ADR-092)— 水面/地面共用单一引擎他站在窗边,看着远处的天际线。七月的阳光已经落下去了,路灯一盏一盏亮起来。
反射从两套变成了一套。互斥消失了,水面和地面可以同时闪耀。
"反射是城市的镜子," Riku 对自己说,"镜子不能互相打架。"
教训:两套互斥机制不是设计,是债务。统一为一套引擎,消除互斥逻辑,让水面和地面共存——这是最简单的方案,也是唯一正确的方案。