🐈

[ Three.js ] 3D猫はいかがですか?

2022/08/11に公開

Three.js?


ブラウザに3Dオブジェクトを簡単にレンダリングしてくれるJavaScriptの3Dライブラリのことです。
最近興味があったので今回簡単にglTFのフォーマットの3Dアニメーションモデルを画面に描画してみました。

全体のソースコードはこちらです。
部分的に説明しているので、全体のソースコードと一緒に見た方がいいかもしれません。
https://github.com/copetit/threejs-for-study

下準備

HTMLファイル準備

今回は簡単に動作を試してみるために、HTMLファイルだけ使うことにしました。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My first three.js app</title>
    <style>
      body {
        margin: 0;
      }
      canvas {
        display: block;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="1000" height="1000"></canvas>
  </body>
</html>

3D Modelをインストール

3D Modelはsketchfabからダウンロードしました。

ダウンロードが可能で著作権に問題ないものにしましょう。今回はこちらの猫を使ってみました。
レッサーパンダにしてみたかったのですが、検索するとなんだか怖いレッサーパンダが多くて猫にしました。
(いつか自分で作ってみたいですね。)


"An Animated Cat" (https://skfb.ly/6YPwH) by Evil_Katz is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).

フォーマットは glTF でダウンロードします。

※ glTF(GL Transmission Format)
JSONによって3Dモデルやシーンを表現するフォーマット

ディレクトリ構成

glTFファイルのzipを解凍し、index.htmlと同じ階層に入れます。

Three.jsを触る

install

npmでインストールする方法もありますが、今回はhtmlファイルだけ利用したので、CDNからのインストールにしました。

https://threejs.org/docs/index.html#manual/ja/introduction/Installation

bodyの中に以下を書いてimportします。

<script type="importmap">
      {
        "imports": {
          "three": "https://unpkg.com/three@0.141.0/build/three.module.js",
          "GLTFLoader": "https://unpkg.com/three@0.141.0/examples/jsm/loaders/GLTFLoader.js",
          "OrbitControls": "https://unpkg.com/three@0.139.2/examples/jsm/controls/OrbitControls.js"
        }
      }
</script>

GLTFLoader

  • glTFを読み込むために利用します。

OrbitControls

  • カメラの位置をコントロールできる。マウスでカメラの位置を変更したり、zoom操作も可能です。

三つの基本要素: scene / camera / renderer

scene

  • modelが配置されて背景、質感などが表示される空間

camera

  • sceneを見るカメラ
  • カメラの位置、角度、方向などを決められます。
  • PerspectiveCameraOrthographicCameraがありますが、遠近感が適用されるカメラのPerspectiveCameraを使いました。オプションについての説明は長くなるため、省略します。

renderer

  • sceneとcameraのデータをもらって画面にレンダリングしてくれる
<script type="module">
  import * as THREE from "three";
  import { GLTFLoader } from "GLTFLoader";
  import { OrbitControls } from "OrbitControls";

  // scene
  const scene = new THREE.Scene();
  scene.background = new THREE.Color("white");

  // camera
  const camera = new THREE.PerspectiveCamera(90, 1000 / 1000, 1, 2000);
  camera.position.set(0, 0, 25);

  // renderer
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector("#canvas"),
  });

  // modelを表示させる
  const loader = new GLTFLoader();
  loader.load("cat/scene.gltf", function (cat) {
    // sceneにmodelを入れる
    scene.add(cat.scene);
    // sceneとcameraをレンダリング
    renderer.render(scene, camera);
  });
</script>

ここまでだと、このように3Dモデルが表示されます。

暗かったのでライトを入れました。また、sRGBEncodingを入れることで本来のモデルの色に近くなりました。

const light = new THREE.AmbientLight(0xffffff, 4);
scene.add(light);

renderer.outputEncoding = THREE.sRGBEncoding;

アニメーションを入れる

この3Dモデルは元々動くモデルだったのでせっかくなので動かしてみます。

AnimationMixer

Modelのアニメーションプレイヤー

// AnimationMixerを作成
const mixer = new THREE.AnimationMixer(cat.scene);

// 全てのアニメーションを再生させる
cat.animations.forEach((clip) => {
  mixer.clipAction(clip).play();
});

scene.add(cat.scene);

AnimationMixerを作るだけだと、画面にレンダリングされないため、requestAnimationFrameを利用して描画させる必要があります。

https://threejs.org/docs/#api/en/animation/AnimationMixer

requestAnimationFrame

requestAnimationFrame(callback)

  • callback関数を渡す
  • ブラウザが再描画可能なタイミングに実行される
  • 1秒に60回程度

以下はanimate()関数は自分自身をrequestAnimationFrame()を使って呼び出していてループをさせています。つまりanimate()は1秒に60回くらい実行されます。

function animate() {
  requestAnimationFrame(animate);
  console.log("moving");
}
animate();

猫の描画のためにこのように書いてみました。requestAnimationFrameでrendererを読んでいます。

function animate() {
  // 毎回レンダリングをすることでアニメーション効果を出す
  renderer.render(scene, camera);
  // 1秒に60回
  requestAnimationFrame(animate);
}
animate();

次は上で作ったAnimationMixerを呼び出します。

Clock + mixer

指定した時間分、AnimationMixerを定期的に動かす必要があります。
Clockは時間を把握するオブジェクトです。ここでは時間差を取得できるgetDeltaを使いました。

1秒に60回くらい(60fps)の頻度でanimate()が実行されるので、その間隔の時間を取得し、その時分のアニメーションフレームを更新します。

const clock = new THREE.Clock();

function animate() {
  // 次に実行されるときの時間差を保存
  const delta = clock.getDelta();
  // その時間差分のアニメーションをフレームを更新させる
  if (mixer) mixer.update(delta);
  // 毎回レンダリングをすることでアニメーション効果
  renderer.render(scene, camera);
  // 1秒に60回
  requestAnimationFrame(animate);
}
animate();

https://threejs.org/docs/#api/en/core/Clock

このようにアニメーションが適用されました。

マウス操作を入れる

マウスで3Dモデルをくるくるさせたりできます。
OrbitControlersの引数にカメラとDOM要素を渡すことで実現できます。

new OrbitControls(camera, renderer.domElement);
  • Orbit: 左ボタンでドラッグ
  • Zoom: マウスホイール
  • pan: 右ボタンでドラッグ

https://threejs.org/docs/#examples/en/controls/OrbitControls
https://ics.media/tutorial-three/camera_orbitcontrols/

ミニ感想

3Dモデルを入れたmixerを再生するだけではなく、requestAnimationFrameを利用して定期的に描画しないといけないところがややこしかったです。

また、Three.js自体は公式ドキュメントを含めてドキュメントの量が少ない印象を受けました。
今回は簡単なオブジェクトレンダリングでしたが、実際にゲームなど規模の大きい開発になると大変そうです。
3Dになると一気に数学計算しないといけない部分も出てきたりするので、そこの理解も深めないといけないと思いました。

Discussion