Three.jsを使ってみよう!!~もりけん塾勉強会~

Three.jsを使ってみよう!! #もりけん塾勉強会Three.js

こんにちは、たかです。

現役フロントエンジニアである森田先生が運営するもりけん塾で勉強をしています。

もりけん塾では月に一度勉強会があります。
塾生や先生が様々なこと発表しています!

先日、その勉強会でThree.jsについてお話させて頂いたのでその内容をこちらにも残していきます。

Three.jsを使ってみよう!!

Three.jsは非常に便利なライブラリで、カンタンにWebGLを扱えるようになります。

WebGLとは、MDNによると

WebGLは使い性ブラウザで、プラグインを使用することでHTML<canvas>内で3DグラフィックスをダウンロードするOpenGL ESWebGLのプログラムはJavaScriptであり、コードと、コンピューターのグラフィックスプロセッシングユニット(GPU)で実行する分コード(シェーダーコード)で設定されます。WebGLコンピュータは他のHTML要素とプログラムされた、他のプログラムダクロやページのシャドウと合成された。

WebGLができれば、それだけでブラウザーに3Dグラフィクスを表示できるということです。
ただ、生のWebGLを扱うのは難易度が高く、
3Dを2Dで表現するための座標変換やGLSLというシェーディング言語?を使う必要がある。

そこで、Three.jsを使っていこうということです。

Three.jsで必要な4要素

最低限、以下の要素が必要になります。

  • renderer:計算し、描画する
  • scene:描写するためのステージ(空間)
  • camera:オブジェクトを写す
  • mesh:オブジェクト(被写体)

図にするとこのようなイメージです。

ThreeJS基本構造

Canvas要素内に、sceneと呼ばれる3D空間を描くステージのようなものを準備します。
そして、そのscene内にcameraとmeshと呼ばれるオブジェクトを準備します。
そのcameraで映し出されたものをrendererが計算し、PCやスマホに表示するという仕組みです。

実際の手順を紹介します。
1. Three.jsのCDNを読み込む

<script src="<https://unpkg.com/three@0.131.3/build/three.min.js>"></script>

2. Body内にcanvas要素を追加する

<body>
 <canvas id="myCanvas"></canvas>
 <script src="main.js"></script>
</body>

3. renderer, scene, camera, meshを作成する

// サイズを指定
const width = innerWidth;
const height = innerHeight;
// レンダラーを作成
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector("#myCanvas")
});
renderer.setSize(width, height);
// シーンを作成
const scene = new THREE.Scene();
// カメラを作成
// new THREE.PerspectiveCamera(視野角, アスペクト比, near, far)
const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
camera.position.set(50, 50, 50); //カメラの初期位置
camera.lookAt(new THREE.Vector3(0, 0, 0)); //カメラの方向

cameraの種類

  • PerspectiveCamera:遠近感が反映されるカメラ
  • OrthographicCamera:平行投影(遠近感が反映されない)されるカメラ
// メッシュを作成
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(20, 20, 20),
new THREE.MeshNormalMaterial()
);
4. レンダリングする
// レンダリング
renderer.render(scene, camera);

ジオメトリの種類

ジオメトリの種類をいくつか紹介します。

  • BoxGeometry:直方体
  • SphereGeometry:球体
  • PlaneGeometry:平面
  • ConeGeometry:三角錐
  • TorusGeometry:ドーナツ状
  • IcosahedronGeometry:二十面体

これらをmeshに指定してあげるだけで好みの形を表現できます
その他にもあるので公式ドキュメントを覗いてみてください。

マテリアルの種類

マテリアルもいくつか紹介します。

  • MeshNormalMaterial:RGBカラーで描かれる
  • MeshBasicMaterial:指定した色で全く質感のなく描かれる
  • MeshToonMaterial:アニメっぽい質感で描かれる
  • MeshLambertMaterial:マットな質感で描かれる
  • MeshPhongMaterial:光沢感のある質感で描かれる
  • MeshStandardMaterial:光の反射や散乱など現実の物理現象を再現してくれる

用途によって、使い分けがされていると思いますが、リアリティを追求するときにはStandardを使用すると良いようです。
これらの他にもあるので、こちらも公式ドキュメント覗いてみてください。

