クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

Three.js入門サイトに私も入門する!!

以前ご紹介した「WebGL」の続きになります。今年こそブラウザ3D事始めんと欲し「書き初め」的に書いた投稿からもうじき半年…。 前回tone.jsと同様、今回も便利なライブラリ「Three.js」を使って一刻も早く体験したく。いざっ!

※目次:

Three.jsとは

WebGLの定番ライブラリに「three.js」がありんす。名前の由来は3Dの3と思われる。
公式サイトはこちら。サンプルみるだけでオラワク*1ですね!
※参考:three.js – JavaScript 3D library

生JSとライブラリとフレームワークの理解」で書いたようにライブラリにはあらかじめ便利なメソッドがたくさん定義されています。
そういえばこの記事の後半でもThree.jsのコードを引用していますね。Three.js本体ファイルを読み込むと、このPerspectiveCamera()WebGLRenderer()などの便利メソッドを実行できるわけです。

three.jsの読み込み

さっそく初めてみる。公式サイト の左メニュー「download」からファイル一式がダウンロードできるんだけども、CodePenはあらかじめthree.jsのCDN*2 の設定が用意されております!

  • 新規投稿画面(New Pen)。「Setting」をクリック
  • 「JavaScript」タブをクリック
  • 一番下の「QuickAdd」を開く
  • 「Three.js」を選択
  • CDNのthree.jsファイルがリンクされます。
  • 最後に「Save & Close」をクリック!

どーですか!とっても・カン・タン・だ!って感じでしょう。さー準備は整った。時がきた!それだけだ。あとはやるだけだ!

Three.js入門サイトにいざ入門!

WebGLで3Dを表現するには、2Dで絵を描くのに比べて一筋縄ではいかないことがわかってきました。かい摘んでもざっとこれだけの工程があります。

  • モデリング:造形
  • マテリアル:色や表面の質感
  • ライティング:光の角度、強さ
  • カメラ:カメラの位置
  • モーション:動き

どんな素敵な造形をしてもカメラやライトを設定しないとまっ暗な倉庫にあるのと同じわけですな。うーむ、こりゃなかなか独学では難しそうです。
そこで!いつもお世話になっているICS MEDIA 内の連載「Three.js入門サイト」に、私も入門をしたく思います!押忍!!

※参考:Three.js入門サイト - ICS MEDIA

サンプル再現!回る箱!

まずは最初の「入門編」のページから取り組もうと思います。サンプルを開くと…おお!箱が回っているゾ!

※参考:簡単なThree.jsのサンプルを試そう - ICS MEDIA

>まずはHTMLファイルを用意して、次のコードを貼り付けて試してください。

実際に貼ってみた。

おお!ここでも箱が回ってる!!

<canvas id="myCanvas"></canvas>

