@react-three/fiber的useThree

@react-three/fiber的useThree

_

useThree 详解

可获取的对象

import { useThree } from "@react-three/fiber";

function MyComponent() {
  const {
    camera,        // ✅ 当前相机
    scene,         // Three.js 场景
    gl,            // WebGL 渲染器
    size,          // 画布尺寸 { width, height }
    viewport,      // 视口信息
    raycaster,     // 射线投射器
    clock,         // 时钟
    mouse,         // 鼠标坐标
    // ... 更多
  } = useThree();
  
  return null;
}

常用场景

// 1. 获取相机位置
const { camera } = useThree();
console.log(camera.position);

// 2. 获取画布尺寸
const { size } = useThree();
console.log(size.width, size.height);

// 3. 获取视口尺寸(3D 空间单位)
const { viewport } = useThree();
console.log(viewport.width, viewport.height);

// 4. 手动渲染
const { gl, scene, camera } = useThree();
gl.render(scene, camera);

更推荐的写法(使用 useFrame)

import { useGLTF, useKeyboardControls } from "@react-three/drei";
import {
  CapsuleCollider,
  RigidBody,
  RapierRigidBody,
} from "@react-three/rapier";
import { useRef, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { Vector3 } from "three";
import { toFixed } from "../../utils/math";

useGLTF.preload("/models/Actor/actor.gltf");

function Player() {
  const SPEED = 4;
  const JUMP = 7;
  
  const { camera } = useThree();
  const { scene } = useGLTF("/models/Actor/actor.gltf");
  const player = useRef<RapierRigidBody>(null);
  const [, getKeys] = useKeyboardControls();
  
  // ✅ 使用 useMemo 避免每次渲染都创建新向量
  const velocity = useMemo(() => new Vector3(), []);
  
  // ✅ 推荐:在 useFrame 中处理移动逻辑
  useFrame(() => {
    if (!player.current) return;
    
    const { forward, backward, left, right, jump } = getKeys();
    
    // 计算移动方向
    velocity
      .set(
        Number(right) - Number(left),
        0,
        Number(backward) - Number(forward)
      )
      .normalize()
      .multiplyScalar(SPEED)
      .applyEuler(camera.rotation);  // ✅ 直接使用 camera
    
    const currentVel = player.current.linvel();
    
    player.current.setLinvel(
      {
        x: toFixed(velocity.x),
        y: currentVel.y,
        z: toFixed(velocity.z),
      },
      true
    );
    
    // 跳跃
    if (jump && Math.abs(currentVel.y) < 0.1) {
      player.current.setLinvel({ x: 0, y: JUMP, z: 0 }, true);
    }
  });
  
  return (
    <group dispose={null}>
      <RigidBody
        ref={player}
        colliders={false}
        type="dynamic"
        enabledRotations={[false, false, false]}
        position={[0, 4, 0]}
        lockRotations
      >
        <primitive object={scene} />
        <CapsuleCollider args={[0.6, 0.3]} />
      </RigidBody>
    </group>
  );
}

export default Player;

两种方案对比

方案

优点

缺点

useKeyboardControls 回调

事件驱动,只在按键时执行

需要返回值,逻辑复杂

useFrame

每帧执行,逻辑清晰,性能稳定

每帧都执行(但可以优化)

useThree 的完整示例

import { useThree, useFrame } from "@react-three/fiber";

function CameraLogger() {
  const { camera, size, viewport } = useThree();
  
  useFrame(() => {
    console.log('相机位置:', camera.position);
    console.log('画布尺寸:', size.width, 'x', size.height);
    console.log('视口尺寸:', viewport.width, 'x', viewport.height);
  });
  
  return null;
}

总结

获取相机的方法:

import { useThree } from "@react-three/fiber";

const { camera } = useThree();

最佳实践:

  1. ✅ 使用 useThree() 获取 camera

  2. ✅ 在 useFrame 中处理移动逻辑(而不是 useKeyboardControls 回调)

  3. ✅ 使用 useMemo 优化向量创建

现在 camera 可以正常使用了!🎮

RigidBody和RapierRigidBody 2025-12-30
Nginx基础学习 2025-12-30

评论区