ここまで、Three.jsのさまざまな機能の使用方法を解説してきました。読者の皆さんが「これは面白い、自分でも何か作ってみよう!」と感じられていたら幸いです。
しかし、記事中で取り上げてきたサンプルコードは、いずれも一方的に映像を流し続けるだけのものでした。3次元映像を自分で操作できるのがリアルタイム3Dグラフィックの醍醐味ですから、このままでは物足りないですね。そこで、マウスによるカメラ操作などの基本的なインタラクティビティの実現方法を解説して、この記事の締めくくりとします。
これまでのサンプルコードには、インタラクティブ性の欠如の他にも以下の欠点がありました。
いずれも一般公開するアプリケーションとしては好ましくないので、先に改善してしまいましょう。最初のサンプル(sample01.html)をベースに、WebGL未サポートブラウザへの対応、描画領域のブラウザウィンドウ全体への拡大とリサイズへの対応を追加したコードを以下に示します。「中略」となっている部分は、基のソースと同じ内容です。
<!DOCTYPE html> <html lang="ja"> <head><meta charset="UTF-8"></head> <body style="margin:0; overflow:hidden;"> <script src="//sample.atmarkit.jp/fux/1210/04/three.min.js"></script> <script src="js/Detector.js"></script> <script> if(!Detector.webgl) Detector.addGetWebGLMessage(); // (1)レンダラの初期化 var renderer = new THREE.WebGLRenderer({ antialias:true }); renderer.setSize(window.innerWidth, window.innerHeight); // 中略... // (3)カメラの作成 var camera = new THREE.PerspectiveCamera( 15, window.innerWidth / window.innerHeight); // 中略... // リサイズへの対応 window.addEventListener('resize', function() { renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); }, false ); </script> </body> </html>
WebGL未サポートブラウザへの対応は、Three.jsのexamplesディレクトリに収められているDetector.jsを使用します。従って、ポストプロセスのサンプルと同様にThree.jsのsrc/examples/jsをコピーしておく必要があります。あとは、2番目のscriptタグでDetector.jsを読み込み、3番目のscriptタグの先頭の1行で対応は完了です。英語のメッセージと情報へのリンクを表示するだけなので親切とはいえませんが、何も表示しないよりはだいぶマシでしょう。
描画領域をブラウザウィンドウ全体に拡大するには、レンダラのsetSize()メソッドに(固定の値ではなく)ブラウザウィンドウのサイズを指定し、PerspectiveCameraコンストラクタの第2引数にも正しいアスペクト比を渡してやります。さらに、bodyタグのスタイルを"margin:0; overflow:hidden;"として余白をなくせば、描画領域がページ読み込み時のブラウザウィンドウにフィットします。
最後に追加したwindow.addEventListener()の呼び出しは、リサイズへの対応です。resizeイベントの発生時にレンダラのサイズとカメラのアスペクト比を再設定し、updateProjectMatrix()メソッドでカメラの内部状態を更新しています。
最も基本的なインタラクティビティとして、マウスによるカメラ操作を上記のソースに追加します。変更部分を以下に示します。
<!DOCTYPE html> <html lang="ja"> <head><meta charset="UTF-8"></head> <body style="margin:0; overflow:hidden;"> <script src="//sample.atmarkit.jp/fux/1210/04/three.min.js"></script> <script src="js/Detector.js"></script> <script src="js/controls/OrbitControls.js"></script> <script> // ...中略... // カメラコントロールを作成 var controls = new THREE.OrbitControls(camera); controls.center = new THREE.Vector3(0, 0, 0); var baseTime = +new Date; function render() { requestAnimationFrame(render); // カメラの状態を更新 controls.update(); mesh.rotation.y = 0.3 * (+new Date - baseTime) / 1000; renderer.render(scene, camera); }; render(); // ...中略... </script> </body> </html>
このコードを追加すると、マウスドラッグによる視点移動(地球の周りを回転)と、ホイールによるズームが可能になります。実質的な追加コードは3行のみで、まず操作対象のカメラオブジェクトを引数にしてOrbitControlsオブジェクトを作成し、カメラの回転移動の中心となる座標をcenterプロパティに設定します。あとは毎回の描画の直前でupdate()メソッドを呼び出すだけで、マウス操作に応じてカメラの各パラメータを自動で制御してくれます。
OrbitControls以外にも、いくつかのカメラ操作クラスがリポジトリのsrc/examples/js/controlsに実装されています。いずれも使い方は概ね同じですので解説は省きますが、Three.jsのリポジトリに含まれるexamples/misc_camera_*.htmlで使われているので、詳細はそちらを参照してください。
画面上の特定の座標にあるオブジェクトを調べる処理を「ピッキング」と呼びます。特にシーンエディタなどのツールで、クリックされたオブジェクトを選択するために必要になります。素で実現するにはすべての物体との交差判定をしなければいけないのですが、Three.jsにはそれが既に実装されており、簡単に実現できます。
例として、マウスカーソルが地球に重なったときにハイライト表示するコードのための追加コードを示します。
// マウスの座標を取得し、-1〜+1の範囲に正規化する var mouseX = -1, mouseY = -1; document.addEventListener('mousemove', function(e) { mouseX = (e.clientX / window.innerWidth ) * 2 - 1; mouseY = (e.clientY / window.innerHeight) * -2 + 1; }, false); var baseTime = +new Date; function render() { requestAnimationFrame(render); // マウスが指した座標に存在するオブジェクトを得る var projector = new THREE.Projector(); var pos = new THREE.Vector3(mouseX, mouseY, -1); var ray = projector.pickingRay(pos, camera); var intersects = ray.intersectObjects(scene.children); // マウスがmeshの上にあれば、マテリアルを変更する if(intersects.length > 0 && intersects[0].object == mesh) { mesh.material.emissive = new THREE.Color(0x888888); } else { mesh.material.emissive = new THREE.Color(0); } mesh.rotation.y = 0.3 * (+new Date - baseTime) / 1000; renderer.render(scene, camera); }; render();
処理を上から見ていくと、まずmousemoveイベントを監視して、随時マウスの座標を取得しています。このとき座標を-1〜+1の範囲にマッピングし、かつY座標を反転しているのは、画面上の座標系とThree.jsの(透視変換後の)座標系を合わせるためです。
render()の中では、最初にProjectorを作成し、マウス座標とカメラをpickingRay()メソッドに渡しています。これにより、視点からマウス座標に向かうRay(光線)が取得できます。intersectObjects()はシーン内の全オブジェクトとRayとの交差判定を行うメソッドです。戻り値は以下のプロパティを持つオブジェクトの配列になります。
パラメータ名 | 説明 | |
---|---|---|
distance | 視点から交点までの距離 | |
point | 交点の座標(THREE.Vector3) | |
face | 交差した面(THREE.Face3またはTHREE.Face4、nullの場合もあり) | |
faceIndex | 面のインデックス(undefinedの場合もあり) | |
object | 交差した物体(THREE.Object3Dまたはその派生クラス) | |
配列はdistanceが小さい順でソートされているので、配列が空でなければ、最初の要素がマウス座標の直下の交点となります。そのobjectがmesh(地球)かどうかでマテリアルのemissiveを操作し、ハイライト表示を制御しています。
このように、Three.jsでは画面上の特定の座標にあるオブジェクトや面を簡単に取得できます。ただし、すべてJavaScriptによる計算なので、多数のオブジェクトやポリゴンを含む複雑なシーンでは処理が重くなります。
もし複雑なシーンでのピッキングが必要であれば、GPUに計算させることも可能です。コードがだいぶ複雑になるので取り上げませんが、Three.jsに付属するwebgl_interactive_cubes_gpu.htmlで実装されているので、参考にすると良いでしょう。
今回はThree.jsを介してWebGLの機能を利用する方法を解説しました。わずかなコード量で多彩な表現ができることに驚かれたのではないでしょうか。従来の3次元グラフィックは、C/C++で複雑なコードや数学を駆使して実現するものでしたが、Three.jsは一般的なJavaScriptの知識だけで十分に活用できます。ぜひ実際に試して、その楽しさを味わってみてください。
次回は引き続きThree.jsを使用して、WebGLのプログラマブルシェーダーについて解説する予定です。お楽しみに!
伊藤 千光
白髪になってもプログラムを組んでいたい、ゲームプログラマあがりのWebエンジニア。日本ファルコム、Microsoft GameStudios JapanにてPCやXbox、Xbox 360のゲームの開発に従事し、グラフィックエンジンやコリジョン、スクリプトなどを幅広く担当。その後、全世界のコンピュータを1つにつなげるWeb技術に魅力を感じ、Webエンジニアに転身。Irvine SystemsにてRuby on Railsによるシステム開発に携わった後、フリーランスに。現在はGoogle AppsやGoogle App Engineなどを利用した案件をこなしつつ、ブログ「WebOS Goodies」にて情報発信を行っている。Google API Expert(ソーシャル担当)。WebOS Goodies : http://webos-goodies.jp/
Copyright © ITmedia, Inc. All Rights Reserved.