@react-three/fiber로 Next.js에서 three.js 사용하기

Youngeui Hong·2024년 1월 20일
2

three.js

목록 보기
1/1
post-thumbnail

🚀 시작하기

+ 의존성 추가

yarn add three @react-three/fiber @react-spring/three @react-three/drei 
yarn add @types/three --dev

📝 nextConfig.js 수정

그리고 nextConfig.js에서 transpilePackages를 수정해주었다.

기본적으로 Next.js는 외부 패키지를 트랜스파일하지 않는데, transpilePackages에 추가하면 해당 외부 패키지를 트랜스파일 대상에 포함시킬 수 있다.

three.js를 트랜스파일 대상에 포함시키는 것은 번들 사이즈를 줄이기 위해서이다.

트랜스파일되지 않은 코드는 번들링되어서 브라우저로 전송되는데, Three.js와 같이 큰 라이브러리의 경우 트랜스파일 대상에 포함시키는 것이 좋다.

/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ["three"],
};

module.exports = nextConfig;

💚 기본 요소

@react-three/fiber (React Three Fiber)는 React 환경에서 three.js를 사용하기 쉽게 해주는 라이브러리이다.

🔻 Canvas

만약 three.js만을 사용할 경우 아래와 같이 SceneCamera을 생성하고, animate()와 같이 render loop를 설정해줘야 한다.

👉🏻 three.js만을 사용한 경우

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)

const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)

function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}

animate()

하지만 @react-three/fiberCanvas를 사용하면 위의 코드는 아래와 같이 간단한 코드로 바꿀 수 있다.

👉🏻 @react-three/fiberCanvas를 사용한 경우

  return (
    <div id="canvas-container">
      <Canvas />
    </div>
  )

🔻 Mesh

<mesh /> 태그는 new THREE.Mesh()와 동일하게 동작한다.

Mesh 컴포넌트는 geometrymaterial을 결합하여 3D 객체를 표현하는 데에 사용된다.

⭐️ geometry
three.js에서 geometry란 상자, 구, 원통 등과 같은 기하학적 모양을 의미한다.

⭐️ material
three.js에서 material은 표면의 속성, 즉 재질을 의미한다. 색상, 빛 반사 등을 material을 통해 설정할 수 있다.


이제 React Three Fiber 공식문서의 예제를 따라 만들어보면서 기초를 익혀보자.

🎨 배경 및 카메라 셋팅

"use client";
import { Canvas } from "@react-three/fiber";
import {
  CameraControls,
  Environment,
  PerspectiveCamera,
} from "@react-three/drei";
import { Box, Cactus, Camera, Level, Sudo } from "@/components/Scene";

export default function Home() {
  return (
    <Canvas flat>
      <CameraControls minPolarAngle={0} maxPolarAngle={Math.PI / 1.6} />
      <ambientLight intensity={Math.PI / 2} />
      <group scale={20} position={[0, -10, 0]}>
        <Level />
        <Sudo />
        <Camera />
        <Cactus />
        <Box position={[-0.8, 1.4, 0.4]} scale={0.15} />
      </group>
      <Environment preset="city" background blur={1} />
      <PerspectiveCamera makeDefault position={[80, 20, 80]} />
    </Canvas>
  );
}

위에서 CameraControls, Environment, PerspectiveCamera@react-three/drei의 컴포넌트이다.

💡 @react-three/drei
@react-three/drei@react-three/fiber를 더 사용하기 쉽게끔 만들어주는 라이브러리라고 할 수 있다. 자세한 설명은 GitHub에서 읽을 수 있고, Storybook에서 값을 변경해가면서 각 컴포넌트를 테스트해볼 수 있다.

🔻 Environment

우선 Environment는 three.js의 scene.environment과 동일한 컴포넌트이다.

preset을 사용하면 내장된 배경을 사용할 수 있어서 편리했다.

숲, 도시, 방 등 꽤 다양한 옵션을 선택할 수 있었고, blur를 1까지 높이면 그라데이션 배경을 연출할 수 있어서 좋았다.

🔻 CameraControls

CameraControlsTHREE.OrbitControls의 기능 및 부가적인 기능을 제공하는 컴포넌트이다.

CameraControls를 사용하면 타겟을 중심으로 한 카메라의 궤도를 설정할 수 있다.

🔻 PerspectiveCamera

