TresJS 入門:オブジェクトのグループ化と自動アニメーション

#Nuxt #TresJS

前回は、TresJSを使って3D空間に骨組み(Geometry)と質感(Material)を持ったメッシュを配置する方法について解説しました。

今回はその続きとして、配置した複数のオブジェクトを「1つのグループ」としてまとめて動かす方法と、静止しているオブジェクトを滑らかに「自転(自動アニメーション)」させる方法について解説していきます。

この記事で学習できること

  • TresGroup を使って、複数の3Dオブジェクトをまとめて管理・配置する方法
  • Vueのテンプレート参照(shallowRef)を使って、3Dオブジェクトのインスタンスにアクセスする方法
  • TresJSが提供する useLoop を用いた、毎フレーム連動の滑らかな回転アニメーションの実装

開発環境

この記事のコードは、以下の環境・バージョンで動作検証を行っています。

  • Nuxt: 4.4.4
  • @tresjs/nuxt: 5.6.0
  • Tailwind CSS: 4.2.4

1. 複数のオブジェクトをまとめる:TresGroup

前回は、正二十面体(Icosahedron)とドーナツ型(Torus)を左右にずらして配置しました。

現状のコードは以下のようになっています。

<template>
  <div class="h-screen w-screen">
    <TresCanvas clear-color="#000000">
      <TresPerspectiveCamera :position="[0, 0, 5]" />

      <TresMesh :position="[-2, 0, 0]">
        <TresIcosahedronGeometry :args="[1, 1]" />
        <TresMeshBasicMaterial wireframe />
      </TresMesh>

      <TresMesh :position="[2, 0, 0]">
        <TresTorusGeometry :args="[0.8, 0.2, 8, 32]" />
        <TresMeshBasicMaterial wireframe />
      </TresMesh>
    </TresCanvas>
  </div>
</template>

ここで、「この2つのオブジェクトをペアのまま、丸ごと上に1マス移動させたい」となった場合、どうすればいいでしょうか。 それぞれの TresMesh:position(Y座標)を書き直すこともできますが、オブジェクトの数が増えると管理が非常に大変になります。

そんな時に便利なのが TresGroup です。HTMLの <div> タグのように、複数の3Dオブジェクトを透明な箱に閉じ込めることができます。

グループ化のコード例

<TresGroup :position="[0, 1, 0]">
  <TresMesh :position="[-2, 0, 0]">
    <TresIcosahedronGeometry :args="[1, 1]" />
    <TresMeshBasicMaterial wireframe />
  </TresMesh>

  <TresMesh :position="[2, 0, 0]">
    <TresTorusGeometry :args="[0.8, 0.2, 8, 32]" />
    <TresMeshBasicMaterial wireframe />
  </TresMesh>
</TresGroup>

TresGroup:position="[0, 1, 0]" を設定することで、中にいる2つのオブジェクトは、それぞれの位置関係(左右に2マス離れている状態)を保ったまま、まとめて上に1マス移動します。 複雑な3Dシーンや、複数のパーツを組み合わせた1つのキャラクター・背景オブジェクトを作る際には必須のテクニックです。

group


2. アニメーションの準備:テンプレート参照とコンポーネントの分割

オブジェクトを回転させるには、まず「どのオブジェクトを回転させるか」をScript側から指定する必要があります。Vueでお馴染みのテンプレート参照を使用しますが、TresJS(Three.js)を扱う上では重要なポイントが2つあります。

  1. ref ではなく shallowRef を使う: Three.jsのオブジェクト(Meshなど)は膨大なプロパティを持つため、通常の ref で深い階層までリアクティブ監視すると、パフォーマンスが著しく低下します。監視の浅い shallowRef を使用するのがベストプラクティスです。
  2. Canvasの「外枠」と「中身」でファイルを分割する: Nuxt 4環境(特に nuxt-security 導入時など)では、TresCanvas の直下にアニメーションロジックを同居させると、CSP(コンテンツセキュリティポリシー)のエラーが発生して画面が動かなくなる罠があります。これを防ぐため、3D空間の中身(シーン)は別コンポーネントに切り分けて定義します。

今回は解説用に、新しく作成する子コンポーネントを TheTest.vue、それを呼び出すページを test.vue として進めます。

1. 3D空間の中身となるコンポーネント(app/components/TheTest.vue)

<script setup lang="ts">
  import type { Mesh } from 'three'

  const boxRef = shallowRef<Mesh | null>(null)
  const torusRef = shallowRef<Mesh | null>(null)
</script>

