React Three Fiber – 3D Text
- Text3D, Center 같은 drei 헬퍼 사용
- Matcap 머티리얼 활용
- Donut 100개 랜덤 생성 및 최적화
- 애니메이션 (useFrame) 적용
1. 기본 환경 설정
export default function Experience() {
return (
<>
<Perf position="top-left" />
<OrbitControls makeDefault />
{/* 기본 텍스트 (폰트 필요) */}
<Text3D font="./fonts/helvetiker_regular.typeface.json">
HELLO R3F
<meshNormalMaterial />
</Text3D>
</>
);
}
- <Text3D>: drei 제공, 내부적으로 TextGeometry 사용
- font="./fonts/helvetiker_regular.typeface.json": JSON 폰트 지정
- <meshNormalMaterial />: 기본적으로 법선 기반 머티리얼
2. 텍스트 중앙 정렬 (Center)
<Center>
<Text3D
font="./fonts/helvetiker_regular.typeface.json"
size={0.75}
height={0.2}
curveSegments={12}
bevelEnabled
bevelThickness={0.02}
bevelSize={0.02}
bevelOffset={0}
bevelSegments={5}>
HELLO R3F
<meshNormalMaterial />
</Text3D>
</Center>
- <Center>: 자동 중앙 정렬
- size, height, bevel*: TextGeometry의 파라미터 그대로 사용 가능
3. Matcap 머티리얼 적용
import { useMatcapTexture } from '@react-three/drei';
export default function Experience() {
// @react-three/drei 내부에 matcap texture 컬렉션에 포함된 matcap을 CDN으로 로딩함
const [matcapTexture] = useMatcapTexture(
'7B5254_E9DCC7_B19986_C8AC91', // matcap ID
256, // 해상도
);
return (
<Center>
<Text3D
font="./fonts/helvetiker_regular.typeface.json"
size={0.75}
height={0.2}>
HELLO R3F
<meshMatcapMaterial matcap={matcapTexture} />
</Text3D>
</Center>
);
}
- useMatcapTexture(ID, size): matcap 자동 로드
- <meshMatcapMaterial matcap={...} />: 재질 적용
4. Donuts 추가
단일 Donut
<mesh>
<torusGeometry />
<meshMatcapMaterial matcap={matcapTexture} />
</mesh>
100개 랜덤 Donut
{
[...Array(100)].map((_, index) => (
<mesh
key={index}
position={[
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
]}
scale={0.2 + Math.random() * 0.2}
rotation={[Math.random() * Math.PI, Math.random() * Math.PI, 0]}>
<torusGeometry />
<meshMatcapMaterial matcap={matcapTexture} />
</mesh>
));
}
- key={index}: React warning 방지
- Math.random() 활용해 위치/스케일/회전 무작위 설정
5. 성능 최적화
문제점
- Donut마다 geometry, material 생성 → 퍼포먼스 저하
- 실제로는 동일한 torusGeometry, 동일한 material을 공유하면 충분
개선 (Three.js 변수 공유)
import * as THREE from 'three';
const torusGeometry = new THREE.TorusGeometry(1, 0.6, 16, 32);
const material = new THREE.MeshMatcapMaterial();
export default function Experience() {
const [matcapTexture] = useMatcapTexture('7B5254_E9DCC7_B19986_C8AC91', 256);
useEffect(() => {
matcapTexture.colorSpace = THREE.SRGBColorSpace;
matcapTexture.needsUpdate = true;
material.matcap = matcapTexture;
material.needsUpdate = true;
}, []);
return (
<>
<Center>
<Text3D
font="./fonts/helvetiker_regular.typeface.json"
size={0.75}
height={0.2}
material={material}>
HELLO R3F
</Text3D>
</Center>
{[...Array(100)].map((_, index) => (
<mesh
key={index}
geometry={torusGeometry}
material={material}
position={[
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
]}
scale={0.2 + Math.random() * 0.2}
rotation={[Math.random() * Math.PI, Math.random() * Math.PI, 0]}
/>
))}
</>
);
}
- geometry, material → 함수 밖에서 생성, 모든 mesh 공유
- useEffect: matcapTexture 설정 및 업데이트
needsUpdate = true 설정하면:
- 속성을 바꾸면 셰이더를 다시 컴파일해야 하기 때문에 반드시 업데이트 필요.
- 처음 텍스처를 로드했을 때 → 자동으로 GPU에 업로드 되지만 중간에 텍스처 속성(wrapS, wrapT, colorSpace, minFilter 등)을 변경하면, GPU에 올라간 데이터와 CPU 쪽 데이터가 불일치.
- 다음 렌더링 프레임에서 Three.js가 감지해서 CPU 메모리에 있는 텍스처 데이터를 다시 GPU VRAM으로 업로드(갱신).
6. Donuts 애니메이션
방법 1 – group 사용
const donutsGroup = useRef()
<group ref={donutsGroup}>
{ [...Array(100)].map((_, i) =>
<mesh key={i} geometry={torusGeometry} material={material} />
)}
</group>
useFrame((_, delta) => {
for (const donut of donutsGroup.current.children) {
donut.rotation.y += delta * 0.2
}
})
방법 2 – 배열 ref 사용 (더 정석)
const donuts = useRef([]);
{
[...Array(100)].map((_, i) => (
<mesh
key={i}
ref={(el) => (donuts.current[i] = el)}
geometry={torusGeometry}
material={material}
/>
));
}
useFrame((_, delta) => {
for (const donut of donuts.current) {
donut.rotation.y += delta * 0.2;
}
});
- useRef([]): 초기값 빈 배열
- ref={(el) => donuts.current[i] = el}: 각 mesh를 배열에 저장
- useFrame: 매 프레임 회전 애니메이션 적용
📌 핵심 요약
- 텍스트: <Text3D> + <Center>로 간단하게 구현 가능
- 재질: useMatcapTexture + <meshMatcapMaterial>로 깔끔한 스타일
- 도넛 생성: Array(100).map() → 랜덤 위치/크기/회전
- 성능 최적화: geometry/material 공유 → draw call 최소화
- 애니메이션: useFrame + ref로 객체 반복 제어 가능

