본문 바로가기

three.js

[three.js][typescript] - Light(1)

728x90

Geometry, Material에 이어 Light(광원)에 대해 알아보자

 

three.js가 제공하는 광원의 종류

 

광원이란 빛을 방출하는 물체나 현상을 말한다.

 

Three.js에서는 Light를 상속받는 6개의 광원 클래스와 그 외 HDRI를 이용하는 광원이 있는데,

 

우린 이중에 DirectionalLight와 HDRI를 이용한 광원을 사용해 보았다.

 

참고로 DirectionalLight, PointLight, SpotLight는 그림자를 생성해 줄 수 있는 광원이다.

 

그럼 위의 광원들에 대해 자세히 알아보자.

 

 

 

 

[three.js][typescript] - Transform(3)

SceneGraph   [three.js][typescript] - Transform(2)행렬(Matrix4)을 적용한 Transform    [three.js][typescript] - Transform(1)Object3D 클래스   Mesh, Scene, Camera, Audio는 Object3D를 상속받는 클래스다.   Object3D는 Position,

yoohwanihn.tistory.com

 

 

기존의 Transform때 사용했던 코드를 활용해서 진행하자.

import { OrbitControls } from 'three/examples/jsm/Addons.js'
import './style.css'
import * as THREE from 'three'
 
class App {
  private renderer: THREE.WebGLRenderer //Renderer Field 추가
  private domApp: Element 
  private scene: THREE.Scene
  private camera?: THREE.PerspectiveCamera //?를 붙이면 PerspectiveCamera Type이나 Undefined Type을 가질 수 있음.(Optional Properties)
  
