주요 콘텐츠로 건너뛰기
AI 튜토리얼

Three.js에서 절차적으로 생성되는 애니메이션 동물 만들기

Procedural Animals 설치 방법, 시드 기반 동물 생성, 움직임과 동작 제어, 이벤트 처리, 워커 구성, 빠른 로딩을 위한 에셋 베이크, 시네마틱 클로즈업부터 대규모 군중까지 용도에 맞는 품질 설정 선택 방법을 알아봅니다.

Three.js에서 절차적으로 생성되는 애니메이션 동물 만들기

three.js Procedural Animals란?

three.js Procedural Animals는 three.js용 런타임 생성 동물 24종을 포함하는 라이브러리입니다. 모델, 텍스처, 리그 또는 애니메이션 클립을 다운로드하는 대신 createAnimal()을 호출하면 라이브러리가 코드로 완전한 애니메이션 동물을 구성합니다.

각 동물은 부호 있는 거리장으로 조형되고, 메시화, 리깅, 스키닝 과정을 거친 뒤 털, 깃털 또는 비늘이 적용되며, 역운동학 기반 보행과 동작으로 애니메이션됩니다. 지원되는 신체 유형에는 발굽이 있는 동물, 발바닥이 있는 동물, 새, 수영 동물, 뱀, 거미, 도약 동물이 포함됩니다.

라이브 쇼케이스에서 모든 종, 변형, 동작 및 디버그 뷰를 살펴보거나 GitHub 저장소에서 소스 코드를 확인할 수 있습니다.

주요 기능

  • 24종: 늑대, 고양이, 말, 토끼부터 새, 물고기, 뱀, 거미까지 다양한 동물을 선택할 수 있습니다.
  • 시드 기반 개체: 시드를 사용하면 별도로 덮어쓰지 않는 한 크기, 비율, 털, 성별, 나이 및 변형 선택이 반복해서 동일하게 생성됩니다.
  • 런타임 애니메이션: 동물이 적절한 보행을 자동으로 선택하며 앉기, 잠자기, 먹기, 공격, 점프 등의 동작을 제공합니다.
  • 5가지 품질 단계: hero, high, medium, low, crowd 중에서 선택할 수 있습니다.
  • 워커 기반 구성: 기본적으로 모듈 웹 워커에서 빌드를 실행하며, 호출 스레드로 대체할 수 있습니다.
  • 오프라인 베이킹: 개체를 .animal 파일로 미리 빌드하여 약 20밀리초 만에 불러올 수 있습니다.
  • 게임 지향 제어: 동물을 조종하고, 목적지로 이동시키고, 물리 바디를 따라가게 하며, 대상을 추적하고, 동작을 재생하고, 게임플레이 이벤트를 받을 수 있습니다.
  • TypeScript 지원: 패키지는 ES 모듈을 사용하며 TypeScript 선언을 포함합니다.

설치

이 라이브러리의 유일한 피어 의존성은 three.js r160 이상입니다. npm으로 패키지와 three.js를 설치합니다.

npm install procedural-animals three

아직 npm에서 패키지를 사용할 수 없다면 GitHub에서 직접 설치합니다.

npm install github:majidmanzarpour/threejs-procedural-animals three

첫 번째 동물 만들기

먼저 three.js와 createAnimal을 가져옵니다. 메시, 리그, 외피 및 관련 데이터를 구성하는 데 시간이 걸리므로 이 함수는 비동기 함수입니다.

import * as THREE from 'three';
import { createAnimal } from 'procedural-animals';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xa9c1e0);

const camera = new THREE.PerspectiveCamera(
  40,
  innerWidth / innerHeight,
  0.1,
  500
);
camera.position.set(6, 3, 8);

const sun = new THREE.DirectionalLight(0xfff3e4, 2.7);
sun.position.set(6, 10, 4);
sun.castShadow = true;

scene.add(
  sun,
  new THREE.HemisphereLight(0xdde6f2, 0x5f5a52, 1.15)
);

