Insight3d

React와 Three.js 렌더링 방식의 차이

2026-01-31
ReactThree.jsR3F

1. React 렌더링 사이클 이해하기

  • React는 기본적으로 상태 변화가 렌더링을 유발하는 구조이다.
  • 그래서 기본적인 렌더링 사이클의 핵심은 "상태가 바뀌면 UI를 다시 계산한다"는 점이다.
리액트 렌더링 사이클 흐름 이미지
React 렌더링 사이클

🎯 React의 핵심 목표는

  • 최소한의 변경만 DOM에 반영
  • 불필요한 연산 줄이기
  • 선언적인 UI 관리

여기서 가장 중요한 점은 : React는 "필요할 때만" 화면을 업데이트 한다.
즉, React는 "언제 화면을 다시 그릴 것인가"를 매우 신중하게 결정한다.


2. 3D 씬 렌더링 사이클 이해하기

  • 3D 엔진(Three.js/WebGL)의 렌더링 방식은 완전히 다르다.
  • 기본적으로 매 프레임마다 장면을 다시 그린다.
3D 렌더링 사이클 흐름 이미지
3D 렌더링 사이클

이 과정은 "지속적인 루프" 이다.
즉, React처럼 이벤트 기반이 아니라 항상 돌아가는 애니메이션 루프 구조다.

3D 렌더링 용어 정리

✅ Scene Graph (씬 그래프)

씬에 있는 모든 오브젝트들의 '가족관계도' 같은 트리 구조다

게임 속 방 하나를 떠올려보면
1
게임 속 방 하나를 떠올려보면
- 방
  ㄴ 책상
      ㄴ 컵
      ㄴ 노트북
  ㄴ 의자
  ㄴ 전동

왜 이 구조가 필요할까?

  • 부모(책상)가 움직이면 자식(컵)도 자동으로 따라가게 만들기 위해 이 구조를 사용한다.
  • 그래서 위치, 회전, 크기 계산에 사용되고 이는 3D 요소로서 필수적이다.

✅ Draw Call (드로우 콜)

CPU가 GPU에게 "이거 그려!"라고 요청하는 명령이다.

✅ Draw Call이 많아질수록

  • CPU -> GPU 지시가 늘어남 -> 준비 시간 늘어남 -> 성능 떨어짐
  • 그래서 3D 최적화에서 draw call 줄이기 = 핵심 전략

✅ CPU-side Traversal (CPU 사이드 트래버설)

CPU가 씬에 있는 모든 물체를 하나씩 확인하는 과정

CPU도 매 프레임마다 하나씩 체크 -> 물체가 많아지면?

  • 확인해야 할 대상 증가 ↑ -> CPU 부담 ↑ -> 프레임 드랍 발생

✅ OverDraw (오버드로우)

같은 픽셀을 여러번 그리는 낭비 작업

언제 발생할까?

  • 투명 물체가 많을 때
  • 파티클 효과 많을 때
  • 겹쳐진 오브젝트 많을 때
  • GPU가 같은 픽셀을 여러번 계산 -> 성능 저하 원인

🎾 표로 한눈에 보기

구분React3D 렌더링
렌더링 트리거상태(State)나 Props 변경 시매 프레임마다
렌더링 주기필요할 때만 다시 그림 (비동기적)1초에 60번 반복 (동기적 루프)
관심 대상DOM / UI 트리3D 씬 / 카메라 / 오브젝트
업데이트 방식Virtual DOM 비교 후 변경된 부분만 갱신전체 씬을 매 프레임 다시 렌더링

3. 문제 1 : 병목 -> 성능 이슈

매 프레임 state 업데이트
1
매 프레임 state 업데이트
// 3D 루프는 초당 60번 실행, React도 초당 60번 재렌더 -> JS 메인 스레드 과부하  -> 결과: 프레임 드랍
useFrame(() => {
  setPosition(prev => prev + 1);
});
React 렌더마다 Three.js 객체 재생성
1
React 렌더마다 Three.js 객체 재생성
// 렌더마다 실행되면 -> GPU 리소스 재업로드 -> 메모리 사용 증가 -> draw call 준비 비용 증가
const material = new THREE.MeshStandardMaterial();
씬 구조를 자주 변경하는 경우
1
씬 구조를 자주 변경하는 경우
// React가 오브젝트를 mount/unmount 하면
- Scene graph 재구성 
- CPU traversal 비용 증가 
- draw call 재정렬