  constructor(){
    console.log("YooHwanIhn");
    this.renderer = new THREE.WebGLRenderer({antialias:true}) // 안티-알리아스 : 높은 렌더링 결과를 얻기 위해 픽셀 사이에 계단 현상을 방지하는 효과 추가
    this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio)) // 고해상도에서 깨짐 방지를 위해 픽셀 비율 지정
 
    this.domApp = document.querySelector('#app')! 
    this.domApp.appendChild(this.renderer.domElement) // renderer.domeElement : canvas Type의 DOM 객체
 
    this.scene = new THREE.Scene()
 
    this.setupCamera()
    this.setupLight()
    this.setupModels()
    this.setupEvents()
  }
 
  private setupCamera(){
    const width = this.domApp.clientWidth
    const height = this.domApp.clientHeight
    
    this.camera = new THREE.PerspectiveCamera(75, width/height, 0.1, 100)
    this.camera.position.z = 5  // (0, 0, 5)

    new OrbitControls(this.camera, this.domApp as HTMLElement)
  }
 
  private setupLight(){
    //빛의 색상 값과 빛의 강도
    const color = 0xffffff  // white
    const intensity = 1
    const light = new THREE.DirectionalLight(color, intensity)  //광원
    light.position.set(-1, 2, 4)
 
    this.scene.add(light)
  }
 
  private setupModels(){
    const axisHelper = new THREE.AxesHelper(10) // 좌표축
    this.scene.add(axisHelper)

    const geomGround = new THREE.PlaneGeometry(5, 5) // ground (5x5)
    const matGround = new THREE.MeshStandardMaterial({
      color: "#2c3e50",
      roughness: 0.5,
      metalness: 0.5,
      side: THREE.DoubleSide
    })
    const ground = new THREE.Mesh(geomGround, matGround)
    ground.rotation.x = -THREE.MathUtils.degToRad(90)
    ground.position.y = -.5
    this.scene.add(ground)

    const geomBigSphere = new THREE.SphereGeometry(1, 32, 16, 9, THREE.MathUtils.degToRad(360), 0, THREE.MathUtils.degToRad(90))
    const matBigSphere = new THREE.MeshStandardMaterial({
      color: "#ffffff",
      roughness: 0.1,
      metalness: 0.2
    });
    const bigSphere = new THREE.Mesh(geomBigSphere,matBigSphere)
    bigSphere.position.y = -.5
    this.scene.add(bigSphere)

    const geomSmallSphere = new THREE.SphereGeometry(0.2)
    const matSmallSphere = new THREE.MeshStandardMaterial({
      color: "#e74c3c",
      roughness: 0.2,
      metalness: 0.5
    })
    const smallSphere = new THREE.Mesh(geomSmallSphere, matSmallSphere)

    const smallSpherePivot = new THREE.Object3D();
    smallSpherePivot.add(smallSphere)
    bigSphere.add(smallSpherePivot);
    smallSphere.position.x = 2
    smallSpherePivot.rotation.y = THREE.MathUtils.degToRad(-45)
    smallSphere.position.y = 0.5 // bigSphere의 y position이 -.5기 때문에
    smallSpherePivot.name = "smallSpherePivot"  // update에서 접근하기 쉽게 명명함

    const cntItems = 8
    const geomTorus = new THREE.TorusGeometry(0.3, 0.1)
    const matTorus = new THREE.MeshStandardMaterial({
      color: "#9b59b6",
      roughness: 0.5,
      metalness: 0.9
    })
    for(let i=0; i<cntItems; i++){
      const torus = new THREE.Mesh(geomTorus, matTorus)
      const torusPivot = new THREE.Object3D() // Torus 역시 반 구를 기준으로 회전하면서 8개를 생성하기 때문에 피봇이 필요함
      bigSphere.add(torusPivot)

      torus.position.x = 2  // smallSphere의 x 포지션과 일치하게함
      torusPivot.position.y = 0.5 // bigSphere의 y 포지션이 -.5임
      torusPivot.rotation.y = THREE.MathUtils.degToRad(360) / cntItems * i 
      torusPivot.add(torus)
    }
  }
 
  //실제 이벤트와 렌더링 처리를 다룰 메서드
  private setupEvents(){
    window.onresize = this.resize.bind(this); // html의 size가 변경될 때마다 호출되는 함수(addEventListener 느낌??)
    this.resize();
 
    this.renderer.setAnimationLoop(this.render.bind(this)) // 연속적으로 render 메서드 호출(모니터 Frame에 맞게)
  }
 
  // html창 resize시 호출할 함수
  private resize(){
    const width = this.domApp.clientWidth
    const height = this.domApp.clientHeight
 
    //앞선 setUpCamera에서 설정한 camera 정보를 다시 수정해줌.
    const camera = this.camera
    if(camera){
      camera.aspect = width / height
      camera.updateProjectionMatrix() // 카메라의 값이 변경되었다면 수정하도록 함.
    }
 
    //renderer도 마찬가지로 사이즈 수정함
    this.renderer.setSize(width, height)
  }
 
  private update(time: number){
    time *= 0.001 // ms -> s 단위로 변경

    const smallSpherePivot = this.scene.getObjectByName("smallSpherePivot")
    if(smallSpherePivot){
      smallSpherePivot.rotation.y = time;
    }
  }
 
  private render(time: number){
    // time : setAnimationLoop의 값에 의해서 결정되는데 단위는 ms
    this.update(time)
 
    this.renderer.render(this.scene, this.camera!)
  }
}
 
new App()

 

기존의 코드에서 변화된 점은 각 Material에 color(색상)와 roughness(거칠기), metalness(금속성)을 설정해 변화를 주었다.

 

 

 

 

 

결과

 

Transform의 결과와 color, roughness, metalness를 제외하고 동일한 결과가 나온다.

 

 

 

 

 

위 코드에서 광원에 대한 부분은 setupLight에 설정되어 있다.

 

DirectionalLight 광원을 사용한 기존의 코드를 지우고 새로 작성해 보자.

 

 

 


 

Ambient Light

 

 

Ambient Light(환경광, 주변광)에 대해 알아보자

 

Ambient Light를 포함한 Light 객체들은 빛의 색상과 빛의 광도(세기)를 인자로 받는다.

 

 

 

 

흰색 광원을 생성하여 이를 scene에 추가하여 확인해 보자.

 

 

Ambient Light 결과

 

 

재질(Material)의 색상과 광원의 색상이 서로 섞여 표현된다.

 

