2020.12.18

コーディング

【three.js入門・第2回】簡単なシーンの作成

今回は簡単なシーンの作成を
やっていくよ

簡単なシーンって
どんなシーンですか?

そうだね
帰宅後の僕なんかどうかな?
すぐに布団に入って寝るだけだから
ほとんど動きもないよ(笑)

はは
冗談だよ

今回は前回紹介した流れに則って、
・シーンの作成
・カメラの準備
・オブジェクト(被写体)を設定してシーンに配置
を実際にコードで書いていこうと思います。
(ライトは無くても描写は見えるから、今回は最低限として省いています)

※この記事の内容はこちらの本を参考にして書いています。詳細が気になった方は是非ご購入を。



O’Reilly Japan | 初めてのThree.js 第2版

下準備

実際にコードを書くための準備をしていきましょう。

three.jsライブラリのダウンロード

公式のドキュメントではnpmでのインストールを推奨しているっぽいですが、入門なのでライブラリファイルを直接ダウンロードしちゃいましょう。
公式サイトのTOPページからダウンロードできます。

three.js公式サイトTOP

ダウンロードされた「three.js-master.zip」を展開すると色々ファイルが入っていると思いますが、今回使用するのは「build」フォルダー内にある「three.js」のみです。

DOKATA’s COMMENT

Three.jsサイトをインターネットに公開する時に通常使用するのは「three.js」ファイルではなく「three.min.js」ファイルの方だよ。
「three.min.js」はUglifyJSというjs用の圧縮・最適化ツールで「three.js」を軽くしたものなんだ。だけど今回は練習だからソースコードが読みやすくデバッグも容易な「three.js」を使っていくよ。

HTMLスケルトンの作成

three.jsの本体を手に入れたので、後は読み込んじゃえば使えます。
手始めに↓こんな感じで空のスケルトンページを作成しましょう。

<!DOCTYPE html>

