12周年記念パーティ開催! 2024/5/10(金) 19:00

three.js入門 第1回 three.jsの基本セットアップ

3D描画に特化したJSライブラリ、three.jsを基本から解説します。第1回では、サンプルコードを見て、three.jsがどのようにセットアップされているか、大まかなところを捉えてみましょう。

発行

著者 小山田 晃浩 フロントエンド・エンジニア
three.js入門 シリーズの記事一覧

はじめに

three.jsは3D描画に特化したJSライブラリです。抽象化された3Dシーングラフ、レンダリング機能、3Dの処理に必要な基本的な数学機能などの高レベルなAPIで構成されています。

現在、three.jsは描画にWebGLを主に利用しますが、WebGL2やWebGPUでのレンダリングの開発も進められています。

ただし、ユーザーは、「描画にどのようなJavaScript APIされているか」をあまり気にする必要はありません。根底にあるJavaScript APIが高度に抽象化されていることにより、「three.jsのコードを書く」ことでそれが描画されます。

なお、three.jsは「描画」に特化しており、次の機能は有しません。

  • 物理演算に関する機能
  • ゲームに関する機能

3Dゲームや3Dシミュレーションを行いたい、という場合には必要に応じて、別のライブラリと連携します。

本シリーズでは、three.jsの基本的な使い方から解説していきます。なお、解説ではthree.js(r132)を利用します。

three.jsのリビジョン

three.jsは約2ヶ月に1度リリースが行われます。「バージョン」を採用しておらず、「リビジョン」の単位で常に緩やかな仕様変更が行われます。多くはパフォーマンス向上やバグフィックス、機能の追加ですが、まれに破壊的な変更が含まれることがあります。

最近の大きな破壊的変更の例では、

  • ジオメトリを廃止し、バッファージオメトリのみに統合
  • d.tsの管理の削除(その後は@types/threeを利用)

などがありました。

基本的なセットアップ

three.jsは、基本として次の手順でセットアップします。

  1. three.js読み込み
  2. カメラ設置
  3. シーン設置
  4. レンダラー設置
  5. オブジェクト設置
  6. レンダーループ(状態が変わったとき、自動的に再描画されません。都度レンダリングする必要があります)

three.jsの公式リポジトリの表紙(README)には、以下の基本コードが掲載されています。

    import * as THREE from './js/three.module.js';

    let camera, scene, renderer;
    let geometry, material, mesh;

    init();

    function init() {

            camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
            camera.position.z = 1;

            scene = new THREE.Scene();

            geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
            material = new THREE.MeshNormalMaterial();

            mesh = new THREE.Mesh( geometry, material );
            scene.add( mesh );

            renderer = new THREE.WebGLRenderer( { antialias: true } );
            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.setAnimationLoop( animation );
            document.body.appendChild( renderer.domElement );

    }

    function animation( time ) {

            mesh.rotation.x = time / 2000;
            mesh.rotation.y = time / 1000;

            renderer.render( scene, camera );

    }

これを実行すると紫色や青などの色がついた立方体が、回転して色が変わっていく様子が描画されます。

今回はthree.jsのコードの全体像をつかむため、この基本コードを例に、それぞれの意味を順を追って見ていきましょう。

1. three.js読み込み

まずはthree.jsを読み込みます。ローカル環境にダウンロードしている場合は、three.module.jsをインポートします。この部分です。

    <script type="module">
    import * as THREE from './js/three.module.js';
    </script>

基本コード例ではローカル環境にあるthree.jsをインポートしていましたが、CDNを利用することももちろんできます。その場合は次のようにインポートします。

    <script type="module">
    import * as THREE from 'https://cdn.skypack.dev/three';
    </script>

three.jsはさまざまなCDNから利用できますが、上記のようにskypack.dev を利用するのがおすすめです。three.js本体の機能のみならjsdelivrやunpkgなどのCDNでも問題ありません。しかしthree.jsに同梱されている公式プラグインを利用する場合は、パッケージ内部で依存importに対応しているskypackを利用する必要があります。

2. カメラ設置

three.jsは、3D空間内で「カメラ」に写した結果を描画しますので、カメラを設置します。

基本コード例では、以下のように、PerspectiveCameraクラスでカメラを作成しています。

    camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
    camera.position.z = 1;

PerspectiveCameraクラスのPerspective(パースペクティブ)とは、要は「遠近法」です。人間の視界のように、近くのものは大きく、遠くのものは小さく映し出されます。

引数は、第一引数に画角(FoV)を、第二引数に表示するcanvas要素の縦横比を、第三引数に視界を開始する最短距離(ニアクリップ)、第四引数に視界が終了する最長距離(ファークリップ)を指定します。

画角(FoV)、ニアクリップ(near)、ファークリップ(far)の関係は、このようなイメージとなります。

カメラを3D空間の中心から少し手前に引いておきます。camera.position.z = 1;zは前後を意味し、+が手前、-が奥です。これにより、このあと、3D空間の中心に配置する物体をカメラに収めることができるようになります。

3. シーン設置

シーンはシーングラフにおけるルートの空間です。シーングラフとは、「表示内容を構成する木構造」であり、HTMLではDOMツリーに相当します。シーンの中に3Dオブジェクトや光源などを設置していきます。つまり、シーンとはHTMLにおける<body>タグのような存在です。

    scene = new THREE.Scene();

