【WebGL】Three.jsをつかってみた (2)

セットアップは前回の記事を参照。
前回BlenderでつくったモデルをThree.jsでレンダリングしてみます。

three_particles

内容

・Cubeにテクスチャを貼付けて動かす。
・BlenderでつくったモデルをThree.jsで動かす。
・Three.jsで10000個のparticleを動かす。
・WebGLRendererでクリックイベントを検出する。
・CSS3DRendererを使う。

Cubeにテクスチャを貼付けて動かす

今回は以下を使います。


./mrdoob-three.js-f396baf/examples/js/controls/TrackballControls.js
./mrdoob-three.js-f396baf/build/three.js

まずは全体から。

var renderer = new THREE.WebGLRenderer({ antialias:true });// レンダラの初期化
renderer.setSize(1000, 1000);// renderer.setSize(width, height)
renderer.setClearColor( new THREE.Color(0xffffff) );/背景色

document.getElementById('canvas-frame').appendChild(renderer.domElement);

var scene = new THREE.Scene();// シーンの初期化

var camera = new THREE.PerspectiveCamera(50, 500 / 500);// カメラの作成
camera.position = new THREE.Vector3(0, 0, 8); //カメラの位置
camera.lookAt(new THREE.Vector3(0, 0, 0));// カメラを向ける方向
scene.add(camera);

var ambient = new THREE.AmbientLight(0xffffff);// ライトの作成
scene.add(ambient);

var texture = THREE.ImageUtils.loadTexture('sample.png', null, function(){// 表示する物体の作成
    texture.magFilter = THREE.NearestFilter;
    texture.minFilter = THREE.NearestFilter;

    var modelRender = new ModelRender(texture, scene);
    modelRender.addBox({x:1, y:1, z:1}, 0, 0);

    var controls = new THREE.EditorControls(camera, renderer.domElement);
    controls.pan = function(){};

    animate(); // call render
});

function animate() {
  console.log("render")
  step++;
  mesh.rotation.set( step/ 100, step / 100, step / 100);
  renderer.render(scene, camera);//レンダリング
  requestAnimationFrame(animate);// 再帰実行
}

簡単に説明すると、まずrendererの初期化をしています。


var renderer = new THREE.WebGLRenderer({ antialias:true });

CanvasRendererも使えますが当然ですがOpenGL程の処理速度はでません。
またrenderer.setSize(width, height)でサイズを指定します。


var scene = new THREE.Scene();

sceneを作成しこれに対してcamera,ambient(ライト),mesh(物体)を追加していきます。


// THREE.PerspectiveCamera ( fov, aspect, near, far )
var camera = new THREE.PerspectiveCamera(50, 500 / 500);

cameraを作成し、camera.positionでカメラの位置を指定しています。

物体に貼付けるtexture画像を先に読み出します。


var texture = THREE.ImageUtils.loadTexture('dnpenguin.png', null, function(){
});

画像を読み出してから、ModelRenderをインスタンス化してmesh(物体)を作成します。
CubeGeometry(直方体)以外にも,SphereGeometry(球体)やCircleGeometry(円)などがあります。

テキストの場合はTextGeometryです。
ただし、テキストではフォントファイル(ex ./examples/fonts/helvetiker_regular.typeface.js)を追加している必要があります。


this.geometry = new THREE.TextGeometry ( 'Natsume Soseki', {});
var ModelRender = function(texture, scene) {
    this.scene = scene;
    this.texture = texture;
    this.boxes = [];
    this.addBox = function(boxSize, boxPos, texPos) {
      var box = new ModelBox(boxSize, boxPos, texPos, true, this);
      this.boxes.push(box);
      return box;
    };
};

var ModelBox = function(boxSize, boxPos, texPos, transparent, render) {
    this.render = render;
    this.texture = this.render.texture.clone();
    this.texture.needsUpdate = true;
    this.material = new THREE.MeshPhongMaterial({ map:this.texture });
    this.material.transparent = transparent;
    this.geometry = new THREE.CubeGeometry(boxSize.x , boxSize.y , boxSize.z , 0, 0, 0);
    mesh = new THREE.Mesh(this.geometry, this.material);
    this.render.scene.add(mesh);
};

そして最後にレンダリングします。HTML5のrequestAnimationFrameでアニメーション化しています。
(最大60fpsで動作)


renderer.render(scene, camera);//レンダリング
requestAnimationFrame(animate);// 再帰実行

BlenderでつくったモデルをThree.jsで動かす

firefoxがローカルだとaccess to restricted uri denied でモデルデータが読み込めないときがありました。
サーバにアップしたら動きました。

var loader = new THREE.JSONLoader();
loader.load( './js/monkey.js', function ( geometry ) {
   mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { overdraw: true} ) );
    scene.add( model );
    animate();

    var controls = new THREE.EditorControls(camera, renderer.domElement);
    controls.pan = function(){};
} );

JSONLoaderクラスでJSONで記述された3Dモデルを読み込んでます。
ただし、meshに貼付けるはずのtextureが表示されませんでした。これは調査中です。

EditorControlsでマウスによるカメラのコントロールを可能にします。
また、flyControlsで浮遊感のある視覚効果を出す事もできます。

Three.jsで10000個のparticleを動かす。

続いてcanvasで描画した星型の図形をWebGLで10000個描画します。

まずはcanvasで星型の図形を書きます。

    var canvas = document.createElement( 'canvas' );
    canvas.width = 200;
    canvas.height = 200;

    var context = canvas.getContext('2d');

    context.fillStyle = 'white';
    context.lineWidth = 0;
    //星形のパスを描く
    context.beginPath();
    context.moveTo(90,60);
    context.lineTo(210,60);
    context.lineTo(110,130);
    context.lineTo(150,20);
    context.lineTo(190,130);
    context.closePath();
    context.fill();
    context.stroke();

次にverticesプロパティに全パーティクルの座標をpushします。

for ( var i = 0; i < this.particles_count; i++ ) {
	this.geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );// 初期位置
	this.geometry.direction.push( new THREE.Vector3( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 ) );// 移動方向
	this.geometry.speed.push( Math.random() * this.particles_speed_max );// スピード
        var color = new THREE.Color( 0xFFFFFF );// 色
        color.setRGB( Math.random(), Math.random(), Math.random() );
        this.geometry.colors.push( color );
}

ParticleSystemは点集合のオブジェクトでWebGL環境で使えます。
THREE.ParticleSystemにgeometryとmaterial(ParticleBasicMaterial)をセットします。
こうすることで、個々の頂点がパーティクルとして扱われます。

	var texture = new THREE.Texture( canvas );
	texture.needsUpdate = true;
		
	var material = new THREE.ParticleBasicMaterial( {
		map : texture, // canvas図形の適用
		size : 5,// particleのサイズ
		blending : THREE.AdditiveBlending,//加算合成
		depthTest : false,
		vertexColors : true,
		transparent : true
	} );

	var mesh = new THREE.ParticleSystem( this.geometry, material );
	mesh.position.set( 0, 0, 0 );
	mesh.sortParticles = true;
	this.scene.add( mesh );

particleの位置を更新します。

	for ( var i = 0; i < this.particles_count; i++ ) {
		var position = this.geometry.vertices[i];
		var direction = this.geometry.direction[i];
		var speed = this.geometry.speed[i];

		position.x += direction.x * speed;
		position.y += direction.y * speed;
		position.z += direction.z * speed;

		if ( position.x > pos_max || position.y > pos_max || position.z > pos_max || position.x < pos_min || position.y < pos_min || position.z < pos_min ) {
			position.x = 0;
			position.y = 0;
			position.z = 0;
		}

	}

あとはrequestAnimationFrameでLoopさせるだけです。

この記事の上にあるような絵がでます。
10000個を描画するにはGPUの力を借りないとほぼ無理なのでこれがWebGLの強みですね。
さらにレンダリングを速くする最適化する手法としてこちらで紹介されています。複数のオブジェクトをまとめたオブジェクトを描画したい場合は効果的ですね。

WebGLRendererでクリックイベントを検出する。

THREE.Projector()を使います。
準備として、rayReceiveObjects配列を作っておいてpushします。

cubes[i].name = "box1"
rayReceiveObjects.push( cubes[i] );

clickした位置に対して、rayを照射することでその範囲に含まれるオブジェクトをintersectsに入れています。
よって、intersects.lengthで判定できます。

var projector = new THREE.Projector();
var raycaster = projector.pickingRay( vector, camera );
var intersects = raycaster.intersectObjects( rayReceiveObjects );
if ( intersects.length > 0 ) {
	console.log(intersects[0].object.name);
}

CSS3DRendererを使う。

examplesにあるcss3d_periodictable.htmlをみてみます。まず初期位置の設定をしています。

// table
var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );

objects.push( object );

var table = [“H”, “Hydrogen”, “1.00794”, 1, 1,・・・];の配列になっています。
renderer = new THREE.CSS3DRenderer();でrendererを宣言し、transform( targets.table, 5000 );の速度で以下の座標に移動しています。

var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;

targets.table.push( object );

CSS3DはDOMとの親和性が高いのが良いですね。