웹개발

[React-Three-Fiber] 같은 3D 모델 여러개를 Canvas에 배치하기(https://sketchfab.com/ 에서 gltf 다운 및 사용하기)

Heeyeon Choi 2024. 12. 7. 16:04
728x90

 

0. Intro 프로젝트 계획 변동 

원래 계획은

 

였습니다. 

 

중앙에 Lottie 가 애니메이션으로 동작하고 주변에 3D 모델링된 별들이 고정된 위치에서 줌인 - 줌아웃 정도의 애니메이션만 동작할 예정이었죠..

 

하지만 실제 React-Three-Fiber 로 개발하다보니, 별들만 3D모델링을 하는게 쉽지 않았습니다.

 

같은 Canvas 에 모델들을 넣기 위해서 지구도 3D 모델 즉, Gltf 포맷으로 넣기로 계획을 수정하였습니다.

 

1. 지구와 별들 리소스 구하기 및 프로젝트에 추가하기

별 리소스 구하는 방법은

https://choi-hee-yeon.tistory.com/232

 

[Next.js] React-Three-fiber 을 사용하여 3D 모델 프로젝트에 추가 및 애니메이션 적용하기

1. 3D 모델 구하기 (gltf)우선 3D 모델을 구하기 위해서 저는 https://lumalabs.ai/genie?view=preview Luma AI - GenieA 3d generative foundation modellumalabs.ai를 사용했습니다!  1-1. 원하는 모델을 입력하기1-2. 원하는

choi-hee-yeon.tistory.com

에 자세히 나와있습니다.

해당 별을 하나만 삽입하는 것을 구현했었는데, 

이름이 다른 여러개의 별로 복제하여 프로젝트 public 폴더에 추가합니다.

=> 추후에 여러개의 별을 추가할 때, 같은 파일로 여러개를 추가하지 못합니다.... (경험입니다 ㅜㅜㅜㅜㅜ )

 

 

 

1-1. 지구 리소스 구하기 및 다운로드 받고 프로젝트에 추가하기

https://sketchfab.com/search?features=downloadable&q=earth&type=models

 

Sketchfab

 

sketchfab.com

지구는 이 사이트에서 구했습니다.

다양한 3D 모델들이 존재하며, 저작권 잘 확인 후, 추후에 프로젝트에 꼭 넣어줍니다.

 

꼭 gltf 포멧으로 다운로드 받습니다!!

 

 

 

다운로드 받은 파일은 .zip으로 저장됩니다.

압축을 해제해여 폴더 전체를 프로젝의 public 폴더 안에 옮겨 놓습니다.

 

절대 gltf 만 옮겨 놓으면 안됩니다!!!!

 

 

2. 코드를 통해 프로젝트에 삽입하기

제가 하고 싶은건 하나의 큰 지구가 중앙에서 자전하고, 3개의 별들이 자전 및 지구 주변을 공전하는 것입니다.

 

2-1. 별 하나 관련 코드

const Star: React.FC<{ orbitRadius: number; speed: number }> = ({ orbitRadius, speed }) => {
  const gltf = useGLTF('/models/star.glb');
  const ref = useRef<THREE.Group>(null);

  useFrame(({ clock }) => {
    if (ref.current) {
      const time = clock.getElapsedTime();
      const x = orbitRadius * Math.cos(time * speed);
      const z = orbitRadius * Math.sin(time * speed);
      ref.current.position.set(x, 0, z);

      ref.current.rotation.y = time * 0.5; // 느린 회전
      const scale = 0.5 + Math.abs(Math.sin(time * 2)) * 0.05; // 크기 변화 범위 0.5 ~ 0.55
      ref.current.scale.set(scale, scale, scale);
    }
  });

  return <primitive ref={ref} object={gltf.scene} />;
};

 

 

- gltf 변수에 저희가 삽입한 별 모델의 경로를 적어줍니다.

- useFrame 을 통해 별 애니메이션을 생성해줍니다.  x, z 축을 시간별로 설정하여 position을 변경해줍니다.

- 자전도 하도록

ref.current.rotation.y = time * 0.5; // 느린 회전

 

합니다.

y축으로 회전하는 것 입니다. 

 

현재 작성한 별의 코드를 3개를 작성해줍니다. 각기 다른 모델을 사용해야 합니다. 

const Star: React.FC<{ orbitRadius: number; speed: number }> = ({ orbitRadius, speed }) => {
const gltf = useGLTF('/models/star.glb');
const ref = useRef<THREE.Group>(null);

useFrame(({ clock }) => {
if (ref.current) {
const time = clock.getElapsedTime();
const x = orbitRadius * Math.cos(time * speed);
const z = orbitRadius * Math.sin(time * speed);
ref.current.position.set(x, 0, z);

ref.current.rotation.y = time * 0.5; // 느린 회전
const scale = 0.5 + Math.abs(Math.sin(time * 2)) * 0.05; // 크기 변화 범위 0.5 ~ 0.55
ref.current.scale.set(scale, scale, scale);
}
});

return <primitive ref={ref} object={gltf.scene} />;
};

const Star2: React.FC<{ orbitRadius: number; speed: number }> = ({ orbitRadius, speed }) => {
const gltf = useGLTF('/models/star2.glb');
const ref = useRef<THREE.Group>(null);

useFrame(({ clock }) => {
if (ref.current) {
const time = clock.getElapsedTime();
const x = orbitRadius * Math.cos(time * speed + Math.PI / 3);
const z = orbitRadius * Math.sin(time * speed + Math.PI / 3);
ref.current.position.set(x, 0, z);

ref.current.rotation.y = time * 0.5;
const scale = 0.5 + Math.abs(Math.sin(time * 2)) * 0.05;
ref.current.scale.set(scale, scale, scale);
}
});

return <primitive ref={ref} object={gltf.scene} />;
};

const Star3: React.FC<{ orbitRadius: number; speed: number }> = ({ orbitRadius, speed }) => {
const gltf = useGLTF('/models/star3.glb');
const ref = useRef<THREE.Group>(null);

useFrame(({ clock }) => {
if (ref.current) {
const time = clock.getElapsedTime();
const x = orbitRadius * Math.cos(time * speed + (2 * Math.PI) / 3);
const z = orbitRadius * Math.sin(time * speed + (2 * Math.PI) / 3);
ref.current.position.set(x, 0, z);

ref.current.rotation.y = time * 0.5;
const scale = 0.5 + Math.abs(Math.sin(time * 2)) * 0.05;
ref.current.scale.set(scale, scale, scale);
}
});

return <primitive ref={ref} object={gltf.scene} />;
};

 

 

const gltf = useGLTF('/models/star3.glb'); 

 

부분만 다릅니다. 주의해서 작성하도록 합니다.

 

2-2. 지구 코드 작성하기

const Earth: React.FC<{ position: [number, number, number] }> = ({ position }) => {
const gltf = useGLTF('/models/earth/scene.gltf');
const ref = useRef<THREE.Group>(null);

useFrame(() => {
if (ref.current) {
ref.current.rotation.y += 0.008; // 천천히 회전
}
});

return <primitive ref={ref} object={gltf.scene} position={position} scale={1.5} />;
};

- 별과 같이, gltf 에 알맞은 모델 경로를 작성하여 줍니다.

- useFrame 을 통해 자전하도록 애니메이션을 작성합니다.

 

2-3. 별과 지구를 같은 Canvas 에 배치하기

const GltfViewer: React.FC<GltfViewerProps> = ({
size = { width: '100%', height: '100%' },
}) => {
return (
<div style={{ width: size.width, height: size.height }}>
<Canvas>
{/* 조명 설정 */}
<ambientLight intensity={0.8} /> {/* 주변 조명 */}
<directionalLight position={[5, 5, 5]} intensity={0.5} /> {/* 방향 조명 */}

{/* GLTF 모델 컴포넌트 */}
<Earth position={[0, 0, 0]} />
<Star orbitRadius={3} speed={2} />
<Star2 orbitRadius={3} speed={2} />
<Star3 orbitRadius={3} speed={2} />

{/* 카메라 컨트롤 */}
<OrbitControls />
</Canvas>
</div>
);
};

 

- ambientLight를 통해 주변조명을 설정합니다.

- directionalLight 를 통해 방향 조명을 설정합니다.

- 지구를 0,0,0 에 위치시킵니다.

- 세 개의 별의 공전 지름을 같게 설정하고, 속도도 같게 설정했습니다. (변경 가능)

 

2-4. GltfViewer.tsx 의 전체코드

'use client';
import React, { useRef } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, useGLTF } from '@react-three/drei';
import * as THREE from 'three';

