Publication 2
2023-02-11
조회 62



Adler가 만드는 
새로운 규칙, 새로운 역할. 




누구나 웹에서 3D를 구현할 수 있도록

한번 보고 "와우!" 감탄사가 나오게 만드는 사이트들은 대부분 Three.js 기반으로 만들어 진 것이라고 보면 됩니다. 웹에서 3D를 구현하는 기술은 Three.js 뿐만 아니라 유니티 등 여러가지 기술이 존재하고 있지만 최근 기술 동향을 보면 웹에서 3D 구현은 Three.js가 다른 기술을 압도하고 있죠. 


그런데도 한국어로 출간된 책은 2016년이 마지막이었습니다. 빠르게 기술이 발전하는 시대에, 영어가 능숙하지 않다면 23년 현재 기준으로 무려 7년이나 동떨어진 자료로 학습할 수 밖에 없는 겁니다. 그래서 책을 쓰기로 결정했습니다. 누구나 웹에서 3D를 구현할 수 있도록.  


자료가 없으면 Adler는 만듭니다

이 책은 웹에서 3D를 구현하려고 하는 모든 개발자를 대상으로 합니다. 따라서 웹의 기초인 HTML, CSS와 기본적인 자바스크립트에 대한 지식만 있다면 이 책 한 권으로 누구나 3D기술을 익힐 수 있습니다. 기초지식이 없는 분들도 인터넷에 있는 기초 웹 개발 강좌나 책을 보시고 시작해도 좋습니다.


HTML5의 기술 발달에 따라 웹 브라우저에서 3D를 구현하기가 쉬워지고 있습니다. 특히 WebGL 출현 이후로 WebGL 기반의 3D 기술이 많이 쏟아지고 있는데, 최근 들어 가장 각광 받는 것 중 하나가 Three.js 라는 자바스크립트 기반의 라이브러리입니다. 여기서 라이브러리란 말에 주목해 볼 필요가 있습니다.  웹 개발에서 사용하는 기술 중 자바스크립트에서는 바닐라스트립트(순수 자바스크립트)를 제외한 일반적인 개발 방식에는 프레임워크와 라이브러리 이렇게 2개의 방식으로 웹을 개발하게 되는데, 프레임워크는 웹 개발에 필요한 일정한 형태와 기능이 갖추어진 구조물이고, 라이브러리는 말 그대로 특정 기능을 위해서 코드와 함수들이 모아진 집합 같은 의미입니다.


대표적인 프레임워크는 Java 서버 개발에 사용되는 Spring이나 Python 서버 개발에 사용되는 Django, Flask, 웹 개발에 사용되는 Angular, Vue.js 등이 대표적인 프레임 워크이고 라이브러리는 Python에서  pip로 설치한 패키지/모듈 (tensorflow, pandas 등) Node.js에서 npm으로 설치한 모듈, 요즘에는 사용 빈도가 줄어든 자바스크립트 기반의 JQuery, 웹개발에서 UI 개발에 사용되는 React.js 그리고 WebGL 개발을 위해 사용되는 Three.js 가 있습니다.

프레임워크와 라이브러리의 가장 큰 차이점은 “프로그래밍 제어의 권한이 누구에게 있는가”라고 정의할 수 있습니다.  라이브러리를 사용할 경우 개발자는 코드를 직접 제어합니다. 필요한 기능이 있을 경우 해당 라이브러리를 호출하여 사용하거나, 기존에 제공된 코드를 가져다 쓰게 됩니다. 이 책에서도 Three.js 공식 사이트에 있는 라이브러리를 가져다 예제를 만들고 동작원리를 설명합니다.


하지만 프레임워크에서 개발자 코드는 프레임워크에 의해서 제어됩니다. 개발자가 짜는 코드는  프레임워크가 만들어 놓은 코드 내에서 수동적으로  동작하기 때문에 프로그래밍 제어의 흐름은 프레임워크가 가지고 있고 사용자는 그 틀안에서 동작하게 되는 것입니다. 대표적인 예가 많은 웹 개발자 들이 사용하는 대표적인 CSS 프레임워크인 부트스트랩을 보시면 부트스트랩에서 제공된 코드를 개발자들이 부트스트랩  틀 안에서 디자인을 입히는 경우가 많습니다.



첫 시작은 Three.js!


앞서 말했듯, 최근 메타버스의 활성화와 보다 비주얼적으로 뛰어난 효과를 원하는 다양한 웹 사이트에서 3D 기술이 적용되고 그 대부분은 Three.js 로 만들어진 사이트라고 봐도 무방합니다. 우리가 흔히 와우 팩터( Wow Factor) 즉 한번 보고 “와우!” 라는 감탄사가 나오게 만드는 사이트들이 Three.js 기반으로 만들어 진 것이라고 보면 됩니다.


2022년 기준 웹은 단순히 2차원 평면에서 3차원으로 가는 과도기라고 할 수 있습니다. 웹에서 3D 구현하는 기술은 Three.js 뿐만 아니라 유니티 등 여러가지 기술이 존재하고 있습니다. 하지만 최근 기술 동향을 보면 웹에서 3D 구현은 Three.js가 다른 기술을 압도하고 있습니다. Three.js 는 다른 기술에 비해 접근이 쉽고, 누구나 만들 수 있고, 자바스크립트의 기초만 알고 있어도 제작이 가능합니다. 또한 웹에서 가장 중요한 로딩 속도에서 면에서 타 기술에 비해 압도적으로 빠르다는 장점이 있습니다. 또한 자바스크립트를 이용하기 때문에 거의 모든 브라우저(모바일 포함)에서 동작합니다.



2023-02-16
조회 47




일단 Three.js 세팅부터 해볼까요

: 모듈 방식을 이용한 환경 및 FPS, resize 설정