import {
useMatcapTexture, // Drei 제공: matcap 텍스처를 간단히 불러오기
Center, // Drei 제공: 자식 오브젝트를 (0,0,0) 중심에 배치
Text3D, // Drei 제공: 3D 텍스트 생성
OrbitControls, // Drei 제공: 마우스로 카메라 회전/줌/이동 제어
} from '@react-three/drei';
import { useFrame } from '@react-three/fiber'; // R3F 루프 훅 (매 프레임 호출)
import { Perf } from 'r3f-perf'; // 성능 모니터링 UI
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
// ----------------------------------------------------
// ✅ 성능 최적화를 위해 매 프레임마다 Geometry/Material을 새로 만들지 않고
// 바깥에서 미리 생성해둠. (Native Three.js 스타일 최적화)
// ----------------------------------------------------
const torusGeometry = new THREE.TorusGeometry();
const material = new THREE.MeshMatcapMaterial();
export default function Experience() {
// ----------------------------------------------------
// useMatcapTexture(ID, size)
// - drei에서 제공하는 Matcap 컬렉션을 CDN으로부터 로드
// - 첫 번째 인자: matcap의 고유 ID (PNG 파일명 기반)
// - 두 번째 인자: 텍스처 해상도(px) → 보통 64/128/256/512
// ----------------------------------------------------
const [matcapTexture] = useMatcapTexture(
'7B5254_E9DCC7_B19986_C8AC91', // 고유 matcap ID (갈색/베이지 톤)
256, // 해상도(px)
);
// ----------------------------------------------------
// useEffect: 텍스처와 머티리얼 초기화
// - R3F는 colorSpace 자동 설정을 해주지만,
// Native Three.js처럼 직접 관리할 때는 sRGB 세팅 필요.
// ----------------------------------------------------
useEffect(() => {
matcapTexture.colorSpace = THREE.SRGBColorSpace; // 색공간 보정
matcapTexture.needsUpdate = true;
material.matcap = matcapTexture; // 불러온 텍스처를 머티리얼에 적용
material.needsUpdate = true;
}, []);
// ----------------------------------------------------
// Ref 배열 활용: 여러 개의 mesh를 회전시키기 위해 개별 참조 저장
// - donuts.current = [mesh1, mesh2, mesh3, ...]
// ----------------------------------------------------
const donuts = useRef([]);
// ----------------------------------------------------
// useFrame: 매 프레임 호출
// - delta: 이전 프레임과의 시간 차 (초 단위)
// - 모든 도넛 mesh를 순회하며 회전값을 갱신
// ----------------------------------------------------
useFrame((state, delta) => {
for (const donut of donuts.current) {
donut.rotation.y += delta * 0.1;
}
});
return (
<>
{/* FPS 및 GPU 메모리 등 성능 정보 표시 */}
<Perf position="top-left" />
{/* 카메라 제어기 */}
<OrbitControls makeDefault />
{/* Center: 자식들을 중심좌표에 맞춤 */}
<Center>
{/* Text3D: Drei 제공, 3D 폰트 출력 */}
<Text3D
material={material} // 미리 만든 matcap 머티리얼 적용
font="./fonts/helvetiker_regular.typeface.json" // JSON 폰트
size={0.75} // 텍스트 크기
height={0.2} // 깊이(Extrude)
curveSegments={12} // 곡선 분할 수 (품질에 영향)
bevelEnabled // 베벨(엣지 라운딩) 활성화
bevelSize={0.02}
bevelThickness={0.02}
bevelOffset={0}
bevelSegments={5}>
HELLO R3F
</Text3D>
</Center>
{/* ------------------------------------------------
도넛 100개 생성
- geometry와 material은 재사용
- 각 mesh는 랜덤 위치, 크기, 회전값을 가짐
------------------------------------------------ */}
{[...Array(100)].map((_, index) => (
<mesh
ref={(element) => {
donuts.current[index] = element; // 각 mesh를 배열에 저장
}}
key={index}
geometry={torusGeometry} // 성능 최적화: 미리 정의한 geometry
material={material} // 성능 최적화: 미리 정의한 material
position={[
(Math.random() - 0.5) * 10, // X: -5 ~ +5
(Math.random() - 0.5) * 10, // Y: -5 ~ +5
(Math.random() - 0.5) * 10, // Z: -5 ~ +5
]}
scale={0.2 + Math.random() * 0.2} // 크기: 0.2 ~ 0.4
rotation={[Math.random() * Math.PI, Math.random() * Math.PI, 0]} // 초기 회전
/>
))}
</>
);
}'Graphic > R3F' 카테고리의 다른 글
| 62 Pointer Events (Mouse Events) (7) | 2025.08.18 |
|---|---|
| 61 Portal Scene (0) | 2025.08.18 |
| 59 Load models(GLTF 모델 로딩과 애니메이션) (2) | 2025.08.18 |
| 58 R3F Environment and Staging (5) | 2025.08.16 |
| 정적 렌더링을 통해 원하는 시기에만 frame 갱신하기 (frameloop="demand") (7) | 2025.08.16 |