どうも!かけちまるです!
Webサイトで容易に3D表現を実装できるThree.js。
初心者向けに概念と簡単な制作物を作成していきます。
本記事の制作物だけでは、実務で使えるレベルになれるわけではありません。
ですが、概要を理解することは大事なので一読しても良いかもしれません。
この記事では、
がわかります。
Three.jsは、Web上で3D表現を容易にできるようにするライブラリです。JavaScriptで書きます。
個人・商用でも無償で利用できます。
WebGLだけで3D表現をしようとすると凄まじい量のコードを書く必要があるのでThree.jsは難しいのに簡単だと言われているのだと思います。
Three.jsでWeb上に3Dオブジェクトを描画するために用意しなければいけないものがあります。
それは、シーン・カメラ・レンダラー・ジオメトリ・マテリアル・メッシュ・ライトです。
このシーン・カメラ・レンダラー・ジオメトリ・マテリアル・メッシュ・ライトを現実世界に例えるとこんな感じです。
1つずつ説明していきます。
シーン・カメラ・レンダラーの用意
それぞれ現実世界に例えると、
というような感じ
ステージをカメラで写し、YouTubeでライブ配信してブラウザに表示するイメージです。
ジオメトリ・マテリアル・メッシュを用意
ジオメトリ・マテリアル・メッシュは今回の立方体にあたるものです。
ジオメトリ・マテリアル・メッシュが何なのかというと
というところでしょうか。
※画像はわかりやすいように球体になっています。
なので、メッシュをステージに置くことでライブ配信でブラウザに映し出すことができそうですが、まだステージが真っ暗なので映りません。
ライトを用意
ライトは、ステージを照らす役割をします。
マテリアルの設定によっては不要です。
ライト → 照明のイメージで大丈夫です。
照明でステージを照らすことでメッシュが見えるようになりました。
まとめると
そんな感じで3Dオブジェクトを映せることがわかりました。
Three.jsの基本とされている立方体をWeb上に表現してみましょう。
手順としては3D表現で必要な概念とほぼ同じです。
ディレクトリ構成は次のようにします。
ディレクトリ構成コピーindex.html css/ -style.css js/ -main.js
CDNでThree.jsを読み込む
CDNでは、モジュール版が推奨なのでモジュール版を読み込みます。
index.htmlへ次のように記述します。hand
タグ内で良いでしょう。
index.htmlコピー<script async src="https://unpkg.com/es-module-shims@1.5.8/dist/es-module-shims.js"></script> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.142.0/build/three.module.js" } } </script> <script src="./js/main.js" type="module"></script>
読み込み方につまづいたらこちらの記事が参考になりそうです。
HTMLとCSSの準備
一応環境を合わせるためにHTMLとCSSを載せときます。
大したことはしていません。
body
タグ内にcanvas
タグを入れるのでbody
タグは書いておいてください。
index.htmlコピー<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" href="./css/style.css"> <script async src="https://unpkg.com/es-module-shims@1.5.8/dist/es-module-shims.js"></script> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.142.0/build/three.module.js" } } </script> <script src="./js/main.js" type="module"></script> </head> <body></body> </html>
style.cssコピー* { margin: 0; padding: 0; box-sizing: border-box; }
シーン・カメラ・レンダラーを用意する
コードにコメントで説明が書いてあるので軽く解説します。
シーンの用意は1行で一瞬ですね。(7行目)
カメラは、PerspectveCamera
を使います。(10行目)
設定は、PerspectveCamera(視野角, アスペクト比, near, far)で設定します。
カメラで設定した範囲が映されることになります。
※カメラやライト・ジオメトリ・マテリアルなどは種類がたくさんあるので公式サイトなどで調べてみてください。
今回、near
を0.1にしているので青い部分が手前にある感じです。
レンダラーは、ブラウザのサイズに合わせてbody
タグ内に挿入しています。(20、21行目)
最後にレンダラーにシーンとカメラをレンダラーに認識させる感じです。(24行目)
main.jsコピーimport * as THREE from 'three'; /** * シーン・カメラ・レンダラーを用意する */ // シーンを用意 const scene = new THREE.Scene(); // カメラを用意 const camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.z = 5; // カメラをz軸方向に5ずらす(後ろにずらす) // レンダラーを用意 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // レンダラーをwindowのサイズと合わせる document.body.appendChild(renderer.domElement); // レンダラー(canvas)をbody内に挿入 // シーンとカメラをレンダラーに読み込ませる(最後に実行するようになっていること) renderer.render(scene, camera);
これでブラウザ全体が真っ黒になったらOKです。
ジオメトリ+マテリアル=メッシュを用意する
今回は立方体を描画するのでジオメトリはBoxGeometry
を使います。(8行目)
設定は、BoxGeometry(幅, 高さ, 奥行き)で設定します。
マテリアルは、光沢感のないマットな質感を表現できるMeshLambertMaterial
を使います。(11行目)
色は青っぽい色にします。(12行目)
色の指定方法ですが、0x3da8e6
の0x
部分は#
と同じ意味合いです。
そして、ジオメトリとマテリアルを合わせてメッシュ化し、シーンに読み込ませます。(16、17行目)
main.jsコピー・・・ document.body.appendChild(renderer.domElement); // レンダラー(canvas)をbody内に挿入 /** * ジオメトリ+マテリアル=メッシュを用意する */ // ジオメトリを用意 const boxGeometry = new THREE.BoxGeometry(1, 1, 1); // (幅, 高さ, 奥行き) // マテリアルを用意 const lambertMaterial = new THREE.MeshLambertMaterial({ color: 0x3da8e6, // 色を設定 }); // ジオメトリ+マテリアル=メッシュ化 const boxMesh = new THREE.Mesh(boxGeometry, lambertMaterial); scene.add(boxMesh); // メッシュをsceneにaddする // シーンとカメラをレンダラーに読み込ませる(最後に実行するようになっていること) renderer.render(scene, camera);
ライトを用意する
ライトは、DirectionalLight
を使います。(9行目)DirectionalLight
は、特定方向に光を照らすやつです。
引数では、光の色と光の強さを設定しています。
それから、ライトの位置を調整します。(10行目)
今回は、左斜め上くらいからライトを当てるようにしています。
最後にライトをシーンに読み込ませます。(11行目)
main.jsコピー・・・ // ジオメトリ+マテリアル=メッシュ化 const boxMesh = new THREE.Mesh(boxGeometry, lambertMaterial); scene.add(boxMesh); /** * ライトを追加する */ const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // (光の色, 光の強さ) directionalLight.position.set(-1, 2, 4); // (x, y, z) scene.add(directionalLight); // ライトをsceneにaddする // シーンとカメラをレンダラーに読み込ませる(最後に実行するようになっていること) renderer.render(scene, camera);
完成
完成はこんな感じです。
コードの全体は以下です。
main.jsコピーimport * as THREE from 'three'; /** * シーン・カメラ・レンダラーを用意する */ // シーンを用意 const scene = new THREE.Scene(); // カメラを用意 const camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.z = 5; // カメラをz軸方向に5ずらす(後ろにずらす) // レンダラーを用意 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // レンダラーをwindowのサイズと合わせる document.body.appendChild(renderer.domElement); // レンダラー(canvas)をbody内に挿入 /** * ジオメトリ+マテリアル=メッシュを用意する */ // ジオメトリを用意 const boxGeometry = new THREE.BoxGeometry(1, 1, 1); // (幅, 高さ, 奥行き) // マテリアルを用意 const lambertMaterial = new THREE.MeshLambertMaterial({ color: 0x3da8e6, // 色を設定 }); // ジオメトリ+マテリアル=メッシュ化 const boxMesh = new THREE.Mesh(boxGeometry, lambertMaterial); scene.add(boxMesh); // メッシュをsceneにaddする /** * ライトを追加する */ const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // (光の色, 光の強さ) directionalLight.position.set(-1, 2, 4); // (x, y, z) scene.add(directionalLight); // ライトをsceneにaddする // シーンとカメラをレンダラーに読み込ませる(最後に実行するようになっていること) renderer.render(scene, camera);
上記の立方体を描画してみるまで実装できたことを前提として機能を追加で実装してみましょう。
アニメーションを実行するためにanimate
関数を用意します。(5行目)
animate
関数内にrenderer.render(scene, camera);
を移します。(7行目)
そして、メッシュをx
とy
方向に傾ける記述を書きます。(9,10行目)
最後に、フレーム毎にanimate
関数を実行する設定とanimate
関数を実行することでアニメーションを実行することができます。(12,14行目)
main.jsコピー・・・ // シーンとカメラをレンダラーに読み込ませる(最後に実行するようになっていること) // renderer.render(scene, camera); function animate() { // シーンとカメラをレンダラーに読み込ませる(最後に実行するようになっていること) renderer.render(scene, camera); boxMesh.rotation.x += 0.01; boxMesh.rotation.y += 0.01; requestAnimationFrame(animate); // 引数内の関数をフレーム単位で読み込む } animate();
ここまでだとブラウザをリサイズするとおかしな感じになるのでこれに対応していきます。
まず、リサイズした際に何を更新した方が良いかというと
だと思います。
ポイントとしては、カメラを更新した際はcamera.updateProjectionMatrix();
を呼び出す必要があるということです。
main.jsコピー・・・ requestAnimationFrame(animete); // 引数内の関数をフレーム単位で読み込む } animete(); // ブラウザのリサイズに対応させる window.addEventListener('resize', onWindowResize); function onWindowResize() { renderer.setSize(window.innerWidth, window.innerHeight); // レンダラーのサイズをれサイズ対応 camera.aspect = window.innerWidth / window.innerHeight; // カメラのアスペクト比をリサイズ対応 camera.updateProjectionMatrix(); // カメラを更新した際に呼び出す必要がある }
立方体の境目が粗くなっている場合はsetPixelRatio(window.devicePixelRatio)
でデバイスのピクセル比を設定します。
main.jsコピー・・・ // レンダラーを用意 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // レンダラーをwindowのサイズと合わせる renderer.setPixelRatio(window.devicePixelRatio); // デバイスのピクセル比を設定 document.body.appendChild(renderer.domElement); // レンダラー(canvas)をbody内に挿入 ・・・
おわり
フィードバックを送信
記事についてのフィードバックはTwitterかお問い合わせフォームから受け付けております。