방학에(이제는 졸업이지만) 기존에 진행했던 프로젝트 리팩토링과 더불어 새로운 기능을 넣기로 했다.
1년 뒤의 나에게 편지를 쓰는 사이트를 구현하던 도중, 이런 생각이 들었다.

내가 편지를 작성 완료하면, 내가 작성한 편지를 3D 이미지로 제공하면 좀 더 재밌는 경험을 제공할 수 있지 않을까?
사실 토스에서 이런 경험을 했기 때문에, 이를 모티브로 제작해보고 싶었다.
React에서는 react-three라는 라이브러리를 통해 3D 이미지를 구현할 수 있다.
three.js는 초심자가 사용하기에는 굉장히 어려운 라이브러리지만,
역시 나 또한 잘 사용하지 못했다.
그래서 야매로 3D 이미지를 구현하는 방식을 구글링을 통해 찾았고, 이를 통해 내가 구현한 방법을 소개하고자 한다.
우선 나의 개발 환경은
React + Next.js 13 + Yarn berry
이다.
[ 개발 환경 세팅 ]
yarn add react-three
yarn add @react-three/fiber
yarn add @react-three/drei
react-three는 기본 three.js를 위한 모듈,
react-three/fiber는 3D 이미지를 그리는 Canvas를 위한 모듈(사실 Canvas만 사용해서 다른 기능이 있는지는 잘 모른다),
react-three/drei는 3D 기본 모형이나 텍스처를 저장할 수 있는 기능을 제공하는 모듈이다.
일단 필요하다고 하니, 모두 설치하자.
const _Box = () => {
const geometry = new BoxGeometry(1, 1, 1);
return <mesh geometry={geometry} />;
};
이렇게 박스를 하나 만들어준다.
BoxGeometry는 육면체를 만들어주는데, 안에 인자는 각각 가로, 세로, 높이를 의미한다.
function CardRotator() {
return (
<Wrapper>
<Canvas>
<_Box />
</Canvas>
</Wrapper>
);
}
그리고 이렇게 CardRotater를 설정해준다.
Canvas는 mesh를 그릴 수 있는 도화지라고 생각하면 된다.
실행하면 ...

네모 하나가 나온다.
착한 사람 눈에만 3D로 보인다.
사실은 회전도, 조명도 주지 않아서 단순한 평면처럼 보이는 것이다.
그럼 색상과, 조명 그리고 회전을 줘보자.
const _Box = () => {
const geometry = new BoxGeometry(1, 1, 1);
const texture = new MeshBasicMaterial({ color: "green" });
return <mesh geometry={geometry} material={texture} />;
};
MeshBasicMaterial에 초록색을 줘봤다.

function CardRotator() {
return (
<Wrapper>
<Canvas>
<OrbitControls autoRotate={true} />
<ambientLight intensity={1} />
<_Box />
</Canvas>
</Wrapper>
);
}
ambientLight로 빛을 주고(여러 방향으로 줄 수 있지만, 가장 기본적으로 넣었다)
OrbitControls로 회전 가능하게 설정했다. autoRotate 효과로 자동으로 회전하게 했다.
그 결과물은?

대충 3D 이미지가 완성됐다.
어떻게 돌아가는지는 알았으니, 내가 만들고 싶은 편지모양의 3D 이미지를 만들어보자 !
처음으로 생각한 건 PlaneGeometry였다. 이는 앞뒤 평면을 3D로 제공하는 객체인데,
앞 뒤 이미지를 따로 적용하기 어렵기도 했고, 카드의 두께가 없게 나와서 입체감이 크게 없었다.
그래서 BoxGeometry를 사용한 뒤, 높이를 작게 줘서 최대한 카드의 형태로 만들기로 했다.
우선 내가 만든 3D 이미지에 원하는 이미지를 넣고 싶다면,
const textureLoader = new TextureLoader();
const textureFront = textureLoader.load("/static/images/card-1.png");
const textureBack = textureLoader.load("/static/images/card-2.png");
TextureLoader를 사용해 이미지를 불러와야 한다. 나는 카드의 앞,뒤에 이미지를 넣기 위해 2가지 사진을 불러왔다.
const materials = [
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ map: textureFront }),
new MeshBasicMaterial({ map: textureBack }),
];
그리고 6면체기 때문에, 각 면에 설정할 MeshBasicMaterial를 설정해준다. 나는 2면에만 이미지를 넣고 싶었기 때문에, 나머지는 모두 흰색을 넣어줬다.
return <mesh geometry={geometry} material={materials}></mesh>;
그리고 mesh 오브젝트의 geometry에는 아까 만들어 둔 BoxGeometry를, material에는 내가 설정한 면들을 넣어준다.
const Card = () => {
const textureLoader = new TextureLoader();
const textureFront = textureLoader.load("/static/images/card-1.png");
const textureBack = textureLoader.load("/static/images/card-2.png");
const geometry = new BoxGeometry(0.8 * 3, 1.4 * 3, 0.1); // 카드 형태를 만들기 위해 조정
const materials = [
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ color: "white" }),
new MeshBasicMaterial({ map: textureFront }),
new MeshBasicMaterial({ map: textureBack }),
];
return <mesh geometry={geometry} material={materials}></mesh>;
};
function CardRotator() {
return (
<Wrapper>
<Canvas>
<OrbitControls autoRotate={true} />
<ambientLight intensity={1} />
<Card />
</Canvas>
</Wrapper>
);
}
[ 결과물 ]

대충 카드 모양의 3D 육면체를 만들었다.
사실 three.js는 카메라나 광원 등 굉장히 많은 옵션을 제공하는데, 나는 단순한 이미지만을 빠르게 만들고 싶었기 때문에 필요한 기능만 사용해서 제작했다.
나중에 시간이 되면 더 복잡한 형태의 물체도 만들어 보고 싶다 !
* 사실 3D 이미지를 sketchfab같은 곳에서 다운받을 수도 있고, 디자이너들이 blender같은 툴을 통해 제작해 줄 수 있는데, 이러한 이미지를 사용하는게 가장 쉬운 듯 하다.