이번 장에서는 Three.js를 모듈 방식으로 실행하기 위한 환경을 설정하고, 사용자의 컴퓨터 환경에 맞게 작동할 수 있도록 FPS와 resize를 설정하는 방법을 알아보도록 하겠습니다.







Adler's Note


가상현실의 핵심이라 할 수 있는 3D는 어떻게 만들 수 있을까요? 초보 개발자나 개발 공부를 막 시작한 디자이너도 쉽게 사용하려면 어떤 공부가 필요할까요? 아들러는 누구나 쉽게 3D 공간을 만드는 방법을 배울 수 있도록 세상에서 가장 친절한 교육자료를 만들고자 합니다. 먼저, 세계에서 가장 유명하지만 한국어 자료가 거의 없어 접하기 힘들었던 Three.js 정보를 하나부터 열까지 나눠드릴게요. 더 많은 이들에게 닿을 수 있도록, 정식 출판물의 일부를 사전 공개합니다.





모듈 방식?

모듈 방식은 ES6에서 도입된 방식으로 자바스크립트를 이용한 프로그래밍이 웹에서 보편화 되면서 발전했습니다. 자바스크립트로 구성된 프로그램들의 크기가 커지자 각각의 기능을 별도로 분리해서 모듈로 만들어 필요한 모듈만 호출하는 방식으로 진화하게 되었습니다. 

 

Three.js 또한 많은 기능을 하나의 자바스크립트 덩어리로 만들기엔 크기나 너무 커지고 다양한 커뮤니티의 개발자들을 통해서 Three.js 의 기능들이 확장되다 보니, Three.js 또한 모듈화 될 수 밖에 없는 상태가 되어 버렸습니다.



모듈 방식의 장점은 무엇일까요?

 

모듈의 장점은 개발할 때 상호 의존적인 스크립트들을 경로를 지정해 주면 해당 모듈을 로드 하기 때문에 해당 모듈에 따른 별도의 <scirpt> 태그를 일일이 작성하지 않는 장점이 있습니다.




Chapter1.

three.js를 실행하기 위한 환경 설정 – 모듈 방식 


그럼 이제부터 진행하는 예제는 모듈을 이용해서 작업해 보도록 하겠습니다. 

가장 먼저 바로 앞장에서 다룬 예제에서는 index.html 파일 하단에 다음과 같은 코드를 적용했습니다.


index.html 


  <script src="./js/three.min.js"></script>

<script src="./js/script.js"></script>


i

이제 이 부분을 아래와 같이 수정해 주시기 바랍니다.

index.html 


<script

  defer

  src="https://ga.jspm.io/npm:es-module-shims@1.6.2/dist/es-module-shims.js"

 ></script>

<script type="importmap">

     {

       "imports": {

         "three": "https://unpkg.com/three@0.147.0/build/three.module.js"

       }

     }

</script>

<script defer type="module" src="./js/script.js"></script>


[코드 1.2-1] 외부 라이브러리를 이용한 모듈형 three.module.js 호출


위와 같이 설정하게 되면 별도의 three.js 파일과 해당 모듈이 없더라도 three.js 개발환경이 완성되는 것입니다. 여기서 중요 사항 몇개를 알려드리겠습니다. 


먼저 첫번째 줄에 나오는 es-module.-shims.js 파일은 몇몇 브라우저(파이어폭스, 사파리 등)에서 모듈화를 지원하지 않는데, 해당 파일을 지정하게 되면 지원되지 않는 브라우저에서도 모듈이 정상적으로 작동하게 됩니다. 


두번째 스크립트에서는 type=”importmap” 이라고 지정하고 import를 이용하여 three 모듈의 경로를 지정하였습니다. 여기서는 외부에 있는 특히 unpkg.com 에 있는 모듈을 호출했는데, 만약 외부의 해당 URL를 호출하지 못하는 경우에는 로컬 상에 있는 파일을 지정해도 됩니다. 

예를 들어 다음과 같이 설정하는 것입니다. 


index.html


<script type="importmap">

     {

       "imports": {

         "three": "../three/build/three.module.js"

       }

     }

</script>


단 이 경우에는 반드시 경로 지정을 정확하게 해 주셔야 합니다. 


또한 크롬 브라우저에서는 반드시 es-module.-shims.js 이 필요하지 않습니다. 하지만 사파리나 파이어폭스 같은 브라우저에서는 이 파일이 없으면 정상 작동하지 않기 때문에 반드시 지정해 주는 것이 좋습니다.  여기서 주의할 점은 반드시 type에는 module을 지정해 줘야 합니다. 


그리고 마지막으로 주의할 점은  이렇게 모듈화된 자바스크립트는 반드시 서버에서 작동해야 합니다. 가상 서버를 사용하던지 실제 서버를 사용하던지 해야만 합니다. 단순하게 파일을 더블 클릭하더라도 해당 브라우저가 실행되지만 결과물은 절대 나타나지 않습니다. 


즉 chpater 1에서 설명 드렸던 Go Live 서버를 이용해서 가상서버를 이용하던지, 실제 호스팅 서버 또는 별도의 서버에 해당 파일을 넣어서 구동해야만 화면이 표시됩니다. PC상에서 모듈화된 자바스크립트 파일은 해당 자바스크립트 파일이 포함된  index.html을 단순 더블클릭 한다고 해서 보이지 않습니다. 경로가 만약  file://index.html 이렇게 되면 화면에 아무것도 나타나질 않습니다. 


Three.js 환경에서 만들어지 모델이나 빛 또는 기타 여러 요소들이 정상적으로 작동하지 않거나, 해당 속성에 대해서 확인하기 위한 도구가 필요합니다. 