Ambient Light는 단순히 Scene에 존재하는 모든 물체에 대해 Shading 없는 단순한 색상으로 렌더링 되도록 한다.

 

대부분의 경우 이 광도(세기) 값을 약하게 하고 장면을 추가함으로써

 

광원의 영향을 받지 않는 물체도 보여지도록 사용하는 경우가 있다.

 

 

 

 

 

 


 

HemisphereLight

 

 

 

HemisphereLight 역시 AmbientLight와 마찬가지로 주변광이다.

 

하지만 HemisphereLight는 2개의 색상 값을 갖는다.

 

 

 

하나는 위에서 비치는 색상 값이고, 나머지 하나는 아래에서 비치는 색상 값이다.

 

그렇기에 인자값은 색상 값 2개와 광도(세기) 값 1개, 총 3개를 받는다.

 

 

 

 

Hemisphere Light 결과

 

 

 

 

 

 


 

Directional Light

 

 

 

Directional Light는 태양과 같은 광원이다. 태양처럼 일정한 방향으로 모든 물체에 동일한 영향을 준다.

 

 

맨 처음 사진처럼 Directional Light는 특정 방향으로의 방향성이 있는 빛이기 때문에 

 

광원의 포지션을 지정해줘야 한다. (태양의 위치가 지정되어 있는 것처럼)

 

그리고 광원이 비추는 대상의 위치(방향)를 지정해줘야 한다. 

 

Directional Light 결과

 

 

초기에 본 결과랑 유사한 결과가 나왔다.

(광원의 position이 다름)

 

이를 좀 더 자세히 비교해 보기 위해 Helper 클래스를 추가해 보자.

 

 

다음과 같은 코드를 추가해 준다.

 

 

Directional Light Helper 결과

 

위의 네모난 사각형이 광원이며 흰색 선이 광원의 타겟 방향이다.

 

광원의 포지션은 (0, 1, 0)으로 y축으로 1만큼 올라가 있고

 

광원의 타겟(방향)은 (0, 0, 0)으로 y축 방향으로 아래방향으로 빛을 비추는 것을 도출할 수 있고

 

결과 역시 일치하는 것을 확인할 수 있다.

 

 

 

 

 

이번에는 광원이 비추는 타겟을 빨간색 구를 추적하도록 변경해 보자.

 

 

이를 위해 광원과 헬퍼를 필드화 한다.

 

 

 

 

그리고 필드화한 광원과 헬퍼에 값을 할당하는 코드를 추가해 준다.

 

그러나 이대로만 실행하면 문제가 생길 수밖에 없다.

 

Geometry를 학습할 때 알아본 것처럼, 빨간색 구는 지속적으로 update 되어 위치가 이동하는 방식이므로 

 

필드화한 값 역시 update 해주어야 한다.

 

그러기 위해선

 

 

 

먼저 움직이는 빨간색 객체를 얻어와야 하는데 이 객체는 smallSpherePivot의 첫 번째 자식이다.

 

 

 

 

 

이 smallSphere의 월드 좌표계의 위치를 얻어서 광원의 타겟 객체에 설정하는 코드를 작성해 준다.

 

광원의 타겟 속성이 변경되었으므로 이 광원을 시각화해 주는 Helper 역시 업데이트해준다.

 

 

 

 

 

 

다행(?)스럽게도 해당 기능을 제공하는 API가 이미 존재했다. (아래 홈페이지 참고)

 

 

three.js docs

 

threejs.org

 

 

 

 

 

 

 

코드를 다 추가하였으니 결과를 확인해 보자.

 

 

 

 

 

이제 제법 그럴싸한 결과가 나왔다.

 

광원의 타겟 포지션이 변화하면서 헬퍼 역시 변화되는 모습을 확인할 수 있다.

 

 

 




 

Point Light

 

 

Point Light는 광원의 위치에서 사방으로 비추는 광원이다.

 

이를 알아보기 위해 기존의 겹치는 코드들을 주석 처리 하자.

 

import { OrbitControls } from 'three/examples/jsm/Addons.js'
import './style.css'
import * as THREE from 'three'
 
