LoginSignup
5
6

More than 1 year has passed since last update.

three.js + TypeScript: 3次元空間で立方体を回してみる

Last updated at Posted at 2022-08-01

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ドキュメント

index.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作例でお確かめください。

src:index.ts
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)です。

src:index.ts
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でお確かめください。

src:index.ts
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作例でご確認いただけます。

src:index.ts
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■立方体を水平・垂直軸で回転する

src:index.ts
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プロパティの定め方は同じです。これで立方体は闇に閉ざされます。

src:index.ts
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()メソッドで加えてください。

src:index.ts
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作例で、立方体の陰影が確かめられます。

src:index.ts
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()メソッドで起動してください。立方体に向けカメラを、マウスドラッグで周回できるようになったでしょう。

src:index.ts
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します。

src:index.ts
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

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6