자바스크립트를 이용해서 개발할 때 가장 많이 사용하는 도구는 아마 브라우저의 콘솔일 것입니다. 마찬가지 three.js 또한 자바스크립트 라이브러리이기 때문에 콘솔의 중요성은 아무리 강조해도 지나치지 않습니다. 이 책을 읽으시는 독자분들은 자바스크립트 기초는 알고 있다는 전재 하에 진행하는 것이므로 해당 학습을 진행할 때도 console.log() 라는 명령어를 많이 사용했을 것입니다.  

(크롬 브라우저에서 PC는  F12 키를 누르거나, 맥에선 option + command + I 키를 누르면 개발자 도구를 볼 수 있습니다. )


Three.js 또한 모든 오브젝트에 대해서 console.log 를 이용해서 해당 속성에 대한 정보를 얻을 수 있습니다. 


다시 chapter 1에 있는 예제에서 다음과 같이 scene을 한번 콘솔에 찍어 보시기 바랍니다. 

const scene = new THREE.Scene();

console.log(scene);


그림을 보시면 해당 scene에 대한 모든 정보들이 콘솔에 찍히는 것을 보실 수 있습니다. 


콘솔을 보시면 해당 속성 앞에 삼각형이 있는데, 이 부분을 클릭하시면 보다 자세하게 해당 속성을 볼 수 있으니, 한번 이 속성들은 어떤 것들이 있는지 보시는 것도 좋을 듯 합니다.


 

특히 three.js에서는 이렇게 console.log를 이용한 해당 속성에 대한 값을 쉽게 얻을 수 있는 방법이 console.log 를 이용한 방법이 가장 간단하기 때문입니다. 

이제 chapter 1에서 사용했던 파일을 그대로 가지고 온 후 다시 한번 모듈화 하여 작업하도록 하겠습니다. chapter 1에서 만든 오브젝트는 애니메이션이 되고 있긴 하지만 약간은 2차원 오브젝트가 회전하는 것 같아서 모듈화와 더불어 약간의 수정을 가해서 실제 3D와 같은 효과를 주도록 하겠습니다. 


index.html


<!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>Three sample</title>

  </head>

  <body>

    <canvas id="three"></canvas>

    <script

      defer

      src="https://ga.jspm.io/npm:es-module-shims@1.6.2/dist/es-module-shims.js"

    ></script>

    <script type="importmap">

      {

        "imports": {

          "three": "https://unpkg.com/three@0.147.0/build/three.module.js"

        }

      }

    </script>

    <script type="module">

      여기  three.js 관련 자바스크립트 코드들 

</script>

  </body>

</html>



chapter 1의 예제와 틀린 점은 body 태그 내부에 <canvas id="three"></canvas> 라는 태그가 들어간 것과 나머지 스크립트 부분을 모듈화 한 것 입니다. 


<script type="module"> 여기 들어가는 자바스크립트 파일 </script>을 한번 보도록 하겠습니다.


Three-sample/1.2.1.module_basic.html


import * as THREE from "three";


const scene = new THREE.Scene();

const canvas = document.querySelector("#three");

const camera = new THREE.PerspectiveCamera(

  75,

  window.innerWidth / window.innerHeight,

  0.1,

  1000

);


const renderer = new THREE.WebGLRenderer({ canvas });

renderer.setSize(window.innerWidth, window.innerHeight);


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

const material = new THREE.MeshStandardMaterial({ color: 0x3fb3d5 });

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

scene.add(cube);


camera.position.z = 5;


const ambientLight = new THREE.AmbientLight(0xffffff);

scene.add(ambientLight);


const pointLight = new THREE.PointLight(0xffffff, 0.5, 10);

pointLight.position.set(1, -0.5, 1);

scene.add(pointLight);


function animate() {

  requestAnimationFrame(animate);

  cube.rotation.x += 0.01;

  cube.rotation.y += 0.01;

  renderer.render(scene, camera);

}

animate();



이제 chapter 1에서 살펴본 코드와 비교하면서 어떤 점이 틀려졌는지 확인해 보도록 하겠습니다.



가장 먼저 아래와 같이 three.module.js 를 호출합니다. 


코드를 보시면 three 는 https://unpkg.com/three@0.147.0/build/three.module.js 입니다. 


import * as THREE from "three"


chapter 1에서는 아래와 같이 body에 appendChild 명령어를 통해서 새로운 돔 엘리먼트를 추가했습니다. 


document.body.appendChild(renderer.domElement);


하지만 새로운 코드에는 body 태그 내부에 canvas 태그를 추가해서 별도의 아이디 선택자로 #three를 추가했고, 해당 아이디를 렌더러에 할당하였습니다. 


const canvas = document.querySelector("#three");

const renderer = new THREE.WebGLRenderer({ canvas });


geometry 의 크기를 좀 더 크게 수정하고 , material의 재질을 변경하도록 하겠습니다.

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

const material = new THREE.MeshStandardMaterial({ color: 0x3fb3d5 }); 


여기서 chapter 1에서 사용된 재질은 MeshBasicMaterial 인데, 이 재질의 장점은 light 즉 빛이 없더라도 지오메트리를 볼 수 있습니다. 단 2차원 평면으로 나온다는 단점은 있지만 대략적으로 지오메트리의 크기, 모양 이런것을 확인하는 용도로는 가장 좋습니다. 라이트를 지정하기 전까진 말이죠. 하지만 굳이 그럴 필요는 없고 3차원 지오메트리를 평면적으로 표현하는 용도로 많이 사용하게 됩니다. 


지금 예제에서 사용된 재질(Material)은 가장 일반적인 Material 입니다. 그래서 명칭도 MeshStandardMaterial 입니다. 이 재질은 라이트가 없으면 보이질 않습니다.  Three.js에서 사용되는 재질은 다음과 같습니다. 일단 여기서는 이런 재질들이 있다고 알아만 두셔도 됩니다.


