개발자일기/3D 웹 세상

React와 Three.js의 결합: react-three-fiber

뫙뭉 2024. 11. 8. 15:53
반응형

 

React는 컴포넌트 기반의 웹 개발에서 널리 사용되는 프레임워크로, 상태 관리와 컴포넌트 재사용성을 강화하여 웹 애플리케이션 개발을 단순화하고 있다. 하지만, 3D 그래픽을 React 환경에서 쉽게 구현하기 위해 등장한 것이 바로 react-three-fiber다. 이번 글에서는 react-three-fiber가 등장하게 된 배경, 역할, 그리고 대표적인 API와 특징들을 살펴본다.

 

이전 글

1. [개발자일기/3D 웹 세상] - 웹에서의 3D 그래픽

2. [개발자일기/3D 웹 세상] - GPU와 렌더링의 관계: GPU가 하는 일

3. [개발자일기/3D 웹 세상] - Three.js의 기본 개념과 원리

 


 

react-three-fiber가 등장하게 된 배경

 

Three.js는 WebGL을 기반으로 3D 그래픽을 브라우저에서 쉽게 구현할 수 있도록 돕는 라이브러리다. 하지만, React와 함께 사용하려고 하면, Three.js의 상태 관리와 React의 상태 관리가 충돌하거나 코드의 일관성을 유지하기 어려운 경우가 발생한다.

 

몇가지 문제들을 예를 들어보자.

Three.js의 애니메이션은 애니메이션 루프(Animation Loop)에서 매 프레임마다 객체를 업데이트하여 동작하게 된다. 예를 들어, Three.js로 큐브를 회전시키는 애니메이션을 구현하려고 할 때 매 프레임마다 큐브의 회전 상태를 업데이트해야 한다.

1. Three.js의 상태 업데이트와 React의 비동기 상태 관리의 차이

React는 컴포넌트 기반의 비동기 상태 관리를 통해 상태가 변경될 때마다 리렌더링이 발생한다. React는 상태가 변경되면 가상 DOM이 업데이트되고, 필요한 경우 실제 DOM과 비교하여 변경사항을 반영한다. 반면, Three.js는 DOM을 관리하지 않고, GPU에서 직접 3D 그래픽을 렌더링한다.

이로 인해 React와 Three.js를 직접 결합할 때, 애니메이션을 구현하려고 상태를 업데이트할 때마다 React 컴포넌트가 계속 리렌더링되는 문제가 발생할 수 있다. 이는 성능 저하를 초래하며, 특히 매 프레임마다 상태가 업데이트되어야 하는 Three.js 애니메이션에서는 큰 문제를 일으킨다.

 

2. 애니메이션 루프에서 React의 상태 관리 충돌 예시

React 컴포넌트에서 Three.js로 만든 큐브를 매 프레임마다 회전시키는 코드를 예시로 보자.

 

import React, { useState, useEffect } from 'react';
import * as THREE from 'three';

function RotatingCube() {
  const [rotation, setRotation] = useState({ x: 0, y: 0 });
  let cube;

  useEffect(() => {
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = 5;

    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    function animate() {
      requestAnimationFrame(animate);

      // React 상태로 회전 업데이트
      setRotation(prevRotation => ({
        x: prevRotation.x + 0.01,
        y: prevRotation.y + 0.01,
      }));

      // Three.js에서 회전 설정
      cube.rotation.x = rotation.x;
      cube.rotation.y = rotation.y;

      renderer.render(scene, camera);
    }

    animate();

    return () => document.body.removeChild(renderer.domElement);
  }, [rotation]);

  return null;
}
 

위 코드에서는 매 프레임마다 setRotation을 호출하여 rotation 상태를 업데이트하고, 업데이트된 rotation 값을 Three.js 큐브 객체의 회전에 적용하고 있다. 이 접근 방식에는 두 가지 문제가 있다.

  1. 무한 리렌더링: React의 상태 업데이트는 컴포넌트의 리렌더링을 유발하기 때문에, 애니메이션 루프에서 setRotation을 계속 호출하면 컴포넌트가 무한히 리렌더링되며 성능이 크게 저하된다.
  2. 애니메이션 속도 차이: React의 상태 업데이트는 가상 DOM과 실제 DOM을 비교하여 최종 업데이트를 적용하기 때문에 Three.js의 애니메이션 루프와 동기화되지 않고, 애니메이션이 끊기거나 속도가 느려지는 문제가 발생할 수 있다.

