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만을 사용할 경우 아래와 같이 Scene
과 Camera
을 생성하고, 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/fiber
의 Canvas
를 사용하면 위의 코드는 아래와 같이 간단한 코드로 바꿀 수 있다.
👉🏻 @react-three/fiber
의 Canvas
를 사용한 경우
return (
<div id="canvas-container">
<Canvas />
</div>
)
Mesh
<mesh />
태그는 new THREE.Mesh()
와 동일하게 동작한다.
Mesh
컴포넌트는 geometry
와 material
을 결합하여 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
CameraControls
는 THREE.OrbitControls
의 기능 및 부가적인 기능을 제공하는 컴포넌트이다.
CameraControls
를 사용하면 타겟을 중심으로 한 카메라의 궤도를 설정할 수 있다.
PerspectiveCamera
three.js에는 다양한 종류의 카메라가 있는데, 그 중 가장 일반적으로 사용되는것이 PerspectiveCamera
인 것 같다.
이 외에도 2차원을 표시할 때 사용되는 OrthographicCamera
, VR에 사용되는 ArrayCamera
, StereoCamera
등이 있다.
useGLTF
3D 모델의 경우 PolyHaven, SketchFab에서 무료 모델을 다운 받거나, Blender에서 직접 만들 수도 있다.
이 모델을 로드하려면 @react-three/drei
의 useGLTF
를 사용하면 되는데, 이 훅은 @react-three/fiber
의 useLoader
에 GLTFLoader
를 인자로 전달하는 것과 동일하게 동작해서, .gltf
또는 .glb
파일을 로드할 수 있도록 해준다.
💡
.gltf
/.glb
파일이란?
.gltf
(GL Transmission Format)는 3D 그래픽 콘텐츠를 전송하기 위한 오픈 표준 파일 포맷이다..gltf
파일은 JSON 형식으로 작성된 메타 데이터와 바이너리 형식의 외부 자원 파일들로 구성된다.
반면.glb
파일은 JSON 형식의 glTF 파일과 관련된 자원들을 하나의 바이너리 파일로 통합하여 표현한 파일이다. 이 타입을 사용하면 파일의 크기를 줄이고 로딩 속도를 향상시킬 수 있다.
useGLTF
는 (GLTF & ObjectMap)[]
을 리턴하는데, ObjectMap
에는 nodes
와 materials
정보가 들어있다.
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>
);
}