앗! 취업에 도움되는(?)Threejs를 vanila 및 react-three-fiber 버전의 예제와 함께 복습해보자. [코드편 1탄]

Design.C·2023년 5월 6일
2
post-thumbnail
그닥 알차진 않았던 개념편 작성이 끝이 났다.
개념편은 정말 threejs를 처음 접했거나,
공식문서를 제대로 읽어보기 귀찮은 사람이 저 글만 보고도 대충은 이해하길 바라는 마음 + 그냥 내가 스스로 복습해보는 마음
위 두 가지 마음을 반반씩 갖고 작성했다.

코드편 에서는...

앞에서 이해한 개념을 바탕으로 기본적인 threejs의 사용법을 알아보는 시간을 가져보려 한다.

먼저, Vanila Javascript에서 해당 코드를 작성해보고,

같은 코드를, React에서 사용 가능한 형태로도 작성해보도록 하겠다. (단, 이 경우에는 Typescript를 이용하여 작성해보도록 하겠다.)

코드 작성에 앞서, 개념편에서 기억해야 할 threejs의 7요소를 상기해보자.
scene, camera, renderer, mesh, geometry, material, light 를 다시 한 번 떠올리며, 이제 본격적으로 코드를 작성해보자.

Vanila Javascript Threejs

먼저, vite를 이용해 간단하게, 기본 환경을 구성해보자.
작업할 폴더를 만들 경로로 이동하여 터미널을 켜고, 아래 커맨드를 입력해보자.
> npm create vite velog-threejs-vanila --template vanila


만든 폴더로 이동 후, 위와 같은 구조로 파일이 생성되었다면 기본 세팅은 성공이다!

이제, index.html을 열고, 우리가 작업할 scene이 render 될 div를 확인해보자.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Vite App</title>
</head>
<body>
  <div id="app"></div> <!--좌측의 div가 바로 우리가 작업할 scene이 나올 div이다.-->
  <script type="module" src="/main.js"></script>
</body>
</html>
확인했다면, 이제 main.js를 열고 threejs 작업을 시작해보자.
먼저, 아래 커맨드를 입력해서 npm으로 threejs를 설치하고, 서버를 구동해보자.
구동 후, 브라우저를 열고, localhost:5173을 입력하여 켜보자.
> npm i three
> npm run dev
설치되었다면, main.js를 열고 threejs를 import 하고, renderer를 만들어보자.
import "./style.css";
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();
다음은, index.html의 id가 app인 div를 가져와서, 이 div 속에, 우리의 renderer를 추가해보고, 이 renderer의 size를 현재 브라우저 화면의 가로와 세로로 설정해주자.
import "./style.css";
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");

app.appendChild(renderer.domElement);

renderer.setSize(window.innerWidth, window.innerHeight);
다음은, 우리의 촬영 무대가 될 scene을 추가해보자.
import "./style.css";
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");

app.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
다음은, 우리의 무대를 촬영할 카메라를 추가해보자.
import "./style.css";
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");

app.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  
);

다음은, 개념편 2탄에서 배웠던 카메라의 세부 속성들을 추가해서 조금 더 고급진 카메라로 만들어보자.
import "./style.css";
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");

app.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  75, // fov(시야각) 값이다.
  window.innerWidth / window.innerHeight, // aspect ratio 값으로서, 카메라의 가로세로 비율 이다.
  0.1, // near 값이다.(이 거리보다 가까운 객체는 카메라에 담기지 않는다.)
  100 // far 값이다.(이 거리보다 먼 객체는 카메라에 담기지 않는다.
);
다음은, 마지막으로 renderer를 이용해, 앞서 만든 scene과 camera를 추가해보자.
import "./style.css";
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");

app.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  75, // fov(시야각) 값이다.
  window.innerWidth / window.innerHeight, // aspect ratio 값으로서, 카메라의 가로세로 비율 이다.
  0.1, // near 값이다.(이 거리보다 가까운 객체는 카메라에 담기지 않는다.)
  100 // far 값이다.(이 거리보다 먼 객체는 카메라에 담기지 않는다.
);

renderer.render(scene, camera); 
그리고, style.css 파일에 아래와 같은 스타일 코드를 추가해주자.
#app {
  position: absolute;
  top: 0;
  left: 0;
}
여기까지 잘 따라왔다면, 그냥 새까만 화면이 나왔을 것이다.
이제 우리 scene의 주인공인 mesh를 하나 추가해보자.
mesh를 추가하기 위해선, geometry와 material이 필요하므로, 이 두 가지도 추가해보도록 하자.
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");
app.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.01,
  1000
);
scene.add(camera);

// 간단하게 박스 모양의 geometry를 추가하고, 너비, 높이, 깊이를 각각 1,1,1로 설정한다.
const geometry = new THREE.BoxGeometry(1, 1, 1);

// 빛의 영향을 받지 않는 MeshBasicMaterial을 추가하고, 색은 개인적으로 좋아하는 초록색으로 했다.
const material = new THREE.MeshBasicMaterial({ color: 'green' });

