React/ReactThreeFiber

3D 공간에서의 시야 조정 : 카메라 이동과 시점 변경

최 수빈 2025. 3. 10. 15:53

 

Camera 조작

 

3D 공간에서 카메라의 위치(position)와 시점(lookAt) 조절의 의미 : 사용자의 시각적 경험 결정

 

 

Leva를 활용한 카메라 조작

 

Leva 라이브러리의 useControls를 사용한 카메라 조작

npm i leva

 

 

useControls를 이용해 카메라의 Z축 위치와 Y축 시점을 조절할 수 있는 슬라이더 생성

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

const Elements = () => {
  const { camera } = useThree();
  const dirLight = useRef(null);
  useHelper(dirLight, THREE.DirectionalLightHelper, 1, "blue");

  useControls({
    positionZ: {
      value: 3,
      min: -10,
      max: 10,
      step: 0.1,
      onChange: (v) => (camera.position.z = v),
    },
    lookAtY: {
      value: 0,
      min: -10,
      max: 10,
      step: 0.1,
      onChange: (v) => camera.lookAt(0, v, 0),
    },
  });

  return (
    <>
      {/* <OrbitControls /> → 주석 처리해야 Leva의 조작이 적용됨 */}
      <ambientLight intensity={0.7} />
      <directionalLight
        ref={dirLight}
        castShadow
        intensity={2}
        target-position={[0, -1, 0]}
        shadow-mapSize={[5000, 5000]}
        position={[-4, -2.1, 4]}
      />
      {cardData.map((props) => (
        <CardComponent key={props.imageUrl} {...props} />
      ))}
    </>
  );
};
  • useControls를 활용, UI 슬라이더로 카메라 이동을 조절
    positionZ : 카메라의 Z축 위치를 조정
    lookAtY : 카메라가 바라보는 Y축 방향을 조정

* <OrbitControls/>을 주석처리해야 useControls의 값이 반영됨

 

 

 

 

 

useFrame을 활용한 실시간 카메라 조작

 

useFrame 

매 프레임마다 특정 동작을 실행

카메라를 지속적으로 특정 지점을 바라보게 만들 수 있음

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

const Elements = () => {
  const { camera } = useThree();
  const dirLight = useRef(null);
  useHelper(dirLight, THREE.DirectionalLightHelper, 1, "blue");

  useFrame(() => {
    console.log("1프레임");
    camera.lookAt(0, 0.5, 0);
  });

  return (
    <>
      <OrbitControls />
      <ambientLight intensity={0.7} />
      <directionalLight
        ref={dirLight}
        castShadow
        intensity={2}
        target-position={[0, -1, 0]}
        shadow-mapSize={[5000, 5000]}
        position={[-4, -2.1, 4]}
      />
      {cardData.map((props) => (
        <CardComponent key={props.imageUrl} {...props} />
      ))}
    </>
  );
};
  • useFrame은 매 프레임마다 특정 동작을 수행
    camera.lookAt(0, 0.5, 0);을 실행, 카메라의 시점을 고정
    console.log("1프레임") : 매 프레임마다 콘솔에 출력되는 "1 프레임" 메세지를 확인할 수 있음

 

 