<template>
  <TresPerspectiveCamera :position="[0, 0, 5]" />

  <TresMesh ref="boxRef" :position="[-2, 0, 0]">
    <TresIcosahedronGeometry :args="[1, 1]" />
    <TresMeshBasicMaterial wireframe />
  </TresMesh>

  <TresMesh ref="torusRef" :position="[2, 0, 0]">
    <TresTorusGeometry :args="[0.8, 0.2, 8, 32]" />
    <TresMeshBasicMaterial wireframe />
  </TresMesh>
</template>

2. 表示を担当するページ側(app/pages/test.vue)

ページ側は、Canvasという「外枠」を用意して、先ほど作ったコンポーネントを呼び出すだけのシンプルな構成にします。

<template>
  <div class="h-screen w-screen">
    <TresCanvas clear-color="#000000">
      <TheTest />
    </TresCanvas>
  </div>
</template>

これで、セキュリティエラーを安全に回避しつつ、Script側から boxRef.valuetorusRef.value を通して、Three.js本来のオブジェクト(rotation プロパティなど)を操作する準備が整いました。


3. 滑らかに回転させる:useLoopの活用

3D空間のアニメーションは、1秒間に何十回(通常は60回など)も画面を描画し直す「レンダリングループ」の中で、少しずつ数値を変化させることで実現します。

TresJSでは、このレンダリングループの中に独自の処理を簡単に挟み込めるコンポーザブル useLoop が用意されています。

先ほど分割して作成した TheTest.vue のScript部分に、useLoop を追加してみましょう。

完成版のコード

<script setup lang="ts">
  import type { Mesh } from 'three'
  import { useLoop } from '@tresjs/core'

  const boxRef = shallowRef<Mesh | null>(null)
  const torusRef = shallowRef<Mesh | null>(null)

  // useLoopからonBeforeRender(描画直前の処理を行うイベント)を取り出す
  const { onBeforeRender } = useLoop()

  // 毎フレーム(画面が描画される直前)に実行される処理
  onBeforeRender(({ delta }) => {
    // 正二十面体をY軸(縦軸)中心に回転させる
    if (boxRef.value) {
      boxRef.value.rotation.y += delta * 0.5
    }

    // ドーナツ型をX軸とY軸の両方で回転させる
    if (torusRef.value) {
      torusRef.value.rotation.x += delta * 0.3
      torusRef.value.rotation.y += delta * 0.5
    }
  })
</script>

<template>
  <TresPerspectiveCamera :position="[0, 0, 5]" />

  <TresMesh ref="boxRef" :position="[-2, 0, 0]">
    <TresIcosahedronGeometry :args="[1, 1]" />
    <TresMeshBasicMaterial wireframe />
  </TresMesh>

  <TresMesh ref="torusRef" :position="[2, 0, 0]">
    <TresTorusGeometry :args="[0.8, 0.2, 8, 32]" />
    <TresMeshBasicMaterial wireframe />
  </TresMesh>
</template>

コードのポイント解説

  • onBeforeRender: 次の画面が描画(レンダリング)される直前に毎回呼び出される関数を登録します。
  • delta: 前回のフレームから今回のフレームまでに経過した「時間(秒)」です(約 0.016 秒など)。
  • rotation: オブジェクトの回転角度(ラジアン)を表します。.x(横軸)、.y(縦軸)、.z(奥行き軸)をそれぞれ操作可能です。

rotation.y += 0.01 のように固定値で足してしまうと、パソコンの性能やモニターのリフレッシュレート(60Hz、144Hzなど)によって回転速度が変わってしまいます。 delta を掛け合わせる(delta * 0.5)ことで、「どの端末で見ても同じ速度で均一に動くアニメーション」 を作ることができます。

このコードを実行すると、静止していた3Dオブジェクトが、本サイトの背景のようにゆっくりと自転し始めるはずです。

animation

おわりに

今回は、複数の3Dオブジェクトをまとめて管理する TresGroup と、コンポーザブルを使った動きの基本である useLoop について解説しました。

  1. 複数の位置調整や一括移動には TresGroup を挟む。
  2. 3Dオブジェクトの参照やパフォーマンス最適化、そしてCSPエラーの回避には shallowRef と コンポーネント分割を活用する。
  3. 端末に依存しない滑らかなアニメーションには、useLoopdelta を使う。

ここまでの機能を使えば、Webサイトの中に「動く3Dアクセント」を取り入れることが十分に可能になります。

ぜひご自身のプロジェクトでも、色々な形を回転させてみてください。