const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(200, 200).rotateX(-Math.PI / 2),
  new THREE.MeshStandardMaterial({ color: 0x77736a })
);
floor.receiveShadow = true;
scene.add(floor);

async function main() {
  const wolf = await createAnimal('wolf', {
    seed: 3,
    quality: 'high'
  });

  scene.add(wolf.object);

  let last = performance.now();

  renderer.setAnimationLoop((now) => {
    const dt = (now - last) / 1000;
    last = now;

    wolf.move({
      speed: wolf.gears.walk,
      heading: now / 1000 * 0.3
    });

    wolf.update(dt);
    camera.lookAt(wolf.position);
    renderer.render(scene, camera);
  });
}

main();

이 예제에는 몇 가지 중요한 규칙이 있습니다.

  1. async 함수 안에서 동물을 생성하고 결과를 await합니다.
  2. animal.object를 장면에 추가하되 해당 객체의 변환은 항등 상태로 유지합니다. 동물이 자체적으로 이동을 관리하기 때문입니다.
  3. move(), moveTo() 또는 follow()와 같은 메서드를 사용해 동물을 조종합니다.
  4. 초 단위로 측정한 dt를 사용하여 프레임마다 정확히 한 번 update(dt)를 호출합니다.
  5. 사용 가능한 기어 이름은 종마다 다를 수 있으므로 animal.gears의 속도를 사용합니다.

진입 모듈에서 최상위 await를 사용하지 마세요. 프로젝트 문서에 따르면 Vite 프로덕션 빌드가 교착 상태에 빠질 수 있습니다.

이동 및 내비게이션

직접 조종

move() 메서드는 초당 미터 단위의 속도와 라디안 단위의 방향을 받습니다. 방향이 0이면 양의 Z축을 향합니다.

wolf.move({
  speed: wolf.gears.walk,
  heading: Math.PI / 2
});

동물의 이동을 멈춰야 할 때는 stop()을 호출합니다.

wolf.stop();

목적지로 이동

moveTo()를 사용하면 동물을 월드 공간의 한 지점으로 이동시킬 수 있습니다. 목적지에 도착하면 arrive 이벤트가 발생합니다.

wolf.moveTo(
  new THREE.Vector3(10, 0, 4),
  { speed: 3 }
);

wolf.on('arrive', () => {
  console.log('The wolf arrived.');
});

물리 바디 따라가기

다른 시스템이 바디를 제어하는 경우 매 프레임 현재 위치와 속도를 follow()에 전달합니다.

wolf.follow(body.position, body.velocity);

대상 추적

lookAt()을 사용하면 동물이 대상을 향해 고개를 돌립니다. 추적을 중지하려면 null을 전달합니다.

wolf.lookAt(player.position);

// Later:
wolf.lookAt(null);

플레이 액션

모든 종은 jump, sit, lie, sleep, eat, drink, attack, hit, death, stand 등의 공통 액션을 지원합니다. 종마다 특화 액션을 제공할 수도 있습니다. 예를 들어 뱀은 몸을 감고 공격할 수 있으며, 새는 이륙하고 착륙할 수 있고, 토끼는 깡충 뛰거나 발을 구를 수 있습니다.

play() 메서드는 결과가 done, interrupted, stopped, refused 중 하나인 Promise를 반환합니다.

const result = await wolf.play('attack');
console.log(result);

종이 특화 액션을 지원한다고 가정하지 말고 animal.actions를 확인하세요. stopAction()으로 이름이 지정된 액션을 중지할 수 있습니다.

wolf.stopAction('attack');

동물 이벤트에 응답하기

이벤트 API를 사용하면 게임 시스템이 애니메이션과 이동에 반응할 수 있습니다. 사용할 수 있는 이벤트로는 footstep, attackHit, actionStart, actionEnd, arrive, takeoff, land, death, vocalize, wingbeat가 있습니다.

const onFootstep = (event) => {
  sfx.play('step', event.position, event.strength);
};

wolf.on('footstep', onFootstep);

wolf.on('attackHit', (event) => {
  damage(event.position, event.direction);
});

