Three.js であそんでみたよ #techtekt Advent Calendar 2022

こんにちは。 エンジニアリング統括部 サービス開発部に所属している鵜飼です🙋🏻‍♀️
普段は React/Next.js でHiPro Directというサービスのフロントエンド開発を行っています。
最近趣味で Three.js と Blender を始めまして、概要や参考にしたサイトなどについて、備忘も兼ねて書いていこうと思います。

Three.js とは、の前に WebGL について

Three.js について調べると、必然的に WebGL という単語を目にします。
なぜなら、Three.js は WebGL を扱いやすくした JavaScript ライブラリだからです。
WebGL とは、MDN web docsによると

WebGL (Web Graphics Library) は、プラグインを使用せずに、互換性のある Web ブラウザー内で高性能のインタラクティブな 3D および 2D グラフィックスをレンダリングするための JavaScript API です。
引用 : https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API

と説明されています。
つまり、Web ブラウザ上で 3D 表現を行うための API が WebGL です。WebGL は、JavaScript と GLSL という言語を組み合わせて記述します。JavaScript はなじみがあるかもしれませんが、 GLSL も扱うとなると学習コストが高く、なかなか手を出しづらいのではないでしょうか。そこで、JavaScript のみで 3D コンテンツを作成できるようにしたライブラリが Three.js です。

Three.js とは

Three.js – JavaScript 3D Library

先ほども書いたように、Three.js は、JavaScript のみで 3D コンテンツを作成できるようにしたライブラリです。 Three.js には多くのクラスが用意されており、それらを用いて球体や立方体を作成したり、3D 空間をブラウザ上に作成したり、3D モデルの座標や角度を調整することができます。 数あるクラスの中でも、頻繁に使用するのが以下の5つです。まずはこれらについて説明していきます。

  • THREE.Camera (カメラ)
  • THREE.Light (ライト)
  • THREE.Mesh (メッシュ)
  • THREE.Scene (シーン)
  • THREE.WebGLRenderer (レンダラー)

THREE.Camera

3D 空間を撮影するカメラ用のクラスです。 カメラには PerspectiveCameraOrthographicCameraの2種類があります。 PerspectiveCameraは遠近感のある投影、OrthographicCameraは平行投影になります。

THREE.Light

3D 空間を照らすライト用のクラスです。 ライトもカメラと同様、さまざまな種類があります。 代表的なものは以下になります。

  • THREE.AmbientLight
    3D空間全体に均一に光を当てる環境光のためのライト。
  • THREE.DirectionalLight
    太陽光のように、特定の方向に向かって平行に照らすライト。
  • THREE.HemisphereLight
    上からの光と下からの光の色をそれぞれ設定できるライト。
  • THREE.PointLight
    電球のように、一点から全方面を小さく照らすライト。
  • THREE.SpotLight
    スポットライトのように、一点から一方向に照らすライト。

こちらのサイトを参考にすると、それぞれのライトの違いがわかりやすいです。

THREE.Mesh

球体や立方体などの3Dオブジェクトを生成するためのクラスです。 簡単な形状であれば、Blender を使わなくても Mesh で表現できます。

THREE.Scene

3Dモデルやカメラやライトを配置する空間です。
add関数を用いて空間にモデルやライトを追加していきます。

THREE.WebGLRenderer

作成したシーンをブラウザ上にレンダリングするためのクラスです。
ブラウザに表示するために必須となります。

簡単に説明しましたが、以上の関係を図に表すとこのようになります。

それでは、実際に作成してみます。

作ってみた

今回作成したものがこちらです。
PC の方はドラッグ&ドロップで動かしたり、マウスホイールで拡大縮小できます。
スマートフォンの方は指で操作してみてください。

See the Pen Untitled by helloiamktn (@helloiamktn) on CodePen.

「Merry Christmas」という文字は Three.js で作成し、ベルや星、キャンディは、Blender で作成したモデルを Three.js を使って読み込んでいます。Three.js では、球体や立方体、円錐などの簡単な形状を作成することはできますが、今回のベルのような複雑な形状を作成することは難しいため、Blender を用いています。

制作過程

大まかな制作過程は以下になります。

  1. シーンの作成
  2. Blender で 3D モデルの作成
  3. Blender で作成したモデルの読み込み
  4. 文字部分の作成
  5. カメラの作成
  6. Renderer の作成
  7. ライトの作成
  8. アニメーションの追加

以下でもう少し具体的に説明していきます。
※コード全文は先ほど載せたCodePenでご確認ください。以下では、ポイントになる部分だけ抜き出して説明しています。

シーンの作成