このようにSceneクラスのインスタンスを用意し、シーンを設置しておきます。

4. オブジェクト設置

描画対象となるオブジェクトを作成、配置します。基本コード例では立方体を設置し、回転とともに色が変わっていきます。

three.jsにおいて、オブジェクトは「メッシュ」(mesh)と呼ばれます。メッシュは「ジオメトリ(形)」(geometry)と「マテリアル(表面材質)」(material)で構成します。

基本コード例では、

  • 幅、高さ、奥行きがそれぞれ1ユニット0.2の大きさの立方体ジオメトリ
  • 面の方向に合わせて色がつく、ノーマルマテリアル

を組み合わせたメッシュを用意しています。コードは次のようになっています。

    geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
    material = new THREE.MeshNormalMaterial();

    mesh = new THREE.Mesh( geometry, material );
    scene.add( mesh );

立方体の数値について

3D空間において、1ユニットをどのように扱うかはユーザーの自由です。ここで設置した立方体は0.2ですが、それが0.2メートルとするのか、0.2センチとするのか、あるいは0.2ピクセルとするのかは、ユーザーが決めます。カメラの位置により、0.2が画面上の何ピクセルとして投影されるのかが決まります。あくまでも相対値の目安としての数値です。 なお、3Dゲームなどの物理演算を伴う場合、1ユニットを1メートルとして扱うことが多いです。これは、メートル法により、重さ、時間(速さ)などの現実世界の法則に合わせた計算がしやすくなるためです。

マテリアルはさまざまな種類がありますので、さらなるマテリアルは今後紹介します。基本コードで利用されているノーマルマテリアルは、デバッグで便利なマテリアルです。

TIPS: ノーマル(Normal)とは

3Dにおいて「ノーマル」は「面の方向」を意味し、日本語では法線と呼ばれます。「通常の」とか「普通の」という意味ではありません。3D空間内での面の向きは、正規化(0から1に収めた範囲)されたx,y,zで表します。また、x,y,zを色に置き換えて、r,g,bとして可視化することもできます。

たとえば真正面向き、つまり、

  • 横方向(x)は、右(1)でも左(0)でもなく、その中間
  • 縦方向(y)は、上(1)でも下(0)でもなく、その中間
  • 手前方向(z)は、奥(0)ではなく、完全に手前(1)に向いている

という状態は、( x:0.5, y:0.5, z:1.0 )で表し、これを色に置き換えると( r:0.5, g:0.5, b:1.0 )となります。また、これはCSSの色でいえば、256倍してrgb(128, 128, 256)です。そのため、真正面は紫色で表されます。

5. レンダラー設置

3Dの世界を<canvas>にレンダリング(描画)するための機能がレンダラーです。レンダラーにより、3Dのシーングラフがカメラに投影され、その内容を<canvas>に表示できます。

基本コード例では次の部分です。

    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.setAnimationLoop( animation );
    document.body.appendChild( renderer.domElement );

まずはWebGLRendererクラスでレンダラーインスタンスを作成します。引数にはオプションを渡します。基本コードでは{ antialias: true }でアンチエイリアスを有効にしていますが、やや負荷のかかる処理のため未指定(既定値)の場合はオフです。

レンダラーインスタンスには、setSize()メソッドでcanvasの大きさを指定します。setAnimationLoopについては次節「レンダーループ」で解説します。

レンダラーインスタンスはrenderer.domElementとしてcanvas要素を生成します。これをHTMLのbody要素に対しappendChildして展開しておきます。

6. レンダーループ

カメラで映し出した、シーンの内容をcanvasに描画する必要があります。

HTMLやSVGのように、大きさや色の値を変更すると、自動で表示内容に反映される描画モデルは「保持モード」と呼ばれます。

一方で、WebGLやCanvas2Dなどの多くの描画APIは、「即時モード」APIと呼ばれ、位置や大きさの変更をしても、再描画には連動しません。位置や大きさなどの状態が変わるたびに明示的に再描画をする必要があります。

three.jsのレンダラーには、「明示的に再描画」を実行し続ける機能があり、それがrenderer.setAnimationLoopです。

setAnimationLoopのコールバックに任意の関数を登録し、その関数内で「値の変化」と「再描画」を同時に行うことにより、状態の変化をリアルタイムで表示に反映できます。

    renderer.setAnimationLoop( animation );

    function animation( time ) {

            mesh.rotation.x = time / 2000;
            mesh.rotation.y = time / 1000;

            renderer.render( scene, camera );

    }

上記の基本コード例では、meshつまり、立方体のx方向回転、y方向回転を経過時間に連動して変化させています。renderer.render( scene, camera )は描画の命令であり、「任意のシーンを任意のカメラで写して描画する」という意味のメソッドです。

これにより、時間の経過に合わせて「オブジェクトの状態を変化させたあとに描画」を実現しています。

ここまでのまとめ

ここまででthree.jsのもっとも基本的なセットアップの流れと、各オブジェクトの役割の概要を解説しました。

次回からは、それぞれをもう少し掘り下げてみていきましょう。まずは、カメラについて解説します。