class App {
  private renderer: THREE.WebGLRenderer //Renderer Field 추가
  private domApp: Element 
  private scene: THREE.Scene
  private camera?: THREE.PerspectiveCamera //?를 붙이면 PerspectiveCamera Type이나 Undefined Type을 가질 수 있음.(Optional Properties)
  
  /*
  private light?: THREE.DirectionalLight
  private helper?: THREE.DirectionalLightHelper
  */

  constructor(){
    console.log("YooHwanIhn");
    this.renderer = new THREE.WebGLRenderer({antialias:true}) // 안티-알리아스 : 높은 렌더링 결과를 얻기 위해 픽셀 사이에 계단 현상을 방지하는 효과 추가
    this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio)) // 고해상도에서 깨짐 방지를 위해 픽셀 비율 지정
 
    this.domApp = document.querySelector('#app')! 
    this.domApp.appendChild(this.renderer.domElement) // renderer.domeElement : canvas Type의 DOM 객체
 
    this.scene = new THREE.Scene()
 
    this.setupCamera()
    this.setupLight()
    this.setupModels()
    this.setupEvents()
  }
 
  private setupCamera(){
    const width = this.domApp.clientWidth
    const height = this.domApp.clientHeight
    
    this.camera = new THREE.PerspectiveCamera(75, width/height, 0.1, 100)
    this.camera.position.z = 5  // (0, 0, 5)

    new OrbitControls(this.camera, this.domApp as HTMLElement)
  }
 
  private setupLight(){
    // const light = new THREE.AmbientLight("#ffffff", 1)
    //const light = new THREE.HemisphereLight("#b0d8f5", "#bb7a1c",1)

    /*
    const light = new THREE.DirectionalLight(0xffffff, 1)
    light.position.set(0, 1, 0)
    light.target.position.set(0, 0, 0)
    this.scene.add(light.target)
    this.light = light

    const helper = new THREE.DirectionalLightHelper(light)
    this.scene.add(helper)
    this.helper = helper

    this.scene.add(light)
    */
  }
 
  private setupModels(){
    const axisHelper = new THREE.AxesHelper(10) // 좌표축
    this.scene.add(axisHelper)

    const geomGround = new THREE.PlaneGeometry(5, 5) // ground (5x5)
    const matGround = new THREE.MeshStandardMaterial({
      color: "#2c3e50",
      roughness: 0.5,
      metalness: 0.5,
      side: THREE.DoubleSide
    })
    const ground = new THREE.Mesh(geomGround, matGround)
    ground.rotation.x = -THREE.MathUtils.degToRad(90)
    ground.position.y = -.5
    this.scene.add(ground)

    const geomBigSphere = new THREE.SphereGeometry(1, 32, 16, 9, THREE.MathUtils.degToRad(360), 0, THREE.MathUtils.degToRad(90))
    const matBigSphere = new THREE.MeshStandardMaterial({
      color: "#ffffff",
      roughness: 0.1,
      metalness: 0.2
    });
    const bigSphere = new THREE.Mesh(geomBigSphere,matBigSphere)
    bigSphere.position.y = -.5
    this.scene.add(bigSphere)

    const geomSmallSphere = new THREE.SphereGeometry(0.2)
    const matSmallSphere = new THREE.MeshStandardMaterial({
      color: "#e74c3c",
      roughness: 0.2,
      metalness: 0.5
    })
    const smallSphere = new THREE.Mesh(geomSmallSphere, matSmallSphere)

    const smallSpherePivot = new THREE.Object3D();
    smallSpherePivot.add(smallSphere)
    bigSphere.add(smallSpherePivot);
    smallSphere.position.x = 2
    smallSpherePivot.rotation.y = THREE.MathUtils.degToRad(-45)
    smallSphere.position.y = 0.5 // bigSphere의 y position이 -.5기 때문에
    smallSpherePivot.name = "smallSpherePivot"  // update에서 접근하기 쉽게 명명함

    const cntItems = 8
    const geomTorus = new THREE.TorusGeometry(0.3, 0.1)
    const matTorus = new THREE.MeshStandardMaterial({
      color: "#9b59b6",
      roughness: 0.5,
      metalness: 0.9
    })
    for(let i=0; i<cntItems; i++){
      const torus = new THREE.Mesh(geomTorus, matTorus)
      const torusPivot = new THREE.Object3D() // Torus 역시 반 구를 기준으로 회전하면서 8개를 생성하기 때문에 피봇이 필요함
      bigSphere.add(torusPivot)

      torus.position.x = 2  // smallSphere의 x 포지션과 일치하게함
      torusPivot.position.y = 0.5 // bigSphere의 y 포지션이 -.5임
      torusPivot.rotation.y = THREE.MathUtils.degToRad(360) / cntItems * i 
      torusPivot.add(torus)
    }
  }
 
  //실제 이벤트와 렌더링 처리를 다룰 메서드
  private setupEvents(){
    window.onresize = this.resize.bind(this); // html의 size가 변경될 때마다 호출되는 함수(addEventListener 느낌??)
    this.resize();
 
    this.renderer.setAnimationLoop(this.render.bind(this)) // 연속적으로 render 메서드 호출(모니터 Frame에 맞게)
  }
 
  // html창 resize시 호출할 함수
  private resize(){
    const width = this.domApp.clientWidth
    const height = this.domApp.clientHeight
 
    //앞선 setUpCamera에서 설정한 camera 정보를 다시 수정해줌.
    const camera = this.camera
    if(camera){
      camera.aspect = width / height
      camera.updateProjectionMatrix() // 카메라의 값이 변경되었다면 수정하도록 함.
    }
 
    //renderer도 마찬가지로 사이즈 수정함
    this.renderer.setSize(width, height)
  }
 
  private update(time: number){
    time *= 0.001 // ms -> s 단위로 변경

    const smallSpherePivot = this.scene.getObjectByName("smallSpherePivot")
    if(smallSpherePivot){
      //smallSpherePivot.rotation.y = time;
      const euler = new THREE.Euler(0, time, 0)
      const quaternion = new THREE.Quaternion().setFromEuler(euler)
      smallSpherePivot.setRotationFromQuaternion(quaternion)

      const smallSphere = smallSpherePivot.children[0]  // 화면상 회전하는 빨간색 구체

      /*
      smallSphere.getWorldPosition(this.light!.target.position) // this.light!는 this.light가 절대 null이나 undefined가 아님을 의미
      this.helper!.update() // this.helper가 null이나 undefined가 아님
      */
    }
  }
 
  private render(time: number){
    // time : setAnimationLoop의 값에 의해서 결정되는데 단위는 ms
    this.update(time)
 
    this.renderer.render(this.scene, this.camera!)
  }
}
 