import './App.css'
import { useRef } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, useHelper } from '@react-three/drei';
import { useLoader, useThree, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { useControls } from "leva";

const CardData = [
  {
    imageUrl: "/card_1.jpg",
    position: [-1.1, -1.15, 1],
    rotationY: 50,
    rotationZ: 30,
  },
  {
    imageUrl: "/card_2.jpg",
    position: [-0.7, -1.05, 0.4],
    rotationY: 45,
    rotationZ: 10,
  },
  {
    imageUrl: "/card_3.jpg",
    position: [0, -1.05, -0.4],
    rotationY: 45,
    rotationZ: 0,
  },
  {
    imageUrl: "/card_4.jpg",
    position: [0.7, -1.15, -1],
    rotationY: 30,
    rotationZ: -10,
  },
  {
    imageUrl: "/card_5.jpg",
    position: [1.3, -1.30, -1.6],
    rotationY: 25,
    rotationZ: -30,
  },
]

const CardComponent = ({ position, rotationY, rotationZ, imageUrl }) => {
  const { scene } = useThree();
  const texture = useLoader(THREE.TextureLoader, imageUrl)
  texture.colorSpace = THREE.SRGBColorSpace;

  scene.rotation.set(0, 20, 0);

  const materials = [
    new THREE.MeshStandardMaterial(), // 오른쪽
    new THREE.MeshStandardMaterial(), // 왼쪽
    new THREE.MeshStandardMaterial(), // 윗면
    new THREE.MeshStandardMaterial(), // 바닥면
    new THREE.MeshStandardMaterial({ map: texture }), // 앞면 (이미지 적용)
    new THREE.MeshStandardMaterial({ map: texture }), // 뒷면 (이미지 적용)
  ];

  return (
    <mesh
      castShadow
      receiveShadow
      position={position}
      rotation-y={THREE.MathUtils.degToRad(rotationY)}
      rotation-z={THREE.MathUtils.degToRad(rotationZ)}
      material={materials}
    >
      <axesHelper />
      <boxGeometry args={[1, 1.6, 0.02]} />
    </mesh>
  )
}

const Elements = () => {
  const lightRef = useRef(null);
  const { camera } = useThree();
  useHelper(lightRef, THREE.DirectionalLightHelper, 1, "blue");

  useFrame(() => {
    console.log("1프레임");
    camera.lookAt(-1.6, 0, 0.3);
  });

  // useControls({
  //   positionX: {
  //     value: 3,
  //     min: -20,
  //     max: 10,
  //     step: 0.1,
  //     onChange: (v) => (camera.position.x = v),
  //   },
  //   positionY: {
  //     value: 3,
  //     min: -20,
  //     max: 10,
  //     step: 0.1,
  //     onChange: (v) => (camera.position.y = v),
  //   },
  //   positionZ: {
  //     value: 3,
  //     min: -20,
  //     max: 10,
  //     step: 0.1,
  //     onChange: (v) => (camera.position.z = v),
  //   },
  //   lookAtX: {
  //     value: 0,
  //     min: -10,
  //     max: 10,
  //     step: 0.1,
  //     onChange: (v) => camera.lookAt(v, 0, 0), // x축 기준 : (v, 0, 0)
  //   },
  //   lookAtY: {
  //     value: 0,
  //     min: -10,
  //     max: 10,
  //     step: 0.1,
  //     onChange: (v) => camera.lookAt(0, v, 0), // x축 기준 : (v, 0, 0)
  //   },
  //   lookAtZ: {
  //     value: 0,
  //     min: -10,
  //     max: 10,
  //     step: 0.1,
  //     onChange: (v) => camera.lookAt(0, 0, v), // x축 기준 : (v, 0, 0)
  //   },
  //   // lookAtY 사용 시 OrbitControls 주석 처리
  // });

  return (
    <>
      {/* <OrbitControls /> */}
      <ambientLight intensity={1} /> // ambientLight : 환경광 (모든 방향에서 균일하게 씌워지는 빛)-모든 오브젝트가 동일한 강도로 조명을 받는 빛
      <directionalLight
        castShadow ref={lightRef}
        intensity={3}
        target-positon={[0, -1, 0]}
        shadow-mapSize={[5000, 5000]}
        position={[-2, 1, 5]} />
      {
        CardData.map((props) => {
          return <CardComponent key={props.imageUrl} {...props} />

        })
      }
    </>
  )
}

function App() {
  return (
    <Canvas shadows camera={{ position: [2, 0, -1.2] }}>
      <Elements />
    </Canvas>
  );
}

export default App;

 

useControls

프레임마다 출력되는 "1프레임" 콘솔 로그

 

 

 

'React > ReactThreeFiber' 카테고리의 다른 글

Light & Shadow  (0) 2025.03.07
Obj 3D & Transform  (0) 2025.03.06
Material & Texture  (1) 2025.03.05
Geometry  (0) 2025.03.04
GUI Controller  (2) 2025.03.03