공부/threeJS

Three.js 따라 강의 - 01 Threejs 란? (정육면체만들기)

캄성 2022. 5. 17. 18:28

requestAnimationFrame

2022.05.17 - [공부/JS] - Three.js 따라 강의 - 00 먼저 알아야 할 것 들

threejs 웹을 통하여 공부하며 개인적으로 정리한 내용을 개인경험을 추가하여 정리한 곳 입니다.

(예제가 실제 실행되지 않는 것을 고쳐 설명할경 정리하기 위하여 작성)

https://threejs.org/ 에서 제공 하는 Three.js Fundamentals 진행 이후,

유료 강의인 Three.js Journey에 대한 리뷰를 진행 할 예정입니다.

Learn Three.js의 경우 기본 이론의 심화 과정 느낌인데, 학생ID 이용시 무료 로 사용한 Ebook도 있습니다.


Three.js란
https://threejs.org/
https://threejs.org/manual/#ko/fundamentals

간단하게 HTML 환경에서 3D 구성을 쉽게 하게 도와주는 툴이며, WebGL을 사용하여 복잡하게 할 것을 간단하게 해준다고 생각 하면 쉽습니다.  코드로 힘들게 구현하는 이미지와 3D 툴의 중간적인 느낌이며,  활용도에 따라 다양한 작품을 만 들 수 있습니다. 위 링크를 통해 완성 품의 예제들 확인이 가능합니다.

 

Threejs 의 구조

 위 도면은 보기 어려워 보이지만 간단한 것을 나타냅니다. 

 랜더를 통해 3D를 구현하며, 랜더는 Scene 씬과, 카메라 2가지로 구성이 되어 있고,

씬은 각 메쉬들의 집합으로 구성되어 있다는 겁니다.

 

  •  Renderer  - Three.js의 핵심이며, 카메라와 씬을 가져와 표현 하는 겁니다.
  • 씬그래프 - Scene 또는 다수의 Mesh, Light, Group, Object3D, Camera로 이루어 진 트리 구조를 나타낸다고 하는데, 결국 Scene의 구성을 나타냅니다. 기본 개념으로, 이것을 바탕으로 코드를 작성해야 나중에 혼돈이 생기지 않습니다.
  • Camera -  반 걸친 건은, 3D에서 카메라의 개념 자체가 씬에 포함된것이 아닌 것을 나타냅니다. Camera는 씬을 바라볼 뿐입니다.
  • Mesh - Material로 한나의 Geotmetry를 그리는 객체, 이며 재사용 가능한 여러개의 Mesh가 하나의 Material또는 Geometry 참조가 가능합니다 즉 여러개의 Mesh가 하나의 외부설정을 참조해서 사용 가능하단 뜻입니다. 쉽게 말해 하나의 물체당 하나의 Mesh라 보시면 됩니다
  • Geometry - 기하학적 정점 데이터 라고 합니다. 즉 도형 모형등 모양을 나타내는 무언가 입니다.
  • Material -  표면 속성이라고도 하면 색 밝기 등을 나타냅니다.
  • texture -  표면 참조 값으로 이미지, 사진등 다양한 것이 될 수 있습니다.
  • Light - 광원 입니다. 여러개도 생성 가능하고, 색 종류도 다양하게 생성 가능 합니다.

기본 -  Hello cube 만들기

 - 실제 설명을 만들게 된 이유 입니다. 현재 전체적인 threejs 코드는.. 변화되는 과정속에 설명대로 하면샘플 코드가 실행되지 않는 경우가 많이 있어서, 그부분을 보완하고자 합니다.

기본 적인 Hello Cube를 위한 구성입니다.

WebGl render와 Perspective camera를 사용한다고 보시면 됩니다.

 Three.js 사용 준비

<script type="module">
import * as THREE from '../../build/three.module.js';
</script>

이라고 설명에 되어 있으나, 저런식으로 가져올 경우 몇가지 규칙성에 위배 되어, 아래와 같이 가져옵니다.