이러한 문제들을 해결하기 위해, React의 컴포넌트 구조와 상태 관리 방식을 그대로 유지하면서 Three.js를 더 자연스럽게 결합하려는 필요성에서 react-three-fiber가 탄생했다.

react-three-fiber는 React 컴포넌트 시스템을 활용해 Three.js 객체를 컴포넌트로 표현할 수 있도록 하여, React 개발자가 익숙한 방식으로 3D 그래픽을 구축하고 관리할 수 있게 한다. 이를 통해 React의 상태 관리 방식에 따라 Three.js의 3D 객체를 제어하고, 컴포넌트 스타일로 3D 장면을 구성하는 데 용이하게 했다.

 

react-three-fiber는 이러한 문제를 해결하기 위해 React와 Three.js의 상태 관리를 분리하여, Three.js의 객체는 React 상태가 아닌 Three.js의 내부 상태에서 관리하도록 설계되었다. Three.js 객체의 애니메이션은 useFrame 훅을 사용하여 GPU에서 직접 제어되고, React의 상태 업데이트와 충돌하지 않도록 한다.

 

import { Canvas, useFrame } from '@react-three/fiber';
import { useRef } from 'react';

function RotatingCube() {
  const meshRef = useRef();

  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]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  );
}

function App() {
  return (
    <Canvas>
      <RotatingCube />
    </Canvas>
  );
}

export default App;
 

위 예제에서 useFrame 훅은 Three.js의 애니메이션 루프와 연결되어 meshRef에 저장된 Three.js 객체의 회전을 직접 제어한다. React의 상태 업데이트 없이 Three.js 객체의 회전을 제어할 수 있어, 애니메이션이 더 부드럽고 성능 저하 없이 작동한다. react-three-fiber는 이런 방식으로 React와 Three.js의 상태 관리가 충돌하지 않도록 돕는다.

 

어떤 식으로 문제를 해결하려했는지 알아봤으니, 본격적으로 react-three-fiber가 하는 역할에 대해 자세히 알아보자


 

react-three-fiber의 역할

 

react-three-fiber는 React와 Three.js를 통합하여, Three.js의 3D 객체를 React의 컴포넌트 구조로 구성할 수 있게 한다. 이를 통해 React의 상태 관리와 이벤트 시스템을 활용하면서도 Three.js의 기능을 그대로 사용할 수 있으며, 코드의 일관성을 유지하면서 3D 그래픽을 다룰 수 있는 장점이 있다.

주요 역할

  1. Three.js를 React 컴포넌트처럼 사용
    • react-three-fiber는 Three.js의 3D 객체들을 React 컴포넌트로 관리할 수 있게 한다. 예를 들어, Three.js에서 사용하는 Scene, Camera, Mesh 등을 React 요소로 표현할 수 있어 더욱 직관적이다.
  2. React의 상태 관리와 연동
    • Three.js의 객체와 애니메이션을 React 상태에 따라 제어할 수 있어 상태가 변할 때마다 장면이 업데이트된다. 이를 통해 애니메이션이나 상호작용이 React의 라이프사이클에 따라 자연스럽게 반응한다.
  3. React의 이벤트와 3D 객체 간 상호작용
    • react-three-fiber는 클릭, 드래그, 호버와 같은 이벤트를 Three.js 객체에 쉽게 연결할 수 있도록 해 준다. 이를 통해 3D 객체에 직접 상호작용을 추가하고, React 컴포넌트처럼 이벤트 핸들러를 작성할 수 있다.

 


 

주요 API와 특징

 

react-three-fiber는 Three.js를 React 방식으로 활용할 수 있게 도와주는 API를 제공한다. 주요 API와 특징을 살펴보자.

 