公式ドキュメント上で、プロパティをいじって見てどのように表示されるか確かめることもできるので確認してみてください。

ライティング(光源)

マテリアルには、光源が不要なものもあれば、必要なものもあります。
マテリアル選択時には、よく確認してみてください。
光源には、以下のような種類があります。

  • AmbientLight:(環境光源)指定したcanvas全体に均等に光を当てることができる
  • DirectionalLight:(平行光源)特定の方向に平行に光が放たれます
  • HemisohereLight:(半球光源)上からの光と下からの光の色を分けられる
  • PointLight:(点光源)一点から放射される、電球のような光になる
  • SpotLight:(スポットライト)一点から一方向に円錐状に光が放たれる

演出したい物によって使い分けることができれば、表現の幅が広がりそうです。

ここまで、理解できれば簡単な3Dオブジェクトを表示することはできるようになります。
こちらは、20面体をNormalMaterialで表示させています。

アニメーションをつける

requestAnimationFlame( )というメソッドを使う。引数に繰り返し行う関数を渡すことで毎フレーム毎に実行できる。
例えば、作成したメッシュを回転させたかった場合、

tick();
function tick() {
  //メッシュを回転させる
  mesh.rotation.y += 0.01;
  //レンダリング
  renderer.render(scene, camera);
  //毎フレームごとにtick()を実行する
  requestAnimationFlame(tick);
}

これを先程のコードに反映させるとこのように動きます。

カメラの制御方法

カメラの制御方法を3つ紹介します。

  • マウス操作(ドラッグ&スクロール)と連動
  • マウス位置と連動
  • 自動回転

過去にこちらについてまとめた記事もあります。

マウス操作(ドラッグ&スクロール)と連動

1. OrbitControl.jsのCDNを読み込む

Three.jsライブラリにはこの機能は含まれないので別途読み込む


<script src="<https://unpkg.com/three@0.131.3/examples/js/controls/OrbitControls.js>"></script>
2. カメラコントローラーを作る
const controls = new THREE.OrbitControls(camera, document.body);
3. 
プロパティ紹介
周回軌道の動きを滑らかにする
controls.enableDamping = true;
controls.dampingFactor = 0.2;
ズーム速度調整
controls.enableZoom = true;
controls.zoomSpeed = 5;
回転スピード調整
controls.enableRotate = true;
controls.rotateSpeed = 5;
パン操作調整
controls.enablePen = true;
controls.panSpeed = 5;
カメラ距離の最大最小を設定
controls.minDistance = 30;
controls.maxDistance = 300;
カメラをアップデート

enableDamping, dampingFactorプロパティを設定したときには、requestAnimationFrame内でカメラコントローラーをアップデートする必要がある。

tick();
function tick() {
  //メッシュを回転させる
  mesh.rotation.y += 0.01;
  //カメラコントローラーをアップデート
  controls.update();
  //レンダリング
  renderer.render(scene, camera);
  //毎フレームごとにtick()を実行する
  requestAnimationFlame(tick);
}

操作方法は以下の3通りです。

  • 左クリックでドラッグで周回移動
  • 右クリックでドラックで視点移動
  • スクロールでズーム

マウス位置と連動

マウスが動いた時にそのX座標を取得する
window.addEventListener("mousemove", (event) => {
  mouseLocationX = event.pageX;
});
マウス位置を3D座標に換算する
//マウスのX座標がwindow幅の何%にあるか調べて360°をかけることで3Dに置換
const targetLocation = (mouseLocationX / window.innerWidth) * 360;
カメラをマウス位置に連動させる

tick();
//フレームごとにするアクションをつくる
function tick() {
  //マウスのX座標がwindow幅の何%にあるか調べて360°をかけることで3Dに置換
  const targetLocation = (mouseLocationX / window.innerWidth) * 360;
  //(目標値-現在地)を計算し、ゆっくりと遷移させる
  location += (targetLocation - location) * 0.02;
  // 角度に応じてカメラの位置を設定
  camera.position.x = Math.cos(THREE.Math.degToRad(location)) * 750;
  camera.position.z = Math.sin(THREE.Math.degToRad(location)) * 750;
  // 原点方向を見つめる
  camera.lookAt(new THREE.Vector3(0, 0, 0));
  //レンダリングする
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}