<script type="importmap">{
      "imports": {
          "three": "https://threejs.org/build/three.module.js"
      }
  }</script

기본적인 방식으로 가져올경우, Imports map 참조에 위배되어, 맵에 추가한느 방식입니다. 

<script type="module">
import * as THREE from 'three';
</script>

위에 참조된것을 바탕으로 다음과 같이 불러오면 이제 THREE라는 명령을 통하여 threejs 사용이 가능합니다.

 

 

이제 Canvas 태그를 작성합니다. 

<body>
  <canvas id="c"></canvas>
</body>

canvas 가 앞으로 애니메이션을 할 도화지라고 생각 하시면 됩니다.

 

Three 불러 오기 기초셋팅

<script type="module">
import * as THREE from 'three';
function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});
  ...
</script>

모듈 형식 스크립트로 three 를 불러 온 후

main 함수를 만들어서 준비합니다. 이부분은 취향 것 하시면 되고,

canvas를 쿼리 셀럭터를 통해서 지정한후, 렌더러 로 지정합니다.

 

cameara setting

const fov = 75;
const aspect = 2;  // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

camera의 기본 값을 셋팅 하는 부분 입니다. 

fov = field of view 시야각

aspect = 가로 세로 비율  // 기본적으로 300/150 

near = 가까운 카메라 뷰 한계 지점 -  화면 출력 되는 부분을 제한. 건물안에 들어가는 느낌 사용 가능

far = 먼 카메라 뷰 한계점 - 멀리 있는 것이 사라지는 느낌 사용 가능

위 사진과 같은 사람이 볼영역을 지정하면 되고, 저렇게 사람이 보는 부분을 절두체라고 합니다.

- camera.position.z = 2;

카메라의 z 거리를 벌려 확인하기 위한 2 사용 (2만큼 뒤로 이동)

사진과 같이 표현 된다고 생각 하면 됩니다. 이부분은 추후 Orbitcontrol 등을 하면서 정확히 이해하실 수 있습니다.

 

Scene 

 이제 씬을 추가하고 

- const scene = new THREE.Scene();

 

Box 추가 및 화면 출력

const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

const material = new THREE.MeshBasicMaterial({color: 0x44aa88});

const cube = new THREE.Mesh(geometry, material);

scene.add(cube);

renderer.render(scene, camera);

BoxGeometry 를 위와 같이 구성한 후, 각 값을 입력하고,

MeshBacsic Material 을 통하여, 색갈을 구성한 후,

위 두가지 값을 Mesh로 하나로 통합 시킵니다.

 그후 씬에 추가한다음, 랜더하며

이렇게 깔끔한 정 4각형이 출력 됩니다.

 

전체 코드--

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="c"></canvas>
    <script type="importmap">{
      "imports": {
          "three": "https://threejs.org/build/three.module.js"
      }
  }</script><!-- Remove this when import maps will be widely supported -->
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
    <script type="module">
        import * as THREE from 'three';
        function main() {
            const canvas = document.querySelector('#c');
            const renderer = new THREE.WebGLRenderer({ canvas });

            const fov = 75;
            const aspect = 2;  // the canvas default
            const near = 0.1;
            const far = 5;
            const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
            camera.position.z = 2;

            const scene = new THREE.Scene();

            const boxWidth = 1;
            const boxHeight = 1;
            const boxDepth = 1;
            const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

            const material = new THREE.MeshBasicMaterial({ color: 0x44aa88 });  // greenish blue

            const cube = new THREE.Mesh(geometry, material);
            scene.add(cube);

            renderer.render(scene, camera);
        }
        main();
    </script>
</body>

</html>

 

중간 정리 차원에서 올린 코드를 참조하여, 부족한 부분을 혹은 바꾸고 싶은 부분을 수정하시면 됩니다.

 

 

애니메이션 

 지금은 이게 4각형인지 아닌지 구분이 되지 않으니, 회전을 시켜 보겠습니다.

