본문 바로가기
공부/etc

Three.js and TypeScript (11)

by Piva 2024. 12. 3.
  • Udemy의 Three.js and TypeScript 강의를 듣고 정리한 내용을 기록한다.
    • 31. GLTF DRACO 부터 33. Rapier Debug Renderer 까지.

DRACO loader

 

three.js docs

 

threejs.org

 

  • 3D Mesh를 압축하거나 압축을 풀기 위해 사용하는 오픈소스 라이브러리.
  • 그동안 사용해온 GLTF 파일도 압축될 수 있으며, GLTFLoader를 통해 불러오는 것도 가능하다.

 

gltf-transform 설치

  • gltf 파일 형식을 읽고, 수정할 수 있게 한다(참고)
  • 강의에서는 모델 파일을 압축하기 위해 사용한다.
// 설치
yarn global add @gltf-transform/cli

// 예제 파일을 압축한다
gltf-transform optimize './public/models/eve$@walk.glb' './public/models/eve$@walk_compressed.glb' --compress draco --texture-compress webp

 

  • 위 명령어는 Input으로 주어진 모델 파일을 Draco를 사용해서 압축한다.
    • ‘draco —texture-compress webp’ 는 가장 흔하게 사용되는 옵션이라고.
    • 명령어 실행 후 압축된 파일이 지정한 이름으로 생성되며, 용량을 확인해보면 확연히 차이가 나는 걸 확인할 수 있음(15 → 1mb)
  • DRACO 옵션으로 압축된 모델은 DRACOLoader를 통해서만 가져와 사용할 수 있다.

실제로 압축된 모델의 용량을 비교하면 확연한 차이가 생겼음을 알 수 있다

 

 

DRACO loader 사용하기

  • addons 에서 가져와 사용하는 것이 가능하다.
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
const glTFLoader = new GLTFLoader();
glTFLoader.setDRACOLoader(dracoLoader);
  • Draco로 압축된 모델을 three.js의 gltfLoader가 가져올 수 있게 하기 위해, DRACOLoader를 선언하고 이를 setDRACOLoader로 설정한다.
  • setDecoderPath 메서드를 통해 압축을 풀 때 사용할 디코더 라이브러리의 경로를 설정한다.
모델을 압축한다고 해서 Scene이 더 빨라지는 것은 아니다. 압축된 파일을 Scene에 띄우기 위해서는 모델을 압축 해제하는 과정이 필요하기에 압축된 모델이 더 늦게 띄워질 가능성도 있기 때문이다.

따라서 모든 파일을 압축하기 보단, 압축 시의 이점이 클지 아닐지를 따져가며 압축 여부를 결정하는 것이 좋다.

왼쪽이 압축된 모델, 오른쪽이 압축되지 않은 모델. 로딩 속도에 차이가 남을 알 수 있다

 

 

 

Physics with Rapier

Rapier란?

  • Rigid Body(리지드 바디, 강체)의 속도나 힘 등을 계산할 때 사용하는 물리 엔진이다.
    • 이러한 조작은 게임이나 시뮬레이션 등에 유용하게 쓰일 수 있다.
  • Three.js와는 별도의 엔진이지만 Three.js에 적용하는 것이 가능하다.
보통 Three.js 의 물리엔진 하면 cannon.js을 많이 쓰는 것 같았는데, 알아보니 아주 오래 전부터 업데이트가 멈춘 상태라 다른 물리 엔진을 적용하려는 사례가 많은 듯 하다. 이번 강의에서 다루는 Rapier는 비교적 새로운 물리 엔진 라이브러리라고.

 

 

설치

yarn add -D @dimforge/rapier3d-compat
@dimforge/rapier3d 을 설치하는 것도 가능한데, Vite에서 0.12.0 버전의 @dimforge/rapier3d가 잘 작동하지 않아서 대신 Combat 버전을 사용한다고.

 

 

Rapier 적용하기

import RAPIER from '@dimforge/rapier3d-compat';

// 일부 코드 생략

// combat 버전에서만 init 메서드가 필요하다.
await RAPIER.init();
const world = new RAPIER.World(gravity);
const dynamicBodies: [THREE.Object3D, RAPIER.RigidBody][] = [];

const cubeBody = world.createRigidBody(RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 5, 0).setCanSleep(false));
const cubeShape = RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5).setMass(1).setRestitution(1.1);
world.createCollider(cubeShape, cubeBody);
dynamicBodies.push([cubeMesh, cubeBody]);

// 중략

