Scene Graph를 이용한 공간구성

Object3D의 파생클래스

  • Mesh : 3차원 면으로 구성된 객체 표현

  • Line : 선형 객체를 표현

    • LineSegments : 두 개의 점을 이어 한 선을 만듬
    • LineLoop : Line 처럼 순서대로 이어오다 마지막 좌표와 첫번째 좌표를 이음
  • Points : 점 표현

    이러한 Mesh, Line, Points에 대한 3차원 객체는 3차원 공간 상에 놓여지게됨._

  • 3D Object가 3차원 공간 상에 놓여지기 위해서는 위치, 회전, 크기 값이 필요

    -> Object 3D는 position(위치), rotation(회전), scale(크기)이라는 속성 가짐

    • position : xyz 축에 대한 위치 값. (모든 기본값 : 0)
    • rotation : xyz 축에 대한 회전 값. (모든 기본값 : 0)
    • scale : xyz 축에 대한 크기의 배수 값.(모든 기본값 : 1 = 원래크기)
    • position, rotation, scale은 4x4 크기의 행렬 정보로 변환
    • 모니터 기준으로 X축은 화면의 오른쪽이 +, 왼쪽이 -
    • 모니터 기준으로 Y축은 화면의 위쪽이 +, 아래쪽이 -
    • 모니터 기준으로 Z축은 모니터의 바깥 방향이 +, 안쪽 방향이 -

3차원 공간 상의 장면 구성 : Scene Graph를 코드로 작성

태양, 지구, 달에 대한 scene graph

 - 검은 글자 : 오른쪽 클래스로 생성된 객체의 이름
 - 회색 글자 : 클래스 타입
 - 화살표 : 자식을 가리킴
/*three.js 라이브러리를 module 버전으로 import*/ 
import * as THREE from '../build/three.module.js';

/*three.module.js에 App이 정의되어 있음*/
class App{

  constructor(){
      /*divContainer*/
      const divContainer = document.querySelector("#webgl-container");
      this._divContainer = divContainer;/*다른 method에서 참조할 수 있도록 필드로 정의*/ //밑줄로 시작:이 App 클래스 내부에서만 사용되는 private field, private method라는 의미(개발자들간의 약속-밑줄로 시작하는 것은 외부에서 호출해서는 안됨)

      /*rederer*/ //THREE = three.js WebGLRenderer = three.js의 클래스 중 하나
      const renderer = new THREE.WebGLRenderer({ antialias: true });//생성할 때 옵션 - 3차원 장면이 렌더링 될 때 오브젝트들의 경계선이 계단 현상 없이 부드럽게 표현
      renderer.setPixelRatio(window.devicePixelRatio);//픽셀의 ratio 값 정의(pixel ratio 값은 window의 devicePixelRatio 속성으로 쉽게 얻을 수 있음) 150%일때 1.5 등
      divContainer.appendChild(renderer.domElement);//생성된 renderer의 domElement를 webjl-container인 divContainer의 자식으로 추가 renderer.domElement : canvas 타입의 dom 객체
      this._renderer = renderer;//rederer가 다른 method에서 참조할 수 있도록 this._renderer로 정의

      /*scene*/
      const scene = new THREE.Scene();//Scene 객체 생성하는 코드 Scene 클래스 불러옴
      this._scene = scene;//scene객체 필드화


      /*하위 3개 코드 따로 정의해야하는 것임*/
      this._setupCamera();//camera 객체 구성
      this._setupLight();//light를 설정하는 메서드
      this._setupModel();//3차원 모델 설정하는 메서드

      window.onresize = this.resize.bind(this);//창 크기가 변경되면 발생하는 onresize 이벤트에 이 클래스의 resize method 지정 : renderer나 camera는 창 크기 변경될 때마다 그 크기에 맞게 속성 값 재설정해야하기 때문에 필요 //bind : resize method 안에서 this가 가르치는 객체가 아닌 이 App 클래스의 객체가 되도록 하기 위함
      this.resize();//창 크기에 맞게 설정

      requestAnimationFrame(this.render.bind(this));//적당한 시점에 또한 최대한 빠르게 renderer method를 호출
  }
  