three.js에는 다양한 종류의 카메라가 있는데, 그 중 가장 일반적으로 사용되는것이 PerspectiveCamera인 것 같다.

이 외에도 2차원을 표시할 때 사용되는 OrthographicCamera, VR에 사용되는 ArrayCamera, StereoCamera 등이 있다.

🧊 3D 모델 로드하기

🔻 useGLTF

3D 모델의 경우 PolyHaven, SketchFab에서 무료 모델을 다운 받거나, Blender에서 직접 만들 수도 있다.

이 모델을 로드하려면 @react-three/dreiuseGLTF를 사용하면 되는데, 이 훅은 @react-three/fiberuseLoaderGLTFLoader를 인자로 전달하는 것과 동일하게 동작해서, .gltf 또는 .glb 파일을 로드할 수 있도록 해준다.

💡 .gltf / .glb 파일이란?
.gltf (GL Transmission Format)는 3D 그래픽 콘텐츠를 전송하기 위한 오픈 표준 파일 포맷이다. .gltf 파일은 JSON 형식으로 작성된 메타 데이터와 바이너리 형식의 외부 자원 파일들로 구성된다.
반면 .glb 파일은 JSON 형식의 glTF 파일과 관련된 자원들을 하나의 바이너리 파일로 통합하여 표현한 파일이다. 이 타입을 사용하면 파일의 크기를 줄이고 로딩 속도를 향상시킬 수 있다.

useGLTF(GLTF & ObjectMap)[]을 리턴하는데, ObjectMap에는 nodesmaterials 정보가 들어있다.

nodes는 모델을 구성하는 노드들의 계층 구조를 보여주고, materials는 객체의 시각적인 정보가 담겨 있다.

아래와 같이 3D 모델이 구성되어 있는 상태에서, 예를 들어 선인장의 geometry 값은 nodes.Cactus.geometry와 같은 방식으로 가지고 올 수 있다.

🤹 애니메이션 효과 넣기

위의 GIF을 보면 큐브, 선인장, 카메라, 강아지의 고개가 움직이는 것을 확인할 수 있다. 이러한 애니메이션 효과를 어떻게 넣을 수 있는지 살펴보자.

🔻 @react-three/drei의 Shaders 활용하기

@react-three/drei의 Shaders를 활용하면 애니메이션, 그림자, 반사 효과 등을 줄 수 있다.

예를 들어 아래 코드와 같이 MeshWobbleMaterial을 사용하면 객체가 흔들리는 효과를 구현할 수 있다.

import { MeshWobbleMaterial, useGLTF } from "@react-three/drei";

export function Cactus() {
  const { nodes, materials } = useGLTF("/glb/level-react-draco.glb");
  return (
    <mesh
      geometry={nodes.Cactus.geometry}
      position={[-0.42, 0.51, -0.62]}
      rotation={[Math.PI / 2, 0, 0]}
    >
      <MeshWobbleMaterial factor={0.6} map={materials.Cactus.map} />
    </mesh>
  );
}

🤔 Math.PI?
rotation 관련 코드에서 Math.PI가 많이 등장하는데, 이는 Math.PI가 180도를 의미하기 때문이다.

🔻 @react-spring/three 활용하기

또는 애니메이션 라이브러리인 @react-spring/three를 활용하는 방법도 있다.

아래 코드는 Camera의 주기적으로 z축을 회전시키는 코드이다.

참고로 <a.group>에서 a는 "animated"의 약자이다.

import { a, useSpring } from "@react-spring/three";

export function Camera() {
  const { nodes, materials } = useGLTF("/glb/level-react-draco.glb");
  const [spring, api] = useSpring(
    () => ({ "rotation-z": 0, config: { friction: 40 } }),
    [],
  );
  
  useEffect(() => {
    let timeout;
    const wander = () => {
      api.start({ "rotation-z": Math.random() });
      timeout = setTimeout(wander, (1 + Math.random() * 2) * 800);
    };
    wander();
    return () => clearTimeout(timeout);
  }, []);
  
  return (
    <a.group
      position={[-0.58, 0.83, -0.03]}
      rotation={[Math.PI / 2, 0, 0.47]}
      {...spring}
    >
      <mesh geometry={nodes.Camera.geometry} material={nodes.Camera.material} />
      <mesh geometry={nodes.Camera_1.geometry} material={materials.Lens} />
    </a.group>
  );
}

0개의 댓글