three.js超入門 第2回 アニメーションと時間ベースでの制御

# 概要
この記事では「three.js超入門」と題して、three.jsの基礎からシェーダーの利用までをやっていきます。
ターゲットは主に「canvas表現を触ったことがないフロントエンドエンジニア」を想定しているので、jsの構文などの説明は省略しています。
three.jsのバージョンは執筆時点で最新の[r98](https://github.com/mrdoob/three.js/releases/tag/r98)を使用します。


[three.js超入門 第0回 3Dコンピュータグラフィックスの基礎](https://qiita.com/watabo_shi/items/f7c559c3cdbcdd0f2629)
[three.js超入門 第1回 レンダリングまでの流れ](https://qiita.com/watabo_shi/items/bf9bcd4569b6d480c608)
three.js超入門 第2回 アニメーションと時間ベースでの制御
[three.js超入門 第3回 マウスやスクロールでのインタラクション](https://qiita.com/watabo_shi/items/0811d03390c18e46be86)
[three.js超入門 第4回 getBoundingClientRect()を使ったDOM要素との連携](https://qiita.com/watabo_shi/items/9ab12f62434a1e858226#_reference-a437d85928b89c9bb5e5)
[three.js超入門 第5回 シェーダー(GLSL)の基礎](https://qiita.com/watabo_shi/items/7325948db5c07f6fe9c7)
[three.js超入門 第6回 ShaderMaterialでメッシュを変形、着色する](https://qiita.com/watabo_shi/items/c0d4ef11cba6f21189ed)
[three.js超入門 第7回 シェーダーに変数を渡す](https://qiita.com/watabo_shi/items/1b48ca54a9034f3614d8)
[three.js超入門 第8回 シェーダーをインタラクティブに動かす](https://qiita.com/watabo_shi/items/39d21d53ca16541fd498)
[three.js超入門 第9回 シェーダーでテクスチャにエフェクトをかける](https://qiita.com/watabo_shi/items/2fc671f2147e799787f9)

[リポジトリ](https://github.com/watab0shi/threejs-workshop)

[前回](https://qiita.com/watabo_shi/items/bf9bcd4569b6d480c608)は3Dオブジェクトを静止画にレンダリングするところまでやりました。
今回はレンダリングのループ処理を作って3Dオブジェクトをアニメーションさせてみます。

# アニメーション
`javascript`でアニメーションを実装するには、`window.requestAnimationFrame()`を使用して描画ループを作成し、その中で数値を動かします。

## 描画ループを作る
`Canvas`クラスに描画ループ用の`render`関数を作成します。
`render`関数内の`requestAnimationFrame`で次のフレームにも`render`関数が呼ばれるようにすることで、ループ処理を作っています。

```javascript:src/00_empty/Canvas/index.js
// ~ 省略 ~

export default class Canvas {
  constructor() {
    // ~ 省略 ~
    
    // 画面に表示
    // this.renderer.render(this.scene, this.camera);

    // 描画ループ開始
    this.render();
  }
  
  render() {
    // 次のフレームを要求
    requestAnimationFrame(() => { this.render(); });

    // 画面に表示
    this.renderer.render(this.scene, this.camera);
  }
};
```

まだループの中でモデルを動かしていないので見た目には変化はありませんが、chromeの検証ツールで`Rendering > Paint flashing`と`Rendering > FPS meter`にチェックを入れると、60fpsで再描画されていることがわかります。
![box-fps.png](https://qiita-image-store.s3.amazonaws.com/0/86966/3ed1f6f1-01f5-3b1a-e71d-061186b72292.png)



## 数値を動かす
それでは、描画ループ内で数値を動かして、アニメーションさせてみましょう。
`render`関数が1秒間に約60回呼ばれるので、1フレーム分の処理では小さい数値を足します。

```javascript:src/00_empty/Canvas/index.js
render() {
  // 次のフレームを要求
  requestAnimationFrame(() => { this.render(); });
  
  // ちょっとずつ回転させる
  this.mesh.rotation.x += 0.01;
  this.mesh.rotation.y += 0.01;
  
  // 画面に表示
  this.renderer.render(this.scene, this.camera);
}
```
![box-rotate.gif](https://qiita-image-store.s3.amazonaws.com/0/86966/b22ca456-42a9-5a59-970c-b8ae8105b76a.gif)

ここで、ひとつ問題があります。
`requestAnimationFrame`は実行するブラウザによって処理の間隔が違うのです。
上記のコードは`60fps`の環境では1秒間に`0.6`まで動きますが、`30fps`の環境では1秒間に`0.3`までしかいきません。これではせっかく数値を調整しても違うブラウザで見た時に「思ってたんと違う、、😢」となってしまいます。
これを解決するには時間を元にパラメーターの数値を制御する必要があります。

# 時間ベースのアニメーション
時間の取得には`performance.now()`を使用します。
`performance.now()`はページ表示時からの経過時間がミリ秒で返ってくるため、`1 / 1000`にして単位を秒にすると数値の計算がしやすくなります。

```javascript:src/00_empty/Canvas/index.js
render() {
  // 次のフレームを要求
  requestAnimationFrame(() => { this.render(); });

  // ミリ秒から秒に変換
  const sec = performance.now() / 1000;

  // 1秒で45°回転する
  this.mesh.rotation.x = sec * (Math.PI / 4);
  this.mesh.rotation.y = sec * (Math.PI / 4);

  // 画面に表示
  this.renderer.render(this.scene, this.camera);
}
```

これでブラウザ間でのfpsの差異に影響されないようになりました。

`rotation`だけでなく、`position`や`scale`もアニメーションさせてみましょう！