feat(camera): 机位视角增加景深虚化与镜头参数编辑
- 截图 / 视频导出在机位视角下走 bokeh 后处理渲染,按传感器画幅裁切 - 镜头监视器复用同一套解析与渲染,隐藏编辑器辅助元素 - 镜头面板支持焦段、画幅、光圈、对焦、虚化编辑,并按播放头写入关键帧 - 位置 / 角度编辑同步进选中关键帧,锁定物体时回填实测对焦距离 - 轨迹点支持整体锁定与隐藏,角色姿势关键帧按播放头插值
This commit is contained in:
@@ -2,7 +2,7 @@ import { beforeEach, expect, it, vi } from "vitest";
|
||||
import { Object3D, PerspectiveCamera, Scene, Vector2 } from "three";
|
||||
import { usePlaybackStore } from "../runtime/playbackStore";
|
||||
import { useDirectorStore } from "../store/directorStore";
|
||||
import { getCameraLookRotationFromTarget } from "../schema/cameraGeometry";
|
||||
import { getCameraLookRotationFromTarget, resolveLens } from "../schema/cameraGeometry";
|
||||
import { exportVideo } from "./videoExport";
|
||||
|
||||
vi.mock("mp4-muxer", () => ({
|
||||
@@ -22,6 +22,14 @@ vi.mock("./videoExportBridge", () => ({
|
||||
getVideoExportContext: () => contextRef.current,
|
||||
}));
|
||||
|
||||
// 相机视角导出会走 renderSceneWithBokeh(真实 EffectComposer / BokehPass 在 jsdom 下无 WebGL)。
|
||||
// 桩里直接委托给 gl.render,既能避开 WebGL,又保留「记录每帧相机位姿」的测试意图
|
||||
// (createFakeBridge 只在 gl.render 时把相机位姿写入 bridge.rendered)。
|
||||
vi.mock("../canvas/viewportBokeh", () => ({
|
||||
renderSceneWithBokeh: (gl: { render: (s: unknown, c: unknown) => void }, scene: unknown, camera: unknown) =>
|
||||
gl.render(scene, camera),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
usePlaybackStore.setState({ isPlaying: false, playhead: 0, duration: 5, loop: false, editingObjectId: null });
|
||||
});
|
||||
@@ -152,13 +160,16 @@ it("renders camera-view exports from the camera shot itself when it has no camer
|
||||
const expected = new PerspectiveCamera();
|
||||
expected.position.set(0, 3, 9);
|
||||
expected.lookAt(0, 1.2, 0);
|
||||
// 导出相机 fov 走「按播放头解析出的镜头 fov」(与监视器一致),而非机位里可能陈旧的 fov 字段
|
||||
const shot = useDirectorStore.getState().project.cameras.find((c) => c.id === shotId)!;
|
||||
const resolvedFov = resolveLens(shot).fov;
|
||||
|
||||
expect(bridge.rendered.length).toBeGreaterThan(0);
|
||||
for (const frame of bridge.rendered) {
|
||||
expect(frame.position[0]).toBeCloseTo(0, 5);
|
||||
expect(frame.position[1]).toBeCloseTo(3, 5);
|
||||
expect(frame.position[2]).toBeCloseTo(9, 5);
|
||||
expect(frame.fov).toBeCloseTo(35, 5);
|
||||
expect(frame.fov).toBeCloseTo(resolvedFov, 5);
|
||||
expect(frame.quaternion[0]).toBeCloseTo(expected.quaternion.x, 5);
|
||||
expect(frame.quaternion[1]).toBeCloseTo(expected.quaternion.y, 5);
|
||||
expect(frame.quaternion[2]).toBeCloseTo(expected.quaternion.z, 5);
|
||||
@@ -186,12 +197,15 @@ it("lets the camera track win over the static shot pose in camera-view exports",
|
||||
}
|
||||
|
||||
// 有轨道时走的是预览同款插值,导出不该再用静态机位覆盖它
|
||||
// fov 走解析后的镜头 fov(与监视器一致),与关键帧里可能陈旧的 fov 字段无关
|
||||
const shot2 = useDirectorStore.getState().project.cameras.find((c) => c.id === shotId)!;
|
||||
const resolvedFov2 = resolveLens(shot2).fov;
|
||||
expect(bridge.rendered.length).toBeGreaterThan(0);
|
||||
for (const frame of bridge.rendered) {
|
||||
expect(frame.position[0]).toBeCloseTo(5, 5);
|
||||
expect(frame.position[1]).toBeCloseTo(1, 5);
|
||||
expect(frame.position[2]).toBeCloseTo(0, 5);
|
||||
expect(frame.fov).toBeCloseTo(40, 5);
|
||||
expect(frame.fov).toBeCloseTo(resolvedFov2, 5);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user