【超基礎】Three.js入門!必要最小限のコードで3Dを表示してみる

1,002views

現在WEBサイト制作や開発をしていますが、Wix様やStudio様など多くのWEBサイト作成プラットフォームが利用されてどんどん自分の仕事の狭さを感じています。ゆひゃです。

そんな中、VRやメタバースなど3D技術を使ったサービスが増えているということもあってWebサイト上で3Dを表現することができるJavaScriptライブラリのThree.jsを触り始めました。

意外と触ってみると難しくないので、今回はThree.jsの超基礎オブジェクトを表示するまでを解説していこうと思います。

3Dを表示するための必要最小限

Three.jsはnpmでインストールしましょう。

#npm init
npm install three

Three.jsでブラウザーに3Dを表示するためには

  • レンダラー(Renderer)
  • シーン(Scene)
  • カメラ(Camera)
  • 3Dオブジェクト

の4つの要素が必要最小限になります。それらの要素の関係を描いてみました。

カメラで3Dオブジェクトを視認している状態がシーン。シーンをブラウザーに表示する役割がレンダラーです。

これらの一つでも欠けるとブラウザーには何も表示されません。ほかにもライトやコントローラーなどの多くの要素があります。必要に応じて追加していく必要があります。

【開発時はあったほうがいい】lil-gui

lil-guiはブラウザ表示しているさまざまな状態を操作することができるライブラリです。

これを使うことで、コードを書き換えることなく視覚的に状態を変化させることができます。3Dのためだけのライブラリではありませんが、視覚的な感覚が大事になる3D開発ととても相性が良く、多くの方が利用しているので使っていきます。

こちらもnpmでインストールします。

npm install lil-gui --save-dev

実際に3D空間を作ってみる

それでは実際に3D空間を作っていきます。

htmlはbodyタグに<script src="...">でThree.js開発をするJavaScriptファイルを読み込むコードのみを書き込んでおきます。

レンダラー(Renderer)

まずは、HTMLにこれから作るコードを表示するための"renderer"を作っていきます。これを作ることで、HTMLにcanvasタグが挿入されます。

import * as THREE from "three"

init()
function init () {
  const width = window.innerWidth
  const height = window.innerHeight
  
  const renderer = new THREE.WebGLRenderer()
  renderer.setPixelRatio( window.devicePixelRatio )
  renderer.setSize( width, height )
  
  document.body.appendChild( renderer.domElement )
}

まず初めにthree.jsをTHREEとしてインポートします。

8行目のrenderer変数に格納しているnew THREE.WebGLRenderer()を見てみると、THREEのWebGLRendererというクラスをインスタンス化していることがわかります。

このようにThree.jsでは、用意されている莫大な量のクラスを適宜インスタンス化して利用することでコードを書き進めていきます。

9,10行目ではwindowサイズやピクセル比率をレンダラーに受け渡して設定しています。

12行目は設定した大きさのrendererをcanvasタグという形でbodyタグに挿入しています。

これでレンダラーの準備ができました。

シーン(Scene)

シーンの設定は簡単です。

...
function init () {
  ...
  
  const scene = new THREE.Scene()
}

...の部分は前回の入力したコードです。

これだけでシーンを設定することができます。

今後追加していく3Dオブジェクトやカメラなどの全てはこのシーンに追加していくことになります。シーンの追加は

scene.add( OPTIONAL_OBJECT )

と、記述するだけでできますが、結構忘れて あれ表示されないぞ となりがちなので注意です。

カメラ(Camara)

Three.jsカメラは、平行投影カメラ(Orthographic Camera)と遠近投影カメラ(Perspective Camera)の2種類あります。

平行投影カメラはよく「シムシティ」のような表示方法をするカメラだと例えられます。遠近投影カメラは比較的直感的な遠近法が適用されたカメラです。

平行投影カメラでは奥の四角と手前の四角の大きさが等しい一方、遠近投影カメラでは奥にいくにつれて四角が小さくなっているのがわかります。

今回はより3D表現近い遠近投影カメラを使います。

それではカメラを追加します。

...

function init() {
  ...

  const camera = new THREE.PerspectiveCamera( 45, width/height, .1, 1000)
  camera.position.set( 30, 30, 60 )
  camera.lookAt( 0, 0, 0 )
  scene.add( camera )
}

6行目でカメラをインスタンス化しています。この時の引数は、

(視野角(fov), 比率, 最小距離(near), 最大距離(far))

です。

視野角は度数で計算されます。30から100くらいまでで設定するのが理想だと思います。

視野角、最小距離、最大距離のデモを用意してみました。(ここまでのコードを入力するだけではまだこの3D空間は表示されないので注意してください。)

右上のControlsというのが先ほど追加したlil-guiを使って追加できるコントローラーです。

fovを変化させるとズームされているように感じますが、実際には見える角度が小さくなっているだけです。

fovの数値を小さくすることで見える範囲が狭まります。

fovの数値を小さくしていくときのイメージ画像です。赤がfov:60の時、青がfov:10の時だと仮定してください。オブジェクトの見える部分が小さくなっていることがわかると思います。

見える範囲は小さくなり、ウィンドウのサイズが変わっていないのでズームされているように見えた訳です。

7行目でカメラの位置を変更しています。Three.jsのオブジェクトの位置は同様の形式で変更することができます。

他にも回転や、大きさの変更も同様の方法で変更できます。

Object3D.position.set( x, y, z ) // 位置
Object3D.rotation.set( x, y, z ) // 回転
Object3D.scale.set( x, y, z ) // 大きさ

8行目はカメラの向く角度です。指定した座標の方向を中心に合わせてくれます。

最後にscene.addしています。これは絶対忘れてはいけません。

