Three.js第一人称视角实现及初始相机聚焦问题求助
解决方案:实现第一人称驾车视角与初始相机定位
一、实现第一人称驾车视角
当前代码中相机跟随在车后方并看向车身,要改成沉浸式第一人称视角,需调整相机的绑定逻辑,让相机随车辆的实际运动方向同步:
修改核心逻辑
在useFrame中替换原相机控制代码,改为从车辆的世界矩阵中提取实时位置与朝向,模拟驾驶位视角:
useFrame((_, delta) => { // 保留原滚动驱动动画逻辑 const action = actions["Take 001"]; const offset = scroll.offset; action.time = THREE.MathUtils.damp( action.time, action.getClip().duration * offset, 100, delta ); if (car) { // 更新车辆世界矩阵,确保位置/朝向实时准确 car.updateMatrixWorld(); // 提取车辆的全局位置与前进方向 const carPosition = new THREE.Vector3(); const carForward = new THREE.Vector3(0, 0, -1); // 根据模型实际朝向调整,示例假设-1z为前进方向 carPosition.setFromMatrixPosition(car.matrixWorld); carForward.applyMatrix4(car.matrixWorld).sub(carPosition).normalize(); // 设置相机在车内的相对位置(可根据模型大小微调) const cameraOffset = new THREE.Vector3(0, 1.2, 0.5); // 高度1.2m,车头前方0.5m const cameraPosition = carPosition.clone().add(cameraOffset.applyMatrix4(car.matrixWorld)); // 让相机看向车辆前进方向的远处,增强沉浸感 const lookTarget = carPosition.clone().add(carForward.multiplyScalar(10)); // 平滑过渡相机位置与朝向,避免视角抖动 state.camera.position.lerp(cameraPosition, delta * 10); state.camera.lookAt(lookTarget); } });
关键说明
- 用
car.updateMatrixWorld()确保车辆矩阵实时更新,避免视角延迟 carForward的初始向量需匹配模型实际朝向(可通过Blender等工具查看模型局部坐标系)cameraOffset的数值需根据模型比例调整,确保相机处于车内合理的驾驶位
二、解决初始相机显示问题
加载时先显示默认视角再切换到车辆视角,是因为模型加载前相机未定位,且ScrollControls可能干扰初始状态。通过以下步骤修复:
1. 初始化相机到第一人称位置
在Model组件中添加useEffect,模型加载完成后直接设置相机初始位置:
import { useThree } from "@react-three/fiber"; // 引入useThree获取相机状态 const Model = ({ ...props }) => { const { state } = useThree(); // ... 其他代码 useEffect(() => { actions["Take 001"].play().paused = true; // 模型加载完成后立即定位相机 if (car) { car.updateMatrixWorld(); const carPosition = new THREE.Vector3(); const carForward = new THREE.Vector3(0, 0, -1); const cameraOffset = new THREE.Vector3(0, 1.2, 0.5); carPosition.setFromMatrixPosition(car.matrixWorld); const cameraPosition = carPosition.clone().add(cameraOffset.applyMatrix4(car.matrixWorld)); const lookTarget = carPosition.clone().add(carForward.multiplyScalar(10)); state.camera.position.copy(cameraPosition); state.camera.lookAt(lookTarget); } }, [actions, car, state]); };
2. 禁用ScrollControls的干扰项
调整ScrollControls属性,避免其自动控制相机:
<ScrollControls pages={10} disableZoom disablePan> <Model scale={1} /> </ScrollControls>
完整修改后的代码
import * as THREE from "three"; import { Suspense, useEffect } from "react"; import { Canvas, useFrame, useLoader, useThree } from "@react-three/fiber"; import { ScrollControls, useScroll, useAnimations, Stage } from "@react-three/drei"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; const Model = ({ ...props }) => { const { state } = useThree(); const scroll = useScroll(); const { scene, animations } = useLoader(GLTFLoader, "/model/scene.gltf"); const { actions } = useAnimations(animations, scene); let car = scene.getObjectByName("car"); useEffect(() => { actions["Take 001"].play().paused = true; // 初始化相机到第一人称视角 if (car) { car.updateMatrixWorld(); const carPosition = new THREE.Vector3(); const carForward = new THREE.Vector3(0, 0, -1); // 根据模型朝向调整 const cameraOffset = new THREE.Vector3(0, 1.2, 0.5); carPosition.setFromMatrixPosition(car.matrixWorld); const cameraPosition = carPosition.clone().add(cameraOffset.applyMatrix4(car.matrixWorld)); const lookTarget = carPosition.clone().add(carForward.multiplyScalar(10)); state.camera.position.copy(cameraPosition); state.camera.lookAt(lookTarget); } }, [actions, car, state]); useFrame((_, delta) => { const action = actions["Take 001"]; const offset = scroll.offset; action.time = THREE.MathUtils.damp( action.time, action.getClip().duration * offset, 100, delta ); if (car) { car.updateMatrixWorld(); const carPosition = new THREE.Vector3(); const carForward = new THREE.Vector3(0, 0, -1); carPosition.setFromMatrixPosition(car.matrixWorld); carForward.applyMatrix4(car.matrixWorld).sub(carPosition).normalize(); const cameraOffset = new THREE.Vector3(0, 1.2, 0.5); const cameraPosition = carPosition.clone().add(cameraOffset.applyMatrix4(car.matrixWorld)); const lookTarget = carPosition.clone().add(carForward.multiplyScalar(10)); state.camera.position.lerp(cameraPosition, delta * 10); state.camera.lookAt(lookTarget); } }); return <primitive object={scene} {...props} />; }; const App = () => { return ( <> <Suspense fallback={<div>加载中...</div>}> <Canvas camera={{ fov: 75 }}> <Stage environment={null}> <ScrollControls pages={10} disableZoom disablePan> <Model scale={1} /> </ScrollControls> </Stage> </Canvas> </Suspense> </> ); }; export default App;
额外提示
- 若模型前进方向不是
-z,需调整carForward的初始向量 - 可通过修改
cameraOffset的数值优化驾驶位的视角位置 - 添加
Suspense的fallback可提升加载时的用户体验
内容的提问来源于stack exchange,提问作者Moncef Grey
相关产品推荐
相关产品推荐

