VuejsでThreejsをつかってみる

Vuejs上でThree.jsを使って3Dモデルの表示をする必要があったので、その際の実装方法を紹介します。

目標

今回は以下のようなものを作ります。

具体的な話をすると以下のようなことをしています。

  • Threejsで空間とCubeを描画
  • Cubeはクォータニオンのデータを使って回転
  • 空間はマウスのクリックとドラックで視点移動(トラックボールコントロール)
MEMO
今回実装はしませんが、このGIF画像では、画面下にスライドがあり時系列データを選択してそのデータをモデルに反映させています(動画サイトのような感じ)
その部分は各自実装してください。

インストール

まずは、Threejs系のモジュールのインストールが必要です。
ここでは、Threejs本体とトラックボールコントロールのモジュールをインストールします。
(トラックボールコントロールについてはThreejsのモジュール内のexampleに入っていて利用する事もできるのですが、ちょっと面倒なのですでにあるモジュールを利用します)

Threejs
npm install --save vue-threejs
trackballcontrols
npm install --save three-trackballcontrols

実装

とりあえずソースコードです。


Threejs
<template>
  <div id="cube" ref="stage"></div>
</template>

<script>
import VueThreejs from 'vue-threejs'
import * as THREE from 'three'
import TrackballControls from 'three-trackballcontrols'

export default {
  name: 'Cube',
  props: {
    qua: Array
  },
  components: {
    VueThreejs,
    TrackballControls
  },
  data () {
    // === scene ===
    const scene = new THREE.Scene()
    // === renderer ===
    const renderer = new THREE.WebGLRenderer()
    // === camera ===
    const camera = new THREE.PerspectiveCamera(
      30,
      1,
      0.1,
      100
    )
    camera.position.z = 5
    // === light ===
    const light = new THREE.HemisphereLight(0xFFFFFF, 0xFFFFFF, 2.0, (0, 1, 0))
    // === Grid ===
    const grid = new THREE.GridHelper(6, 10, 0x888888, 0x888888)
    grid.position.y = -0.6
    // === model ===
    const geometry = new THREE.BoxGeometry(1, 0.5, 1.25)
    const material = [
      new THREE.MeshStandardMaterial({ color: 0xFFFFFF }),
      new THREE.MeshStandardMaterial({ color: 0xF4D06F }),
      new THREE.MeshStandardMaterial({ color: 0xFF8811 }),
      new THREE.MeshStandardMaterial({ color: 0x9DD9D2 }),
      new THREE.MeshStandardMaterial({ color: 0xFFA8F0 }),
      new THREE.MeshStandardMaterial({ color: 0x392F5A })
    ]
    const cube = new THREE.Mesh(geometry, material)

    return {
      scene: scene,
      renderer: renderer,
      camera: camera,
      light: light,
      cube: cube,
      grid: grid,
      trackball: null
    }
  },
  created () {
    // === sceneにmodel,light, cameraを追加 ===
    this.scene.add(this.camera)
    this.scene.add(this.light)
    this.scene.add(this.cube)
    this.scene.add(this.grid)
  },
  mounted () {
    this.$refs.stage.appendChild(this.renderer.domElement)
    var stage = document.getElementById('cube')
    this.renderer.setSize(stage.offsetWidth / 1.25, 500)
    this.camera.aspect = (stage.offsetWidth / 1.25) / 500
    this.camera.updateProjectionMatrix()
    // === TrackBall ===
    this.trackball = new TrackballControls(this.camera, this.renderer.domElement)
    this.trackball.noZoom = true
    this.trackball.noPan = true
    this.trackball.rotateSpeed = 1.0
    this.animate()
  },
  methods: {
    animate () {
      requestAnimationFrame(this.animate)
      this.quaternion()
      this.renderer.render(this.scene, this.camera)
      this.trackball.update()
    },
    quaternion () {
      if (typeof this.qua[0] === 'undefined') {
        return
      }
      var q = new THREE.Quaternion(this.qua[2], this.qua[3], this.qua[1], this.qua[0])
      this.cube.quaternion.copy(q)
    }
  },
  watch: {
    'qua': function () {
      this.quaternion()
      this.renderer.render(this.scene, this.camera)
    }
  }
}
</script>


MEMO
このソースコードは子コンポーネントして作られているため、利用する際は親コンポーネントに埋め込んで使ってください
データ(クォータニオン)を与える部分を変更すれば親コンポーネントとしても利用できます

解説

