サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

three.js入門 04: マテリアルを使う


「three.js入門」03「ジオメトリーを使う」は、幾何学的な情報であるジオメトリーをいくつかの基本的な立体でご紹介しました。本稿は立体の表面の見た目を決める素材となるマテリアルについてご説明します。

01 立体をワイヤーフレームで描く

「three.js入門」03のサンプル003「three.js r85: Rotating a icosahedron」に手を加えるかたちで進めます。マテリアルはMeshNormalMaterialからMeshPhongMaterialに差し替えます。そうすると、光を当てなければ暗闇で見えません(「three.js入門」02の項01「立方体の素材をフォンマテリアルに差し替える」参照)。そこで、WebGLRenderer()コンストラクタの引数にオブジェクトを与え、alphaプロパティはtrueとします。すると、闇は拭われて立体が姿を現すのです。もっとも、このままでは立体は黒い塊にしかなりません。MeshPhongMaterial()コンストラクタも引数オブジェクトで、wireframeプロパティをtrueにすると、立体がワイヤーフレームで描かれます(図001)。コードはサンプル001としてjsdo.itに掲げました。


function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer({alpha: true});

}
function createMesh(radius) {

	// var material = new THREE.MeshNormalMaterial();
	var material = new THREE.MeshPhongMaterial({wireframe: true});

}

02 フォンマテリアルをライトで照らす

改めて、光を定めましょう。WebGLRenderer()コンストラクタは引数なしに戻します。MeshPhongMaterial()コンストラクタも、ワイヤーフレームはやめて、colorプロパティのカラー値を関数(createMesh())の引数(color)から与えるようにします。加える光源はふたつです。ひとつは、平行光源をDirectionalLight()コンストラクタでつくります(「three.js入門」02の項02「シーンにライトを加える」参照)。もうひとつは、電球のように光が広がる点光源(point light)です(図002)。遠ざかるほど、明るさは弱まります。コンストラクタはPointLight()で、第1引数に渡すのはDirectionalLight()と同じくカラー値です。


function init() {

	// mesh = createMesh(side);
	mesh = createMesh(side, 0x0000FF);

	scene.add(createLight(0xFFFFFF, 10, 0, 25));
	scene.add(createPointLight(0xFFFFFF, -50, 50, 50));

}
function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer();  // {alpha: true});

}
// function createMesh(radius) {
function createMesh(radius, color) {

	var material = new THREE.MeshPhongMaterial({color: color});  // , wireframe: true});

}
function createLight(color, x, y, z) {
	var light = new THREE.DirectionalLight(color);
	light.position.set(x, y, z);
	return light;
}
function createPointLight(color, x, y, z) {
	var light = new THREE.PointLight(color);
	light.position.set(x, y, z);
	return light;
}

図002■3次元表現で使われる光源の種類

図002
*Tcpp's fileより引用

これで、正二十面体がふたつの光源に照らされて3次元空間で回ります(図003)。スクリプトは以下のコード001にまとめ、サンプル002をjsdo.itに掲げました。

コード001■ふたつの光源で正二十面体を照らして3次元空間で回す


var width = window.innerWidth;
var height = window.innerHeight;
var side = Math.min(width, height) / 50;
var scene;
var camera;
var renderer;
var mesh;
function init() {
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(30, width / height, 1, 1000);
	renderer = createRenderer(width, height);
	mesh = createMesh(side, 0x0000FF);
	camera.position.z = 100;
	scene.add(mesh);
	scene.add(createLight(0xFFFFFF, 10, 0, 25));
	scene.add(createPointLight(0xFFFFFF, -50, 50, 50));
	update();
}
function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);
	document.body.appendChild(renderer.domElement);
	return renderer;
}
function createMesh(radius, color) {
	var geometry = new THREE.IcosahedronGeometry(radius);
	var material = new THREE.MeshPhongMaterial({color: color});
	var mesh = new THREE.Mesh(geometry, material);
	return mesh;
}
function createLight(color, x, y, z) {
	var light = new THREE.DirectionalLight(color);
	light.position.set(x, y, z);
	return light;
}
function createPointLight(color, x, y, z) {
	var light = new THREE.PointLight(color);
	light.position.set(x, y, z);
	return light;
}
function update() {
	mesh.rotation.x += 0.01;
	mesh.rotation.y += 0.01;
	requestAnimationFrame(update);
	renderer.render(scene, camera);
}
window.addEventListener('DOMContentLoaded', init);

03 鏡面反射光の色を加える

MeshPhongMaterialの表面は光を反射します。鏡面反射光の色は、MeshPhongMaterial.specularプロパティで与えられます。デフォルト値は0x111111のかなり暗いグレーです。MeshPhongMaterial()コンストラクタの引数にオブジェクトを渡して定めることもできます。以下のサンプル003は、前掲コード001をつぎのように書き替えて、明るい緑の鏡面反射光を加えました(図004)。