// Remove a listener when it is no longer needed.
wolf.off('footstep', onFootstep);

지형과 물 설정하기

ground 옵션은 X 및 Z 좌표에 대한 지형 높이를 반환하는 함수입니다. 좌표는 animal.object.parent의 좌표 공간에 있어야 하며, 반환되는 높이는 장면에 렌더링된 지형과 일치해야 합니다.

const deer = await createAnimal('deer', {
  ground: (x, z) => terrainHeightAt(x, z),
  position: new THREE.Vector3(4, 0, 8),
  heading: Math.PI
});

수영 동물에는 water 함수와 수면 아래의 시작 위치가 필요합니다. 이 함수는 물이 없는 곳에서는 수면 높이를 반환하고, 그렇지 않으면 null을 반환합니다.

const fish = await createAnimal('fish', {
  water: (x, z) => 2,
  position: new THREE.Vector3(0, 1, 0)
});

scene.add(fish.object);

육상 동물도 물을 마시거나 물속을 걸을 때 물 정의를 사용할 수 있습니다. 새에는 날아갈 수 있는 THREE.Vector3 지점을 담은 perches 배열을 전달할 수 있습니다.

종, 변형, 개체 선택하기

이 라이브러리는 늑대, 개, 여우, 고양이, 사자, 치타, 곰, 쥐, 토끼, 말, 사슴, 소, 돼지, 양, 염소, 멧돼지, 개구리, 닭, 까마귀, 독수리, 물고기, 상어, 뱀, 거미를 포함합니다.

seed를 사용하면 동일한 개체를 반복해서 생성할 수 있습니다. 변형, 성별, 나이를 직접 선택할 수도 있습니다.

const cat = await createAnimal('cat', {
  seed: 12,
  quality: 'high',
  variant: 'tuxedo',
  sex: 'female',
  age: 'adult'
});

문자열 종 식별자는 필요할 때 로드됩니다. 애플리케이션에 한 종만 필요한 경우 해당 종을 직접 가져오면 번들을 간결하게 유지할 수 있습니다.

import wolfSpecies from 'procedural-animals/species/wolf';
import { createAnimal } from 'procedural-animals';

const wolf = await createAnimal(wolfSpecies, {
  seed: 3
});

적절한 품질 등급 선택하기

  • hero: 약 110,000~130,000개의 버텍스로 시네마틱 클로즈업에 적합합니다.
  • high: 약 65,000~80,000개의 버텍스로 플레이어의 동물에 적합합니다. 기본값입니다.
  • medium: 약 25,000~32,000개의 버텍스로 중거리 뷰와 휴대폰에 적합합니다.
  • low: 약 11,000~15,000개의 버텍스로 약 10~20마리 규모의 무리에 적합합니다.
  • crowd: 약 5,000~8,000개의 버텍스로 약 100마리 규모의 군중에 적합하며, 드로 콜은 한 번만 사용합니다.

setQuality()를 사용하면 기존 메시의 털 디테일을 변경할 수 있습니다. 대규모 군중에서는 crowd 등급과 setLod(2)를 함께 사용해 움직임 디테일을 줄이세요.

const sheep = await createAnimal('sheep', {
  seed: 5,
  quality: 'crowd'
});

sheep.setLod(2);

빌드 결과는 종, 시드, 품질, 변형별로 캐시됩니다. 따라서 6개의 시드를 사용하는 100마리 무리에는 서로 다른 빌드 6개만 필요합니다. 문서에 명시된 CPU 비용은 프레임당 동물 한 마리 기준 약 0.1밀리초입니다.

웹 워커 설정하기

동물 빌드에는 약 0.4~6초가 걸립니다. 기본적으로 생성 작업은 모듈 Web Worker에서 실행됩니다. Worker를 사용할 수 없으면 라이브러리는 호출 스레드로 대체합니다.

Vite 설정

// vite.config.js
export default {
  worker: { format: 'es' },
  build: { target: 'es2022' },
  optimizeDeps: {
    exclude: ['procedural-animals']
  }
};