재질(Material)종류

속성

LineBasicMaterial

선(와이어프레임)을 만들 때 사용하는 재질

LineDashedMaterial

점선으로 와이어프레임 만들 때 사용하는 재질

Material

재질을 위한 추상적인 기본 클래스.

MeshBasicMaterial 

간단한 음영(플랫 또는 와이어프레임)만 생성해 주는 재질

MeshDepthMaterial

깊이로 지오메트리를 그리기 위한 재료. 깊이는 가깝고 먼 비행기의 카메라를 기반으로 한다. 흰색이 가장 가깝고, 검은색이 가장 멀다.

MeshDistanceMaterial

MeshDistanceMaterial은 PointLights로 그림자 매핑을 구현하는 데 내부적으로 사용됩니다.

MeshLambertMaterial

반사 하이라이트가 없는 반짝이는 표면을 위한 재료.

MeshMatcapMaterial

MeshMatcapMaterial은 재료 색상과 음영을 인코딩하는 MatCap(또는 Lit Sphere) 텍스처로 정의됩니다.

MeshNormalMaterial

일반 벡터를 RGB 색상에 매핑하는 재료.

MeshPhongMaterial

반사 하이라이트가 있는 반짝이는 표면을 위한 재료.

MeshPhysicalMaterial

더 진보된 물리적 기반 렌더링을 제공하는 MeshStandardMaterial의 확장

MeshStandardMaterial

금속 강성 워크플로우를 사용하는 표준 물리적 기반 재료.

MeshToonMaterial

툰 셰이딩을 구현하는 재료.

PointsMaterial

포인트가 사용하는 기본 자료.

RawShaderMaterial

이 클래스는 내장된 유니폼과 속성의 정의가 GLSL 셰이더 코드에 자동으로 추가되지 않는다는 점을 제외하고는 ShaderMaterial과 마찬가지로 작동합니다.

ShaderMaterial

사용자 지정 셰이더로 렌더링된 재료. 셰이더는 GPU에서 실행되는 GLSL로 작성된 작은 프로그램입니다. 필요하다면 사용자 지정 셰이더를 사용하고 싶을 수도 있습니다.

ShadowMaterial

이 물질은 그림자를 받을 수 있지만, 그렇지 않으면 완전히 투명하다.

SpriteMaterial

스프라이트와 함께 사용하기 위한 재료.


이제 재질을 변경했고, 변경된 재질이 실제 사물처럼 보이게 하는 빛(light)을 추가하도록 하겠습니다. Light 관련해서는 따로 다루도록 하겠습니다.


가장 먼저 ambientLight 를 추가 합니다. ambientLight는 번역하면 주변광으로 해석할 수 있는데, 이 빛은 방향성이 없는 빛입니다. 만들어진 오브젝트의 재질이 MeshBasicMaterial 이 아닌 경우 빛이 없을 경우 아무것도 나타나질 않는데, 이 광원을 추가해 줌으로써 해당 오브젝트가 보이게 됩니다.  주변광만 추가했을 경우에는 chapter 1에서 만들었던 예제와 다름 없는 결과를 얻을 수 있습니다. AmbientLight인 경우 색상만 지정할 수 있습니다.


const ambientLight = new THREE.AmbientLight(0xffffff);

scene.add(ambientLight);


코드를 보시면 ambientLight를 정의하고 해당 장면(scene)에 추가 해줍니다. 


이제 여기에 pointLight를 적용해 보겠습니다. pointLight는 해석되는 그대로 점광원으로 해석되는데, 일종의 전구 역할을 하는 것으로 보면 됩니다. 전구도 비추는 방향이 있고, 전구의 색 그리고 밝기가 있는 것 처럼 이 pointLight 또한 방향과 색 그리고 밝기를 지정합니다.


const pointLight = new THREE.PointLight(0xffffff, 0.5, 10);

pointLight.position.set(1, -0.5, 1);

scene.add(pointLight);


pointLight의 첫번째 요소는 빛의 색상, 두번째 요소는 밝기, 3번째 요소는 거리입니다. 그리고 방향(position)을 지정해 주고, 해당 장면에 추가합니다.


이게 결과값을 보면 다음과 같습니다.


결과를 보시면 실제 3차원 큐브가 회전하는 모습을 볼 수 있을 것입니다. 

여기서 드는 의문 하나는 그럼 ambientLight가 없으면 사물이 어떻게 보여질까? 하는 궁금증이 있을 것인데, 이 부분은 여러분이 직접 해당 코드를 주석 처리해서 결과를 확인해 보시길 바랍니다. 


chapter 2에서는 three.js의 개발환경 세팅 중에서 모듈을 이용한 개발환경과 chapter 1에서 실습했던 내용을 보다 좀 더 구체적인 3차원 모델로 어떻게 바꾸는 지에 대해서 간단하게 설명을 했습니다. 


지금까지 작업한 코드는 Three-sample/1.2.1.module_basic.html 에서 확인 가능합니다.



Chapter 2. 

FPS와 resize 설정 방법


이 상태에서 cube를 회전시키기 위해 animation 부분에 다음의 코드를 적용시켜보겠습니다.


cube.rotation.x += 0.01;

cube.rotation.y += 0.01;


즉 cube를 x축과 y축 기준으로 값을 0.01 씩 증감시키는 역할을 합니다. 


그런데 이런 방식으로 처리하게 되면 문제가 발생하는데, 이 값 즉 0.01 이라고 하는 값은 사용하고 있는 모니터의 주사율 값에 영향을 받게 됩니다. 이 말은 일반적으로 사용되는 60Hz의 모니터에서의 회전 속도와 고 주사율 특히 게이밍 모니터의 60Hz 이상의 모니터에서의 회전 속도가 다르게 됩니다.


