three.jsを触ってみた

Blog

はじめまして。6月に入社したエンジニアの中田です。
入社したばかりの私ですが、この度ブログを書かせていただくことになりました…!
記念すべき1回目のブログは何について書こう…と色々悩んでいたのですが、そういえば最近WebGLについて学び始めたのでそれにしよう!と思い、今回はこのWebGLを簡単に扱うことのできるthree.jsというライブラリについて、実際にデモを作りながら紹介していこうと思います。

three.jsとは

three.jsは、WebGLというブラウザ上で3次元CGを表示(描画)する技術において、↑で説明したようにWebGLをより簡単に扱えるようにしたJavaScriptのライブラリです。
JavaScriptを少ないコードで直感的に書けるようにしたのがjQueryであるように、WebGLを少ないコードで簡単に扱えるようにしたのがthree.js…という感じですね(伝われ)
最近ですと、特にAwwwardsを受賞しているサイトでこのthree.jsを使用した表現が多く見受けられます。(以下はthree.jsを使用しているサイトです。どれも表現力が凄いですね!!)

このthree.js、もちろんWebGLを使用して同様のことをすることも可能ですが、three.jsを使用しない場合だとGLSL(OpenGL Shading Language)という言語を使用して書く必要があり、これがかなり複雑になるので今回はthree.jsを使用してデモを作成することにしました。
three.jsに圧倒的感謝です、、、m(_ _)m

回転する球体をつくってみる

今回は、以下のような回転する球体をthree.jsでつくってみようと思います。

scriptの読み込み

まず最初にthree.jsを読み込みます。今回はCDNで読み込んで使用します。

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r119/three.min.js"></script>

必要なものの準備

three.jsで作成したものを表示するためには、具体的に以下のようなものが必要になります。1つずつ順番に解説していきます。

  • レンダラー・・・WebGLをレンダリングするためのもの。
  • シーン・・・3Dのオブジェクトを表示するための空間。ここに作成したオブジェクト(球体)やライトなどを置いていく。
  • カメラ・・・3D空間を見る視点(どの視点から見るか)。
  • メッシュ・・・球体や直方体を作成するために必要なもの。メッシュを作成するにはジオメトリ(形状)とマテリアル(表面の質感)の2つが必要になる。
  • ライト・・・シーンを照らすライト。

なお、これから書いていくコードは全てページの読み込みが完了次第行うようにするため、initという関数内に記述していき、loadが完了次第initを実行するようにします。
加えて、カメラやリサイズ処理でビューポートの幅と高さが必要になるので、widthとheightでそれぞれのサイズを取得しています。

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

function init(){
    // サイズを取得
    const width = window.innerWidth;
    const height = window.innerHeight;

    //ここに記述していきます
}

レンダラー

レンダラーは以下のように記述します。renderer.setClearColorでは指定した色でcanvasの背景を塗りつぶし、renderer.domElementでcanvas領域にレンダラーを追加しています(HTML上にcanvasタグが作成されます)。

// レンダラーを作成
const renderer = new THREE.WebGLRenderer();
// 指定した色で背景を塗りつぶす
renderer.setClearColor(new THREE.Color("rgb(51, 51, 51)"));
//canvas領域にレンダラーを追加する
document.body.appendChild(renderer.domElement);

シーン

シーンは以下の1行だけでOKです。

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

カメラ

カメラについては以下のように記述します。new THREE.PerspectiveCameraではカメラの視野角とアスペクト比を指定し、camera.position.setではカメラの位置を(x,y,z)の順番で指定しています。
今回はxとyが0なのでカメラが原点を向くようになっています。zについては値が大きくなるほどカメラが離れ、小さくするほど近づきます。

// カメラを作成
const camera = new THREE.PerspectiveCamera(40, width / height);
// カメラの位置をセットする
camera.position.set(0, 0, 1000);

メッシュ

いよいよメッシュの作成です。冒頭に説明したようにメッシュの作成にはジオメトリとマテリアルが必要なので、まず最初にこれらを作成しています。
ジオメトリのnew THREE.SphereGeometryでは(球体の半径,水平方向のセグメント,垂直方向のセグメント)の順番で指定しています。
マテリアルについてはcolorのところで球体を塗りつぶす色を指定し、wireframe: trueで見た目をワイヤーフレームのようにしています。2つが作成できたらこれらをもとにメッシュを作成し、作成したメッシュをscene.add()で3D空間に追加しています。