まずは、3D モデルやカメラなどを配置するためのシーンを作ります。

const scene = new THREE.Scene();

以降、scene.add()でシーンに 3D モデルやカメラなどを追加することができます。

Blender で 3D モデルの作成

今回は、星、ベル、キャンディを Blender で作り、Three.js で読み込むことにしました。
ここでは具体的な作り方は言及しませんが、YouTube にたくさんチュートリアルがあるので、ぜひそれらを見ながらいろいろなモデルを作ってみてください。

blenderで作成した星blenderで作成したベル
星は艶のある質感、ベルはマットな質感にしています

作成したら、glb という形式でエクスポートします。

Blender で作成したモデルの読み込み

Three.js で glb ファイルを読み込むにはGLTFLoaderを使います。
load関数の第二引数のコールバック関数で、モデルのサイズや座標を調整します。

gltfLoader.load("読み込みたいモデルのファイルパス", (glb) => {
    // 読み込んだ3Dモデルのサイズや座標を調整する処理を必要に応じて書く
    glb.scene.scale.set(0.8, 0.8, 0.8);
    glb.scene.position.set(6, -3, 0);
    glb.scene.rotation.set(0, 0, 0.8);
    // シーンに追加
    scene.add(glb.scene);
  }
);  

文字部分の作成

次に、「Merry Christmas」という文字を作成していきます。
文字は Blender ではなく Three.js で行います。
流れは以下のようになります。

  1. FontLoaderを使って任意のフォントファイルを読み込む
  2. TextGeometryの第一引数に表示したい文字列を、第二引数にサイズや厚さなどのオプションを渡してインスタンスを生成
  3. THREE.MeshLambertMaterialを用いて色を設定し、インスタンスを生成
  4. THREE.Meshの第一引数に2.で生成した TextGeometryを、第二引数に3.で生成したMeshLambertMaterialを渡す
  5. 文字の座標調整
  6. シーンに追加

コードはこのようになっています。

    // 1. FontLoader を使って任意のフォントファイルを読み込む
const fontLoader = new FontLoader();
fontLoader.load(
  "filePath",
  (font) => {
    // 以下はオプション
    const options = {
      font: font,
      size: 1.5,
      height: 0.5,
      curveSegments: 5,
      bevelEnabled: true,
      bevelThickness: 0.03,
      bevelSize: 0.02,
      bevelOffset: 0,
      bevelSegments: 4
    };
    // 2. TextGeometry の第一引数に表示したい文字列を、第二引数にサイズや厚さなどのオプションを渡してインスタンスを生成
    const merryGeometry = new TextGeometry("Merry", options).center();
    const christmasGeometry = new TextGeometry("Christmas", options).center();

    // 3. THREE.MeshLambertMaterial を用いて色を設定し、インスタンスを生成
    const textMaterial = new THREE.MeshLambertMaterial({
      color: "#DCDCDC"
    });

    // 4. THREE.Mesh の第一引数に2.で生成した TextGeometry を、第二引数に3.で生成した MeshLambertMaterial を渡す
    const merryText = new THREE.Mesh(merryGeometry, textMaterial);
    const christmasText = new THREE.Mesh(christmasGeometry, textMaterial);

    // 5. 文字の座標調整
    merryText.position.y = 1;
    christmasText.position.y = -1;

    // 6. シーンに追加
    scene.add(merryText, christmasText);
  }
);

カメラの作成

今回はTHREE.PerspectiveCameraを使用します。
PerspectiveCameraは遠近感のあるカメラなので、遠くのものは小さく写り、近くのものは大きく写ります。

const camera = new THREE.PerspectiveCamera(
    75, // FOV ( field of view ) / 視野角。小さいとレンズが望遠に、大きいと広角になる。
    window.innerWidth / window.innerHeight, // アスペクト比
    0.1, // near。これより近いモデルは描画されない。
    1000 // far。これより離れたモデルは描画されない。
);
camera.position.set(0, 0, 10);

Renderer の作成

THREE.WebGLRendererを使ってブラウザ上にレンダリングします。

const renderer = new THREE.WebGLRenderer({
  canvas
});

renderer.setSize(sizes.width, sizes.height);
renderer.setClearColor("#265A3C", 1);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 以下3行は Blender で作成したモデルの色を Three.js 上でも再現するためのおまじない
renderer.physicallyCorrectLights = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;

renderer.render(scene, camera);

しかし、このままでは真っ暗な画面しか映りません。そこでライトが必要になります。

ライトの作成

今回は、環境光であるTHREE.AmbientLightと、太陽光のような役割を持つTHREE.DirectionalLightを使用します。 ライティングにおいては、このように AmbientLight + 任意のライトを使うことが多いです。

