在React Three Fiber中使用OrbitControls时如何固定场景灯光
解决OrbitControls旋转物体时灯光固定的问题
核心问题
- 你的
hemisphereLight被放在了包裹模型的mesh内部,成为物体的子元素,会随物体一起旋转,无法保持世界空间位置固定。 - 默认
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
相关产品推荐
相关产品推荐