// mesh에 앞서 만든 geometry와 material을 인자로 넣어서 만든다.
const mesh = new THREE.Mesh(geometry, material);

// scene에 방금 만든 mesh를 추가한다.
scene.add(mesh);

renderer.render(scene, camera);
위와 같은 코드를 작성 후, 브라우저를 새로고침해보면

이유는 바로, 카메라가 엉뚱한 곳을 보고 있기 때문이다.
약간의 수학이 등장할 수 있으니, 주의하길바란다!
scene에 추가된 camera 혹은 mesh는 position을 설정해주지 않으면, 기본적으로 좌표(0,0,0)에 위치하게 된다. 즉 원점에 존재하는 것이다.
그렇기에, 현재 상황은 배우와 카메라 감독이 같은곳에 서있기 때문에, 카메라에 아무것도 담기지 않은 것이다.

자 그럼, 카메라의 위치를 아래와 같이 수정해보자.
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");
app.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.01,
  1000
);
// 카메라와 z축 위치를 5로 바꿔준다.
camera.position.z = 5;
scene.add(camera);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: "green" });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

renderer.render(scene, camera);
오, 이제 앵글에 내가 만든 mesh가 보인다. 하지만 뭔가 박스 같지가 않고 그냥 네모같다. 카메라의 위치를 다음과 같이 또 한번 수정해보자.
import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

const app = document.querySelector("#app");
app.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);

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

// 카메라의 y축 위치를 2로 바꿔준다.
camera.position.y = 2;
scene.add(camera);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: "green" });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

renderer.render(scene, camera);
오, 이제 확실히 박스를 살짝 위에서 본 듯한 앵글이 나오기 시작한다.
threejs의 세계에는 라이브러리 이름처럼 3개의 축이 존재한다.

위 그림처럼, 화살표 방향이 + 방향이고, 그 반대는 - 방향이다.
따라서, 현재 상황은 아래와 같은 상황이 된 것이다.

이해가 잘 가지 않는다면, 카메라의 위치를 이리저리 바꿔본다면 금방 이해가 갈 것이다.

코드편 1탄은 여기서 끝이다.

아래는 위와 동일하게 동작하는 코드를 React화한 코드를 작성해 볼 예정이다.

React Threejs

Threejs를 React에서 활용코자 할 때는,기본 threejs 이외에도,
react-three-fiber와 react-three-drei라는 라이브러리를 적절히 활용해야 한다.
자 그렇다면, 터미널을 열고 기본적인 세팅을 시작해보자.
> npm create vite velog-threejs-react --template react-ts
> cd velog-threejs-react
> npm i
> npm i three @react-three/fiber @react-three/drei
> npm i @types/three --dev
아래와 같은 구조의 폴더가 만들어졌다면 성공!

사용하지 않을 코드를 지워주어, App.tsx를 아래와 같이 만들어주자.
// App.tsx
function App() {
  return null;
}

export default App;
vanila javascript의 threejs(이하 vThree)와 react 래퍼 라이브러리(이하 rThree) 의 사용법에 있어서의 차이점은, 아래와 같다.
vThree의 경우, three의 각 요소를 생성할 때, 파라미터로 전달하는 변수들이 rThree에서는 하위 컴포넌트로서 작성하게 된다는 점이다. 또한 별도의 renderer를 따로 만들어줄 필요가 없다.
// App.tsx
import "./App.css";
import { Canvas } from "@react-three/fiber";

function App() {
  return (
    // 위의 vanila와 동일한 결과물을 나오게 하기위해 스타일 코드를 추가했다.
    <div style={{ width: "100vw", height: "100vh", background: "#000" }}>
    
    // fiber에 내장된 canvas 객체이다.
      <Canvas
// camera를 props로 추가하였다.
        camera={{
          isPerspectiveCamera:true,
          fov: 75,
          aspect: window.innerWidth / window.innerHeight,
          near: 0.01,
          far: 1000,
          position: [0, 2, 5],
        }}
      >
        // Canvas 하위에 추가될 mesh를 컴포넌트로 추가하였다.
        <mesh position={[0, 0, 0]}>
          // 기존에 Mesh에 파라미터로 전달되던 geometry가 하위 컴포넌트로 추가되었다.
          <boxGeometry args={[1, 1, 1]} />
          // 기존에 Mesh에 파라미터로 전달되던 material이 하위 컴포넌트로 추가되었다.
          <meshBasicMaterial color={"green"} />
        </mesh>
      </Canvas>
    </div>
  );
}

export default App;
화면 상에서 mesh가 놓여져있는 높이에 약간의 차이가 나는 것 빼곤 vThree와 rThree에서 동일한 결과가 나왔을 것이다.

코드편 1탄은 여기서 끝...

다음 탄에서 계속...

profile
코더가 아닌 프로그래머를 지향하는 개발자

0개의 댓글