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