function createMesh(radius, color) {

    var material = new THREE.MeshPhongMaterial({color: color, specular: 0x00FF00});

}

04 バンプマップで表面に質感を加える

バンプマップは、表面の凹凸をグレースケールのビットマップ(テクスチャ)で定めて反射の表現に用いる手法です。かたち(ジオメトリー)は変えることなく、立体に質感が加わります。MeshPhongMaterial.bumpMapプロパティにテクスチャを与えます。今回使うのは、図005のテクスチャです。

図005■表面の凹凸をグレースケールのテクスチャで表す

図005

テクスチャを読み込むために用いるのは、TextureLoaderクラスです。TextureLoader.load()メソッドで、ロードが行われます。第1引数にテクスチャのURL、第2引数には読み込み終えたときのコールバック関数を渡します。コールバックが引数に受け取るのは、読み込んだTextureオブジェクトです。


TextureLoaderオブジェクト.load(テクスチャのURL, ロード時のコールバック)

function コールバック(テクスチャ) {}

バンプマップのテクスチャは別のドメイン(オリジン)から読み込みます。この場合には、TextureLoader.crossOriginプロパティにtrueを与えなければなりません(デフォルト値undefined)。そうすると、crossorigin属性が別オリジンからロードできるように定められます(「HTML5 における CORS について」参照)

前掲コード001は、つぎのように書き替えます。テクスチャが読み込み終わったコールバックで、立体をつくり(createMesh())、シーンに加えてから(Object3D.add()メソッド)、描画を始めます(update())。MeshPhongMaterial.bumpMapプロパティに与えるテクスチャは、MeshPhongMaterial()コンストラクタを呼び出す引数のオブジェクトに加えました。


function init() {

	var textureLoader = new THREE.TextureLoader();
	textureLoader.crossOrigin = true;
	// mesh = createMesh(side, 0x0000FF);
	textureLoader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/4268-bump.jpg', function(texture) {
		mesh = createMesh(side, 0x0000FF, texture);
		scene.add(mesh);
		update();
	});

	// scene.add(mesh);

	// update();
}

// function createMesh(radius, color) {
function createMesh(radius, color, texture) {

	var material = new THREE.MeshPhongMaterial({color: color, bumpMap: texture});

}

これでフォンマテリアルにバンプマップが与えられて、立体の表面に凹凸の質感が加わります。スクリプトは以下のコード002にまとめました。jsdo.itのサンプルは別オリジンの読み込みがあると埋め込みできなくなるため、「three.js r85: Rotating a icosahedron with bump map」に掲げました。

図005■フォンマテリアルにバンプマップで立体の表面に質感が加わる

図005
>> jsdo.itへ

コード002■フォンマテリアルにバンプマップで立体の表面の質感を加える


var width = window.innerWidth;
var height = window.innerHeight;
var side = Math.min(width, height) / 50;
var scene;
var camera;
var renderer;
var mesh;
function init() {
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(30, width / height, 1, 1000);
	renderer = createRenderer(width, height);
	var textureLoader = new THREE.TextureLoader();
	textureLoader.crossOrigin = true;
	textureLoader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/4268-bump.jpg', function(texture) {
		mesh = createMesh(side, 0x0000FF, texture);
		scene.add(mesh);
		update();
	});
	camera.position.z = 100;
	scene.add(createLight(0xFFFFFF, 10, 0, 25));
	scene.add(createPointLight(0xFFFFFF, -50, 50, 50));
}
function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);
	document.body.appendChild(renderer.domElement);
	return renderer;
}
function createMesh(radius, color, texture) {
	var geometry = new THREE.IcosahedronGeometry(radius);
	var material = new THREE.MeshPhongMaterial({color: color, bumpMap: texture});
	var mesh = new THREE.Mesh(geometry, material);
	return mesh;
}
function createLight(color, x, y, z) {
	var light = new THREE.DirectionalLight(color);
	light.position.set(x, y, z);
	return light;
}
function createPointLight(color, x, y, z) {
	var light = new THREE.PointLight(color);
	light.position.set(x, y, z);
	return light;
}
function update() {
	mesh.rotation.x += 0.01;
	mesh.rotation.y += 0.01;
	requestAnimationFrame(update);
	renderer.render(scene, camera);
}
window.addEventListener('DOMContentLoaded', init);

05 テクスチャマップを球体の表面に貼る

