You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 06:35:20