function render(time) {
  time *= 0.001;  // convert time to seconds
 
  cube.rotation.x = time;
  cube.rotation.y = time;
 
  renderer.render(scene, camera);
 
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

위의 코들르 main 함수 안에 넣으시면 됩니다.

requestAnimationFrame 는 브라우저에 에니메이션 프레임을 요청하는 값 입니다.

즉   renderer.render(scene, camera); 이걸 통해 무한히 화면을 렌더링해준다고 생각 하시면 됩니다.

 

광원 추가

좀더 사실 적인 느낌을 주기 위화여 광원을 추가합니다.

 

{
  const color = 0xFFFFFF;
  const intensity = 1;
  const light = new THREE.DirectionalLight(color, intensity);
  light.position.set(-1, 2, 4);
  scene.add(light);
}

기본적으로 0,0,0을 비추며 설정에 따라 다른곳을 비출 수 있습니다.

또한 const material = new THREE.MeshPhongMaterial({ color: 0x44aa88 });  // greenish blue

처럼 material의 종류를 바꿔줘야합니다.  MeshBasicMaterial은 광원에 반을하지 않습니다.

 

우측이 현재 진행된 상태입니다. 기존 좌측 모델에서 빛이 추가 되었습니다.

 

박스 추가

지난 글에서 추가된 것 처럼, 고정된 Geometry에서 박스 재질만 바꿔서 표현 합니다.

 

function makeInstance(geometry, color, x) {
  const material = new THREE.MeshPhongMaterial({color});
 
  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);
 
  cube.position.x = x;
 
  return cube;
}

기존 Matarial 코드를 제거하고 교체 합니다.

 

 

function render(time) {
  time *= 0.001;  // convert time to seconds
 
  cubes.forEach((cube, ndx) => {
    const speed = 1 + ndx * .1;
    const rot = time * speed;
    cube.rotation.x = rot;
    cube.rotation.y = rot;
  });

거기에 추가로 각 cube를 별도로 움직이게 설정 합니다.

 

이제 위와 같은 구조로 완료 되었습니다. 

굉장히 중요한 기본 개념으로 앞으로 참조하시면 됩니다.

 

 

 

최종 코드

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="c"></canvas>
    <script type="importmap">{
      "imports": {
          "three": "https://threejs.org/build/three.module.js"
      }
  }</script><!-- Remove this when import maps will be widely supported -->
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
    <script type="module">
        import * as THREE from 'three';
        function main() {
            const canvas = document.querySelector('#c');
            const renderer = new THREE.WebGLRenderer({ canvas });

            const fov = 75;
            const aspect = 2;  // the canvas default
            const near = 0.1;
            const far = 5;
            const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
            camera.position.z = 2;

            const scene = new THREE.Scene();

            {
                const color = 0xFFFFFF;
                const intensity = 1;
                const light = new THREE.DirectionalLight(color, intensity);
                light.position.set(-1, 2, 4);
                scene.add(light);
            }

            const boxWidth = 1;
            const boxHeight = 1;
            const boxDepth = 1;
            const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

            function makeInstance(geometry, color, x) {
                const material = new THREE.MeshPhongMaterial({ color });

                const cube = new THREE.Mesh(geometry, material);
                scene.add(cube);

                cube.position.x = x;

                return cube;
            }

            const cubes = [
                makeInstance(geometry, 0x44aa88, 0),
                makeInstance(geometry, 0x8844aa, -2),
                makeInstance(geometry, 0xaa8844, 2),
            ];

            function render(time) {
                time *= 0.001;  // convert time to seconds

                cubes.forEach((cube, ndx) => {
                    const speed = 1 + ndx * .1;
                    const rot = time * speed;
                    cube.rotation.x = rot;
                    cube.rotation.y = rot;
                });

                renderer.render(scene, camera);

                requestAnimationFrame(render);
            }
            requestAnimationFrame(render);

        }
        main();
    </script>
</body>

</html>