function animate() {
  requestAnimationFrame(animate);
  
  for (let i = 0, n = dynamicBodies.length; i < n; i++) {
    dynamicBodies[i][0].position.copy(dynamicBodies[i][1].translation());
    dynamicBodies[i][0].quaternion.copy(dynamicBodies[i][1].rotation());
  }

  controls.update();

  renderer.render(scene, camera);
}
  • RAPIER.World 생성자를 통해 리지드 바디들을 포함할 World 객체를 만든다.
  • World.createRigidBody 메서드를 통해 생성한 World에 새 리지드 바디를 추가한다.
    • 이 때, createRigidBody 메서드에 RigidBodyDesc 객체를 넣어야 한다.
    • RigidBodyDesc.dynamic() 메서드를 통해 해당 리지드 바디가 외부의 힘의 영향을 받을 수 있게 한다(즉, 힘을 받으면 움직일 수 있다).
    • 반대로, RigidBodyDesc.fixed() 를 적용하면 외부의 힘이 가해져도 영향을 받지 않게 된다(힘을 받아도 움직이지 않는다).
    • setCanSleep 메서드는 물리 관련 설정이 바뀌었을 때, 이것이 바로 적용될지 아닐지를 나타낸다. false로 설정할 경우 변경값이 바로 적용된다.
  • RAPIER.ColliderDesc 를 통해 Collider를 설정한다.
    • Collider란, 해당 물체로의 접촉을 감지할 수 있도록 만들어진 기하학적 모양 혹은 형상(shape)을 의미한다.
    • 이걸 설정하면 외부의 힘에 의해 리지드 바디가 영향 받을 수 있게 한다.
    • cuboid를 통해 Collider의 모양을 육면체로 설정한다. 이 외에도 다양한 모양이 존재한다.
    • setMass를 통해 질량을 설정한다.
    • setRestitution 메서드를 통해 얼마나 튕길지 그 정도를 결정한다.
  • 위에서 만든 RigidBody와 Collider를 world.createCollider로 결합하여 world에 추가한다.
  • Animation Loop에서 Rapier에 의해 변화하는 위치 좌표와 회전값을 Three.js의 Object3D 객체에 적용시키며 물리 엔진이 적용된 움직임을 만들어낸다.

육면체에 물리 엔진이 적용되어 바닥에 닿음과 동시에 튕기는 것을 확인할 수 있다

 

 

+) Collider를 복잡한 Mesh에 적용하기 위해서는?

  • Collider를 적용하고자 하는 Mesh를 이루는 각 정점 정보를 사용해 Collider를 만들 수 있다.
import RAPIER from '@dimforge/rapier3d-compat';
import * as THREE from 'three';

const torusKnotMesh = new THREE.Mesh(new THREE.TorusKnotGeometry(), new THREE.MeshNormalMaterial());
const vertices = new Float32Array(torusKnotMesh.geometry.attributes.position.array);
let indices = new Uint32Array((torusKnotMesh.geometry.index as THREE.BufferAttribute).array);
const torusKnotShape = (RAPIER.ColliderDesc.trimesh(vertices, indices) as RAPIER.ColliderDesc)
  .setMass(1)
  .setRestitution(1.1);

 

  • 위 코드에서는 정점 정보를 받아 이를 TriMesh 형태로 그려낸다.
    • TriMesh는 모델의 폴리곤을 삼각형 형태로 그려내는 것을 의미한다.
    • TriMesh 외에도 다른 옵션을 선택할 수 있다.
    • 예를 들어 convexHull은, 해당 정점들 중 일부를 이용해 만들 수 있는 모든 점을 포함하는 다각형을 의미한다. 다소 정밀도는 떨어지지만 가장 간단한 방법이다.

 

 

Rapier Debug Renderer

  • Rapier를 사용할 때, 시각적으로 보이지 않기 때문에 개발에 어려움을 겪거나 실수를 할 수 있다.
  • 이러한 실수를 방지하기 위해 Collider나 Shape를 이루는 정점 배열 내 점들을 연결하여 시각적으로 보이게 할 수 있다.
Rapier 공식 문서에 따르면 Rapier 내부적으로 구현된 렌더링 기능이 없기 때문에, 개발자가 알아서 구현해야 한다. 다만, World 객체를 통해 내부에 존재하는 물체 혹은 Collider 들의 정점 및 색상 정보를 알 수는 있다. 이 때 debugRender() 메서드를 사용한다.

 

import RAPIER from '@dimforge/rapier3d-compat';
import * as THREE from 'three';

const mesh = new THREE.LineSegments(new THREE.BufferGeometry(), new THREE.LineBasicMaterial({ color: 0xffffff, vertexColors: true }));
const world = new RAPIER.World(gravity);

const { vertices, colors } = world.debugRender();
mesh.geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
mesh.geometry.setAttribute('color', new THREE.BufferAttribute(colors, 4));
  • world.debugRender 메서드를 통해 정점 및 색상 정보를 가져온다.
  • mesh에 LineSegments 객체를 생성한다.
    • LineSegments 객체는 주어진 정점들 사이를 연결하는 선들의 집합을 나타낸다.
    • 생성자는 인자로 Geometry 객체와 Material 객체를 받는다.
  • 생성한 LineSegments mesh에 setAttribute로 world에서 받은 정점 및 색상 정보를 할당한다.

Collider와 Shape가 시각적으로 보이는 모습

 

+) OBJLoader

  • 그동안 강의에서 사용한 GLTFLoader처럼 모델을 로딩하는데 사용하는 로더 클래스이다.
  • obj 파일 형식의 모델을 로딩할 때 사용한다.
.obj : 사람이 읽을 수 있는 형태로 3D 기하학 구조의 각 정점, 텍스처 좌표의 UV 위치 등을 표현한 간단한 데이터 포맷이라고 한다. Three.js에서는 addons를 통해 사용한다.
  • OBJLoader를 통해 불러온 모델은 기본적으로 MeshPhoneMaterial로 불러와진다.

  • 강의에서 gltf-transform을 글로벌로 설치하는데, 어째선지 내 노트북에 yarn global 관련 설정이 되어있지 않아 약간 애먹었다.
  • 아래의 공식 문서를 보고 해결. yarn global의 경로를 PATH에 추가하여 해결했다.

https://classic.yarnpkg.com/en/docs/cli/global/

 

Yarn

Fast, reliable, and secure dependency management.

classic.yarnpkg.com

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

Three.js and TypeScript (10)  (0) 2024.11.25
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