  _setupCamera(){/*Camera 생성하는 코드*/
      /*three.js가 3차원 그래픽 출력할 영역에 대한 가로와 세로에 대한 크기 얻어옴 */
      const width = this._divContainer.clientWidth;
      const height = this._divContainer.clientHeight;
      /*얻어온 크기를 이용해 카메라 객체 생성*/
      const camera = new THREE.PerspectiveCamera(
          75,
          width / height,
          0.1,
          100
      );
      camera.position.z = 25;
      this._camera = camera;//또 다른 method에서 사용할 수 있도록 함
  }

  _setupLight(){/*Light(광원) 생성하는 코드*/
      const color = 0xffffff;//광원 색상
      const intensity = 1;//광원 세기 값
      const light = new THREE.DirectionalLight(color,intensity);//위 두가지 값으로 광원 생성
      light.position.set(-1,2,4);//광원 위치 설정
      this._scene.add(light); //생성한 광원을 scene 객체의 구성요소로 추가
  }

  _setupModel(){
      //Object3D 타입의 SolarSystem 객체 생성 후 scene에 추가
     const solarSystem = new THREE.Object3D();
     this._scene.add(solarSystem);

     //구 모양의 지오메트리 생성
     const radius = 1; //반지름 1
     const widthSegments = 12;
     const heightSegments = 12;
     const sphereGeometry = new THREE.SphereGeometry(radius,widthSegments,heightSegments);

     //태양의 재질 생성
     const sunMaterial = new THREE.MeshPhongMaterial({
      emissive : 0xffff00, flatShading: true});
      
     //지오메트리와 태양의 재질을 이용해 sunMesh 객체 생성, solarSystem에 추가
      const sunMesh = new THREE.Mesh(sphereGeometry,sunMaterial);
      sunMesh.scale.set(3,3,3);//원래 지오메트리가 갖는 크기보다 xyz축에 대해 3배의 크기로 표시하기 위함
      solarSystem.add(sunMesh);

      /*지구*/
      const earthOrbit = new THREE.Object3D();
      solarSystem.add(earthOrbit);

      const earthMaterial = new THREE.MeshPhongMaterial({
          color:0x2233ff, emissive:0x112244, flatShading:true
      });

      const earthMesh = new THREE.Mesh(sphereGeometry,earthMaterial);
      earthOrbit.position.x = 10;//태양에서 x축으로 거리 10만큼 떨어진 위치에 지구가 배치 되도록 하기 위함
      earthOrbit.add(earthMesh);

      /*달*/
      const moonOrbit = new THREE.Object3D();
      moonOrbit.position.x = 2;//지구로부터 2만큼 떨어지고 태양으로부터 12만큼 떨어짐
      earthOrbit.add(moonOrbit);

      const moonMaterial = new THREE.MeshPhongMaterial({
          color:0x888888, emissive:0x222222, flatShading:true });
      
          const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
          moonMesh.scale.set(0.5,0.5,0.5);
          moonOrbit.add(moonMesh);

          this._solarSystem = solarSystem;
          this._earthOrbit = earthOrbit;
          this._moonOrbit = moonOrbit;
      }

  resize(){
      const width = this._divContainer.clientWidth;
      const height = this._divContainer.clientHeight;

      this._camera.aspect = width / height;
      this._camera.updateProjectionMatrix();

      this._renderer.setSize(width, height);
  }

  render(time){//time:렌더링 시작 후 경과된 시간 값(밀리 초) - scene의 애니메이션에 이용할 수 있음. requestAnimationFrame 함수가 render 함수에 전달해주는 값.
      this._renderer.render(this._scene, this._camera);//renderer가 scene을 카메라 시점으로 렌더링 하는 코드
      this.update(time);//속성 값 변경 : 애니메이션 효과 
      requestAnimationFrame(this.render.bind(this)); //render method 반복해 호출 되도록 함(무조건은 아니고 적당한 시점에 최대한 빠르게)
  }

  update(time){
      time *= 0.001; // second unit:전달받은 time에 0.001을 곱해서 second 단위로 변환
      this._solarSystem.rotation.y = time / 2;
      this._earthOrbit.rotation.y = time * 2;
      this._moonOrbit.rotation.y = time *5;
  }
}//class

window.onload = function(){
  new App();
}

좋은 웹페이지 즐겨찾기