일반적인 모니터의 주사율60Hz(헤르츠)를 기준으로 60Hz라는 의미는 1초에 화면 깜빡임을 60번 한다는 의미입니다. 그렇다면 144Hz는 1초에 144번 화면을 깜빡이게 되는게 되는데, 바로 위의 코드에서 보시면 자바스크립트에서 0.01은 1초라는 의미인데, 60Hz 모니터에서는 cube가 60번을 회전 하지만, 144Hz 에서는 1초에 cube가 144번 회전을 하게 됩니다. 


따라서 요즘처럼 게이밍 모니터 대중화가 되면서 주사율이 최대 244Hz 까지 나오는 상황에서 이런 방식으로 처리할 경우 회전 속도가 엄청나게 차이가 나게 됩니다. 


최근에 많이 사용되는 LCD 모니터 계열은 일반적으로 최소 30Hz 부터 244Hz 까지 시중에 나온 상태이고 앞으로는 이 주사율이 더 높은 모니터들이 출시 될 예정입니다. 따라서 해당 주사율에 영향을 받지 않게 애니메이션을 처리해야 합니다. 


우선 Three.js에서는 현재의 화면의 FPS(Frame per second)를 보여주는 기능이 내장되어 있습니다. 우선 이 기능을 현재의 코드에 적용해서 주사율이 다른 모니터에서 지금까지 작업한 코드를 적용해서 어떻게 작용하는 확인해 보도록 하겠습니다. 


이 기능은 Three.js의 부가 기능 중 하나로 해당 모듈을 따로 호출해야 합니다. 


먼저 다음과 같이 importmap 부분에 해당 모듈이 들어가 있는 디렉토리를 호출합니다.


<script type="importmap">

      {

        "imports": {

          "three": "https://unpkg.com/three@0.147.0/build/three.module.js",

          "three/examples/jsm/": "https://unpkg.com/three@0.147.0/examples/jsm/"

        }

      }

    </script>



코드를 보시면 three/example/jsm/ 이라는 디렉토리를 지정해 준 것을 볼 수 있는데, 이 디렉토리에는 three.js를 사용하면서 쓸 수 있는 많은 부가적인 기능이 들어가 있는 디렉토리 입니다. 이 부분은 아마 이제부터 사용할 예제에서는 거의 필수적으로 사용하게 되기 때문에 importmap 부분은 항상 이렇게 처리해 두시면 편리합니다.


FPS를 표시하는 기능은 Stats 라는 모듈로 실제 자바스크립트 코드에서는 다음과 같이 호출합니다.


import Stats from "three/examples/jsm/libs/stats.module"


그 후에 코드 내부에 다음의 코드를 삽입해 줍니다.


const stats = Stats(); 

document.body.appendChild(stats.dom);


그 후 애니메이션이 동작하는 부분에 항상 정보가 업데이트 되어야 하기 때문에 다음과 같이 stats.update() 함수를 호출합니다. 


function animate() {

        requestAnimationFrame(animate);

        cube.rotation.x += 0.01;

        cube.rotation.y += 0.01;

        renderer.render(scene, camera);

        stats.update();

}


여기까지 예제는 Three-sample/1.2.2.add_stat.html 에서 확인 가능합니다. 


아래 그림을 비교해서 보시면  화면 왼쪽 상단에 60 FPS 와 120 FPS가 표시되어 있는 것을 보실 수 있습니다. 현재 필자가 사용하는 모니터 중 하나가 120Hz 성능을 나타내는데, 60Hz 모니터에서의 cube 회전 속도보다, 120Hz 모니터에서의 cube 회전 속도가 훨씬 빠르게 회전하는 것을 실제로 확인할 수 있습니다.

 

그렇다면 이렇게 주사율이 낮은 모니터와 주사율이 높은 모니터에서 애니메이션 속도가 차이 난다고 하면, 실제 three.js 를 가지고 만드는 애니메이션 처리시 많은 문제점이 발생할 수 있습니다. 


그렇다면 이것을 방지할 수 있는 방법이 없을까요? 


물론 있습니다. 그리고 그렇게 어렵지 않습니다. 


이 문제를 해결하는 방법은 몇가지가 있지만, 가장 간단한 방법을 알려드리겠습니다. Three.js 에서는 내장되어 있는 시계 함수 즉 Clock() 이라는 함수가 있습니다. 이 함수를 사용하면 가장 간단하게 처리 가능합니다. 


먼저 다음과 같이 clock 이라는 변수명에 해당 함수를 할당해 줍니다.


const clock = new THREE.Clock();     


그 후에 이 clock를 콘솔에 나타내 보겠습니다. 


console.log(clock);

여기서 보이는 Clock 값은 여러분의 화면과 이 책의 화면과 차이가 날 수 있습니다. 왜냐하면 그림에서 보시는 elapsedTime  이 함수를 호출한 후 경과 값에서 차이가 나기 때문입니다. 이건 중요한 문제는 아니고 , 내장되어 있는 clock의 값은 항상 일정하다는 것이 핵심입니다. 즉 이것은 시계와 같은 기능으로 모니터의 주사율에 영향을 받지 않고 항상 일정한 값으로 증감하는 것입니다. 


이제 이 clock 함수를 이용해서 보정을 해보도록 하겠습니다.


일반적으로 애니메이션에서 시간이 경과되는 값을 delta time 라는 명칭을 많이 사용합니다. 이건 게임이나 애니메이션 관련 모든 분야에서 동일하게 사용되는 명칭입니다.  여기서 delta의 의미는 값의 차이를 의미하게 됩니다. 


따라서 여기서도 delta 라고 하겠습니다.