かなり部分的で大雑把な解説をします。
具体的にメソッドの引数の内容等が知りたい方は、以下の公式サイトを参照してください。
参考 Threejsthreejs.org

Dataセクション

基本的には変数をまとめて宣言するのですが、3Dモデルや画面、カメラなどの初期設定をしています。
やってることは、コンストで宣言して変数に設定した項目を格納して他のメソッド内から使えるようにしている感じです。

Data
  data () {
    // === scene ===
    const scene = new THREE.Scene()
    // === renderer ===
    const renderer = new THREE.WebGLRenderer()
    // === camera ===
    const camera = new THREE.PerspectiveCamera(
      30,
      1,
      0.1,
      100
    )
    camera.position.z = 5
    // === light ===
    const light = new THREE.HemisphereLight(0xFFFFFF, 0xFFFFFF, 2.0, (0, 1, 0))
    // === Grid ===
    const grid = new THREE.GridHelper(6, 10, 0x888888, 0x888888)
    grid.position.y = -0.6
    // === model ===
    const geometry = new THREE.BoxGeometry(1, 0.5, 1.25)
    const material = [
      new THREE.MeshStandardMaterial({ color: 0xFFFFFF }),
      new THREE.MeshStandardMaterial({ color: 0xF4D06F }),
      new THREE.MeshStandardMaterial({ color: 0xFF8811 }),
      new THREE.MeshStandardMaterial({ color: 0x9DD9D2 }),
      new THREE.MeshStandardMaterial({ color: 0xFFA8F0 }),
      new THREE.MeshStandardMaterial({ color: 0x392F5A })
    ]
    const cube = new THREE.Mesh(geometry, material)

    return {
      scene: scene,
      renderer: renderer,
      camera: camera,
      light: light,
      cube: cube,
      grid: grid,
      trackball: null
    }
  },

Createセクション

このセクションはインスタンスが作成された瞬間に発火します。
Dataセクションで定義されたsceneにカメラなどの要素を追加していきます。

Create
  created () {
    // === sceneにmodel,light, cameraを追加 ===
    this.scene.add(this.camera)
    this.scene.add(this.light)
    this.scene.add(this.cube)
    this.scene.add(this.grid)
  },

Mountedセクション

このセクションはインスタンスがマウントされたら発火します。
DOM要素にこれ以降アクセス可能となるので、ここで画面サイズを取得してブラウザの大きさにThreejsのレンダリングサイズそれに対応したカメラサイズを再定義しています。
またここでトラックボールコントロールについて定義しています。

Mounted
  mounted () {
    this.$refs.stage.appendChild(this.renderer.domElement)
    var stage = document.getElementById('cube')
    this.renderer.setSize(stage.offsetWidth / 1.25, 500)
    this.camera.aspect = (stage.offsetWidth / 1.25) / 500
    this.camera.updateProjectionMatrix()
    // === TrackBall ===
    this.trackball = new TrackballControls(this.camera, this.renderer.domElement)
    this.trackball.noZoom = true
    this.trackball.noPan = true
    this.trackball.rotateSpeed = 1.0
    this.animate()
  },

Methodsセクション

animateでクォータニオンを利用してモデルの回転を描画します。
quaternionでは、クォータニオンの持つ4つの変数を設定します(ここでは順番がバラバラですが、適宜変更してください)

Methods
  methods: {
    animate () {
      requestAnimationFrame(this.animate)
      this.quaternion()
      this.renderer.render(this.scene, this.camera)
      this.trackball.update()
    },
    quaternion () {
      if (typeof this.qua[0] === 'undefined') {
        return
      }
      var q = new THREE.Quaternion(this.qua[2], this.qua[3], this.qua[1], this.qua[0])
      this.cube.quaternion.copy(q)
    }
  },

Watchセクション

Threejsには直接関係ないのですが、この仕様だと親コンポーネントからpropsでデータを取得しているので、このWatchでデータ変更を監視しています。
またデータの更新を確認後メソッドを呼び出すことで再描画しています。

Watch
  watch: {
    'qua': function () {
      this.quaternion()
      this.renderer.render(this.scene, this.camera)
    }
  }

まとめ

以前はVuejsの中でThreejsを使うのにちょっと面倒くささを感じていたのですが、優秀なモジュールも多数あるので、意外とさらっとかけました。
React等を含めSPAがかなり書きやすいのでとてもうれしく思います。

参考 Vuejsjp.vuejs.org 参考 Threejsthreejs.org

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください