Three.js를 사용할 때 애니메이션은 스톱 모션처럼 작동합니다.
오브젝트를 움직이고 렌더링을 수행합니다. 그런 다음 개체를 조금 더 이동하고 다시 렌더링을 수행합니다.
이 작업이 반복적으로 수행되면서 오브젝트가 움직이는 것 처럼 보입니다.
렌더링 간에 오브젝트를 더 많이 움직일수록 오브젝트가 더 빨리 움직이는 것처럼 보입니다.
보고 있는 화면은 특정 주파수로 실행되는 데 이를 프레임 속도라고 합니다.
프레임 속도는 대부분 화면에 따라 달라지지만 컴퓨터 자체에도 한계가 있습니다.
대부분의 화면은 초당 60프레임으로 실행됩니다.
계산을 해보면 16ms마다 한 프레임씩 실행된다는 뜻입니다.
그러나 일부 화면은 훨씬 빠르게 실행될 수 있으며 컴퓨터가 처리하기 어려운 경우 더 느리게 실행됩니다.
프레임 속도에 관계없이 각 프레임에서 객체를 이동하고 렌더링을 수행하는 함수를 실행하고 싶을 때,
네이티브 JavaScript 방식은 window.requestAnimationFrame(...) 메서드를 사용하는 것입니다.
1. requestAnimationFrame 사용하기
requestAnimationFrame 의 주된 목적은 각 프레임에서 코드를 실행하는 것이 아니라 다음 프레임에서 사용자가 제공한 함수를 실행하는 것입니다. 하지만 이 함수가 다음 프레임에서도 동일한 함수를 실행하기 위해 requestAnimationFrame을 사용한다면, 결국 매 프레임마다 함수가 계속 실행되는 결과를 얻을 수 있으며, 이것이 바로 우리가 원하는 것입니다. tick이라는 함수를 만들고 이 함수를 한 번 호출합니다. 이 함수에서 window.requestAnimationFrame(...)을 사용하여 이 함수를 호출합니다.
const tick = () =>
{
console.log('tick')
window.requestAnimationFrame(tick) // 재귀함수로 다음 프레임부터 계속 호출
}
tick()
무한 호출하는 이 함수 안에 renderer.render(...) 호출하고 큐브 회전을 늘릴 수 있습니다
const tick = () =>
{
mesh.rotation.y += 0.01 // y축(수직)을 기준으로 돌게된다
renderer.render(scene, camera)
window.requestAnimationFrame(tick) // 재귀함수로 다음 프레임부터 계속 호출
}
tick()
하지만 프레임 속도가 높은 컴퓨터에서 이 코드를 테스트하면 큐브가 더 빨리 회전하고, 프레임 속도가 낮은 컴퓨터에서 테스트하면 큐브가 더 느리게 회전한다는 문제가 발생할 것입니다.
2. 프레임 속도 적용하기
애니메이션을 프레임 속도에 맞게 조정하려면 마지막 틱 이후 시간이 얼마나 지났는지 알아야 합니다.
먼저 시간을 측정하는 방법이 필요합니다.
자바스크립트에서는 Date.now()를 사용하여 현재 타임스탬프를 가져올 수 있습니다
현재 타임스탬프에서 이전 프레임의 타임스탬프를 빼고 이 값을 오브젝트에 애니메이션을 적용할 때 사용합니다.
// Animation
let time = Date.now()
const tick = () => {
// update objects
const currentTime = Date.now()
const deltaTime = currentTime - time
time = currentTime
log(deltaTime)
mesh.rotation.y += 0.01
// render
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick()
화면이 60fps로 실행되는 경우 델타타임은 약 16이 되어야 하므로 값을 곱하여 자유롭게 줄일 수 있습니다. 이제 마지막 프레임 이후 소요된 시간을 기준으로 회전하므로 프레임 속도에 관계없이 모든 화면과 모든 컴퓨터에서 이 회전 속도가 동일합니다.
3. Clock 메서드 사용
위 방법과 다른 방법으로 시간 계산을 처리하는 Clock이라는 이름의 기본 제공 메서드가 Three.js에 있습니다.
Clock 변수를 인스턴스화하고 getElapsedTime()과 같은 기본 제공 메서드를 사용하기만 하면 됩니다.
이 메서드는 시계가 생성된 후 몇 초가 지났는지 반환합니다.
const clock = new Clock()
const tick = () => {
const elapsedTime = clock.getElapsedTime()
log(elapsedTime)
mesh.rotation.y = elapsedTime
// render
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick()
}
위치 속성으로 사물을 이동하는 데에도 사용할 수 있습니다. Math.sin(...)과 결합하는 예제입니다.
const clock = new Clock()
const tick = () => {
const elapsedTime = clock.getElapsedTime()
log(elapsedTime)
mesh.position.x = Math.cos(elapsedTime)
mesh.position.y = Math.sin(elapsedTime)
// render
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick()
}
1. Math.cos(elapsedTime)은 x 좌표를 결정합니다
- -1에서 1 사이의 값을 생성
- mesh를 수평으로 왼쪽에서 오른쪽으로 이동시킴
2. Math.sin(elapsedTime)은 y 좌표를 결정합니다
- -1에서 1 사이의 값을 생성
- mesh를 수직으로 위아래로 이동시킴
이 두 함수를 조합하면
- mesh가 원형 경로를 그리며 이동
- 시계 방향으로 회전
- 반지름이 1인 원을 그림
이동 방향
- 시작: (1, 0)에서 시작
- 1/4 회전: (0, 1)로 이동
- 1/2 회전: (-1, 0)으로 이동
- 3/4 회전: (0, -1)로 이동
- 완전한 회전: 다시 (1, 0)으로 돌아옴
물체 (Mesh) 뿐만이 아닌 카메라와 같은 모든 Object3D에 애니메이션을 적용할 수 있습니다:
const clock = new Clock()
const tick = () => {
const elapsedTime = clock.getElapsedTime()
camera.position.x = Math.cos(elapsedTime)
camera.position.y = Math.sin(elapsedTime)
camera.lookAt(mesh.position) // 이 부분을 안쓰면 위 mesh를 움직인 것과 같은 효과가 난다
// render
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick()
}
이전에서 설명했던 것 처럼 camera.lookAt( ) 메서드는 항상 카메라가 특정 객체의 중심을 보도록 하므로,
객체 주위를 도는 듯한 동적인 시점이 만들어졌습니다.
사용 가능한 또 다른 메서드는 getDelta(...)이지만 Clock 클래스 코드에서 무슨 일이 일어나고 있는지 정확히 알지 못하면 사용해서는 안 됩니다. 이 함수를 사용하면 애니메이션이 엉망이 되어 원치 않는 결과가 나올 수 있습니다.
4. GSAP 라이브러리사용
때로는 다른 라이브러리를 사용해야 하는 매우 구체적인 방식으로 씬에 애니메이션을 적용하고 싶을 때가 있습니다. 수많은 애니메이션 라이브러리가 있지만 가장 유명한 라이브러리는 GSAP입니다. 프로젝트에 GSAP를 추가하려면 IDE 터미널에서 npm i gsap 를 쓰면 됩니다.
npm i gsap
import { ref, onMounted } from 'vue'
import gsap from 'gsap'
import {
PerspectiveCamera,
Scene,
BoxGeometry,
MeshBasicMaterial,
Mesh,
WebGLRenderer,
Clock
} from 'three'
이전 애니메이션과 관련된 코드에 주석을 달되 렌더링과 함께 틱 함수를 유지합니다.
그런 다음 gsap.to(...)를 사용하여 트윈(A에서 B로의 애니메이션)이라고 부르는 것을 만들 수 있습니다:
gsap.to(mesh.position, { duration: 1, delay: 1, x: 2 })
const tick = () =>
{
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()
GSAP에는 요청 애니메이션 프레임이 내장되어 있으므로 애니메이션을 직접 업데이트할 필요는 없지만, 큐브가 움직이는 것을 보려면 각 프레임에서 장면 렌더링을 계속 수행해야 합니다.
간단한 움직임이 아닌 칼을 휘두르는 동작 등의 에니메이션을 적용하려면 라이브러리를 쓰는 것이 좋습니다.
도움되는 링크들 :
'Three.js' 카테고리의 다른 글
[Three.js] 2. 물체(Object)의 변형 position scale rotation group (0) | 2024.08.14 |
---|---|
[Three.js] 1. vue 로 빨간 육각면체 만들기 (0) | 2024.07.28 |
[Three.js] 0. WebGL 와 Three.js 의 관계 (2) | 2024.07.24 |