フォンマテリアルにテクスチャを画像として貼りましょう。この場合に用いるのが、MeshPhongMaterial.mapプロパティです。ジオメトリーはSphereGeometryにして球体をつくりましょう。テクスチャマップとして使うのは、つぎの図006の画像です。HTMLドキュメントと同じ階層にフォルダ(images)をつくって納めます(pens.png')。

図006■球体の表面に貼るテクスチャ

図006
>> pens.png
(1024×512px)

テクスチャは、やはりTextureLoaderクラスで読み込みます。ピクセル(テクセル)のカラーがマッピングされますので、マテリアルには白(0xFFFFFF)を与えましょう。MeshPhongMaterial.mapプロパティに定めるテクスチャは、コンストラクタのオブジェクトに引数で加えました。これでフォンマテリアルの球体には、前掲図006のテクスチャがマッピングされます(図007)。


function init() {

	// textureLoader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/4268-bump.jpg', function(texture) {
	textureLoader.load('images/pens.png', function(texture) {
		// mesh = createMesh(side, 0x0000FF, texture);
		mesh = createMesh(side, 0xFFFFFF, texture);

	});

}

function createMesh(radius, color, texture) {
	// var geometry = new THREE.IcosahedronGeometry(radius);
	var geometry = new THREE.SphereGeometry(radius, 32, 32);
	var material = new THREE.MeshPhongMaterial({color: color, map: texture});  // bumpMap: texture});

}

図007■球体の表面にテクスチャがマッピングされた

図007
>> jsdo.itへ

これまでのサンプルは、立体をアニメーションで回しました。ここでは、カメラの位置を動かすことにしましょう。ダウンロードしたthree.jsライブラリの「examples/js/controls/」に含まれているOrbitControls.jsを使えば、マウスで簡単にカメラが操作できます。JavaScriptファイルをライブラリ用のフォルダ(ここではlib)に納めたら、<script>要素に読み込んで、つぎのようにOrbitControls()コンストラクタにカメラを渡して呼び出すだけです(詳しくは「three.jsのOrbitControls.jsで簡単にカメラをマウスコントロールする」をお読みください)。


<script src="lib/OrbitControls.js"></script>


var controls;
function init() {

	controls = new THREE.OrbitControls(camera);

}

カメラを動かすためのマウス操作は、つぎのとおりです。

さらに、OrbitControls.autoRotateプロパティにtrueを与えると、カメラは立体を中心にゆっくりと周回します。この場合、アニメーションの再描画メソッド(update())の中からOrbitControls.update()メソッドを呼び出してください。なお、立体のx軸周りの回転は、動きがわかりにくくなりそうなので外しました。


function init() {

	controls = new THREE.OrbitControls(camera);
	controls.autoRotate = true;

}

function update() {
	// mesh.rotation.x += 0.01;
	mesh.rotation.y += 0.01;

	controls.update();

}

書き上げたスクリプトは、つぎのコード003にまとめたとおりです。jsdo.itには「three.js r85: Rotating a sphere with texture」として掲げました。

コード003■球体にテクスチャマップを貼ってマウスでカメラ操作する


var width = window.innerWidth;
var height = window.innerHeight;
var side = Math.min(width, height) / 50;
var scene;
var camera;
var renderer;
var mesh;
var controls;
function init() {
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(30, width / height, 1, 1000);
	renderer = createRenderer(width, height);
	var textureLoader = new THREE.TextureLoader();
	textureLoader.crossOrigin = true;
	textureLoader.load('images/pens.png', function(texture) {
		mesh = createMesh(side, 0xFFFFFF, texture);
		scene.add(mesh);
		update();
	});
	controls = new THREE.OrbitControls(camera);
	controls.autoRotate = true;
	camera.position.z = 100;
	scene.add(createLight(0xFFFFFF, 10, 0, 25));
	scene.add(createPointLight(0xFFFFFF, -50, 50, 50));
}
function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);
	document.body.appendChild(renderer.domElement);
	return renderer;
}
function createMesh(radius, color, texture) {
	var geometry = new THREE.SphereGeometry(radius, 32, 32);
	var material = new THREE.MeshPhongMaterial({color: color, map: texture});
	var mesh = new THREE.Mesh(geometry, material);
	return mesh;
}
function createLight(color, x, y, z) {
	var light = new THREE.DirectionalLight(color);
	light.position.set(x, y, z);
	return light;
}
function createPointLight(color, x, y, z) {
	var light = new THREE.PointLight(color);
	light.position.set(x, y, z);
	return light;
}
function update() {
	mesh.rotation.y += 0.01;
	requestAnimationFrame(update);
	controls.update();
	renderer.render(scene, camera);
}
window.addEventListener('DOMContentLoaded', init);


作成者: 野中文雄
作成日: 2017年5月8日


Copyright © 2001-2016 Fumio Nonaka.  All rights reserved.