<html>
  <head>
    <title>簡単なシーンの作成</title>
    <script src="libs/three.js"></script>
    <style>
      body{
        /* ページ全体を使用するためにmarginを0に設定して
        overflowをhiddenに設定する */

        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>

    <!-- 出力を保持するdiv -->
    <div id="WebGL-output"></div>

    <!-- サンプルを実行するJavaScriptコード -->
    <script>

      // すべての読み込みが終わってからThree.js関連の処理を実行します
      function init() {
        // Three.js関係の処理を以下に追加します
      };
      window.onload = init;

    </script>
  </body>
</html>

6行目で「three.js」を読み込んでおりますが、ファイル階層なんかは自分の好きなように変えちゃってください。
今回は入門なのでいじるファイルを一つにするためにstyleもjsもhtml内に書いちゃいますが、cssもjsも別ファイルにして読み込んだほうがデバッグとかやりやすいと思いますので分かる人はそうしましょう。

さて、htmlを作成したのですからブラウザで見てみましょう。

three.js公式サイトTOP

真っ白ですね。
何も描画してないですからね。
さぁ今からここに諸々を追加していくのです。

シーンの作成

まずはシーンの作成です。
さっきつくったhtmlスケルトンのinit関数の中に書いていきます。

const scene = new THREE.Scene();

これだけ。
はい次。

DOKATA’s COMMENT

THREE.Sceneはシーングラフとも呼ばれることのある構造体だよ。
描画に必要な情報をすべて保持するコンテナだね。
シーンという名のコンテナに、オブジェクトやライト、カメラなんかをどんどん入れていくってことだね。
フォグ効果を追加できる「fogプロパティ」や、オブジェクトのマテリアルを設定できる「overrideMaterialプロパティ」なんてのも持っているよ。

カメラの作成

次にカメラを作成しましょう。
前回軽く説明したようにthree.jsにはタイプの異なる2種類のカメラ(PerspectiveCamera、OrthographicCamera)があります。
今回は一般的な見た目になるPerspectiveCameraを使ってみましょう。

const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

シーンの作成と違って色々と引数が付いていますね。
引数に関しても前回一覧にしているのですが、同じものをまた載せときます↓

引数 説明
fov FOVはField Of View(視野)の略。これはカメラの位置から見えるシーンの範囲である。
例えば人間はおよそ180度のFOV(視野)を持つが、鳥類には360度すべてのFOVを持つものもある。しかしほとんどのコンピュータースクリーンは視界のすべてを完全に覆うわけではないので、通常はもう少し小さな値が選ばれる。ゲームなどでは多くの場合FOVは60度から90度の間の値が選ばれる。
推奨デフォルト値:50
aspect 描画される出力領域の横幅と縦幅の比。
上記のfovで設定するのは水平方向のfovであり、縦横比によって水平方向のFOVと垂直方向のFOVの差が決定される。
推奨デフォルト値:window.innerWidth/window.innerHeight
near nearプロパティはカメラのどのくらい近くからThree.jsが描画を開始するかを指定する。
通常は非常に小さな値に設定し、カメラの位置からすべてを直接描画する。
推奨デフォルト値:0.1
far farプロパティはカメラからどのくらい遠くまで見えるかを指定する。
値が小さすぎると、シーンの一部がおそらく描画されない。逆に大きすぎると、場合によっては描画のパフォーマンスに影響を与えてしまう。
推奨デフォルト値:2000
zoom zoomプロパティを使用するとシーンにズームインまたはズームアウトすることができる。
1より小さな値を使用するとシーンからズームアウトし、1より大きな値を使用するとズームインする。負の値を指定するとシーンが上下逆に描画されることに注意。なお、このプロパティはコンストラクタ引数としては設定できず、カメラ作成後に値を設定する必要がある。
推奨デフォルト値:1

第1引数から第4引数を指定しています。
第5引数のzoomは省略してる形ですね。

さて、カメラを作成したからシーンに追加だ!
ってなことで

scene.add(camera);

と、したくなるかもしれません。

が、ちょっと待ってください。
実はカメラは単純にaddするだけではダメなのです。

前回の記事ではわかりやすさを優先して詳しくは説明してなかったのですが、rendererというものを準備しなくてはいけません。

rendererオブジェクト

rendererオブジェクトはcameraオブジェクトの角度に基づいてブラウザ内でsceneオブジェクトがどのように見えるかを計算するものです。
レンダリングするものだからレンダラー。
レンダリングとはデータをもとに見た目を生成することって感じの認識で良いのかなと思います。

WebGLRendererを使ってこのように作成しましょう。

const renderer = new THREE.WebGLRenderer();

作成したら、レンダリングするサイズの設定と背景色の設定をしておきましょうか。

renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor(new THREE.Color(0xEEEEEE));

そしてhtmlの要素に追加します。
今回はIDが「WebGL-output」の要素(すなわちhtmlスケルトン作成の時に書いておいた<div>要素)に追加です。

document.getElementById("WebGL-output").appendChild(renderer.domElement);

これでレンダラーの準備も整いました。
でもまだレンダリングはされていません。
レンダリングさせるには以下の命令が必要です。

renderer.render( scene, camera );

レンダラーに対して、「cameraを元にsceneをレンダリングしろ!」と言ってるのですね。
ですが、まだシーンには描画すべきものを何も入れていませんから命令したって何も写りません。

この後、いくつか描画させるオブジェクトを追加していきますので、その後に改めて

renderer.render( scene, camera );

↑のコードをいれることにしましょう。

DOKATA’s COMMENT

three.jsにはいくつかのレンダラーが付属しているけど、それらは主に、古いブラウザを使用しているユーザーやWebGLをサポートしていないユーザーのフォールバックとして使われるものだよ。
現在はほとんどのモダンブラウザがWebGLに対応しているから基本的にはWebGLを使えばいいね。

被写体の追加

いよいよ実際に描画されるオブジェクトを追加していきましょう。
前回言ったように形状(ジオメトリ)と見た目(マテリアル)をメッシュでまとめてシーンに追加って流れです。

一番最初は地面のようなものを置いてみましょうか。
平面を地面に見立ててみます。

平面のジオメトリはTHREE.PlaneGeometry、
見た目はとりあえず名前通り基本的なマテリアルTHREE.MeshBasicMaterialを設定しておきましょう。
コードにすると以下になります。

const planeGeometry = new THREE.PlaneGeometry(60, 20); // 幅:60、高さ20の平面
const planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc}); // 灰色の基本的なマテリアル
const plane = new THREE.Mesh(planeGeometry, planeMaterial); // ジオメトリとマテリアルでメッシュを作成

scene.add(plane); // シーンに追加をお忘れなく!

さぁ! これで被写体がシーンに入りましたので、レンダリングすれば見れるはずです!

renderer.render( scene, camera );

↑のコードを最後に追加してブラウザで見てみましょう!

写りませんね……
背景色はレンダラーの作成をした際にsetClearColorで設定した色(0xEEEEEE)になってますので、レンダリングはできているようですが……

原因は何でしょう?

正解は
「オブジェクトの位置とカメラの位置・向きがあっていない」
ということです。

カメラは何もないところを映してますよーということです。

ということでカメラの設定をしましょう!

camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);

カメラの位置座標とレンズをどこに向けるのかの設定です。
映したい画によって色々自分なりに設定する部分ですね。今回は見やすく上空から鳥瞰気味のカメラアングルとなります。
lookAtを用いると(デフォルト値の場合)座標原点を常に向いていてくれますよ。
(※camera.lookAt(new THREE.Vector3(x, y, z)); のようにして座標を変えることも可能です)

上記を追加したら再度ブラウザで見てみましょう。

何か写りましたね。

このマテリアルだといまいち立体感が分からないと思うので、一つ便利機能を追加しましょうか。

