Dev_logroome

Three.js와 R3F로 3D 모델 렌더링하기 – RoomE #2

2025-03-31
ProjectReactThree.js

📌 프로젝트에서 Three.js 활용

01. 3D 모델 불러오기

@react-three.dreiuseGLTF 훅을 활용했다.

jsx
1
const { scene } = useGLTF(modelPath) as GLTFResult;
<primitive object={scene} scale={0.68} rotation={[0, -Math.PI / 4, 0]} />
  • modelPath 경로의 GLTF 모델을 로드하여 scene 으로 저장한다.
  • primitive 를 사용하여 GLTF 모델을 Three.jsobject 형태로 변환 후 렌더링

02. 위치 및 그림자 설정

jsx
1
useEffect(() => {
  if (scene) {
    scene.position.set(0, 0, 0);
    scene.traverse((object) => {
      if (object.isMesh) {
        object.castShadow = true;
        object.receiveShadow = true;
      }
    });
  }
}, [scene]);
  • 모델의 위치를 설정, scene.traverse 를 이용하여 모델 내의 모든 Mesh 요소에 그림자 설정을 적용

03. 모델 렌더링

jsx
1
<Canvas shadows camera={CAMERA_CONFIG}>
  <RoomLighting />
  <Suspense fallback={null}>
    <Center>
      <primitive object={scene} scale={0.68} rotation={[0, -Math.PI / 4, 0]} />
    </Center>
  </Suspense>
  <OrbitControls enableRotate={false} enablePan={false} minDistance={5} maxDistance={12} />
</Canvas>
  • Canvas 컴포넌트를 사용하여 렌더링
  • primitive 를 통해 Three.js의 scene 객체를 직접 렌더링
  • OrbitControls 를 추가하여 카메라 이동 범위를 제한

04. 조명 및 카메라 설정

재사용성과 관리의 용이성을 고려해서 별도의 constants 파일로 분리해서 사용하였다.

카메라 설정

js
1
export const CAMERA_CONFIG = {
  position: new Vector3(0, 4, 10),
  fov: 30,
};

조명 설정

렌더링에서 가장 중요한 건 조명이다. 왜냐하면 조명의 유/무의 차이로 퀄리티가 달라보이기 때문에, 조명 설정이 가장 중요했다.

jsx
1
export function RoomLighting() {
  const { mainLight, ambient, fill } = LIGHT_CONFIG;
 
  return (
    <>
      <ambientLight intensity={ambient.intensity} color={ambient.color} />
      <directionalLight
        position={[mainLight.position[0], mainLight.position[1], mainLight.position[2]]}
        intensity={mainLight.intensity}
        color={mainLight.color}
        castShadow
        shadow-mapSize-width={mainLight.shadowConfig.mapSize[0]}
        shadow-mapSize-height={mainLight.shadowConfig.mapSize[1]}
        shadow-camera-near={mainLight.shadowConfig.camera.near}
        shadow-camera-far={mainLight.shadowConfig.camera.far}
        shadow-bias={mainLight.shadowConfig.bias}
      />
      <pointLight
        position={[fill.position[0], fill.position[1], fill.position[2]]} 
        intensity={fill.intensity}
        color={fill.color}
      />
    </>
  );
}

05. 가구 모델링 분리 및 관리

방 내부에 배치되는 가구(책장, CD플레이어, 저금통, 방명록)은 useRoomItems 훅으로 따로 만들어 관리하고 있다.

가구 토글 기능

➡️ㅤ사용자는 가구 토글 기능을 이용해 CD 플레이어, 책장를 배치하거나 삭제할 수 있다.

jsx
1
const { items } = useRoomItems({ roomId, furnitures });
  • 방마다 배치된 가구의 종류가 다를 수 있기 때문에 roomId 에 따라 동적으로 구성
jsx
1
{items.map((item) => (
  <Suspense key={item.id}>
    <Furnitures item={item} onInteract={handleInteraction} onHover={handleHover} />
  </Suspense>
))}
  • items 배열을 순회하여 Furnitures 컴포넌트를 렌더링
  • onInteractonHover 이벤트 핸들러를 통해 가구와의 상호작용을 구현
테마 토글 기능

사용자가 원하는 테마로 변경 가능하다

06. 상호작용 구현 ( 가구 클릭 및 호버 이벤트 )

방명록
방명록
책장 도서
책장(도서)
CD플레이어(음악)
CD플레이어(음악)
저금통(포인트)
저금통(포인트)
jsx
1
const handlePointerOver = (e: MouseEvent) => {
  e.stopPropagation();
  scene.traverse((object) => {
    if (object.isMesh) {
      object.material.emissive = object.material.color;
      object.material.emissiveIntensity = 0.5;
    }
  });
};
 
const handlePointerOut = (e: MouseEvent) => {
  e.stopPropagation();
  scene.traverse((object) => {
    if (object.isMesh) {
      object.material.emissiveIntensity = 0;
    }
  });
};
  • 호버하면(onPointerOver), emissive 값을 변경하여 하이라이트 효과 적용
  • 호버 해제 시(onPointerOut), emissiveIntensity0으로 설정하여 원래 상태로 복구

💬ㅤ비하인드

처음 사이트에 모델링을 렌더링 했을 때는 보이지도 않아서 뭐가 잘못되었는지... 카메라 세팅 끝에 방을 정중앙에 불러올 수 있었다.

오류1

이제 가구들은 먼지처럼 그저 흩어져 있었다. 사실 이 부분은 노가다로 위치를 찾고 회전을 시키는 것 밖에는 없는 것 같아서 일일히 맞추었다.

오류2