three.jsは、WebGLを使って3次元表現ができるライブラリです。素のWebGLでは難しくなってしまうコードが、すっきりと短くわかりやすく書けます。本稿は公式サイト「Creating a scene」(シーンの作成)の作例をもとにしたチュートリアル解説です。アプリケーションはモジュールに分け、TypeScriptも採り入れました。もっとも、TypeScriptについては型推論が働くおかげで、明示的な型づけはほとんどしていません。本稿で書くコード例のでき上がりはCodeSandboxに公開しました。
本稿作例のでき上がり
環境をつくる
作業はすぐに始められて、簡単に試せるようにCodeSandboxで進めることにします。テンプレートはVanilla TypeScriptを用いました。依存(Dependencies)には、threeと@types/three(TypeScript用)を加えてください。
ローカル環境で作業したい場合には、「Three.js を TypeScript で使う方法 ~ 初めての Three.js 入門編 ~」を参考にされるとよいでしょう。Viteビルドシステムを使っており、ファイルの構成や名前が少し異なるので、そこは読み替えてください。ご参考までに、本稿末尾にGitHubリポジトリの公開リンクを添えました。
まず、メインのHTMLドキュメントindex.htmlは、つぎのように書き替えます。
コード001■メインのHTMLドキュメント
<html>
<head>
<title>Three.js + TypeScript: Simple example</title>
<meta charset="UTF-8" />
</head>
<body>
<canvas id="canvas"></canvas>
<script src="src/index.ts"></script>
</body>
</html>
3次元空間をつくる
three.jsでまずつくるのは、つぎの3つのオブジェクトです。
- シーン(scene)
- 3次元空間を表し、3Dオブジェクトやライトが加えられる。
- カメラ(camera)
- 3次元空間を映し、レンダラーに送って画面に投影する。
- レンダラー(renderer)
- カメラから受け取った画像を、画面の再描画のたびに更新する。
シーンのコンストラクタはScene()です。カメラにはいくつか種類があります。今回使うのは、標準的なPerspectiveCameraです。Perspectiveは透視投影(perspective projection)を意味し、対象物を人が見たのと近い表現になります。4つの引数については、このあと解説しましょう(構文001)。WebGLによる描画を担うのがWebGLRendererです。引数オブジェクトのcanvasプロパティには、<canvas>要素の参照を渡してください。
構文001■PerspectiveCamera()コンストラクタ
PerspectiveCamera(fov: Number, aspect: Number, near: Number, far: Number)
-
fov: 視野(field of view)またはカメラの用語で画角。写される光景の範囲を角度(度数)で示す。数字が大きいと広角で、写る範囲は広くなるかわり、対象物は相対的に小さくなる。小さい角度は望遠に近づき、対象物が相対的に大きく写る。 -
aspect: アスペクト比で、描画する矩形領域の幅/高さ。矩形領域の比と合わなければ、画像の水平・垂直比が歪む。 -
near: オブジェクトを描画するもっとも近い距離。これより近くのオブジェクトは画面に描かれない。 -
far: オブジェクトを描画するもっとも遠い距離。これより遠くのオブジェクトは画面に描かれない。
距離や長さは、1を基本とした数値で表します。カメラの設定でオブジェクトは大きくも小さくも描かれるので、単位は気にせず比率と捉えて構いません。
メインのTypeScriptモジュールsrc:index.tsに、シーンとカメラおよびレンダラーをつぎのように定めましょう。これで、3次元空間のいわば舞台ができ上がりました。もっとも、映し出すオブジェクトが何もありませんので、ただの暗闇です。なお、src/styles.cssには最低限のスタイルを書き加えました。あとに掲げるサンプル001のCodeSandbox作例でお確かめください。
import * as THREE from 'three';
import './styles.css';
function init() {
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const size = { width: window.innerWidth, height: window.innerHeight };
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
45,
size.width / size.height,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(size.width, size.height);
}
init();
3次元空間に立方体を加える
では、3次元空間に3Dオブジェクトをつくって加えましょう。3Dオブジェクトには形状の定めとなるジオメトリと、表面素材を決めるマテリアルがなければなりません。そして、ジオメトリにマテリアルを適用して3Dオブジェクトにつくり上げるのがメッシュです。
立方体はBoxGeometryでつくります。3つの引数は、幅と高さ、そして奥行きです。マテリアルは、とりあえず扱いやすいMeshBasicMaterialを用いましょう。引数オブジェクトのcolorプロパティには、CSSと同じかたちでカラー値を与えます。16進数の場合は、数値でも構いません。このふたつをMeshに引数として渡せば、3Dオブジェクトが返されます。シーンにはadd()メソッドで加えてください。そして、カメラの位置を定めるのはpositionプロパティです。z座標は手前が正になります。レンダラーのrender()メソッドで描画するのを忘れてはいけません。引数はシーン(scene)とカメラ(camera)です。
function init() {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 'aqua' });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
renderer.render(scene, camera);
}
これで、3次元空間の原点(0, 0, 0)を中心に(デフォルト)立方体が置かれたはずです。もっとも、カメラに正面向きなので、立方体か正方形平面か区別がつきません。立方体(cube)のrotationプロパティを使って、x軸とy軸で少し回転しましょう(角度の単位はラジアンです)。結果は、CodeSandboxに公開した以下のサンプル001でお確かめください。
function init() {
cube.rotation.x += 0.5;
cube.rotation.y += 0.5;
renderer.render(scene, camera);
}
サンプル001■Three.js + TypeScript: Simple example 01
立方体に回転のアニメーションを加える
Window.requestAnimationFrame()メソッドで、立方体に回転のアニメーションを加えましょう。描画ごとの立方体の回転角は少なめにします。requestAnimationFrame()の呼び出しは、再描画のたび繰り返すことにお気をつけください。モジュールsrc:index.tsの記述全体を、いったんコード002にまとめました。動きは、以下のサンプル002のCodeSandbox作例でご確認いただけます。
function init() {
const animate = () => {
requestAnimationFrame(animate);
// cube.rotation.x += 0.5;
cube.rotation.x += 0.01;
// cube.rotation.y += 0.5;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
}
コード002■立方体を水平・垂直軸で回転する
import * as THREE from 'three';
import './styles.css';
function init() {
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const size = { width: window.innerWidth, height: window.innerHeight };
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
45,
size.width / size.height,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(size.width, size.height);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 'aqua' });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
const animate = () => {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
}
init();
サンプル002■three.js + TypeScript: Simple example 02
立方体の面に陰影を与える
立方体の面の表現に、気になるところがあるかもしれません。面に陰影がないことです。これは、用いたマテリアルMeshBasicMaterialに原因があります。3次元空間は何もしなければ本来暗闇です。けれど、MeshBasicMaterialに与えたカラーは、光がなくても見える、いわば発光する性質をもっています。カラーを光として放っているのですから、陰影のつきようがありません。
そこで、マテリアルをMeshPhongMaterialに差し替えます。表面は鏡面反射する光沢があるものの、カラーは発光しません。引数オブジェクトのcolorプロパティの定め方は同じです。これで立方体は闇に閉ざされます。
function init() {
// const material = new THREE.MeshBasicMaterial({ color: 'aqua' });
const material = new THREE.MeshPhongMaterial({ color: 'aqua' });
}
光源として加えるのは、まずDirectionalLightです。太陽光のようにひとつの方向から、光が平行に差し込みます(「平行光源」)。引数には与えるのはカラーです。white(0xffffff)では陰影のコントラストが少し強いので、明るさを抑えました。光が差してくる方向を定めるのはライト(directionalLight)のposition.set()メソッドです。原点に向かって差し込みます。引数値(1, 1, 1)は、原点(0, 0, 0)と立方体の初期位置の前面右上の座標(0.5, 0.5, 0.5)を結んだ直線の方向です(右斜め上手前)。光源(directionalLight)は、シーン(scene)にadd()メソッドで加えてください。
function init() {
const directionalLight = new THREE.DirectionalLight('gray');
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
}
なお、光源の明るさは第2引数で、1を100%とした比率で調整することもできます。
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.9);
ただ、このままですと、光の当たらない面が黒く潰れます。そこで、3次元空間内を等しく照らすために使うのがAmbientLight(環境光)です。こちらも、white(0xffffff)では陰影が飛んでしまうので、明るさを抑えました。以下のサンプル003のCodeSandbox作例で、立方体の陰影が確かめられます。
function init() {
const ambientLight = new THREE.AmbientLight('gray');
scene.add(ambientLight);
}
サンプル003■three.js + TypeScript: Simple example 03
OrbitControlsで簡単にカメラを制御する
OrbitControlsは、マウス操作で簡単にカメラを制御するためのクラスです。一定の座標を中心にして、マウスドラッグでカメラを周回させられます。
CodeSandboxの依存(Dependencies)には、three-orbitcontrols-tsを加えてください。OrbitControls()コンストラクタの引数は、カメラ(camera)とWebGLが描くDOM要素です。レンダラーのインスタンス(renderer)作成時に与えたDOM要素は、domElementプロパティで取り出せます。コントロール(controls)のtarget.set()メソッドの引数が、カメラを動かす中心座標です。コントロールはupdate()メソッドで起動してください。立方体に向けカメラを、マウスドラッグで周回できるようになったでしょう。
import { OrbitControls } from 'three-orbitcontrols-ts';
function init() {
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();
}
なお、ローカルに開発環境を構築した場合、OrbitControlsはつぎのパスからimportします。
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
実は、OrbitControlsでできるカメラのマウス操作は、あとふたつあります。
- 右クリックでドラッグ: カメラのパン。カメラは固定したまま、フレーミングを水平・垂直方向に移動させる。
- マウスホイール: カメラのズーミング。カメラを向けた方向に、映像を拡大・縮小する。
残念ながらCodeSandbox(サンプル004)では、つぎのような警告が出て操作できません。
WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.
WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.
サンプル004■three.js + TypeScript: Simple example 04
ローカルの開発環境であれば、上記ふたつの操作も可能です。Viteで構築したGitHubのリポジトリをご参考までに公開します。冒頭に述べたとおり、ファイルの構成や名前が少し異なるので、その点はお気をつけください。
GitHub: FumioNonaka/three-typescript