HTMLの方はたったの1行。canvas要素があって#myCanvasというid名がついてますね。
そしてCSSは記述全くなし!すべてはJSに書かれているようです。
JSはこんなです。

 // ページの読み込みを待つ
    window.addEventListener('load', init);

    function init() {

      // サイズを指定
      const width = 960;
      const height = 540;

      // レンダラーを作成
      const renderer = new THREE.WebGLRenderer({
        canvas: document.querySelector('#myCanvas')
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(width, height);

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

      // カメラを作成
      const camera = new THREE.PerspectiveCamera(45, width / height);
      camera.position.set(0, 0, +1000);

      // 箱を作成
      const geometry = new THREE.BoxGeometry(400, 400, 400);
      const material = new THREE.MeshNormalMaterial();
      const box = new THREE.Mesh(geometry, material);
      scene.add(box);

      tick();

      // 毎フレーム時に実行されるループイベントです
      function tick() {
        box.rotation.y += 0.01;
        renderer.render(scene, camera); // レンダリング

        requestAnimationFrame(tick);
      }
    }

…いきなりこれだけ見てもちょっとわかんないですね、はい。分解して見ていきませう。

イベントリスナ

まず最初のところ。コメントには「ページ読み込みを待つ」とある。

 // ページの読み込みを待つ
    window.addEventListener('load', init);

まずwindow。これは「JSの基本-前編」でも書いたように、ブラウザ全体を表す最上位のオブジェクトです。
その次のaddEventListener()は「JSの基本-後編」でも書いたイベントリスナ。「第1引数の時に第2引数する」という意味になる。
で、第1引数はloadで「読み込んだ時」、第2引数のinitは関数名。イベントリスナで読み込む関数名にはカッコが付かないんでしたね。

まとめると「ブラウザを読み込んだ時に、関数initを実行する!」という意味になります。ページが読み込み終わったあとに実行させたいということですね。

関数init

そのあとには関数init()の設定が書かれています。外側を見るとこんな感じ。

//関数init
    function init() {
        処理内容1、2、3〜〜〜
    }

この処理内容の頭にコメントが付いているのでそれをかい摘むとこんなです。

//関数initの中身

    function init() {

      // サイズを指定

      // レンダラーを作成

      // シーンを作成

      // カメラを作成

      // 箱を作成

      // 毎フレーム時に実行されるループイベントです

    }

では、関数initの中身を一つずつ見て行きませう。

サイズを指定

一つ目のコメント「サイズを指定」の中身はこんなです。

      // サイズを指定
      const width = 960;
      const height = 540;

constというのは変数の一種です。お馴染みvar*3、let*4に続く第3の変数。巻き上げもできなければ、再代入(値上書き)もできません。 「変数widthに960、変数heightに540」という値を入れ、この値は絶対的な固定値になります。 そしてこの値は画面のサイズに違いないですな。きっと…。

レンダラーを作成

次「レンダラーを作成」を見てきましょう。WebGLで書いたように「レンダリング」とはデータ情報を画像や映像などに変換することでしたね。

      // レンダラーを作成
      const renderer = new THREE.WebGLRenderer({
        canvas: document.querySelector('#myCanvas')
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(width, height);

またconstで変数renderer(レンダラー)を定義しています。

//変数rendererを定義
const renderer = 値

newで予めThree.jsに用意されたTHREEオブジェクトを複製してます。これによって変数rendererTHREEオブジェクトの「インスタンス」になります。

//THREEオブジェクトのインスタンス作成
      const renderer = new THREE.処理〜

※参考:【JS】newとプロトタイプとクラス - クモのようにコツコツと

そこにWebGLRenderer()というメソッドが続きます。

//THREEオブジェクトのインスタンス作成
      const renderer = new THREE.WebGLRenderer(引数)

WebGLRenderer()メソッドの中には波括弧{}で連想配列が入ってますね。

//THREEオブジェクトのインスタンス作成
      const renderer = new THREE.WebGLRenderer({
      //連想配列
      プロパティ:値
})

canvasプロパティの値はDOMのdocument.querySelector(セレクタ)です。DOMのセレクタはHTMLにある#myCanvasです。

//THREEオブジェクトのインスタンス作成
      const renderer = new THREE.WebGLRenderer({
      canvas: document.querySelector('#myCanvas')
})

これで、rendererインスタンスと#myCanvasセレクタを紐付けているわけです。

その下には インスタンスrenderersetPixelRatio()setSize()の二つのメソッドを実行しています。どちらもTHREEオブジェクトに予め定義されているメソッドのようです。

     //変数rendererのメソッドを実行
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(width, height);

setPixelRatio()の引数はwindow.devicePixelRatio。「WindowプロパティのdevicePixelRatioは現在のディスプレイにおけるCSS解像度と物理解像度の比を返します。」とのこと。

※参考:Window.devicePixelRatio - Web API | MDN

setSize()は引数が2つありwidth, height。「サイズを指定」の変数の値(960、540)が入りますね。

シーンを作成

「シーンを作成」のコードを見ていきましょう

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

一行ですね。変数scenenewTHREEオブジェクトのインスタンスにして、Scene()(シーン)メソッドが入ります。次っ!

カメラを作成

次は「カメラを作成」です。

      // カメラを作成
      const camera = new THREE.PerspectiveCamera(45, width / height);
      camera.position.set(0, 0, +1000);

変数cameraTHREEのインスタンス化し、PerspectiveCamera()メソッドが入ります。第1引数は画角で45度、第2引数は縦横比でwidth / height(960 / 540)。

※参考:写真用語集 - 画角 - キヤノンイメージゲートウェイ

その下では、cameraインスタンスのpositionプロパティのset()メソッド。引数は3つで0, 0, +1000。カメラを置く位置のようです。ここを打ち変えると位置がかわりそうです。

箱を作成

いよいよ立方体の部分ですね。

      // 箱を作成
      const geometry = new THREE.BoxGeometry(400, 400, 400);

まず変数geometryTHREEインスタンス化し、BoxGeometry()メソッドを入れています。このメソッドは箱型のジオメトリ(立体)とのこと。引数は3つで400, 400, 400で、幅、高さ、奥行きですね。

      const material = new THREE.MeshNormalMaterial();

次に変数materialTHREEのインスタンス化してMeshNormalMaterial()メソッドを入れています。MeshNormalMaterial()は予め用意されたマテリアルで、ライトを設定しなくても形状が見えるそうです。

※参考:Three.jsのさまざまなマテリアル - ICS MEDIA

      const box = new THREE.Mesh(geometry, material);

同様に変数boxTHREEのインスタンスのMesh()メソッドが入っています。引数は2つで上のgeometrymaterialが入ります。

      scene.add(box);

最後にsceneインスタンスのadd()メソッドを実行。中の引数にはboxインスタンスを代入し、シーンに箱を追加しています。

ループイベント(アニメ)

その次に唐突にtick()メソッドが書かれていますね。このメソッドはすぐ下に定義されています。

      tick();

     function tick() {
          //処理内容
      }

処理内容の中身はこんなです。

        box.rotation.y += 0.01;

boxインスタンスのrotationプロパティのy0.01を加算。rotationはCSSにもあるように、回転ですね。「箱が0.01回転する」という意味。

       renderer.render(scene, camera); // レンダリング

次にrendererインスタンスのrender()メソッドを実行。引数は2つでscenecameraが入ります。「シーンとカメラをレンダリング」という意味。

        requestAnimationFrame(tick);

最後の処理はrequestAnimationFrame()メソッド。名前の通りフレームアニメーションですね。引数はには関数tick自体が入ります。

まとめると「フレームごとに箱を0.01回転させて、シーンとカメラにレンダリングする」という意味になります。
これで、箱が回転するアニメーションになったわけですね。

あらためて復習

では、もう一回コードを見ていきませう。

 // ページの読み込みを待つ
    window.addEventListener('load', init);

    function init() {

      // サイズを指定
      const width = 960;
      const height = 540;

      // レンダラーを作成
      const renderer = new THREE.WebGLRenderer({
        canvas: document.querySelector('#myCanvas')
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(width, height);

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

      // カメラを作成
      const camera = new THREE.PerspectiveCamera(45, width / height);
      camera.position.set(0, 0, +1000);

      // 箱を作成
      const geometry = new THREE.BoxGeometry(400, 400, 400);
      const material = new THREE.MeshNormalMaterial();
      const box = new THREE.Mesh(geometry, material);
      scene.add(box);

      tick();

      // 毎フレーム時に実行されるループイベントです
      function tick() {
        box.rotation.y += 0.01;
        renderer.render(scene, camera); // レンダリング

        requestAnimationFrame(tick);
      }
    }

ページの読み込みを待つ

  • ページを読み込んだら関数initを実行
  • 関数initを定義

サイズを指定

  • サイズを幅960、高さ540に指定

レンダラーを作成

  • rendererインスタンスで#myCanvasをレンダラーに
  • rendererインスタンスに画像解像度を返す
  • rendererインスタンスにサイズをセット

シーンを作成

  • sceneインスタンスにシーンを作成

カメラを作成

  • cameraインスタンスにPerspectiveCamera()メソッドで画角と縦横比を設定
  • cameraインスタンスの位置をセット

箱を作成

  • geometryインスタンスに高さ400、幅400、奥行き400の箱を作成
  • materialインスタンスにMeshNormalMaterial()でマテリアル設定
  • boxインスタンスにgeometrymaterialの設定値を入れる
  • sceneインスタンスにboxインスタンス(箱)を入れる

ループイベント(アニメ)

  • 関数tickを実行
  • 関数tickを定義
  • boxインスタンスを0.01回転
  • rendererインスタンスにシーンとカメラをレンダリング
  • requestAnimationFrame()メソッドでフレームごとに関数tickを実行

どうでしょうか。最初より読めてきたでしょうか。

まとめ

うーむ、箱をシーンに置いて回転させるだけでも随分とコードがあり、パッと見ただけではなかなか内容がイメージできないので、細かく見ていきました。
それでもライブラリを使わないともっともっと膨大なコードになると思われます。無からコードだけで3Dアニメが出現するのはなんとも嬉しい体験です。

3Dの道は一日にしてならず。引き続きよろしくお願いしまっす!

*1:オラワクワクしてくっぞ!

*2:コンテンツデリバリネットワークの略。ネットワーク上で共有されるWebコンテンツです。嬉しやー

*3:巻き上げ(変数設定より前にも適用可能)ができる変数界の総合格闘技

*4:巻き上げ不可!