const axes = new THREE.AxisHelper(100);
scene.add(axes);

これで座標軸が追加されました。

座標軸が追加されたことで、地面に見立てようと追加した平面がどんな感じで存在していたのかもなんとなくわかります。
幅60、高さ20の平面が原点を中央に立っている。

地面でいてほしいので横にしなきゃいけませんね。

scene.add(plane)をする前のところに、planeの位置を変更するコードも追加しましょう。

plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;

1行目でX軸を軸として(-0.5 * Math.PI)分、すなわち90度(※ラジアン法。2πで一回転の360度だから、0.5πは四分の一の90度)回転させて、そのあとにx,y,zの値を設定してます。

地面らしくなりました。

それじゃあお次は立方体を追加してみましょう。
立方体をつくるにはTHREE.BoxGeometryのジオメトリを使います。

const cubeGeometry = new THREE.BoxGeometry(4, 4, 4); // 幅、高さ、奥行きが全て4
const cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true}); // 赤色で骨組みが見えるように
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = -4;
cube.position.y = 3;
cube.position.z = 0;
scene.add(cube);

MeshBasicMaterialではwireframeをtrueにするとワイヤーフレームとして表示でき、デバック時などに有用です。

もう一個くらい追加してみましょう。
立方体の次は球体でも。

const sphereGeometry = new THREE.SphereGeometry(4, 20, 20); //半径が4で、水平方向、垂直方向にしようされるセグメントの数がどちらも20
const sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff, wireframe: true}); //青色で骨組みが見えるように
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.x = 20;
sphere.position.y = 4;
sphere.position.z = 2;
scene.add(sphere);

出ました。

流れは分かりましたかね?

慣れた人はオブジェクトの大きさや位置やマテリアルを変更してみたり、カメラの位置と向きを調整してみたりすると立体空間のイメージがさらについてくると思います。

DOKATA’s COMMENT

途中で唐突に追加された座標軸(THREE.AxisHelper)のようにthree.jsには他にも色々便利な機能があるよ。
開発を進めていくなかで自分自身の理解も深めるためにヘルパー的な便利機能はどんどん調べて使ってみるといいと思うよ。

まとめ

今回は下準備も含めてとりあえずなにか表示させるところまでやってみました。
カメラとレンダラーの関係は初めて出てきましたが、最初に書いてしまえばそうそういじる部分でもないので大丈夫だと思います。

最後にここまで書いてきたソースコードをまとめて載せておきます。

次回は今回作ったこのシーンにライトを追加したり、動き(アニメーション)をつけてみましょう!

<!DOCTYPE html>
 
<html>
  <head>
    <title>簡単なシーンの作成</title>
    <script src="libs/three.js"></script>
    <style>
      body{
        /* ページ全体を使用するためにmarginを0に設定して
        overflowをhiddenに設定する */
 
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
 
    <!-- 出力を保持するdiv -->
    <div id="WebGL-output"></div>
 
    <!-- サンプルを実行するJavaScriptコード -->
    <script>

      // すべての読み込みが終わってからThree.js関連の処理を実行します
      function init() {
        // Three.js関係の処理を以下に追加します

        // シーンの作成
        const scene = new THREE.Scene();

        // カメラの作成
        const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

        // レンダラーの作成と設定
        const renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.getElementById("WebGL-output").appendChild(renderer.domElement);

        // 座標軸の追加
        const axes = new THREE.AxisHelper(100);
        scene.add(axes);

        // 地面の追加
        const planeGeometry = new THREE.PlaneGeometry(60, 20);// 幅:60、高さ20の平面
        const planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc});// 灰色の基本的なマテリアル
        const plane = new THREE.Mesh(planeGeometry, planeMaterial);// ジオメトリとマテリアルでメッシュを作成
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        scene.add(plane);

        // 立方体の追加
        const cubeGeometry = new THREE.BoxGeometry(4, 4, 4); // 幅、高さ、奥行きが全て4
        const cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true}); // 赤色で骨組みが見えるように
        const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;
        scene.add(cube);

        // 球体の追加
        const sphereGeometry = new THREE.SphereGeometry(4, 20, 20); //半径が4で、水平方向、垂直方向にしようされるセグメントの数がどちらも20
        const sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff, wireframe: true}); //青色で骨組みが見えるように
        const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        sphere.position.x = 20;
        sphere.position.y = 4;
        sphere.position.z = 2;
        scene.add(sphere);

        // カメラの位置と向きの設定
        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        // レンダリング命令
        renderer.render(scene, camera);
      };
      window.onload = init;

    </script>
  </body>
</html>

ご覧いただきありがとうございました。

この記事を書いた人

葉っぱ一号

葉っぱ一号

フロントエンドエンジニア

おいしいお店を探すのが好きです。おいしいお店のために遠出もしちゃいます。遠出したい衝動のためにおいしいお店を探すのかもしれません。そんなものですよね。