実際のコードはこちら

カメラを自動で回転させる

カメラを周回させるためには三角関数を使用します。
数学の復習なので、思い出してみてください。

難しければ、横方向がcosθ、縦方向がsinθと覚えてしまっても良いかもしれません。
まずは、角度を入れる変数を用意し、フレームごとに値が増えるようにします。

let deg = 0;
.
.
.
deg += 0.5;

続いて、ラジアンに変換します。
三角関数には、30°や45°などの度数法で表されるものではなく、π/6やπ/4などの弧度法で表されるものを利用します。

const radian = (deg * Math.PI) / 180;

最後に、この値と三角関数を利用して、カメラのpositionを決定します。

camera.position.x = 100 * Math.cos(radian);
camera.position.z = 100 * Math.sin(radian);

これで、カメラがフレームごとに位置を変え、オブジェクトの周りをくるくると回るようになりました。
実際のコードはこちらです。

また、Three.jsにはラジアンへの変換をしてくれる便利なものも用意されています。

THREE.Math.degToRad(deg)

こちら利用することで、度数をラジアンに変換できます。

//degToRad を利用してラジアンに変換する方法
camera.position.x = 100 * Math.cos(THREE.Math.degToRad(deg));
camera.position.z = 100 * Math.sin(THREE.Math.degToRad(deg));

画面のリサイズ処理

3Dオブジェクトが表示された後に、ユーザーがウィンドウ幅を変更したときに対応できるように設定します。

はじめに、リサイズイベントが発生したときに、関数が動くようにします。

window.addEventListener("resize", onResize);

続いて、呼び出した関数の中身を作ります。
現在の画面サイズを取得します。

width = window.innerWidth;
height = window.innerHeight;

レンダラーのピクセル比とサイズを更新します。
デスクトップでは、ディスプレイごとにピクセル比が異なる可能性があります。

renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);

カメラのアスペクト比も狂うので、更新します。

camera.aspect = width / height; camera.updateProjectionMatrix();

まとめると以下の通り、

// リサイズイベント発生時に実行
window.addEventListener("resize", onResize);

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

  // レンダラーのサイズを調整する
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);

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

これで、ウィンドウ幅が変更されても画面いっぱいに表示することが出来ます。

指定の画像をオブジェクトにつける

画像を準備しておけば、それをオブジェクトの表面に表示させることが出来ます。
今回は、こちらから地球と月のテクスチャ画像をダウンロードしました。

画像を読み込む

THREE.TextureLoader(); を使用して画像を読み込みます。

const loader = new THREE.TextureLoader();
const earthTexture = loader.load("./img/earthmap1k.jpg");
const moonTexture = loader.load("./img/moonmap1k.jpg");

マテリアルで読み込んだ画像を指定する

マテリアルのmapプロパティを使用して、読み込んだ画像を指定します。

//地球を作成
const earth = new THREE.Mesh(
  new THREE.SphereGeometry(80, 32, 32),
  new THREE.MeshStandardMaterial({ map: earthTexture })
);
// 月を作成
const moon = new THREE.Mesh(
  new THREE.SphereGeometry(20, 32, 32),
  new THREE.MeshStandardMaterial({ map: moonTexture })
);

この2ステップで画像をオブジェクトに表示させることが出来ます。色々と試してみてください。

上で紹介したカメラがオブジェクトの周りを周回する処理を同じように
三角関数とdegtoRadを使用すれば、月が地球の周りを公転させることが出来ます。
コードはこちら。

最後に

最後まで読んでいただき、ありがとうございます。

もりけん塾の勉強会で紹介した内容(説明できなかった部分も含めて)を書きました。

WebGLは触っていてとてもワクワクします、色々なものを作ってみたいと心から思わされてます。

小さいゲーム空間とか作れたら子どもも喜びそうだけど、プロが作るものと比較したら負けそうですね…。

引き続き、WebGLについても学んでいき、ブログにまとめていこうと思います。

※現在、もりけん塾の森田先生に学習のフォローをしていただいております。

もりけん塾では JavaScript の課題に取り組んでいます!

もりけん塾Twitterはこちら(@terrace_tech)

先生のブログはこちら↓↓↓

コメント

タイトルとURLをコピーしました