3Dオブジェクト

それでは本命の3Dオブジェクトを設置します。

Three.jsの3Dオブジェクトは、ジオメトリ(Geometry)とマテリアル(Material)によって構成されます。

ジオメトリとは、簡単にいうと形を構成する要素であり、Three.jsでは球体や多角形などさまざまな種類のジオメトリが用意されています。その他にもBlenderなどで作った3Dオブジェクトをインポートすることもできます。

マテリアルとは、3Dオブジェクトの表面の素材要素です。こちらもさまざまなマテリアルが既存で用意されています。

今後すべてのジオメトリとマテリアルについて解説する記事を書こうと思います。

それでは今回使うオブジェクトを作っていきます。

今回はジオメトリはBoxGeometry、マテリアルはMeshNormalMaterialを使います。

...

function init () {
  ...

  const boxGeo = new THREE.BoxGeometry( 10, 10, 10 )
  const boxMat = new THREE.MeshNormalMaterial()
  const box = new THREE.Mesh( boxGeo, boxMat )
  box.position.y = 15
  scene.add( box )
}

6行目でジオメトリを設定しています。引数は、横幅・高さ・奥行きです。

7行目はマテリアルです。ライトなしで視覚できるマテリアルを設定しています。

8行目でboxが完成します。Meshは"網目状の構造"という意味の英単語で、THREE.Meshクラスに、ジオメトリとマテリアルを指定すると3Dオブジェクトとしてインスタンス化できます。

9行目ではy軸方向に15動かしています。

10行目はsceneに追加しています。これは絶対忘れてはいけません。

これで3Dオブジェクトを表示する準備が整いました。

3D空間を表示する

ここまでのコードを流れに沿って入力してきた人(多分いない)はまだ表示されていない状態だと思います。

この後にレンダラーのrenderメソッドを呼び出す必要があります。renderメソッドの呼び出しはアニメーションフレームの中で行うのが通例です。アニメーションフレーム内でrenderすることで今後コントローラーや物理エンジンを追加した時に3Dオブジェクトを動かすことができます。

といったメリットがあるのでとりあえず今はアニメーションフレーム内で呼び出すものだと思ってください。

それを踏まえてコード全文です。

import * as THREE from 'three'

init()
function init () {
  const width = window.innerWidth
  const height = window.innerHeight

  const renderer = new THREE.WebGLRenderer()
  renderer.setPixelRatio( window.devicePixelRatio )
  renderer.setSize( width, height )
  
  document.body.appendChild( renderer.domElement )

  const scene = new THREE.Scene()

  const camera = new THREE.PerspectiveCamera( 45, width/height, .1, 1000 )
  camera.position.set( 30, 30, 60 )
  camera.lookAt( 0, 0, 0 )
  scene.add( camera )

  const boxGeo = new THREE.BoxGeometry( 10, 10, 10 )
  const boxMat = new THREE.MeshNormalMaterial()
  const box = new THREE.Mesh( boxGeo, boxMat )
  box.position.y = 15
  scene.add( box )

  const animate = () => {
    requestAnimationFrame( animate )
    
    renderer.render( scene, camera )
  }
  animate()
}

renderメソッドは引数にsceneとcameraが必要です。

おまけ〜地面を作る

この状態だと物体が3D空間にある感じがしないので、地面を作ろうと思います。

地面はPlaneGeometryを使い、マテリアルはワイヤーフレームだけ表示するためなんでもいいので適当にMeshBasicMaterialでも使っておきます。

...

function init () {
...

const planeGeo = new THREE.PlaneGeometry( 1000, 1000, 50, 50 )
const planeMat = new THREE.MeshBasicMaterial({
  color: 0x333333,
  wireframe: true
})
const plane = new THREE.Mesh( planeGeo, planeMat )
plane.rotation.set( -Math.PI/2, 0, 0 )
scene.add( plane )

...
}

今回のポイントは7行目と12行目です。

7行目のマテリアルを設定するクラスの引数部分に注目です。マテリアルの細かい設定は第一引数にJson形式で渡します。今回は#333333のワイヤーフレームを表示するオプションを設定しています。

12行目はx軸方向に-90度回転させています。というのも初期値で壁のように垂直状態で生成されてしまうので、今回地面として使う関係で90度回転させる必要があったわけです。

これで地面がある3D空間に3Dオブジェクトが表示されてる感じがしてきました。

コントローラーを追加する

3D空間を表示できたわけですが、まだのっぺり感があるような気がするかもしれません。

そこでドラッグやスクロールでカメラの位置を変更できるコントローラーを追加してみます。

中身を所々省略しているので注意してください。

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

init()
function init () {

...

cosnt camera ...

...

const controls = new OrbitControls( camera, renderer.domElement )

const animate = () => {
  requestAnimationFrame( animate )

  controls.update()
  renderer.render( scene, camera )
}
animate()

これでマウスやタッチパッドを使ってぐりぐりすることができるようになりました。

ここまでのデモを作ってみました。コントローラーは右上のlil-guiにあるコントロールを有効のチェックボックスをオンにしてください。

まとめ

3Dオブジェクトを表示することはできたでしょうか。今回は必要最小限の知識とコードで表示することを目的にThree.jsのコードを作ってみました。

今後はもっとジオメトリやマテリアルについて掘り下げていこうと思います。

また、今回Three.jsはnpmでインストールしました。Three.jsをインポートしたJSファイルはとても容量が大きくなるので、minify化するのも大切です。

Three.jsでは通常のサイトでは表現できないデザインを作れる一方で、ロード時間も長くなりがちなので、初期ローディングをつけるなどの工夫も大切かもしれません。

カテゴリー