본문 바로가기
공부/etc

Three.js and TypeScript (10)

by Piva 2024. 11. 25.
  • Udemy의 Three.js and TypeScript 강의를 듣고 정리한 내용을 기록한다.
    • 29. JEasings 부터 30. GLTF Animations 까지.

JEasings

  • 애니메이션에 사용할 수 있는 자바스크립트의 Easing 엔진.
Easing: 사전적 의미로는 ‘완화’라는 의미를 갖는다. 애니메이팅에서는 애니메이션을 자연스럽게 만들기 위해 천천히 움직이도록 완화하는 기법을 의미하는 것 같다.
  • 일정 시간동안 물체의 속성을 새로운 값으로 변하도록 해주는 기능을 제공한다.
  • 아래의 값들을 설정할 수 있다.
    • 변환 시간
    • 새로이 설정할(변환될) 값
    • 시작하기 까지의 딜레이
    • 변환이 끝났을 때 실행될 스크립트
    • 변환 시간 동안 변환에 걸리는 시간을 변경하는 Easing 함수
    • 등등…
강사분이 직접 만드신 패키지같다. Tween.js에 기반한 패키지라고.

 

 

사용 방법

  • yarn 이나 npm 등을 사용해 패키지를 설치한다.
yarn add -D jeasings
  • 아래와 같이 불러와 사용한다.
import JEASINGS from 'jeasings';
  • JEasings를 사용해 변하는 값을 업데이트 하기 위해서는, 애니메이션 루프에서 update 함수를 호출해야 한다.
import * as THREE from 'three';
import JEASINGS from 'jeasings';

/* 생략 */
function animate() {
  requestAnimationFrame(animate);
  JEASINGS.update();
}

animate();

 

  • JEasings을 통해 값을 변환하기 위해서는 JEasing 객체를 생성한다.
import * as THREE from 'three';
import JEASINGS from 'jeasings';

/* 일부 코드 생략 */
// Raycaster를 사용하여 얻어낸 교차점의 좌표 P
const p = intersects[0].point;
new JEASINGS.JEasing(controls.target)
  .to(
    {
      x: p.x,
      y: p.y,
      z: p.z
    },
    500
  )
      //.delay (1000)
      //.easing(JEASINGS.Cubic.Out)
      //.onUpdate(() => render())
  .start();
  • JEasing 생성자로 객체를 선언하며, 변환할 값인 controls.target(OrbitControls의 target)을 전달하고 있다.
    • to 함수를 통해 변환 후의 목표 값과 걸리는 시간을 설정한다.
    • 다른 함수들을 체이닝하여 부가적인 세팅을 할 수 있다.
      • delay: 변환 효과를 바로 주지 않고, 설정한 값만큼의 시간이 흐른 후 시작한다.
      • easing: 변환에 사용되는 Easing 함수를 설정한다.
      • onUpdate: 매 Update 시 실행할 로직을 추가할 수 있다.
    • start 함수로 Easing을 시작한다.

Easing 을 통해 더블 클릭 시 OrbitControls의 타겟 좌표를 바꾸는 모습

 

 

Easing 함수에는 아래와 같은 함수들이 존재한다(링크).


 

Easing Functions Cheat Sheet

Easing functions specify the speed of animation to make the movement more natural. Real objects don’t just move at a constant speed, and do not start and stop in an instant. This page helps you choose the right easing function.

easings.net

 

JEasings에서는 JEASINGS.[방정식 이름].[방향] 의 포맷으로 함수를 사용할 수 있다(참고).

 

 

 

GLTF Animations

Mixamo: 다양한 3D 모델과 애니메이션을 다운로드 받을 수 있는 사이트(링크).
에셋을 다운받기 위해서는 어도비 계정이 필요하다.
  • 실습을 위해 Mixamo 에서 애니메이션이 붙어있는 모델 파일을 fbx 형식으로 받는다.
  • 받은 파일을 Blender를 사용해 glTF 파일로 변경해준다.
  • 받은 모델을 Three.js에서 로딩하고, console.log로 확인해보면 animations 속성이 존재하고 같이 붙여준 animation이 들어가 있음을 알 수 있다.

 

AnimationMixer

  • Scene에 존재하는 특정 물체의 애니메이션을 재생시키는 플레이어 역할을 수행한다.
  • AnimationMixer 생성자로 AnimationMixer 객체를 생성하여 사용할 수 있다.
    • 애니메이션을 갖는 복수의 모델이 있다면, 한 모델 당 하나의 mixer를 가져야 한다.
import * as THREE from 'three';

let mixer: THREE.AnimationMixer;

new GLTFLoader().load('models/eve$@walk.glb', (gltf) => {
  mixer = new THREE.AnimationMixer(gltf.scene);
  console.log(gltf);

  mixer.clipAction(gltf.animations[0]).play();

  scene.add(gltf.scene);
})

// 생략

const clock = new THREE.Clock();
let delta = 0;

function animate() {
  requestAnimationFrame(animate);

  delta = clock.getDelta();

  controls.update();

  mixer.update(delta);
  renderer.render(scene, camera);
}
  • AnimationMixer 생성자를 사용해 mixer 변수에 할당하고, 생성자의 인자로 로딩한 모델을 넘겨준다.
  • clipAction 메서드를 사용해 모델 객체에 존재하는 animation 중 하나를 가져와, play 메서드로 재생한다.
  • Animation 루프에서는 mixer.update()를 통해 애니메이션을 업데이트한다.

모델 파일에 존재하는 애니메이션(걷기)을 재생한 모습

 

 

 

  • 모델과 함께 붙어있는 애니메이션이 아닌, 오직 애니메이션’만’을 가져와 모델에 재생시키는 것도 가능하다.
    • 다만, 이 경우 재생하려는 애니메이션이 현재 모델과 잘 맞는지 미리 살펴봐야 한다.
import * as THREE from 'three';

async function loadEve() {
  const loader = new GLTFLoader()
  const [eve, idle] = await Promise.all([
    loader.loadAsync('models/eve$@walk.glb'),
    loader.loadAsync('models/eve@idle.glb'),
  ]);

  mixer = new THREE.AnimationMixer(eve.scene);

  mixer.clipAction(idle.animations[0]).play();

  scene.add(eve.scene);
}
await loadEve();
  • 모델(eve$@walk.glb)과 가만히 서있는 애니메이션(eve@idle.glb)을 가져온다.
  • clipAction에 모델이 갖고 있는 애니메이션이 아닌, 따로 가져온 애니메이션을 할당하여 재생한다.

모델 파일과 따로 가져온 애니메이션(서있기)을 재생한 모습

 

 

한 모델의 애니메이션 전환하기

  • 하나의 모델에 여러 개의 애니메이션이 있고, 이를 서로 전환하고 싶을 때는 별도의 객체로 애니메이션 정보를 관리해야 한다.
import * as THREE from 'three';

// 생략

let animationActions: { [key: string]: THREE.AnimationAction } = {};
let activeAction: THREE.AnimationAction;

async function loadEve() {
  const loader = new GLTFLoader()
  const [eve, idle, run] = await Promise.all([
    loader.loadAsync('models/eve$@walk.glb'),
    loader.loadAsync('models/eve@idle.glb'),
    loader.loadAsync('models/eve@run.glb')
  ]);

  mixer = new THREE.AnimationMixer(eve.scene);

  animationActions['idle'] = mixer.clipAction(idle.animations[0]);
  animationActions['walk'] = mixer.clipAction(eve.animations[0]);
  animationActions['run'] = mixer.clipAction(run.animations[0]);

  animationActions['idle'].play();
  activeAction = animationActions['idle'];

  scene.add(eve.scene);
}
await loadEve();
  • 모델과 애니메이션을 로딩한 후, 각 애니메이션을 clipAction으로 가져와 animationActions라는 객체에 할당한다.
    • 이를 통해 메모리 내에 애니메이션을 담아두고, 언제든 불러와 쓸 수 있다.
  • 현재 실행 중인 애니메이션을 activeAction 이라는 변수로 관리한다.
    • animationActions의 값(value)과 activeAction은 AnimationAction 타입을 가지며, 이를 통해 애니메이션을 재생하는 등의 함수를 사용할 수 있다.

'공부 > etc' 카테고리의 다른 글

Three.js and TypeScript (11)  (2) 2024.12.03
Three.js and TypeScript (9)  (0) 2024.11.22
Three.js and TypeScript (8)  (1) 2024.11.21
Three.js and TypeScript (7)  (2) 2024.11.20
Three.js and TypeScript (6)  (0) 2024.11.19