[Three.js journey 강의노트] 06

9rganizedChaos·2021년 7월 15일
1
post-thumbnail

🙌🏻 해당 글은 Three.js Journey의 강의 노트입니다.

06 Animations

이번 레슨에서는 ThreeJS에서 애니메이션을 적용하는 다양한 방법을 알아볼 것이다.
첫 번째는 requestAnimationFrame, 두 번째는 Clock, 마지막은 library를 이용하는 방법이다.

Three.js에서 애니메이션은 결국 스톱모션이다. Object를 조금씩 이동하며 계속해서 렌더링을 수행하는 것이다. 렌더링 사이사이에 Object를 더 많이 움직일 수록 빨리 움직이는 것처럼 보인다. 때문에 보고 있는 화면의 frame rate이 중요하다 대부분의 화면은 초딩 60프레임으로 실행된다. 그러나 어떤 화면은 더 높은 frame rate를 갖고 있기도 하고, 그런 경우 같은 코드더라도 더 빠르게 실행될 수 있다. 우리는 화면의 frame rate에 구애받지 않고, 같은 속도로 Object에 애니메이션을 적용해주길 원한다. 이를 수행하는 기본적인 방법은 window.requestAnimationFrame(...) 메서드이다.

1️⃣ Using requestAnimationFrame

requestAnimationFrame의 주요 목적은 사실 각 프레임에서 코드를 실행하는 것이 아니다! requestAnimationFrame은 다음 프레임에 제공한 함수를 실행한다. 그리고 그 다음 함수가 또 다시 requestAnimationFrame을 사용한다면, 해당 함수가 실행된다. (기본적으로 재귀의 형태라고 할 수 있다.)

아래와 같이 tick이라는 함수를 만들어주면 "tick"이 콘솔에 계속해서 찍히는 모습을 확인할 수 있다.

const tick = () => {
    console.log('tick')
    window.requestAnimationFrame(tick)
}

tick()

이 원리를 이용해, 우리는 mesh를 계속해서 조금씩 회전시켜줄 것이다.
mesh를 조금 회전시킨후 렌더하고, 다시 조금 더 회전시키고 렌더하는 원리이다!
mesh를 회전시키고 나서 다시 렌더시켜주는 것을 잊어서는 안 된다.
(기본적으로 ThreeJS에서 렌더하지 않으면 scene에 변화된 모습이 노출되지 않는다.)

const tick = () => {
    mesh.rotation.y += 0.01
    renderer.render(scene, camera)
    window.requestAnimationFrame(tick)
}

tick()

이제 큐브가 회전하는 것을 확인할 수 있는데, 문제는 현재 이 애니메이션은 화면의 frame rate에 따라 회전속도가 달라진다는 점이다.

현재 나는 위와 같이 총 세 개의 디스플레이를 활용하고 있는데, 각 화면에서 같은 코드를 실행했음에도 가운데 오브젝트가 회전하는 속도가 모두 다르게 출력된다.

Adaptation to the framerate

위와 같이 디스플레이의 framerate에 구애받지 않고 의도한 대로 애니메이션을 출력해주기 위해서 우리는 '시간'을 기준으로 애니메이션을 실행해줄 수 있다. 먼저 JS의 내장 매서드인 Date.now()를 이용해 타임스탬프를 가져오자. 타임스탬프는 1970년 1월 1일을 기준점으로 지난 시간을 밀리초로 출력해준다. 현재 프레임의 타임스탬프에서 이전 프레임의 타임스탬프를 빼서 deltaTime을 얻어온다.

let time = Date.now()

const tick = () => {
    const currentTime = Date.now()
    const deltaTime = currentTime - time
    time = currentTime
  
    mesh.rotation.y += 0.01 * deltaTime
    // deltaTime이 0인 경우 mesh가 회전하지 않는다.
    renderer.render(scene, camera);
    window.requestAnimationFrame(tick);
}

tick()

60fps에서 deltaTime이 16 정도가 출력되므로, 큐브가 엄청 빠른 속도로 돌게 된다. 0.01로 적었던 값을 재량껏 줄여주자. 모쪼록 이제는 얼만큼의 시간이 지났냐에 따라 회전의 양을 정해주므로, 모니터의 주사율과 무관하게 어떤 스크린에서도 같은 속도의 애니메이션을 출력한다.

2️⃣ Using Clock

지금까지 사용한 방법도 나쁘지 않지만, Three.js에는 Clock이라는 내장 솔루션이 존재한다. Clock 클래스를 간편하게 인스턴스화하고, getElapsedTime()와 같은 내장 메서드를 사용하기만 하면 된다. 이 메서드는 Clock이 생성된 후 몇 초가 지났는지 반환한다.

const clock = new THREE.Clock();

const tick = () => {
  const elapsedTime = clock.getElapsedTime();
  
  mesh.position.x = Math.cos(elapsedTime);
  mesh.position.y = Math.sin(elapsedTime);
  camera.lookAt(mesh.position);

  renderer.render(scene, camera);

  window.requestAnimationFrame(tick);
};

tick();

getDelta라는 메서드를 활용할 수도 있는데, 사실 이는 Clock class를 완벽히 이해하기 전에는 활용하기가 쉽지 않으므로, 우선은 getElapsedTime을 활용해서 애니메이션을 만드는 것을 추천한다.

3️⃣ Using a library

때때로 우리는 특정한 방식대로 scene에 애니메이션을 입혀주고 싶을 수 있다. 이 때 라이브러리를 필요로 하게된다. 세상엔 정말 수도 없이 많은 라이브러리들이 존재하는데, 오늘은 그 중 유명하다 할 수 있는 GSAP을 활용해볼 것이다.

터미널에서 run npm install --save gsap@3.5.1을 실행하고, script.js에서 import gsap from 'gsap'와 같이 gsap을 임포트해준다. gsap.to(...)를 이용해 tween(an animation from A to B)을 만들어줄 수 있다!

gsap.to(mesh.position, { duration: 1, delay: 1, x: 2 });

const tick = () => {
  renderer.render(scene, camera);
  window.requestAnimationFrame(tick);
};

tick();

GSAP은 자체적으로 requestAnimationFrame이 내장되어있기 떄문에 애니메이션을 스스로 업데이트 해줄 필요가 없다. (그러나 render는 여전히 해주어야 한다.)

ThreeJS로 코드를 작성하면서, 알아본 세 가지 방법 중 때에 따라 적절한 방법을 골라야 한다.
단순하면서 반복되는 애니메이션이라면 nativeJS를, 정교한 애니메이션이라면 라이브러리를 사용하는 편이 좋을 것이다.

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

0개의 댓글