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

在React Three Fiber中使用OrbitControls时如何固定场景灯光

解决OrbitControls旋转物体时灯光固定的问题

核心问题

  1. 你的hemisphereLight被放在了包裹模型的mesh内部,成为物体的子元素,会随物体一起旋转,无法保持世界空间位置固定。
  2. 默认OrbitControls控制的是相机环绕,而非物体自身旋转,这和你“旋转立方体让不同面被固定灯光照亮”的需求不匹配。

解决方案

方案1:灯光固定在世界空间,旋转物体(符合你的需求)

将灯光移出物体层级,改为控制物体旋转而非相机:

1. 修改Island组件(移除内部灯光,添加ref支持)

import React, { Suspense, useEffect, useState, useRef, forwardRef } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Preload, useGLTF } from "@react-three/drei";

import CanvasLoader from "../Loader";

// 用forwardRef包裹,以便获取物体mesh的引用
const Island = forwardRef(({ isMobile }, ref) => {
  const island = useGLTF("./block/scene.gltf");

  return (
    <mesh ref={ref}>
      <primitive
        object={island.scene}
        scale={isMobile ? 0.7 : 0.005}
        position={isMobile ? [0, -4, -2.2] : [0, -4.2, 0]}
        rotation={[0, 0, 0]}
      />
    </mesh>
  );
});

Island.displayName = "Island";

2. 修改IslandCanvas组件(灯光移至世界空间,让OrbitControls控制物体)

const IslandCanvas = () => {
  const [isMobile, setIsMobile] = useState(false);
  const islandRef = useRef(null);
  const controlsRef = useRef(null);

  useEffect(() => {
    const mediaQuery = window.matchMedia("(max-width: 500px)");
    setIsMobile(mediaQuery.matches);
    const handleMediaQueryChange = (event) => {
      setIsMobile(event.matches);
    };
    mediaQuery.addEventListener("change", handleMediaQueryChange);
    return () => {
      mediaQuery.removeEventListener("change", handleMediaQueryChange);
    };
  }, []);

  useEffect(() => {
    if (controlsRef.current && islandRef.current) {
      // 将OrbitControls的控制目标从相机改为物体mesh
      controlsRef.current.object = islandRef.current;
      // 禁用不需要的控制选项
      controlsRef.current.enablePan = false;
      controlsRef.current.enableZoom = false;
      // 限制旋转为水平方向(绕Y轴)
      controlsRef.current.minPolarAngle = Math.PI / 2;
      controlsRef.current.maxPolarAngle = Math.PI / 2;
      // 更新控制器状态
      controlsRef.current.update();
    }
  }, []);

  return (
    <Canvas
      frameloop='demand'
      shadows
      dpr={[1, 2]}
      camera={{ position: [0, 0, 20], fov: 25 }} // 相机固定在物体前方
      gl={{ preserveDrawingBuffer: true }}
    >
      {/* 灯光放在Canvas根层级,处于世界空间,位置固定 */}
      <hemisphereLight intensity={0.15} groundColor='black' />
      <pointLight intensity={1} position={[0, -30, 1000]} />

      <Suspense fallback={<CanvasLoader />}>
        <group>
          <OrbitControls
            ref={controlsRef}
            enableZoom={false}
            maxPolarAngle={Math.PI / 2}
            minPolarAngle={Math.PI / 2}
          />
          <Island ref={islandRef} isMobile={isMobile} />
        </group>
      </Suspense>

      <Preload all />
    </Canvas>
  );
};

export default IslandCanvas;

方案2:灯光固定在世界空间,保持相机环绕(如果你的需求是相机动、物体不动)

如果只是希望灯光固定,继续用OrbitControls控制相机环绕,只需修改Island组件,移除内部的hemisphereLight,将其移到Canvas根层级即可,无需修改OrbitControls的控制目标:

// 修改后的Island组件
const Island = ({ isMobile }) => {
  const island = useGLTF("./block/scene.gltf");

  return (
    <mesh>
      <primitive
        object={island.scene}
        scale={isMobile ? 0.7 : 0.005}
        position={isMobile ? [0, -4, -2.2] : [0, -4.2, 0]}
        rotation={[0, 0, 0]}
      />
    </mesh>
  );
};

// IslandCanvas中把灯光移到Canvas根层级
<Canvas>
  <hemisphereLight intensity={0.15} groundColor='black' />
  <pointLight intensity={1} position={[0, -30, 1000]} />
  {/* 其他内容不变 */}
</Canvas>

关键修改说明

  • 灯光层级调整:将灯光从物体子层级移到场景根层级,确保灯光处于世界空间,位置不会随物体旋转改变。
  • 控制目标切换:通过ref获取OrbitControls和物体mesh,将控制器的控制对象改为物体,实现拖动时旋转物体而非相机。
  • 相机固定:调整相机位置到物体前方固定点,保证观察视角稳定,聚焦于物体旋转的效果。

内容的提问来源于stack exchange,提问作者ObsequiousNoodle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 18:57:05