우선 처음 시작하는 delta 값은 0으로 설정하겠습니다.


let delta = 0;


여기서 delta는 let으로 설정한 이유는 delta의 값이 계속해서 변하기 때문입니다. Const 로 지정하게 되면 에러가 생깁니다. Const 로 지정하면 값이 변할 수 없기 때문입니다.


이제 애니메이션이 적용되는 부분에 다음과 같이 delta를 할당합니다. 


function animate() {

        delta = clock.getDelta();


이제 여기서 delta 값을 console.log을 이용해서 확인해 보겠습니다.


위 그림을 확인해 보시면 delta 값이 일정하게 0.015X 부터 0.018X 까지 표시되는 것을 보실 수 있습니다. 즉 평균적으로 0.017X 속도로 값이 나타나는 것을 확인할 수 있습니다. 이 값을 애니메이션에 적용하면 어떤 주사율을 가진 모니터에서 보더라도 cube가 동일한 속도로 회전 하는 것을 알 수 있습니다. 


애니메이션 부분 코드를 다음과 같이 처리하도록 하겠습니다.


function animate() {

        delta = clock.getDelta();

        console.log(delta);

        requestAnimationFrame(animate);

        cube.rotation.x += delta * 0.5;

        cube.rotation.+= delta * 0.5;

        renderer.render(scene, camera);

        stats.update();

      }



코드에서 delta 값에 0.5를 곱한 것은 애니메이션의 속도를 제어하는 역할을 합니다. 여러분들도 이 값을 수정하여 애니메이션의 속도를 제어할 수 있습니다.


여기까지 예제는 three-sample/1.2.3.animation_speed.html 에서 확인 가능합니다.


이번 장에서는 Three.js를 모듈 방식으로 실행하기 위한 환경을 설정하고, 사용자 컴퓨터 환경에 맞게 FPS를 설정하는 법을 알아보았습니다.


다음 장에서는 Three.js 렌더링 size를 사용자 환경에 맞게 Resize하는법과 텍스처 맵핑에 대해 알아본 후 그림자를 추가한 간단한 애니메이션을 구현해보겠습니다.


2023-02-08
조회 120




Tech 회사는 책을 씁니다. 

: 그래서, Three.js가 뭐냐면요!




Adler's Note


가상현실의 핵심이라 할 수 있는 3D는 어떻게 만들 수 있을까요? 초보 개발자나 개발 공부를 막 시작한 디자이너도 쉽게 사용하려면 어떤 공부가 필요할까요? 아들러는 누구나 쉽게 3D 공간을 만드는 방법을 배울 수 있도록 세상에서 가장 친절한 교육자료를 만들고자 합니다. 먼저, 세계에서 가장 유명하지만 한국어 자료가 거의 없어 접하기 힘들었던 Three.js 정보를 하나부터 열까지 나눠드릴게요. 더 많은 이들에게 닿을 수 있도록, 정식 출판물의 일부를 사전 공개합니다.




Three.js? WebGL? 

Three.js에 앞서 WebGL에 대해서 설명하도록 하겠습니다. WebGL은 Web Graphics Library의 약자이며, 웹에서 2D 또는 3D 그래픽을 표현해 주기 위한 자바스크립트 API입니다. HTML5의 canvas 태그내부에서 WebGL을 이용하여 그래픽을 표현해 주는데, WebGL은 그래픽카드를 이용한 하드웨어 가속도 가능하다는 장점이 있습니다.


하지만 HTML5의 Canvas 내부에 3D 삼각형 하나를 그리려면, 네이티브 WebGL은 최소 100줄의 코드가 필요합니다.  그 코드에는 카메라, 조명, 3D모델, 애니메이션 등이 필요합니다. 아주 간단한 삼각형인데도 말이죠. 이것이 네이티브 WebGL이 어려운 이유입니다.



Three.js는 누구나 쉽게 배울 수 있어요


Three.js는 WebGL을 기반으로 작동하는 자바스크립트 라이브러리입니다. 3D를 처리하는 과정을 획기적으로 간소화 하는 것을 목표로 하기 때문에, 단 몇 줄의 코드만으로도 웹페이지에 3D 모형과 애니메이션을 구현 할 수 있습니다. 더 쉽게 WebGL에서 제공하는 모든 라이브러리를 Three.js를 이용해서 구현할 수 있는 거죠.


자바스크립트를 다룰 수 있는 개발자나 디자이너라면 사용법을 익히는 시간도 많이 소요되지 않습니다. 덕분에 Three.js는 현재 가장 인기 있는 WebGL 라이브러리입니다. 매우 안정적이고, 많은 기능을 제공하며, 문서화도 잘 되어있죠. 실제로 WebGL과 관련해 가장 큰 커뮤니티는 Three.js 커뮤니티라고 할만큼 이미 많은 개발자들이 사용하고 있습니다.




Chapter1.
개발환경 세팅, Three.js를 Scene(장면) 만들기


Visual Studio Code(축약해서 VS Code)를 기본 에디터로 사용하도록 하겠습니다. 인터넷에 VS Code에 관한 내용은 정말 넘쳐나기 때문에 자세한 설명은 생략하고, 예제를 좀더 편리하게 실행하기 위해서 필요한 Extensions(확장프로그램)을 소개해 드리겠습니다.


 

Live Server


라이브 서버는 예제를 위해서 뿐 아니라, html 파일을 서버 없이 실시간으로 확인 할 수 있기 때문에 많은 개발자들이 사용하고 있고, 간단한 예제 파일은 라이브서버를 이용해서 확인 가능합니다.


이제 https://threejs.org/ 사이트에 들어간 후 download 버튼을 클릭하면 three.js-master.zip 파일을 다운 받을 수 있습니다.



Three.js-master.zip 파일에는 three.js로 구현된 다양한 파일들과 예제파일들 그리고 많은 문서들이 있습니다.


three.js-master.zip 파일의 압축을 풀면 많은 파일들이 나오는데, build 폴더에 있는 three.min.js 파일 하나만 복사하여 three-sample 폴더 내의 js 폴더에 복사해 준 후 전체 코드를 설정합니다. 일단 가장 기본적인 예제를 통해서 여러분들에게 three.js의 작동 원리만 설명해 드릴 예정입니다.


index.html


<!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>Three sample</title>

    <link rel="stylesheet" href="css/style.css" />

  </head>

  <body>

    <script src="./js/three.min.js"></script>

    <script src="./js/script.js"></script>

  </body>

</html>



 이 예제에서는 위와 같이 일반적으로 three.js 를 이용한 웹 사이트인 경우 최소한의HTML 코드만 들어갑니다. 그리고 지금 보시는 예제에서 CSS 파일 또한 굉장히 단순합니다.


css폴더에 있는 style.css 파일을 열고 다음과 같이 설정해 줍니다.


style.css


body {

  margin: 0;

  padding: 0;

}

canvas {

  width: 100%;

  height: 100%;

}



여기까지 HTML과 CSS 속성을 입력하면, 예제를 위한 거의 모든 준비가 다 끝난 상태입니다. 이제 남은 일은 script.js 파일에 three.js와 관련된 속성만 입력하면 됩니다.


우선 script.js 파일에 속성을 하나씩 입력하기 전에 3D 환경에 대해서 간단하게 설명하도록 하겠습니다. 지금까지 웹은 2차원 공간에서 모든 작업이 이루어져 있었습니다. 즉 화면의 X축(가로)과 Y축(세로)로 구성이 되어 있는데 three.js에서는 3차원 공간을 다룹니다. 즉 X,Y,Z 이렇게 3차원 공간으로 구성이 되어 있는 것이죠.


자 이제부터 하나의 장면을 생성하는 코드를 짜보도록 하겠습니다. 코드가 제공되기 하지만 여러분들이 하나씩 직접 한번 입력해 보시면 좋겠습니다. 그냥 여기서는 코드를 입력만 해 보시기 바랍니다.


가장 먼저 해야 할 일은 3차원 공간을 위한 SCENE입니다. 따라서 scene을 하나 만들어야 합니다.


scene라는 변수를 선언해 줍니다.


const scene = new THREE.Scene();


그리고 카메라를 설치합니다. PerspectiveCamera라는 내장함수를 사용합니다.


const camera = new THREE.PerspectiveCamera(

  75,

  window.innerWidth / window.innerHeight,

  0.1,

  1000

);


PerspectiveCamera 내장 함수에는 별도의 속성을 지정할 수 있습니다.


여기서 ‘75’ 라고 되어있는 숫자 부분은 FOV(Field of View)라고 하는 부분인데  해석하자면 ‘시야각’ 으로 해석 할 수 있습니다. 숫자가 낮을 수로 사물이 크게 보이고, 숫자가 크면 사물은 작게 보입니다. 즉 숫자가 작으면 광각 숫자가 크면 망원이라고 보면 됩니다.


아래 부분은 화면 비율(aspect)을 나타냅니다. 숫자로 바로 대입해도 됩니다.  최근 모니터들은 16:9 비율을 사용하고, 예전 오래된 TV인 경우 4:3 비율을 사용했는데, 아래의 코드는 현재 열려 있는 브라우저의 비율을 나타내는 것입니다. 일반적인 경우 아래와 같은 값을 입력하는 것이 가장 좋습니다.


window.innerWidth / window.innerHeight,


그리고 마지막 부분의 2개의 값은 카메라가 비추는 최소 근거리 값(near)과 최대 원거리 값(far)을 나타내는 것입니다.  0.1이 근거리를 100은 원거리는 나타냅니다. 이 부분은 생략해도 무방합니다.


0.1,

1000


카메라는 기본적으로 다음과 같이 변수를 이용해서 입력할 수도 있습니다.


const fov = 75;  

const aspect = window.innerWidth / window.innerHeight

const near = 0.1;

const far = 1000;

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);