4. 문제 2 : 불일치 -> UX 이슈

구체적인 예시 : 사용자가 방 테마를 변경하면 그에 따라 3D 모델 경로도 변경되어야 한다.

1) React vs Three.js : 불일치 구간이 존재

react vs three.js 플로우차트
테마 변경 플로우차트

위 플로우차트를 보면 React와 Three.js가 서로 다른 속도로 움직인다는 걸 알 수 있다.
문제는 이 속도 차이가 단순한 이론이 아니라, 실제 사용자 경험과 성능에 직접적인 영향을 준다는 점이다.

아래는 실제로 자주 발생하는 대표적인 불일치 포인트들이다.

결국 핵심은

  • React는 상태 기준으로 움직이고
  • Three.js는 프레임 기준으로 움직인다.

➡️ 이 둘을 그냥 연결하면 UI는 이미 미래로 갔는데, 3D는 아직 과거에 머무르는 상황이 발생한다.


5. 그래서 필요한 것 — 동기화 레이어

이 문제를 해결하려면

  • React가 3D 프레임 루프에 끌려가지 않도록 하고
  • 3D가 React 상태 변경에 과하게 반응하지 않도록 해야 한다.

즉, 누가 언제 업데이트할지 조율해주는 레이어가 필요하다.


6. R3F는 무엇을 해결해줄까?

R3F는 React와 Three.js 사이의 "브릿지" 역할을 한다.

R3F의 핵심 아이디어

[ React는 “구조만” 관리 ]

  • 어떤 객체가 있어야 하는지만 정의
  • 매 프레임 계산은 하지 않음

[ Three.js는 프레임 루프 유지 ]

  • R3F가 내부적으로 루프 관리
  • React 재렌더와 분리

[ 객체 단위 diff 처리 ]

  • 전체 씬 재생성이 아니라
  • 필요한 부분만 업데이트

✨ 코드 구조는 이렇게 단순해진다 (하지만 “진짜” 중요한 건 내부에서 일어나는 일)

tsx
1
function RoomModel({ modelPath }) {
  const { scene } = useGLTF(modelPath);
  return <primitive object={scene} />;
}

R3F의 가치가 드러나는 지점은 “코드가 짧다”가 아니라,
React 렌더링과 Three 렌더링 사이에서 어떤 레벨로 동기화가 일어나는지다.


🚨 R3F가 “상태 변화”를 처리하는 레벨 : DOM이 아니라 “Three 객체 그래프”

React는 원래 DOM을 대상으로 diff를 계산한다.
R3F는 같은 방식으로, Three.js 객체의 트리를 대상으로 diff를 계산한다.

즉, R3F에서 <mesh /> 같은 JSX는 -> “Three 객체를 생성/수정하는 선언”으로 바뀐다.

그래서 중요한 차이

  • React DOM: div/span 같은 DOM 노드 patch
  • R3F: THREE.Mesh, THREE.Material, THREE.Geometry 같은 Three 객체 patch

이 덕분에 “전체 씬을 갈아끼우는” 게 아니라 바뀐 속성만 객체에 반영한다.


🚨 “렌더링 주도권”을 분리한다: React 렌더 ≠ Three 프레임

직접 구현하면 이런 실수를 하기 쉽다.

  • 애니메이션 값이 바뀔 때마다 setState를 쏴서 React가 60fps로 재렌더
  • 씬을 구성하는 객체를 매 렌더마다 재생성

🚨 R3F의 diff가 강력한 이유 : “객체 재생성”이 아니라 “속성 패치”

예를 들어 position만 바뀌는 상황을 보자.

잘못된 패턴(자주 보이는 병목
1
잘못된 패턴(자주 보이는 병목
// position이 바뀔 때마다 mesh를 새로 만들거나
// material을 매 렌더마다 새로 생성
const material = new THREE.MeshStandardMaterial({ metalness: 0.2 });
// ❌ 렌더마다 새 material 생성 -> GPU 상태 변경/업로드 비용 증가

🚨 로딩 캐싱을 “상태 동기화” 관점으로 보기 : 깜빡임/레이스 컨디션 완화

앞에서 말한 불일치(깜빡임)과 레이스 컨디션은 대부분 “비동기 로딩”이 원인이었다.

즉, 로딩 자체를 없애는 게 아니라, 로딩 구간을 “사용자가 납득 가능한 형태로” 연출할 수 있게 된다.


🚨 dispose는 “자동”이라기보다 “생명주기”를 React 모델로 가져온 것