포트폴리오 사이트를 업데이트하던 중 3D 그래픽적인 요소를 넣으면 좋을 것 같다는 생각이 들었다.
먼저 간단한 도형을 구현하는 것부터 시작해보자
1. 프로젝트 설정 및 기본 코드
먼저 three.js를 React와 TypeScript 환경에서 사용하는 기본적인 방법을 알아보자. TypeScript에서는 three.js의 객체에 적절한 타입을 설정해줘야 한다. useRef와 useEffect 훅을 사용해 three.js 객체를 안전하게 다룰 수 있다.
프로젝트 설치
프로젝트를 시작하려면 three.js와 react-three-fiber 패키지를 설치해야 한다.
다음 명령어를 터미널에 입력하여 설치하자:
npm install three @react-three/fiber
이 명령어는 three.js와 react-three-fiber 라이브러리를 설치해준다. 이 후 TypeScript에서 해당 라이브러리들을 사용할 수 있다.
기본적인 three.js 설정
먼저 scene, camera, renderer를 설정하고, boxGeometry를 이용해 큐브를 만든다. renderer의 크기를 설정하고, canvas 요소를 DOM에 추가한 뒤, 큐브를 씬에 추가한다. 그런 다음, 큐브가 회전하도록 애니메이션을 설정한다.
기본 예제 (TypeScript)
// App.tsx
import React, { useRef, useEffect } from "react";
import * as THREE from "three";
const App: React.FC = () => {
const mountRef = useRef<HTMLDivElement | null>(null); // 3D 요소를 렌더링할 DOM 요소를 참조
useEffect(() => {
if (!mountRef.current) return; // mountRef가 null인 경우 처리
// 씬(Scene), 카메라(Camera), 렌더러(Renderer) 설정
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
// 렌더러 크기 설정
renderer.setSize(window.innerWidth, window.innerHeight);
mountRef.current.appendChild(renderer.domElement); // 캔버스 DOM에 추가
// 큐브(메쉬) 만들기
const geometry = new THREE.BoxGeometry(); // 큐브 형태
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 초록색 재질
const cube = new THREE.Mesh(geometry, material);
scene.add(cube); // 씬에 큐브 추가
camera.position.z = 5; // 카메라 위치 설정
// 애니메이션 루프
const animate = () => {
requestAnimationFrame(animate);
// 큐브 회전
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// 씬과 카메라로 렌더링
renderer.render(scene, camera);
};
animate(); // 애니메이션 시작
// 컴포넌트가 언마운트될 때 렌더러를 정리
return () => {
mountRef.current?.removeChild(renderer.domElement);
};
}, []); // 빈 배열을 넣어 최초 한번만 실행되게 함
return (
<div ref={mountRef} />
);
};
export default App;
useRef<HTMLDivElement | null>(null)
:useRef
훅을 사용하여div
요소를 참조하는데,HTMLDivElement
타입을 지정하여 해당 DOM 요소에 대한 타입을 안전하게 설정합니다.THREE
네임스페이스:three.js
의 객체들(예:Scene
,Camera
,Renderer
)은THREE
네임스페이스 내에 정의되어 있습니다.three.js
의 타입 정의가 이미 잘 되어 있어서, TypeScript는 이 네임스페이스를 기반으로 객체를 관리합니다.animate
함수:requestAnimationFrame
을 사용하여 애니메이션을 업데이트합니다.
2. 컴포넌트화 및 상태 관리
TypeScript에서 three.js 객체를 컴포넌트화하고 상태를 관리할 때는 각 객체에 타입을 명확하게 지정해주는 것이 중요하다. 이를 통해 코드의 가독성을 높이고, 타입 안정성을 보장할 수 있다.
회전하는 큐브 컴포넌트 만들기
Cube 컴포넌트를 만들어 회전하는 큐브를 구현한다. color prop을 받아 큐브의 색을 지정할 수 있게 하여, 재사용 가능한 컴포넌트를 만든다.
// Cube.tsx
import React, { useRef, useEffect } from "react";
import * as THREE from "three";
import { useFrame } from "@react-three/fiber";
interface CubeProps {
color: string;
}
const Cube = ({ color }: CubeProps) => {
const meshRef = useRef<THREE.Mesh | null>(null);
useEffect(() => {
if (meshRef.current) {
meshRef.current.rotation.x = 0.5;
meshRef.current.rotation.y = 0.5;
}
}, []);
useFrame(() => {
if (meshRef.current) {
meshRef.current.rotation.x += 0.01;
meshRef.current.rotation.y += 0.01;
}
});
return (
<mesh ref={meshRef}>
<boxGeometry args={[1, 1, 1]} />
<meshBasicMaterial color={color} />
</mesh>
);
};
export default Cube;
App.tsx에서 Cube 컴포넌트 사용하기
Canvas 컴포넌트를 사용해 3D 환경을 설정하고, Cube 컴포넌트를 포함시켜 화면에 회전하는 큐브를 렌더링한다.
// App.tsx
import React from "react";
import { Canvas } from "@react-three/fiber"; // react-three-fiber 라이브러리 사용
import Cube from "./Cube";
const App = () => {
return (
<Canvas>
<Cube color="red" />
</Canvas>
);
};
export default App;
이 예제에서는 Cube
컴포넌트에 color
prop을 전달하고, 이를 바탕으로 three.js
의 큐브 객체를 생성합니다. CubeProps
타입을 사용하여 prop을 명확하게 정의하고, useRef
로 THREE.Mesh
객체에 접근합니다.
3. react-three-fiber로 3D 모델 만들기
react-three-fiber는 three.js를 React 스타일로 관리할 수 있게 도와주는 라이브러리로, JSX 문법을 사용해 3D 객체를 정의할 수 있다. 이를 통해 three.js의 복잡한 API를 간단히 사용할 수 있다.
import { Canvas } from '@react-three/fiber';
const App= () => {
return (
<Canvas>
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshBasicMaterial color="orange" />
</mesh>
</Canvas>
);
};
예제: 나무 3D 모델링
다음으로, 간단하게 나무를 만들어보자
import React from "react";
import { Canvas } from "@react-three/fiber";
import * as THREE from "three";
const TreeTrunk = () => {
return (
<mesh position={[0, 1, 0]} castShadow>
<cylinderGeometry args={[0.2, 0.2, 2, 32]} /> {/* 나무 줄기 */}
<meshStandardMaterial color="sienna" />
</mesh>
);
};
const TreeLeaves = () => {
return (
<group>
<mesh position={[0, 2.5, 0]} castShadow> {/* 잎 아래 부분 */}
<sphereGeometry args={[0.8, 32, 32]} />
<meshStandardMaterial color="green" />
</mesh>
<mesh position={[0, 3.2, 0]} castShadow> {/* 잎 위 부분 */}
<sphereGeometry args={[0.6, 32, 32]} />
<meshStandardMaterial color="green" />
</mesh>
</group>
);
};
const Tree = () => {
return (
<group>
<TreeTrunk />
<TreeLeaves />
</group>
);
};
const ForestScene= () => {
return (
<Canvas shadows camera={{ position: [0, 5, 10], fov: 40 }}>
<ambientLight intensity={0.5} />
<directionalLight
position={[5, 10, 5]}
castShadow
intensity={0.7}
shadow-mapSize-width={1024}
shadow-mapSize-height={1024}
/>
{/* 바닥 */}
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
<planeGeometry args={[20, 20]} />
<shadowMaterial opacity={0.3} />
</mesh>
<Tree />
</Canvas>
);
};
export default ForestScene;
완성!
다음에는 기존에 있는 모델들을 가져와서 불러오는 작업을 공부해봐야겠다.
'Front-End > React' 카테고리의 다른 글
[React] Hook 상태관리와 성능최적화 (0) | 2024.05.12 |
---|---|
[React] 생명주기 (0) | 2024.05.11 |
[React] full-page 라이브러리 TypeScript 지원 안되는 에러 (0) | 2024.03.19 |