webpack 5와 Parcel 2에는 추가 워커 설정이 필요하지 않습니다.

기타 번들러

다른 번들러를 사용하는 경우 워커를 수동으로 번들하세요.

npx esbuild node_modules/procedural-animals/src/worker.js --bundle --format=iife --minify --outfile=public/animal-worker.js

그런 다음 워커를 생성하는 팩토리를 등록하세요.

import { setWorkerFactory } from 'procedural-animals';

setWorkerFactory(
  () => new Worker('/animal-worker.js')
);

특정 동물에 대해 워커 생성을 비활성화할 수 있지만, 이렇게 하면 빌드가 끝날 때까지 호출 스레드가 차단됩니다.

const wolf = await createAnimal('wolf', {
  worker: false
});

빠른 로딩을 위해 동물 베이크하기

특정 개체를 사용하려면 미리 .animal 파일로 구워 둘 수 있습니다. 이렇게 하면 런타임 빌드 지연이 README에서 약 20밀리초로 추정하는 로딩 시간으로 대체됩니다.

npx procedural-animals-bake wolf --seed 3 --quality high -o public/wolf-3.animal

생성된 파일을 ArrayBuffer로 가져온 다음 baked 옵션에 전달합니다.

const wolf = await createAnimal('wolf', {
  baked: fetch('/wolf-3.animal')
    .then((response) => response.arrayBuffer())
});

구워진 개체는 명령으로 준비한 종 및 구성과 일치해야 합니다.

고급 팁

읽기 전용 모션 상태 사용

카메라, 게임플레이 또는 인터페이스를 조정하려면 position, heading, speed, velocity, state를 읽으세요. 움직임을 만들기 위해 동물의 루트 객체를 조작하지 마세요.

이름이 지정된 지점에 소품 연결

attachments 컬렉션은 mouth, head, back과 같은 지점을 제공합니다. 새는 발 연결 지점도 노출하며, 말은 seat, 등자, 재갈을 비롯한 승마 관련 지점을 제공합니다. 애니메이션 위치를 수동으로 근사하지 말고 이러한 연결 객체에 소품을 부모로 연결하세요.

디버그 및 피복 제어 사용

동물 API에는 albedo, weights, material, bare 모드를 지원하는 setDebug(mode, on)이 포함되어 있습니다. 털 셸을 켜거나 끄려면 setCoverings(on)을 사용하세요.

wolf.setDebug('weights', true);
wolf.setCoverings(false);

지점을 향한 방향 계산

heading 0은 양의 Z 방향을 향하므로, 대상 방향을 계산할 때 Math.atan2(dx, dz)를 사용하세요.

const dx = target.x - wolf.position.x;
const dz = target.z - wolf.position.z;
const heading = Math.atan2(dx, dz);

wolf.move({
  speed: wolf.gears.walk,
  heading
});

리소스 정리

동물을 영구적으로 제거할 때는 dispose()를 호출하세요. GPU 리소스를 해제하고 장면에서 동물을 제거합니다.

wolf.dispose();

유용한 내보내기 및 문서

주요 패키지는 listSpecies, loadSpecies, setWorkerFactory, clearBuildCache, QUALITY도 내보냅니다.

import {
  createAnimal,
  listSpecies,
  loadSpecies,
  setWorkerFactory,
  clearBuildCache,
  QUALITY
} from 'procedural-animals';

모든 옵션, 이벤트 페이로드, 상태 필드 및 레시피는 저장소의 전체 참조 문서와 TypeScript 선언을 참조하세요.

결론

three.js Procedural Animals는 다양하고 리깅된 애니메이션 야생동물을 three.js 애플리케이션에 추가할 수 있는 코드 중심 방식을 제공합니다. createAnimal()로 시작하고 반환된 객체를 장면에 추가한 뒤 동물 API를 통해 조종하고 프레임마다 한 번씩 업데이트하세요. 워커, 베이킹, 시드 기반 캐싱, 다양한 품질 단계 덕분에 동일한 워크플로를 세밀한 클로즈업과 대규모 군중 모두에 사용할 수 있습니다.