0. Intro 프로젝트 계획 변동
원래 계획은
였습니다.
중앙에 Lottie 가 애니메이션으로 동작하고 주변에 3D 모델링된 별들이 고정된 위치에서 줌인 - 줌아웃 정도의 애니메이션만 동작할 예정이었죠..
하지만 실제 React-Three-Fiber 로 개발하다보니, 별들만 3D모델링을 하는게 쉽지 않았습니다.
같은 Canvas 에 모델들을 넣기 위해서 지구도 3D 모델 즉, Gltf 포맷으로 넣기로 계획을 수정하였습니다.
1. 지구와 별들 리소스 구하기 및 프로젝트에 추가하기
별 리소스 구하는 방법은
https://choi-hee-yeon.tistory.com/232
에 자세히 나와있습니다.
해당 별을 하나만 삽입하는 것을 구현했었는데,
이름이 다른 여러개의 별로 복제하여 프로젝트 public 폴더에 추가합니다.
=> 추후에 여러개의 별을 추가할 때, 같은 파일로 여러개를 추가하지 못합니다.... (경험입니다 ㅜㅜㅜㅜㅜ )
1-1. 지구 리소스 구하기 및 다운로드 받고 프로젝트에 추가하기
https://sketchfab.com/search?features=downloadable&q=earth&type=models
지구는 이 사이트에서 구했습니다.
다양한 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. 결과 영상