fix(editor): 监视器在两个视角下统一自渲染
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import { PerspectiveCamera as DreiPerspectiveCamera } from "@react-three/drei";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { PerspectiveCamera as ThreePerspectiveCamera, Raycaster, Scene, Vector2 } from "three";
|
||||
import { useDirectorStore } from "../store/directorStore";
|
||||
import { usePlaybackStore } from "../runtime/playbackStore";
|
||||
@@ -29,7 +29,9 @@ import { evaluateCameraViewAtTime, getActiveCameraIdAtTime } from "../schema/tim
|
||||
* getActiveCameraIdAtTime 解析;位姿、镜头(焦段/光圈/对焦/景深强度)、画幅全部取自这台机位,
|
||||
* 保证监视器所见与导出的机位视角严格一致。
|
||||
*
|
||||
* 刻意做成独立的第二个 Canvas:景深后处理只作用在监视器上,不影响主视口 / 截图的渲染管线。
|
||||
* 刻意做成独立的第二个 Canvas,并且无论主视口处于「导演视角」还是「机位视角」都自行渲染:
|
||||
* 景深后处理只作用在监视器上,不影响主视口 / 截图的渲染管线;画面内容 / 虚化 / 取景
|
||||
* 也不会因为主视口切换视角而改变。
|
||||
*/
|
||||
|
||||
function LensMonitorCamera({
|
||||
@@ -124,70 +126,8 @@ const PITCH_LIMIT = 89;
|
||||
/** 区分「单击对焦」与「拖拽瞄准」的位移阈值(像素) */
|
||||
const CLICK_THRESHOLD_PX = 4;
|
||||
|
||||
/**
|
||||
* 机位视角下监视器的「镜像」:主视口此刻渲染的就是机位画面,监视器没必要再开一个 Canvas
|
||||
* 把整个场景重渲一遍(第二份场景 + 第二套景深后处理,既卡,又容易和主视口取景 / 虚化漂移)。
|
||||
* 这里直接把主视口画布按「传感器画幅」居中裁一块拷过来,逐像素与主视口一致。
|
||||
*
|
||||
* fov 是垂直的:视口比传感器画幅更宽时按宽度裁,垂直取景保持不变 ——
|
||||
* 结果与监视器自己用传感器画幅渲染完全一致。
|
||||
*/
|
||||
function MonitorMirror({ sensorAspect }: { sensorAspect: number }) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let frame = 0;
|
||||
const draw = () => {
|
||||
frame = requestAnimationFrame(draw);
|
||||
const target = canvasRef.current;
|
||||
const source = document.querySelector<HTMLCanvasElement>(".director-canvas canvas");
|
||||
if (!target || !source) return;
|
||||
|
||||
// 输出尺寸跟随容器(CSS 像素)
|
||||
const cssWidth = target.clientWidth;
|
||||
const cssHeight = target.clientHeight;
|
||||
if (!cssWidth || !cssHeight) return;
|
||||
if (target.width !== cssWidth || target.height !== cssHeight) {
|
||||
target.width = cssWidth;
|
||||
target.height = cssHeight;
|
||||
}
|
||||
|
||||
const sourceWidth = source.width;
|
||||
const sourceHeight = source.height;
|
||||
if (!sourceWidth || !sourceHeight) return;
|
||||
|
||||
let cropWidth = sourceWidth;
|
||||
let cropHeight = Math.round(sourceWidth / sensorAspect);
|
||||
if (cropHeight > sourceHeight) {
|
||||
cropHeight = sourceHeight;
|
||||
cropWidth = Math.round(sourceHeight * sensorAspect);
|
||||
}
|
||||
|
||||
const context = target.getContext("2d");
|
||||
if (!context) return;
|
||||
context.drawImage(
|
||||
source,
|
||||
Math.round((sourceWidth - cropWidth) / 2),
|
||||
Math.round((sourceHeight - cropHeight) / 2),
|
||||
cropWidth,
|
||||
cropHeight,
|
||||
0,
|
||||
0,
|
||||
target.width,
|
||||
target.height
|
||||
);
|
||||
};
|
||||
|
||||
frame = requestAnimationFrame(draw);
|
||||
return () => cancelAnimationFrame(frame);
|
||||
}, [sensorAspect]);
|
||||
|
||||
return <canvas className="lens-monitor-canvas" ref={canvasRef} />;
|
||||
}
|
||||
|
||||
export function LensMonitor({ rightOffset = 12 }: { rightOffset?: number }) {
|
||||
const sceneSettings = useDirectorStore((state) => state.project.scene);
|
||||
const viewMode = useDirectorStore((state) => state.viewMode);
|
||||
const assets = useDirectorStore((state) => state.project.assets);
|
||||
const panoramaAssetId = useDirectorStore((state) => state.project.panoramaAssetId);
|
||||
const panoramaAsset = assets.find((item) => item.id === panoramaAssetId);
|
||||
@@ -305,12 +245,10 @@ export function LensMonitor({ rightOffset = 12 }: { rightOffset?: number }) {
|
||||
onPointerCancel={endDrag}
|
||||
>
|
||||
{camera && lens ? (
|
||||
// 机位视角下主视口本身就是机位画面 → 监视器直接镜像它,不再开第二个 Canvas 重渲整个场景
|
||||
//(省掉一份场景渲染 + 一套景深后处理,而且与主视口逐像素一致)。
|
||||
// 其它视角(例如导演视角下想同时看机位画面)才需要监视器自己渲染。
|
||||
viewMode === "camera" ? (
|
||||
<MonitorMirror sensorAspect={sensorAspect} />
|
||||
) : (
|
||||
// 无论主视口是导演视角还是机位视角,监视器都自行渲染机位画面。
|
||||
// 曾试过「机位视角下镜像主视口」省一份渲染,但主视口会带上网格 / 地面坐标轴 / 轨迹 /
|
||||
// 角色标签 / 选中 gizmo,且虚化按主视口宽度计算 —— 切到机位视角后监视器就和导演视角下
|
||||
// 的干净机位画面对不上。统一自渲染才能保证两个视角下所见完全一致。
|
||||
<Canvas
|
||||
className="lens-monitor-canvas"
|
||||
dpr={[1, 1.5]}
|
||||
@@ -343,7 +281,6 @@ export function LensMonitor({ rightOffset = 12 }: { rightOffset?: number }) {
|
||||
/>
|
||||
<SceneRoot editorHelpers={false} />
|
||||
</Canvas>
|
||||
)
|
||||
) : (
|
||||
<div className="lens-monitor-empty">无激活机位</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user