// ジオメトリを作成
const geometry = new THREE.SphereGeometry(300, 30, 30);

// マテリアルを作成
const material = new THREE.MeshStandardMaterial({
    // 色を指定  
    color: new THREE.Color("rgb(0, 159, 140)"),
    // 見た目をワイヤーフレームにする
    wireframe: true
});

// メッシュ(球体)を作成
const mesh = new THREE.Mesh(geometry, material);

// 3D空間にメッシュを追加
scene.add(mesh);

ライト

最後にライトです。今回はAmbientLight(環境光源)という3D空間全体を均等に照らす光と、directionalLight(平行光源)という特定の方向から照らす光を使用しています。どちらのライトも(色,光の強さ)の順でそれぞれ指定をしています。
作成ができたらメッシュ同様にscene.add()で3D空間に追加します。

// 環境光源を作成
const ambient = new THREE.AmbientLight(0xFFFFFF, 1);
// シーンに追加
scene.add(ambient);

// 平行光源を作成
const directional = new THREE.DirectionalLight(0xFFFFFF, 1);
// 平行光源の位置をセットする
directional.position.set(1, 1, 1);
// シーンに追加
scene.add(directional);

アニメーション

ここまでで球体の作成はできたので、次に回転のアニメーションを加えます。
アニメーションにはrenderという関数を別途作成し、その中でメッシュの回転と、時間の経過でrender関数を呼び出し続けるためにrequestAnimationFrame()を使用しています。また、three.jsの表示結果を更新するためにrenderer.render(scene, camera)でシーンとカメラを更新しています。

function render(){
    // メッシュを回転させる
    mesh.rotation.x += 0.01;
    mesh.rotation.y += 0.01;
    // 時間経過でrender関数(アニメーション)を更新する
    requestAnimationFrame(render);
   //シーンとカメラをレンダリングして画面を更新する
    renderer.render(scene, camera);
}

// render関数を実行する
render();

リサイズ処理

これで球体が作成でき、かつアニメーションもするようになりましたが、できればどのデバイスで見ても球体が綺麗に表示されるようにしたいですよね。ということで最後にリサイズの処理を記述します。

CSS

CSSではbodyに余白を消すためのmargin: 0、オーバースクロール(Macでスクロールした時に、スクロールの勢いで一瞬画面外まで出てしまうアレです!)を防止するためにoverflow: hiddenをそれぞれ指定しています。
margin: 0についてはリセットCSSで既に指定されているケースが多いと思いますが、、

body {
  margin: 0;
  overflow: hidden;
}

JS

JSでは新たにonResizeという関数を作成し、画面のリサイズが発生したらこの関数を実行するようにします。(初期化するために最初に一度実行します)
関数の中身については、renderer.setPixelRatioで表示しているディスプレイに合わせてレンダラーのPixelRatioを更新し、renderer.setSizeでレンダラーのサイズを画面の幅と高さに合わせています。カメラはcamera.aspectでアスペクト比を正したあとで、camera.updateProjectionMatrix()を呼び出してアスペクト比の変更を有効にしています。
これでリサイズしても全画面で、かつ綺麗に表示されるようになりました。

// 画面のリサイズが行われたらonResize関数を実行する
window.addEventListener("resize", onResize);

function onResize() {
    // レンダラーのPixelRatioを更新する
    renderer.setPixelRatio(window.devicePixelRatio);
    // レンダラーのサイズを画面の幅に合わせる
    renderer.setSize(width, height);

    // カメラのアスペクト比を正す
    camera.aspect = width / height;
    // アスペクト比の変更を有効にする
    camera.updateProjectionMatrix();
}

// onResize関数を実行する
onResize();

まとめ

いかがだったでしょうか?three.jsを使用すれば、球体や立方体のようなオブジェクトであれば比較的簡単に作れるので、是非つくってみてくださいね!
私もthree.jsだけでなく、GLSLを使用したオブジェクトも作れるよう精進したいと思います…!
それでは!

参考サイト