new App()

 

코드를 주석처리하였으면 PointLight 객체를 사용하는 광원과 헬퍼를 추가하자.

 

 

 

 

PointLight 광원은 distance 속성을 제공하는데, 이 distance에 지정된 거리까지만 광원의 영향을 받는다.

 

기본값인 0을 설정하면 모든 거리에 광원의 영향을 받는다.

 

 

 

 

 

distance를 0으로 설정한 Point Light 결과

 

모든 Geometry에 광원이 영향을 주는 모습이다.

 

distance를 5로 변경시켜 보자

 

 

 

 

 

 

distance를 5로 설정한 Point Light 결과

 

BigSphere의 일부만 광원의 영향을 받고, torus와 smallSphere, ground는 광원의 영향을 받지 않는 모습이다.

 

그럼, 이 광원의 위치가 움직이는 빨간색 구를 추적하도록 해보자.

 

 

 

 

update를 하기 위해 light와 helper를 필드화 한다.

 

 

 

 

 

그리고 필드 값을 변경시켜 주기 위해 setupLight 메서드에 this 키워드로 객체를 수정해 준다.

 

 

 

 

그리고 update 메서드에 빨간색의 구체(smallSphere)의 위치를 광원의 위치에 설정하도록 해주자.

 

Directional Light 객체를 다룰 때처럼 광원의 위치가 변경되었으므로 광원에 대한 helper도 update 해주어야 한다.

 

 

 

 

 