1.  Canvas 컴포넌트

react-three-fiber의 중심은 Canvas 컴포넌트다. Canvas는 Three.js의 렌더링 컨텍스트를 제공하며, 이 컴포넌트 내부에 Three.js 장면을 구성할 수 있다. React의 최상위 요소처럼 작동하며, 모든 3D 객체가 이 Canvas 안에서 관리된다.

 

import { Canvas } from '@react-three/fiber';

function App() {
  return (
    <Canvas>
      {/* 여기서부터 3D 장면을 구성 */}
    </Canvas>
  );
}

 

 

2.  Three.js 객체를 React 컴포넌트로 사용

Canvas 내에서는 Three.js의 객체들을 React 컴포넌트 형태로 사용할 수 있다. 예를 들어, BoxGeometry, MeshStandardMaterial, Mesh 등을 컴포넌트로 작성할 수 있으며, 이러한 컴포넌트는 각각의 props를 통해 쉽게 제어 가능하다.

 

function Box() {
  return (
    <mesh>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  );
}
 
  • mesh: Three.js의 Mesh 객체로 3D 모델의 기본 단위가 된다.
  • boxGeometry: Box 형태의 기하체를 생성하는 컴포넌트로, args 속성으로 가로, 세로, 깊이를 설정할 수 있다.
  • meshStandardMaterial: Three.js에서 표준 재질을 나타내며, 색상 등의 속성을 지정할 수 있다.

 

3.  애니메이션과 상태 관리

react-three-fiber에서는 애니메이션과 상태 관리를 위해 React의 상태애니메이션 루프를 결합하여 동적인 효과를 쉽게 추가할 수 있다. useFrame 훅을 사용해 매 프레임마다 상태를 업데이트하여 애니메이션을 부드럽게 표현할 수 있다.

 

import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';

function RotatingBox() {
  const meshRef = useRef();

  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]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  );
}
 

useFrame 훅은 매 프레임마다 호출되며, 이를 통해 mesh 객체의 회전을 지속적으로 업데이트할 수 있다.

4.  카메라와 조명 설정

react-three-fiber는 Three.js의 카메라와 조명을 컴포넌트처럼 설정할 수 있다. 카메라는 Canvas 컴포넌트 내에 자동으로 포함되며, 위치를 조정하거나 직접 설정할 수 있다. 또한, Three.js의 ambientLight, pointLight 등을 통해 조명을 추가할 수 있다.

 

<Canvas camera={{ position: [0, 0, 5] }}>
  <ambientLight intensity={0.5} />
  <pointLight position={[10, 10, 10]} />
  <RotatingBox />
</Canvas>
 

 


 

react-three-fiber에 대해 정리하자면!

  1. React 스타일의 컴포넌트 구조: Three.js의 객체를 React 컴포넌트처럼 작성할 수 있어 유지 보수와 확장성이 뛰어나다.
  2. React의 상태와 연동된 애니메이션: React의 상태와 Three.js 객체의 애니메이션을 쉽게 연동할 수 있어 상호작용과 상태 변화가 자연스럽다.
  3. 사용자 친화적인 이벤트 핸들링: 클릭, 드래그와 같은 이벤트를 Three.js 객체에 바로 적용할 수 있어 사용자 친화적이다.
  4. Three.js의 모든 기능을 그대로 사용 가능: Three.js의 기본 기능을 React 방식으로 활용할 수 있으면서도, 필요한 경우 Three.js의 기능을 직접 사용할 수 있다.

 


 

마무리이

 

react-three-fiber는 React 개발자가 익숙한 방식으로 Three.js의 3D 그래픽을 사용할 수 있게 해 주는 강력한 라이브러리다. Three.js와 React를 결합하여 3D 장면을 더욱 직관적이고 관리하기 쉽게 만들었으며, 이를 통해 복잡한 3D 그래픽도 컴포넌트 기반으로 쉽게 다룰 수 있다. 다음 글에서는 react-three-fiber의 사용법을 자세히 다루고, 간단한 예제를 통해 실습해 보겠다.

 
 
반응형