이제 화면 렌더링을 위한 렌더러를 설치합니다. 여기도 마찬가지 변수에 three.js의 내장 함수를 선언해 주고, 선언된 변수에 화면 사이즈를 지정합니다.


const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);


이제 브라우저 화면에 렌더링 된 화면을 넣어줍니다. 가장 기본적인 자바스크립트를 이용한 HTML 태그 넣는 방식이죠.


document.body.appendChild(renderer.domElement);


여기까지 3D 화면을 만들기 위한 준비 과정이고 이제 우리가 정육면체를 하나 만들어서 화면에 넣어 보겠습니다.


geometry 라는 변수에 BoxGeometry값을 지정합니다. 변수 값은 반드시 geometry로 안 하셔도 됩니다. 그냥 box 라고 해도 무방합니다.  정육면체이기 때문에 X,Y,Z 축에 동일하게 크기를 1로 할당합니다. 


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


일반적인 BoxGeometry는 다음과 같은 값을 지정할 수 있습니다.


BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)


Width는 좌표상의 X축 길이, height는 좌표상의 Y축 길이, depth는 Z축 길이입니다. 값은 float(실수)를 입력합니다. 측면의 너비를 따라 분할된 직사각형 면의 수를 세그먼트(segment)라고 하는데 생략해도 무방합니다.


이제 재질을 지정합니다. 단순하게 색상만 지정합니다. 색상은 웹에서 흔히 사용하는 16진수 값을 이용하는데 웹에서는 #3fb3d45로 지정하는데 반해 three.js에서는 # 대신 0x를 붙힙니다.


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


