바닐라 JS에서의 Three.js를 공부하고 이제 React에서의 활용법을 공부중이다.
꽤나 달라서 그냥 바닐라js로 구현할까 고민을 잠깐했지만 도전해보려 한다!🧐
공식문서와 유뷰브 강의를 보고 코드를 적용해보자!
영어자료라 새삼 토익 공부하는 느낌에 일석이조 같다는 생각이 든다ㅋㅋㅋ
당연하게도 npm 다운을 받아야한다.
npm i three
npm install three @react-three/fiber
npm install @react-three/drei
react-three/fiber는 three.js를 component 기반 방식으로 사용할 수 있게 한다. jsx를 가져와 캔버스 용 three.js 코드로 변환한다.
react-three/drei는 fiber의 components들을 미리 구현해놔서 우리가 재사용하면 되는 패키지이다.
camera, orbit 등 유용한 components들이 많다.
(여담으로 독일어로 3이 drei이다! 독일어를 교양으로 배웠어서 반가웠다 ^o^)
// Create a box geometry
const geometry = new THREE.BoxBufferGeometry(10, 10, 10);
// Create a material
const material = new THREE.MeshStandardMaterial();
// Create a mesh with the defined geometry and material
mesh = new THREE.Mesh(geometry, material);
// Add the mesh to the scene![](https://velog.velcdn.com/images%2Fjeon-yj%2Fpost%2Fdeb9bfc7-3c33-41d6-a4fc-18c7409ef553%2F%EC%BA%A1%EC%B2%98.PNG)
scene.add(mesh);
이 js 코드는 react-three-fiber로 아래와 같다.
<mesh>
<boxBufferGemometry attach='geometry' args={[10, 10, 10]} />
<meshBasicMaterial attach='material' />
</mesh>
많이 다르지만 어렵지 않고 오히려 간단하다.
scene은 react/fiber에서 import된 Canvas 컴포넌트로 만든다.
이후 three.js로 만들 컴포넌트들은 이 Canvas 컴포넌트 안에 들어가야한다.
import React from 'react'
import { Canvas } from '@react-three/fiber'
const App = () => (
<Canvas>
<pointLight position={[10, 10, 10]} />
<mesh>
<sphereBufferGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
</Canvas>
위 코드 <mesh>
에서 알 수 있듯이 three.js components는 일반 react components와 다르게 소문자이다.
(알고있겠지만 React 컴포넌트의 첫글자는 무조건 대문자로 써야한다. 지켜야할 룰이며 관례이다.)
참고한 유튜버분께서 열심히 설명하였지만 짧은 영어 실력으로 이해한 바로는 three.js 컴포넌트를 소문자로 써야 renderer가 three.js의 internal components라고 인식한다는 것이다.
mesh 컴포넌트 안에는 차례로 geometry와 material을 컴포넌트로 나타낸다. 캔버스에 light를 넣지않으면 material 색을 지정해도 보이지 않는다(검정색으로 보인다). 빛이 있어야 비로소 색이 있기때문이다.
pointLight는 말 그대로 한 지점에서 쏘는 빛이고,
ambientLight은 태양처럼 전체적으로 물체에 빛을 쏜다. scene에 동일하게 빛을 비춘다.
결과: css나 다른 속성을 적용하지 않아서 hotpink색 구만 덩그러니 있다 .
import React, { useRef } from "react";
import { useFrame, useLoader } from "@react-three/fiber";
import { TextureLoader } from "three";
import * as THREE from "three";
import { OrbitControls, Stars } from "@react-three/drei";
import EarthDayMap from "../../assets/img/8k_earth_daymap.jpg";
import EarthNormalMap from "../../assets/img/8k_earth_normal_map.jpg";
import EarthSpecularMap from "../../assets/img/8k_earth_specular_map.jpg";
import EarthCloudsMap from "../../assets/img/8k_earth_clouds.jpg";
export default function Earth() {
const [colorMap, normalMap, specularMap, cloudsMap] = useLoader(
TextureLoader,
[EarthDayMap, EarthNormalMap, EarthSpecularMap, EarthCloudsMap]
);
const earthRef = useRef();
const cloudsRef = useRef();
//회전을 위해
useFrame(({ clock }) => {
const elapsedTime = clock.getElapsedTime();
earthRef.current.rotation.y = elapsedTime / 6;
cloudsRef.current.rotation.y = elapsedTime / 6;
});
return (
<>
<pointLight color="#f6f3ea" position={[2, 0, 2]} intensity={1} />
<Stars
radius={300}
depth={60}
count={20000}
factor={7}
saturation={0}
fade={true}
/>
<mesh ref={cloudsRef}>
{/* sphereGeometry의 인자는 순서대로 반지름, 너비, 높이 이다 */}
<sphereGeometry args={[1.005, 32, 32]} />
<meshPhongMaterial
map={cloudsMap}
opacity={0.3}
depthWrite={true}
transparent={true}
side={THREE.DoubleSide}
/>
</mesh>
<mesh ref={earthRef}>
<sphereGeometry args={[1, 32, 32]} />
<meshPhongMaterial specularMap={specularMap} />
<meshStandardMaterial
map={colorMap}
normalMap={normalMap}
metalness={0.4}
roughness={0.7}
/>
<OrbitControls
enableZoom={true}
enablePan={true}
enableRotate={true}
zoomSpeed={0.6}
panSpeed={0.5}
rotateSpeed={0.4}
/>
</mesh>
</>
);
}
-useFrame : 매 프레임을 렌더할때 실행된다.
-useLoader : Assets이나 에러 핸들링을 쉽게 하기 위해 사용된다. 첫번째 인자로 GLTFLoader, OBJLoader, TextureLoader, FontLoader, etc가 올수 있고 두번째 인자로 assets(주소), 세번째로는 콜백함수를 지정할 수 있다.
참고로 useLoad로 로드된 assets는 기본적으로 cached된다. 공식문서에서는
Assets loaded with useLoader are cached by default. The urls given serve as cache-keys. This allows you to re-use loaded data everywhere in the component tree.
Be very careful with mutating or disposing of loaded assets, especially when you plan to re-use them. Refer to the automatic disposal section in the API.
이때문에 error boundaries 관련 에러가 떠서 해결하느라 진땀을 뺐다ㅜㅜ
결과:
게시글 너무 잘봤습니다 ! 그런데 저도 자꾸 boundries 에러가 떠서 어떻게 해결하셨는지 공유가능할까요? ㅠㅠ