React와 Three.js를 결합하면 웹에서 상호작용 가능한 3D 경험을 효율적으로 구현할 수 있습니다. 실무에서는 @react-three/fiber(이하 R3F)를 사용해 React 컴포넌트처럼 3D 씬을 선언적으로 구성하는 방식이 유지보수에 유리합니다. 아래는 빠르게 3D 박스와 GLTF 모델을 렌더링하고, 상호작용과 성능 최적화를 적용하는 실전 가이드입니다.
1. 개요
목표는 다음과 같습니다: Canvas로 씬 생성, 박스 메쉬 렌더링 및 회전 애니메이션, OrbitControls로 카메라 조작, GLTF 모델 로딩, 기본적인 이벤트 처리와 성능 최적화입니다.
2. 설치
R3F와 유틸을 설치합니다. three는 필수이며, drei는 컨트롤과 로더 등 편의 컴포넌트를 제공합니다.
// Vite 또는 CRA 프로젝트에서 설치합니다
npm i three @react-three/fiber @react-three/drei
3. 기본 씬 구성
Canvas 내부에 조명, 카메라, 메쉬를 배치합니다. useFrame으로 박스를 매 프레임 회전시킵니다.
import { Canvas, useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function Box() {
const meshRef = useRef()
useFrame((state, delta) => {
if (!meshRef.current) return
meshRef.current.rotation.y += delta
meshRef.current.rotation.x += delta * 0.5
})
return (
<mesh ref={meshRef} castShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
)
}
export default function Scene() {
return (
<Canvas
shadows
dpr={[1, 2]} // 레티나 대응 및 성능 균형
camera={{ position: [3, 3, 3], fov: 50 }}
gl={{ antialias: true, powerPreference: 'high-performance' }}
>
<ambientLight intensity={0.4} />
<directionalLight position={[5, 5, 5]} intensity={1} castShadow />
<Box />
</Canvas>
)
}
4. 상호작용(OrbitControls)
카메라를 마우스로 회전/줌하려면 OrbitControls를 추가합니다. damping을 켜면 움직임이 부드러워집니다.
import { OrbitControls } from '@react-three/drei'
export default function Scene() {
return (
<Canvas camera={{ position: [3, 3, 3], fov: 50 }}>
<ambientLight intensity={0.4} />
<directionalLight position={[5, 5, 5]} intensity={1} />
<Box />
<OrbitControls enableDamping dampingFactor={0.1} />
</Canvas>
)
}
5. 모델 로딩(GLTF)
실무에서는 GLTF/GLB 모델을 많이 사용합니다. public 디렉터리에 모델을 두고 useGLTF로 로딩합니다. Suspense로 로딩 상태를 처리할 수 있습니다.
import { Suspense } from 'react'
import { useGLTF } from '@react-three/drei'
function Model(props) {
const { scene } = useGLTF('/models/robot.glb') // public/models/robot.glb
return <primitive object={scene} {...props} />
}
// 미리 로드(초기 화면 지연 감소)
useGLTF.preload('/models/robot.glb')
export default function Scene() {
return (
<Canvas camera={{ position: [2, 2, 4], fov: 45 }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} intensity={1} />
<Suspense fallback={null}>
<Model position={[0, 0, 0]} />
</Suspense>
<OrbitControls />
</Canvas>
)
}
6. 성능 최적화 체크리스트
3D는 비용이 큽니다. 다음 옵션을 조합해 성능을 확보합니다.
- Canvas dpr을 배열로 설정해 고해상도에서 부하를 낮춥니다(dpr={[1,2]}).
- frameloop="demand"로 변경하면 상태 변화가 없을 때 렌더를 멈출 수 있습니다.
- 그림자(shadow) 사용 시 필요한 오브젝트에만 castShadow/receiveShadow를 설정합니다.
- 재사용 가능한 Geometry/Material은 useMemo로 캐싱하고, 컴포넌트 언마운트 시 dispose를 호출합니다.
- 무거운 모델은 텍스처 압축(KTX2)과 드라코(Draco) 압축을 적용합니다.
import * as THREE from 'three'
import { Canvas } from '@react-three/fiber'
import { useMemo, useEffect } from 'react'
function OptimizedBox() {
const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), [])
const material = useMemo(() => new THREE.MeshStandardMaterial({ color: '#4e9' }), [])
useEffect(() => () => {
geometry.dispose()
material.dispose()
}, [geometry, material])
return <mesh geometry={geometry}></mesh>
}
function Scene() {
return (
<Canvas frameloop="demand" dpr={[1, 2]}>
<ambientLight intensity={0.4} />
<OptimizedBox />
</Canvas>
)
}
7. 이벤트 및 상태 연동
메쉬에 포인터 이벤트를 연결해 하이라이트나 선택을 구현합니다. React 상태로 색상이나 회전 속도를 제어합니다.
import { useState } from 'react'
function SelectableBox() {
const [hovered, setHovered] = useState(false)
const [active, setActive] = useState(false)
return (
<mesh
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
onClick={() => setActive((p) => !p)}
scale={active ? 1.2 : 1}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
8. 좌표계와 카메라 팁
Three.js는 오른손 좌표계를 사용합니다. 기본 단위는 임의의 단위(미터가 아님)이며, 씬에 맞는 스케일을 결정하는 것이 중요합니다. 카메라 near/far는 너무 작거나 크면 Z-fighting이나 깊이 정밀도 문제가 생깁니다. 예: camera={{ near: 0.1, far: 100 }}로 시작하고 상황에 맞게 조정합니다.
9. 흔한 문제 해결
- 모델 경로: Vite/CRA에서는 public 폴더에 GLB/텍스처를 넣고 "/models/xxx.glb"로 참조합니다.
- CORS: 외부 CDN에서 모델을 가져오면 CORS 헤더가 필요합니다. 가능하면 같은 도메인에 배치합니다.
- 성능 급락: 그림자 해상도를 너무 높게 설정하거나, 매 프레임 상태 업데이트를 남발하면 프레임이 떨어집니다. 필요 시 frameloop="demand"로 전환합니다.
- 반사/금속성 표현: 환경맵(HDR)을 로드해야 사실적인 금속 재질이 보입니다. drei의 Environment 컴포넌트를 검토합니다.
10. 배포 팁
- GLTF/GLB를 gltf-transform 등으로 최적화하고 텍스처는 WebP/KTX2로 변환합니다.
- 정적 파일 캐싱(Cache-Control)과 gzip/br 압축을 활성화합니다.
- 초기 로딩을 줄이려면 핵심 씬만 우선 렌더, 나머지 모델은 lazy-load와 Suspense로 후속 로딩합니다.
마무리
React와 Three.js(R3F)를 활용하면 3D 씬을 컴포넌트 단위로 관리하며 빠르게 구현할 수 있습니다. 위 예제를 시작점으로 조명, 머티리얼, 모델 최적화를 단계적으로 적용하면 실무 요구사항을 충분히 충족할 수 있습니다.
'React' 카테고리의 다른 글
| React 앱에서 다국어(i18n) 지원하기 (2) | 2026.04.21 |
|---|---|
| React에서 Service Worker로 PWA 기능 추가하기 (0) | 2026.04.21 |
| React에서 Infinite Scroll 구현하기 (0) | 2026.04.20 |
| React 앱 성능 최적화를 위한 메모이제이션 기법 (0) | 2026.04.19 |
| React에서 WebSocket 연결 구현하기 (0) | 2026.04.18 |