const ambientLight = new THREE.AmbientLight(0xffffff, 1);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.position.set(0, 0.3, 3);

scene.add(ambientLight, directionalLight);

これで画面にモデルが表示されるようになります。

アニメーションの追加

今回は、GSAP という GreenSock 社が開発している JavaScript アニメーションライブラリを使いました。
GSAP を使って時間経過ごとに星やベルの角度を変え、回転したり揺れているように見せています。 以下のコードが GSAP を使っている部分です。

    const tl = gsap.timeline({
      repeat: -1,
      defaults: { duration: 2, ease: "easeIn" }
    });

    tl.to(glb.scene.rotation, { z: 1 });
    tl.to(glb.scene.rotation, { z: 0 });

しかし、この処理だけではアニメーションは動作しません。 Three.js でアニメーションを付けたい場合は、以下のように繰り替えしレンダリングをする処理を書くことで、アニメーションを付けることができます。

const animate = () => {
  controls.update();

  renderer.render(scene, camera);
  window.requestAnimationFrame(animate);
};

animate();

lil-gui について

モデルやカメラ、ライトなどの座標や角度を調整したいときに便利なのが lil-gui です。 これを使うことで、ブラウザ上で座標や角度を簡単に操作することができるようになります。 例えば、モデルの座標を調整したい場合は以下のように記述します。

import GUI from "lil-gui";

const gui = new GUI();

gltfLoader.load("読み込みたいモデルのファイルパス", (glb) => {
    gui.add(glb.scene.position, "x", -10, 10);
    gui.add(glb.scene.position, "y", -10, 10);
    gui.add(glb.scene.position, "z", -10, 10);
  }

こうすることで、以下のようにブラウザの右側に座標を操作するための GUI が表示されます。あとは数値を調整し、座標が決定したらその数値をposition.set()の引数に渡します。
datのGIF動画
視覚的に位置を確認できるため、適切な座標や角度になるまでコードを書き換えては確認してまたコードを書き換えて...といった手間がなくなり、楽に座標や角度の数値を求めることができるようになります。

OrbitControls について

今回作成したもののように、マウス操作ができるようにするにはOrbitControlsを使います。
以下の2行を追加するだけなので簡単です。
2行目のenableDampingを true にすることで、少しぬるっとした動きになります。

const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;

以上が制作過程となります。
それでは最後に、Three.js と Blender を始める際に参考にしたサイトを紹介します。

参考にしたサイト

Three.js

Three.js に関しては、基礎的なことが網羅されている以下のサイトがわかりやすかったです。

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

Three.js は、カメラやシーンといった、普段のアプリケーション開発では使わない概念ばかりで、初めは理解が難しかったです。Blender でモデルを作成するようになってからは、用いられる概念が Three.js とほぼ同じということもあり、シーンとカメラの関係性など、Three.js への理解がより深まったような気がします。そのため、Three.js を始める前に、Blender で簡単なものを作ってみるのも良いかもしれません。

Blender

Blender 初心者の方には、以下の YouTube チャンネルがおすすめです。

3D Bibi - YouTube
Yuki's blender school - YouTube

Blender は、ショートカットと機能を覚えるまでが大変だと思うのですが、YouTube を見ながら同じように作っているといつの間にかできることが増えているので、とにかく手を動かしてみることが大切です。 上記の YouTube で基本的な操作に慣れてきたら、海外の方の動画を見るとより作れるものの幅が広がって楽しいです。
注意点として、Blender 関連の YouTube は、作成した 3D モデルをThree.js でブラウザに表示させることが前提ではない場合が多いため、完成後に Blender で 3D モデルの面や頂点を減らすといった最適化が必要になります。

忘れてはならない

アプリケーション開発にも言えることですが、公式ドキュメントも活用しましょう💁🏻‍♀️

three.js docs
Blender 3.3 Reference Manual — Blender Manual

それから、Three.js と Blender で躓いた時は日本語よりも英語の記事を探す方が解決までが早かったので、英語から逃げないことをおすすめします😉

最後に

ここまで読んでくださりありがとうございました!
この記事を読んで、少しでも Three.js と Blender に興味を持っていただけたら幸いです🎄🎅🏻 🔔

鵜飼 琴乃 Kotono Ukai

エンジニアリング統括部 サービス開発部 第1グループ エンジニア

 2019年にアパレル企画営業職からWebエンジニアに転職。Reactを使用した受託開発業務を経験した後、2022年4月にパーソルキャリア入社。現在は HiPro Direct のフロントエンドを担当している。

※2022年12月現在の情報です。