interface GltfViewerProps {
size?: { width: string; height: string }; // Canvas 크기
}

const Star: React.FC<{ orbitRadius: number; speed: number }> = ({ orbitRadius, speed }) => {
const gltf = useGLTF('/models/star.glb');
const ref = useRef<THREE.Group>(null);

useFrame(({ clock }) => {
if (ref.current) {
const time = clock.getElapsedTime();
const x = orbitRadius * Math.cos(time * speed);
const z = orbitRadius * Math.sin(time * speed);
ref.current.position.set(x, 0, z);

ref.current.rotation.y = time * 0.5; // 느린 회전
const scale = 0.5 + Math.abs(Math.sin(time * 2)) * 0.05; // 크기 변화 범위 0.5 ~ 0.55
ref.current.scale.set(scale, scale, scale);
}
});

return <primitive ref={ref} object={gltf.scene} />;
};

const Star2: React.FC<{ orbitRadius: number; speed: number }> = ({ orbitRadius, speed }) => {
const gltf = useGLTF('/models/star2.glb');
const ref = useRef<THREE.Group>(null);

useFrame(({ clock }) => {
if (ref.current) {
const time = clock.getElapsedTime();
const x = orbitRadius * Math.cos(time * speed + Math.PI / 3);
const z = orbitRadius * Math.sin(time * speed + Math.PI / 3);
ref.current.position.set(x, 0, z);

ref.current.rotation.y = time * 0.5;
const scale = 0.5 + Math.abs(Math.sin(time * 2)) * 0.05;
ref.current.scale.set(scale, scale, scale);
}
});

return <primitive ref={ref} object={gltf.scene} />;
};

