fix(canvas): 3D 导演台 - 修复白膜视频导出渲染循环逐帧崩溃 (#439)

导出白膜视频得到的是 0 秒视频:文件实测仅 1 帧(ffprobe
nb_read_frames=1,duration=0.001s),却无任何报错并照常回写画布。

根因:applyClaySceneMaterials 把场景内所有非 actor mesh 的材质换成
clay MeshStandardMaterial,drei Grid 的 GridMaterial(带 uniforms 的
ShaderMaterial)也在其中。Grid 组件内部 useFrame 每帧读取
material.uniforms.worldCamProjPosition,材质被换后 uniforms 为
undefined,渲染循环逐帧抛 TypeError,R3F 不再产出新帧,
captureStream 只录到启动前手动 gl.render 的那一帧;5 秒后
setTimeout 照常 stop,残缺视频被当作成功结果。开启轨迹显示
(drei Line 同为 uniforms 材质)或视口白膜显示模式时同样触发。

修复:
- applyClaySceneMaterials 跳过带 uniforms 的 ShaderMaterial,
  并抽到 lib/canvas/director/director-clay-materials.ts 补回归测试;
- recordCanvas 加固:录制期间监听 window 未捕获错误,首个渲染
  错误立即中止并报错;录制结束用 <video> 探测真实时长(Chrome
  MediaRecorder 的 webm 无时长头,需 seek 到末尾),低于预期一半
  报「时长异常」,杜绝残缺视频再被静默回写。
This commit is contained in:
VicoHu
2026-09-08 16:58:41 +08:00
committed by GitHub
parent ad5eabcf3f
commit 6ef7cb8b1c
4 changed files with 128 additions and 17 deletions

View File

@@ -0,0 +1,29 @@
import { Mesh, MeshStandardMaterial, Scene, type Material } from "three";
/**
* 将场景整体切换为白膜材质,返回恢复函数;恢复时销毁共享的 clay 材质。
*
* 两类 mesh 必须跳过,否则切换本身会破坏渲染:
* - directorActor mesh 的材质由展示层按展示状态自行管理;
* - 带 uniforms 的 ShaderMaterial(drei Grid/Line 等)由组件自身 useFrame 逐帧读写 uniforms,
* 换成 clay 后渲染循环会逐帧抛错、画布冻结,白膜录制只剩启动前手动渲染的首帧。
*/
export function applyClaySceneMaterials(scene: Scene) {
const clayMaterial = new MeshStandardMaterial({ color: "#d6d9dd", roughness: 0.88, metalness: 0 });
const originals: Array<{ mesh: Mesh; material: Material | Material[] }> = [];
scene.traverse((child) => {
const mesh = child as Mesh;
if (!mesh.isMesh || mesh.userData.directorActor) return;
const material = mesh.material;
const shaderBacked = Array.isArray(material) ? material.some((item) => "uniforms" in item) : "uniforms" in material;
if (shaderBacked) return;
originals.push({ mesh, material });
mesh.material = clayMaterial;
});
return () => {
originals.forEach(({ mesh, material }) => {
mesh.material = material;
});
clayMaterial.dispose();
};
}