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:
56
web/test/director-clay-materials.test.ts
Normal file
56
web/test/director-clay-materials.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { Line, LineBasicMaterial, Mesh, MeshBasicMaterial, MeshStandardMaterial, PlaneGeometry, Scene, ShaderMaterial, type Material } from "three";
|
||||
|
||||
import { applyClaySceneMaterials } from "../src/lib/canvas/director/director-clay-materials";
|
||||
|
||||
const geometry = new PlaneGeometry(1, 1);
|
||||
|
||||
describe("导演台白膜材质切换", () => {
|
||||
test("标准材质 mesh 换成 clay,恢复后还原为原材质", () => {
|
||||
const scene = new Scene();
|
||||
const original = new MeshStandardMaterial({ color: "#ff0000" });
|
||||
const mesh = new Mesh(geometry, original);
|
||||
scene.add(mesh);
|
||||
const restore = applyClaySceneMaterials(scene);
|
||||
expect(mesh.material).not.toBe(original);
|
||||
expect(mesh.material).toBeInstanceOf(MeshStandardMaterial);
|
||||
restore();
|
||||
expect(mesh.material).toBe(original);
|
||||
});
|
||||
|
||||
test("带 uniforms 的 shader 材质保持原样(Grid 逐帧崩溃回归)", () => {
|
||||
const scene = new Scene();
|
||||
const shader = new ShaderMaterial({ uniforms: { worldCamProjPosition: { value: null } } });
|
||||
const grid = new Mesh(geometry, shader);
|
||||
scene.add(grid);
|
||||
const restore = applyClaySceneMaterials(scene);
|
||||
expect(grid.material).toBe(shader);
|
||||
restore();
|
||||
expect(grid.material).toBe(shader);
|
||||
});
|
||||
|
||||
test("directorActor mesh 与非 Mesh 对象不受影响", () => {
|
||||
const scene = new Scene();
|
||||
const actorMaterial = new MeshBasicMaterial();
|
||||
const actor = new Mesh(geometry, actorMaterial);
|
||||
actor.userData.directorActor = true;
|
||||
const line = new Line(geometry, new LineBasicMaterial());
|
||||
scene.add(actor, line);
|
||||
const restore = applyClaySceneMaterials(scene);
|
||||
expect(actor.material).toBe(actorMaterial);
|
||||
expect(line.material).toBeInstanceOf(LineBasicMaterial);
|
||||
restore();
|
||||
expect(actor.material).toBe(actorMaterial);
|
||||
});
|
||||
|
||||
test("材质数组混入 uniforms 材质时整组跳过", () => {
|
||||
const scene = new Scene();
|
||||
const materials: Material[] = [new MeshStandardMaterial(), new ShaderMaterial({ uniforms: {} })];
|
||||
const mesh = new Mesh(geometry, materials);
|
||||
scene.add(mesh);
|
||||
const restore = applyClaySceneMaterials(scene);
|
||||
expect(mesh.material).toBe(materials);
|
||||
restore();
|
||||
expect(mesh.material).toBe(materials);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user