| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 스토어드 프로시저
- String
- 다익스트라
- SQL
- DP
- Dijkstra
- 이진탐색
- two pointer
- Brute Force
- Stored Procedure
- Hash
- MYSQL
- union find
- Two Points
- Trie
- 그래프
- binary search
- Today
- Total
codingfarm
R3F의 3D 컴포넌트 본문
1. 소개
그동안 실무에서 3d 개발을 위해 R3F를 중점적으로 사용해왔다.
캔버스 사이즈 조절, 카메라 시야각 자동 설정, 렌더링 사이클 관리, 메모리 누수 방지, ray-cast 관리 및 이벤트 제공 등 바닐라 Three.js 에서는 제공해주지 않던, 굉장히 많은 편의성 기능들을 누리는 중이다.
이전에는 renderer 생성 -> 캔버스 검색 -> html 엘리먼트 부착 -> 캔버스 사이즈 설정-> 카메라 사이즈 설정 -> 렌더링 사이클 호출 등. 복잡한 과정을 거쳐야 했기에, 간단한 테스트를 하나 수행하기 위해 보일러플레이트 코드를 통째로 복붙해야 했지만, 이제는 그저 <Canvas> 컴포넌트를 만들고, 내부에 원하는 요소들을 추가하기만 하면 된다.
하지만, R3F 컴포넌트를 통해 Three.js의 3d 객체를 정말로 리액트 컴포넌트 다루듯이 사용했다가 오작동 하는 경우가 많았다.
이번 포스팅을 통해 R3F 컴포넌트의 본질과 동작원리, 그리고 안티패턴과 효율적인 사용법들에 대해 작성해보려한다.
2. 특징과 장점
R3F는 React-three-fiber 를 칭하며, Three.js를 React 방식으로 다루게 해주는 라이브러리이다. 즉, 3D 그래픽을 리액트 개발자들에게 익숙한 선언적 방식으로 개발 가능하게 해준다.
또한 아래와 같은 여러 가지 편의성 기능들을 제공해준다.
- Canvas 컴포넌트를 통해 렌더러 생성~캔버스 부착의 과정을 생략
- canvas resize 자체 대응
- 메모리 관리
- 컴포넌트 단위로 scene 구현
- ray cast 자체 관리를 통한 mouse event 기능 제공
- useThree 훅을 통한 주요 요소들(renderer, scene, mouse 등)의 접근 편이성 제공
- 모델 로더용 훅 제공
- 애니메이션 및 렌더링 루 자체 관리
- react 생태계 완전 활용(useState, useRef, zustand 등)
- 물리엔진 자체 연동(cannon.js)
- drei를 통한 생산성 증가(OrbitControls, Text, Stage...)
이외에도 webXR 대응도 간편하게 가능하다는 정보를 봤지만 아직 XR 개발은 경험해보질 못해서 잘 모르겠다.
실제로 바닐라 Three.js 만을 다뤄오던 입장에서, R3F가 제공해주던 기능들이 주는 개발 효율의 증대를 크게 체감할 수 있었다.
그렇기에 요즘에는 대부분의 프로젝트에서는 항상 R3F를 사용하며, 인스턴싱이 동반되는 대규모 렌더링이 필요한 에디터 및 엔진 개발 급의 프로젝트에서도 R3F를 바탕으로 개발을 진행한다.
3. 동작원리
https://r3f.docs.pmnd.rs/tutorials/how-it-works
three.js는 WebGL을 간편하게 사용할 수 있도록 래핑하는 라이브러리이며, R3F는 이 three.js를 react 환경에서 더욱 더 간편하게 사용가능하도록 해주는 렌더러이다.
react-dom이 JSX를 DOM 노드로 생성하는것처럼, R3F는 JSX를 Three.js 객체로 생성하는 작업을 수행해준다.
즉, Fiber 컴포넌트는 THREE 객체 생성 및 scene 추가 작업을 매우 간편하게 수행해준다.
가령 공식 홈페이지에서 제공해주는 코드 차이는 아래와 같다.
import { Canvas } from '@react-three/fiber'
function MyApp() {
return (
<Canvas>
<group>
<mesh>
<meshNormalMaterial />
<boxGeometry args={[2, 2, 2]} />
</mesh>
</group>
</Canvas>
)
}
import * as THREE from 'three'
const scene = new THREE.Scene() // <Canvas>
const group = new THREE.Group() // <group>
const mesh = new THREE.Mesh() // <mesh />
const material = new THREE.MeshNormalMaterial() // <meshNormalMaterial />
const geometry = new THREE.BoxGeometry(2, 2, 2) // <boxGeometry />
mesh.material = material
mesh.geometry = geometry
group.add(mesh)
scene.add(group)
단순히 scene에 추가하는 작업만으로 보았을때에는, 그렇게 큰차이가 느껴지지 않을 수 있다.
하지만 Fiber는 위 작업 이외에도, 카메라 배치, 렌더루프 설정, 포인터 이벤트 관리, 톤매핑, 윈도우 사이즈 재설정 등 일련의 작업을 자체적으로 수행해주므로, Three.js에서 충족하기 위해 필요한 추가 코드 작업량의 차이는 최소 5배는 난다고 봐도 과언이 아니다.
4. 주의사항(지속 업데이트 중)
https://r3f.docs.pmnd.rs/advanced/pitfalls#%E2%9D%8C-setstate-in-useframe-is-bad
이렇듯, r3f는 여러 편의성 기능들을 대거 지원해주며, THREE.js 객체를 리액트 컴포넌트를 통해 다룰 수 있게 해주지만, 일반적인 2D 컴포넌트 다루듯이 사용하기에는 주의사항이 몇가지 따른다.
4-1. useState와 useFrame
useState는 컴포넌트의 리렌더링을 발생시키기 때문에 useFrame 처럼 매프레임마다 동작되는 작업에서 useState 를 갱신하는 작업을 수행해선 안된다. 이는 useState 뿐만 아니라, zustand 처럼 리렌더 트리거를 발생시키거나, 참조불변성을 전제로 하는 자료구조를 다룰때에도 마찬가지다.
4-2. primitive
r3f에서는 이미 생성된 Three.js 객체를 R3F의 JSX트리에 포함시키기 위해 <primitive> 컴포넌트를 사용한다.
주로 useGLTF 등을 통해 로드된 객체를 화면에 배치하기 위해 즐겨 사용된다.
하지만 아래와 같은 주의사항들이 따른다.
- R3F에 의해 메모리 해제 되지 않음
- "생성"이 아닌 "등록"을 수행하기에, 언마운트시에도 geometry와 material을 해제하지 않음
- 오브젝트 참조 교체시 event 가 동작하지 않음
- R3F 이벤트 시스템(레이캐스트)가 예전객체에 붙어있던 이벤트바인딩을 새 객체로 이관하질 못해서 발생
- 해결법 : object 변경시, key를 교체하여 강제로 remount 시키면 이벤트 재등록이 수행
5. 마치며
실무를 통해 R3F에 익숙해져 가는 중이다. 이제는 오히려 Three.js가 불편해서 사용을 꺼리고, R3F를 주로 사용하지만, 아직도 "이게 안돼?" 또는 "이걸 지원 안해?" 같은 순간들에 자주 부딪힌다.
특히 대규모 렌더링을 위해 InstancedMesh를 사용할때면, R3F의 이벤트를 사용하지도 않고, Three.js의 transform 관련 기능들도 대거 사용불가능하게 되어, 굳이 R3F를 써야하나 현자타임을 느끼다가도, R3F의 편리함에 갈팡질팡 하는 순간을 자주 마주한다.
또한 r3f 컴포넌트의 리렌더링과 three.js 객체의 생애 주기는 별개라는 사실이 다소 어색할때가 많다. three.js 객체는 멀쩡하지만, r3f 컴포넌트가 리렌더링 되어 이벤트가 끊어지는 현상을 마주하기도 했고...
그렇기에 "4. 주의사항" 항목은 지속적으로 업데이트 해 나갈 예정이다.
'computer graphics > Three.js' 카테고리의 다른 글
| PostProcessing EffectComposer (0) | 2026.01.01 |
|---|---|
| Post Process (0) | 2025.12.27 |