이 방식이 Three.js에서 물체에 색을 입히는 가장 일반적인 방법이고, 색상은 이런 방법 이외에도 여러가지 방법으로 지정해 줄 수 도 있습니다. 그냥 일반적인 색상으로 값을 지정해도 됩니다. 단 그런 경우에는 반드시 “ ”(따옴표) 로 지정해 줘야 합니다. 


색상 지정은 여러분들이 편한 방법으로 하시면 됩니다.
다음과 같이 지정된 재질은 전부 빨간 색상으로 재질 색상이 지정됩니다.


const material = new THREE.MeshBasicMaterial({ color: "red" });


또는


const material = new THREE.MeshBasicMaterial({ color: "hsl(0,100%,50%)" });


또는


const material = new THREE.MeshBasicMaterial({ color: "rgb(255,0,0)" });


이제 cube라고 정의한 부분에 앞서 만든 geometry와 material을 적용합니다.


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


이제 장면(Scene)에 만들어진 cube를 추가합니다.


scene.add(cube);


여기까지 입력하셨으면, 전체 코드의 80%가 완성된 것입니다. 하지만 여기까지 입력해도 화면에는 아무것도 나타나질 않습니다. 왜 화면에 아무것도 보이지 않을까요? 지금까지 화면을 설정하고 카메라 가지고 와서 모델까지 세웠는데, 카메라를 가지고만 왔지 카메라로 해당 모델을 찍고 있지 않고 있기 때문입니다.   이제 카메라에 해당 모델 쪽으로 방향을 지정해 보겠습니다.


camera.position.x = 0;

camera.position.= 0;

camera.position.z = 5;


여기서 camera 라는 변수에 position위치 값을 지정했는데, X축과 Y축은 0, Z축은 5를 지정했습니다.


여기까지 입력하더라도 화면에는 아무것도 나타나지 않습니다.


왜 일까요?


원인은 화면에 렌더링을 해주지 않고 있기 때문입니다.


마지막으로 앞서 정의된 renderer에 scene과 camera를 지정하겠습니다.


renderer.render(scene, camera);


여기까지 script.js 파일에 입력 한 후 VS Code의 하단에 표시된  Go Live 부분을 클릭해 줍니다. 결과를 확인 가능합니다. 근데 화면을 보시면 3차원이 아니라 2차원으로 박스 하나만 덩그러니 나옵니다. 사실 이 박스는 2차원이 아니라 3차원인데, 현재 설정 상 2차원으로 보이는 것 뿐입니다.



지금까지 만들어진 코드에서 카메라의 위치를 아래와 같이 약간만 조정해 보겠습니다.


camera.position.x = 1;

camera.position.y = 1;

camera.position.z = 5;



결과를 보시면 카메라의 위치를 약간 조정했더니 평면이었던 박스가 갑자기 3차원으로 변하는 모습을 볼 수 있습니다. 즉 해당 박스의 모습은 2차원이 아니라 3차원 공간에 있던 것입니다.  앞 전에 설명했던 카메라의 속성 값이나, geometry에 정의된 new THREE.BoxGeometry(1, 1, 1); 의 값 그리고 camera.position의 여러 값을 여러분들이 조정해 보시면 실시간으로 여러분들이 설정한 값으로 변하는 모습을 확인할 수 있습니다.


하지만 카메라의 위치를 변경하면 cube의 위치가 중심점이 아니라 화면 왼쪽 하단에 위치하게 됩니다.


예제에서 카메라의 위치 말고 현재 만들어진 cube의 방향을 움직여 보겠습니다.  

camera.position.x 와 camera.position.y  값이 0 이면 해당 속성은 굳이 적용하지 않아도 됩니다.


따라서 아래의 코드와 같이 Z 축에만 값을 적용하고 cube에 rotation(회전) 속성을 X축과 Y축에 적용해 보겠습니다.


camera.position.= 5;

cube.rotation.= 1;

cube.rotation.y = 2;

renderer.render(scene, camera);



결과를 보시면 cube는 중앙에 위치해 있고 X축과 Y축으로 회전이 되어 있는 모습을 볼 수 있습니다.


일반적으로 3D라고 하면 동작이 되어야 하겠죠? 지금까지 만들어진 cube를 움직여 보겠습니다. 즉 애니메이션을 적용하도록 하겠습니다. 애니메이션을 적용하기 위해선 별도의 함수를 만들어 적용하면 됩니다.


아래의 코드를 살펴보면 animate 라는 함수를 정의 하고 내부에  requestAnimationFrame(animate); 라는 three.js의 내장 함수를 적용하고 바로 앞전에 설명한 cube.rotaion 부분에 0.01씩 값을 증가하게 처리하고 renderer 를 적용했습니다.


그리고 마지막으로 animate 함수를 실행하였습니다.


function animate() {

  requestAnimationFrame(animate);

  cube.rotation.x += 0.01;

  cube.rotation.y += 0.01;

  renderer.render(scene, camera);

}

animate();


script.js


const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(

  75,

  window.innerWidth / window.innerHeight,

  0.1,

  1000

);

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

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

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

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

scene.add(cube);

camera.position.z = 2;

function animate() {

  requestAnimationFrame(animate);

  cube.rotation.x += 0.01;

  cube.rotation.y += 0.01;

  renderer.render(scene, camera);

}


animate();


Three-sample/1.1.basic.html


실행 결과물은 직접 확인하시기 바랍니다. 아마 cube가 X축과 Y축으로 회전하고 있을 것입니다.


다음 장에서는 Three.js를 모듈 방식으로 실행하기 위한 환경을 설정하고, 사용자에 컴퓨터 환경에 맞게 작동할 수 있도록 FPS와 resize를 설정하는 방법을 알아보도록 하겠습니다.