위와 같이 헬퍼 역시 update 해준다.

 

그럼, 결과를 확인해 보자

 

 

 

 

광원의 위치를 변화시킨 Point Light 결과

 

광원의 위치가 빨간색 구를 추적하여 변화하는 것을 확인할 수 있다.

 

다음 시간에 이어서 광원(Lights)에 대해 알아보자

 

 

 

 

전체 코드(main.ts)

 

import { OrbitControls } from 'three/examples/jsm/Addons.js'
import './style.css'
import * as THREE from 'three'

class App {
  private renderer: THREE.WebGLRenderer //Renderer Field 추가
  private domApp: Element
  private scene: THREE.Scene
  private camera?: THREE.PerspectiveCamera //?를 붙이면 PerspectiveCamera Type이나 Undefined Type을 가질 수 있음.(Optional Properties)

  /*
  private light?: THREE.DirectionalLight
  private helper?: THREE.DirectionalLightHelper
  */

  private light?: THREE.PointLight
  private helper?: THREE.PointLightHelper

  constructor() {
    console.log("YooHwanIhn");
    this.renderer = new THREE.WebGLRenderer({ antialias: true }) // 안티-알리아스 : 높은 렌더링 결과를 얻기 위해 픽셀 사이에 계단 현상을 방지하는 효과 추가
    this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio)) // 고해상도에서 깨짐 방지를 위해 픽셀 비율 지정

    this.domApp = document.querySelector('#app')!
    this.domApp.appendChild(this.renderer.domElement) // renderer.domeElement : canvas Type의 DOM 객체

    this.scene = new THREE.Scene()

    this.setupCamera()
    this.setupLight()
    this.setupModels()
    this.setupEvents()
  }

  private setupCamera() {
    const width = this.domApp.clientWidth
    const height = this.domApp.clientHeight

    this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100)
    this.camera.position.z = 5  // (0, 0, 5)

    new OrbitControls(this.camera, this.domApp as HTMLElement)
  }

  private setupLight() {
    // const light = new THREE.AmbientLight("#ffffff", 1)
    //const light = new THREE.HemisphereLight("#b0d8f5", "#bb7a1c",1)

    /*
    const light = new THREE.DirectionalLight(0xffffff, 1)
    light.position.set(0, 1, 0)
    light.target.position.set(0, 0, 0)
    this.scene.add(light.target)
    this.light = light

    const helper = new THREE.DirectionalLightHelper(light)
    this.scene.add(helper)
    this.helper = helper

    this.scene.add(light)
    */

    const light = new THREE.PointLight(0xffffff, 20)
    light.position.set(0, 5, 0)
    light.distance = 5  // 광원의 영향을 받을 범위 (default 0 = 모든 범위)
    this.scene.add(light)
    this.light = light

    const helper = new THREE.PointLightHelper(light)
    this.scene.add(helper)
    this.helper = helper
  }

  private setupModels() {
    const axisHelper = new THREE.AxesHelper(10) // 좌표축
    this.scene.add(axisHelper)

    const geomGround = new THREE.PlaneGeometry(5, 5) // ground (5x5)
    const matGround = new THREE.MeshStandardMaterial({
      color: "#2c3e50",
      roughness: 0.5,
      metalness: 0.5,
      side: THREE.DoubleSide
    })
    const ground = new THREE.Mesh(geomGround, matGround)
    ground.rotation.x = -THREE.MathUtils.degToRad(90)
    ground.position.y = -.5
    this.scene.add(ground)

    const geomBigSphere = new THREE.SphereGeometry(1, 32, 16, 9, THREE.MathUtils.degToRad(360), 0, THREE.MathUtils.degToRad(90))
    const matBigSphere = new THREE.MeshStandardMaterial({
      color: "#ffffff",
      roughness: 0.1,
      metalness: 0.2
    });
    const bigSphere = new THREE.Mesh(geomBigSphere, matBigSphere)
    bigSphere.position.y = -.5
    this.scene.add(bigSphere)

    const geomSmallSphere = new THREE.SphereGeometry(0.2)
    const matSmallSphere = new THREE.MeshStandardMaterial({
      color: "#e74c3c",
      roughness: 0.2,
      metalness: 0.5
    })
    const smallSphere = new THREE.Mesh(geomSmallSphere, matSmallSphere)

    const smallSpherePivot = new THREE.Object3D();
    smallSpherePivot.add(smallSphere)
    bigSphere.add(smallSpherePivot);
    smallSphere.position.x = 2
    smallSpherePivot.rotation.y = THREE.MathUtils.degToRad(-45)
    smallSphere.position.y = 0.5 // bigSphere의 y position이 -.5기 때문에
    smallSpherePivot.name = "smallSpherePivot"  // update에서 접근하기 쉽게 명명함

    const cntItems = 8
    const geomTorus = new THREE.TorusGeometry(0.3, 0.1)
    const matTorus = new THREE.MeshStandardMaterial({
      color: "#9b59b6",
      roughness: 0.5,
      metalness: 0.9
    })
    for (let i = 0; i < cntItems; i++) {
      const torus = new THREE.Mesh(geomTorus, matTorus)
      const torusPivot = new THREE.Object3D() // Torus 역시 반 구를 기준으로 회전하면서 8개를 생성하기 때문에 피봇이 필요함
      bigSphere.add(torusPivot)

      torus.position.x = 2  // smallSphere의 x 포지션과 일치하게함
      torusPivot.position.y = 0.5 // bigSphere의 y 포지션이 -.5임
      torusPivot.rotation.y = THREE.MathUtils.degToRad(360) / cntItems * i
      torusPivot.add(torus)
    }
  }

  //실제 이벤트와 렌더링 처리를 다룰 메서드
  private setupEvents() {
    window.onresize = this.resize.bind(this); // html의 size가 변경될 때마다 호출되는 함수(addEventListener 느낌??)
    this.resize();

    this.renderer.setAnimationLoop(this.render.bind(this)) // 연속적으로 render 메서드 호출(모니터 Frame에 맞게)
  }

  // html창 resize시 호출할 함수
  private resize() {
    const width = this.domApp.clientWidth
    const height = this.domApp.clientHeight

    //앞선 setUpCamera에서 설정한 camera 정보를 다시 수정해줌.
    const camera = this.camera
    if (camera) {
      camera.aspect = width / height
      camera.updateProjectionMatrix() // 카메라의 값이 변경되었다면 수정하도록 함.
    }

    //renderer도 마찬가지로 사이즈 수정함
    this.renderer.setSize(width, height)
  }

  private update(time: number) {
    time *= 0.001 // ms -> s 단위로 변경

    const smallSpherePivot = this.scene.getObjectByName("smallSpherePivot")
    if (smallSpherePivot) {
      //smallSpherePivot.rotation.y = time;
      const euler = new THREE.Euler(0, time, 0)
      const quaternion = new THREE.Quaternion().setFromEuler(euler)
      smallSpherePivot.setRotationFromQuaternion(quaternion)

      const smallSphere = smallSpherePivot.children[0]  // 화면상 회전하는 빨간색 구체

      /*
      // Directional Light
      smallSphere.getWorldPosition(this.light!.target.position) // this.light!는 this.light가 절대 null이나 undefined가 아님을 의미
      this.helper!.update() // this.helper가 null이나 undefined가 아님
      */

      smallSphere.getWorldPosition(this.light!.position) // smallSphere 위치를 광원의 위치로
      this.helper!.update()
    }
  }

  private render(time: number) {
    // time : setAnimationLoop의 값에 의해서 결정되는데 단위는 ms
    this.update(time)

    this.renderer.render(this.scene, this.camera!)
  }
}

new App()

'three.js' 카테고리의 다른 글

[three.js][typescript] - Light(3) : HDRI  (5) 2024.07.16
[three.js][typescript] - Light(2)  (1) 2024.07.16
[three.js][typescript] - Material(7)  (0) 2024.07.12
[three.js][typescript] - Material(6)  (0) 2024.07.11
[three.js][typescript] - Material(5)  (0) 2024.07.10