const Star3: React.FC<{ orbitRadius: number; speed: number }> = ({ orbitRadius, speed }) => {
const gltf = useGLTF('/models/star3.glb');
const ref = useRef<THREE.Group>(null);

useFrame(({ clock }) => {
if (ref.current) {
const time = clock.getElapsedTime();
const x = orbitRadius * Math.cos(time * speed + (2 * Math.PI) / 3);
const z = orbitRadius * Math.sin(time * speed + (2 * Math.PI) / 3);
ref.current.position.set(x, 0, z);

ref.current.rotation.y = time * 0.5;
const scale = 0.5 + Math.abs(Math.sin(time * 2)) * 0.05;
ref.current.scale.set(scale, scale, scale);
}
});

return <primitive ref={ref} object={gltf.scene} />;
};

const Earth: React.FC<{ position: [number, number, number] }> = ({ position }) => {
const gltf = useGLTF('/models/earth/scene.gltf');
const ref = useRef<THREE.Group>(null);

useFrame(() => {
if (ref.current) {
ref.current.rotation.y += 0.008; // 천천히 회전
}
});

return <primitive ref={ref} object={gltf.scene} position={position} scale={1.5} />;
};

const GltfViewer: React.FC<GltfViewerProps> = ({
size = { width: '100%', height: '100%' },
}) => {
return (
<div style={{ width: size.width, height: size.height }}>
<Canvas>
{/* 조명 설정 */}
<ambientLight intensity={0.8} /> {/* 주변 조명 */}
<directionalLight position={[5, 5, 5]} intensity={0.5} /> {/* 방향 조명 */}

{/* GLTF 모델 컴포넌트 */}
<Earth position={[0, 0, 0]} />
<Star orbitRadius={3} speed={2} />
<Star2 orbitRadius={3} speed={2} />
<Star3 orbitRadius={3} speed={2} />

{/* 카메라 컨트롤 */}
<OrbitControls />
</Canvas>
</div>
);
};

export default GltfViewer;

 

 

3. GltfViewer를 실제 page 에 사용하기

원하는 곳에 다음 코드를 작성하면 됩니다.

{/* GltfViewer: 절대 위치 */}
<div className="w-full h-full">
<GltfViewer/>
</div>

 

 

4. 결과 영상

 

 

728x90