cannon.jsの物理演算をthree.jsの3D世界に反映させる
2015.07.16
こんにちは、ユニトラストの櫻井です。
今回はJavaScriptのthree.jsとcannon.jsを用いて、
3Dの物理演算結果をウェブブラウザにレンダリングする方法を説明していきたいと思います。
まずは、three.jsとcannon.jsについて説明していきます。
three.jsとは
three.jsとは、ウェブブラウザ上で3Dを描写するための標準仕様、WebGLを扱うためのJavaScriptのライブラリです。
本ライブラリはオープンソースで開発されており、WebGLを手軽に扱えるものとして広く普及しています。
ちなみにWebGLとは、各種OSに移植され広く普及している3Dレンダリング用API、
OpenGLをJavaScript上で扱えるようにしたものとなります。
公式サイトにはあるサンプルを見ると、three.jsの多機能さ、拡張性の高さが分かります。
cannon.jsとは
cannon.jsとは、3次元上での物体の物理的な挙動を導出する、物理エンジンを提供するライブラリです。
物理エンジンを提供するJavaScriptのライブラリはいくつかありますが、
cannon.jsはthree.jsとの連携を意識した作りとなっているのが特徴的です。
こちらも公式サイトに様々なサンプルがあり、three.jsとの組み合わせがスタンダードとなっています。
説明用サンプル
上記は、three.jsとcannon.jsを組み合わせた、無重力空間で球体を衝突させるサンプルプログラムです。
操作方法
カメラ移動:マウスの上下左右
前進:左クリックまたはW
後退:右クリックまたはS
左移動:A
右移動:D
ソースコード
<!DOCTYPE html> <html> <head> <title>技術ブログ用</title> <meta charset="utf-8"> </head> <body> <script src="three.js"></script> <script src="FirstPersonControls.js"></script> <script src="Cannon.js"></script> <script> var world = null, scene = null, clock = null, camera = null, controls = null, earth = null, earthSphere = null, meteor = null, meteorSphere = null, renderer = null; // 光りあれ!! let_there_be_light(); // 世界創造 function let_there_be_light() { initWorld(); initEnviroment(); initLight() initEarth(); initEarthMotion(); initMeteor(); initMeteorMotion(); initParticles(); initRenderer(); renderLoop(); } //物理世界設定 function initWorld(){ //物理世界の取得 world = new CANNON.World(); //衝突した物体を検出するためのオブジェクト world.broadphase = new CANNON.NaiveBroadphase(); //正確さを増すための反復計算回数 world.solver.iterations = 10; //計算結果の不正確さの許容値 world.solver.tolerance = 0.1; } //環境設定 function initEnviroment(){ //シーン(画面)の取得 scene = new THREE.Scene(); //経過時間の取得 clock = new THREE.Clock(); //XYZ軸の表示(引数は表示範囲) axis = new THREE.AxisHelper(100000); //軸の開始位置 axis.position.set(0,0,0); //画面への軸の追加 scene.add(axis); //カメラの取得(視野角,アスペクト比,表示開始位置,表示終了位置) camera = new THREE.PerspectiveCamera(50, 800 / 600, 1, 100000); //カメラ位置の設定 camera.position.set(40000,40000,40000); //一人称視点の取得 controls = new THREE.FirstPersonControls(camera); //移動速度 controls.movementSpeed = 3000; //カメラ旋回速度 controls.lookSpeed = 0.1; //カメラ向き controls.lon = -85; } //ライト設定 function initLight(){ //ライトの取得(色、強度) light = new THREE.DirectionalLight(0xffffff, 1); //ライト位置 light.position.set(300, 300, 300); //画面へのライトの追加 scene.add(light); //環境光の取得 amb = new THREE.AmbientLight(0xffffff); //画面への環境光の追加 scene.add(amb); } //地球の設定 function initEarth(){ //骨格の取得(半径、緯度分割数、軽度分割数) var geometry = new THREE.SphereGeometry(10000, 100, 100); //テクスチャの初期設定 var texture = THREE.ImageUtils.loadTexture( 'land_ocean_ice_cloud_2048.jpg' ); //テクスチャの設定 var material = new THREE.MeshBasicMaterial( { map: texture } ); //骨格とテクスチャを組み合わせる earth = new THREE.Mesh(geometry, material); //画面への物体の追加 scene.add(earth); } //地球の運動の設定 function initEarthMotion(){ //質量のある物体を取得 earthSphere = new CANNON.Body({mass: 10000}); //形状設定 earthSphere.addShape(new CANNON.Sphere(10000)); //位置設定 earthSphere.position.set(25000,25000,25000); //角速度の設定 earthSphere.angularVelocity.set(0.1, 0.1, 0.01); //物理世界へ追加 world.add(earthSphere); } //隕石の設定 function initMeteor(){ //地球とほぼ同様 var geometry = new THREE.SphereGeometry(1000, 100, 100); var texture = THREE.ImageUtils.loadTexture( 'land_ocean_ice_cloud_2048.jpg' ); var material = new THREE.MeshBasicMaterial( { map: texture } ); meteor = new THREE.Mesh(geometry, material); scene.add(meteor); } //隕石運動の設定 function initMeteorMotion(){ //地球とほぼ同様 meteorSphere = new CANNON.Body({mass: 1000}); meteorSphere.addShape(new CANNON.Sphere(1000)); meteorSphere.position.set(60000,60000,25000); meteorSphere.velocity.set(-10000,-10000,0); world.add(meteorSphere); } //パーティクルの設定 function initParticles() { var geometry = new THREE.Geometry(); //位置情報をランダムに設定 for (var i = 0; i < 100000; i++) { geometry.vertices.push(new THREE.Vector3( Math.random() * 100000, Math.random() * 100000, Math.random() * 100000 )); } //自己発光するマテリアルを設定 var material = new THREE.ParticleBasicMaterial({size: 1}); //位置情報とマテリアルをもとにパーティクルを生成 var particles = new THREE.ParticleSystem(geometry, material); //画面へのパーティクルの追加 scene.add(particles); } //レンダラーの設定 function initRenderer(){ //レンダラーを取得 renderer = new THREE.WebGLRenderer({antialias: true}); //画面サイズの設定 renderer.setSize(800, 600); //DOM操作 document.body.appendChild(renderer.domElement); } //レンダリング function renderLoop () { //ループの準備 requestAnimationFrame( renderLoop ); //物理世界の時間経過 world.step(1/60); //物理世界の地球の位置を画面表示用オブジェクトに反映 earth.position.copy(earthSphere.position); earth.quaternion.copy(earthSphere.quaternion); //物理世界の隕石の位置を画面表示用オブジェクトに反映 meteor.position.copy(meteorSphere.position); meteor.quaternion.copy(meteorSphere.quaternion); //画面の描写 renderer.render( scene, camera ); //入力時間とその操作の反映 controls.update(clock.getDelta()); } </script> </body> </html>
サンプルの説明
three.jsとcannon.jsを組み合わせて物理演算結果を描写する場合、
cannon.jsで作成した計算用オブジェクトの座標を、three.jsの表示用オブジェクトの座標にコピーします。
上記のポイントさえ押さえておけば、プログラムの理解は簡単です。
それでは、次はプログラムをメソッド単位で見ていきます。
物理演算用空間の作成
//物理世界設定 function initWorld(){ //物理世界の取得 world = new CANNON.World(); //衝突した物体を検出するためのオブジェクト world.broadphase = new CANNON.NaiveBroadphase(); //正確さを増すための反復計算回数 world.solver.iterations = 10; //計算結果の不正確さの許容値 world.solver.tolerance = 0.1; }
物理演算をするための空間は、上記のコードで作成することができます。
ちなみに今回は使用しませんでしたが、World.gravity()を利用すれば、
XYZ軸の好きな方向に重力を設定することができます。
画面とカメラの作成
//環境設定 function initEnviroment(){ //シーン(画面)の取得 scene = new THREE.Scene(); //経過時間の取得 clock = new THREE.Clock(); //XYZ軸の表示(引数は表示範囲) var axis = new THREE.AxisHelper(100000); //軸の開始位置 axis.position.set(0,0,0); //画面への軸の追加 scene.add(axis); //カメラの取得(視野角,アスペクト比,表示開始位置,表示終了位置) camera = new THREE.PerspectiveCamera(50, 800 / 600, 1, 100000); //カメラ位置の設定 camera.position.set(40000,40000,40000); //一人称視点の取得 controls = new THREE.FirstPersonControls(camera); //移動速度 controls.movementSpeed = 3000; //カメラ旋回速度 controls.lookSpeed = 0.1; //カメラ向き controls.lon = -85; }
three.jsで画面を描写する場合は、下記のオブジェクトが必要となります。
- Scene:画面(場面)の単位となるシーン
- Camera:向きや位置などを設定するためのカメラ
- Light:物体を照らすためのライト
- Mesh:立方体や円柱などの物体
- Renderer:描写を司るレンダラー
ここでは、上記のうちSceneとCameraをインスタンス化し、利用可能な状態にしています。
ちなみに、FirstPersonContolsはthree.jsで使用できるカメラ用ライブラリの1つで、
ゲームの一人称視点らしいマウス・キーボード操作をプログラムに組み込むことができます。
ライトの作成
//ライト設定 function initLight(){ //ライトの取得(色、強度) var light = new THREE.DirectionalLight(0xffffff, 1); //ライト位置 light.position.set(300, 300, 300); //画面へのライトの追加 scene.add(light); //環境光の取得 var amb = new THREE.AmbientLight(0xffffff); //画面への環境光の追加 scene.add(amb); }
ライトには、
- 平行光源:光の方向に向いた面を照らす
- 点光源:点の周りの全方位を照らす
- スポットライト光源:1点を中心に照らす
- 環境光源:穏やかに周りを照らす
等、様々な種類があります。
今回は平行光源と環境光源を用いていますが、球体自体に色や影の色を指定したわけではないので、
ライトの陰影による見た目の変化はほとんどありません。
球体の作成
//地球の設定 function initEarth(){ //骨格の取得(半径、緯度分割数、軽度分割数) var geometry = new THREE.SphereGeometry(10000, 100, 100); //テクスチャの初期設定 var texture = THREE.ImageUtils.loadTexture( 'land_ocean_ice_cloud_2048.jpg' ); //テクスチャの設定 var material = new THREE.MeshBasicMaterial( { map: texture } ); //骨格とテクスチャを組み合わせる earth = new THREE.Mesh(geometry, material); //画面への物体の追加 scene.add(earth); } //地球の運動の設定 function initEarthMotion(){ //質量のある物体を取得 earthSphere = new CANNON.Body({mass: 10000}); //形状設定 earthSphere.addShape(new CANNON.Sphere(10000)); //位置設定 earthSphere.position.set(25000,25000,25000); //角速度の設定 earthSphere.angularVelocity.set(0.1, 0.1, 0.01); //物理世界へ追加 world.add(earthSphere); }
表示用の球体と、物理演算用の2つの球体をインスタンス化しています。
地球の衛星写真は、
NASAの所有する「http://visibleearth.nasa.gov/view.php?id=57735」の画像を利用しています。
パーティクル(粒子)の作成
//パーティクルの設定 function initParticles() { var geometry = new THREE.Geometry(); //位置情報をランダムに設定 for (var i = 0; i < 100000; i++) { geometry.vertices.push(new THREE.Vector3( Math.random() * 100000, Math.random() * 100000, Math.random() * 100000 )); } //自己発光するマテリアルを設定 var material = new THREE.ParticleBasicMaterial({size: 1}); //位置情報とマテリアルをもとにパーティクルを生成 var particles = new THREE.ParticleSystem(geometry, material); //画面へのパーティクルの追加 scene.add(particles); }
物理演算とは関係ありませんが、サンプルでは宇宙らしさを出すためにパーティクルを設定しています。
three.jsでは、THREE.ParticleSystem()に座標の集合とサイズを持ったマテリアルを渡すことで、
手軽にパーティクルを作成することができます。
レンダラーの設定
//レンダラーの設定 function initRenderer(){ //レンダラーを取得 renderer = new THREE.WebGLRenderer({antialias: true}); //画面サイズの設定 renderer.setSize(800, 600); //DOM操作 document.body.appendChild(renderer.domElement); }
画面を描写するレンダラーをインスタンス化しています。
THREE.WebGLRenderer()の代わりにHTML5のCanvasを出力するTHREE.CanvasRenderer()を使用すると、
WebGLに対応していないコンピュータやブラウザに対しても画面をレンダリングすることができます。
描写のアニメーション設定
//レンダリング function renderLoop () { //ループの準備 requestAnimationFrame( renderLoop ); //物理世界の時間経過 world.step(1/60); //物理世界の地球の位置を画面表示用オブジェクトに反映 earth.position.copy(earthSphere.position); earth.quaternion.copy(earthSphere.quaternion); //物理世界の隕石の位置を画面表示用オブジェクトに反映 meteor.position.copy(meteorSphere.position); meteor.quaternion.copy(meteorSphere.quaternion); //画面の描写 renderer.render( scene, camera ); //入力時間とその操作の反映 controls.update(clock.getDelta()); }
こちらでは、cannon.jsによる物理演算結果を、three.jsの表示用のオブジェクトに反映しています。
また、レンダリングを逐次的に実行することで、アニメーションが生成されています。
ちなみに、controls.update()はFirstPersonControlls.jsのメソッドで、
デバイスからの操作の入力時間を受け取り、操作を画面に反映しています。
まとめ
今回作成したサンプルは非常にシンプルですが、これだけの記述でも手軽に物理演算結果を3Dとしてレンダリングできます。
また、three.jsはBlender等のソフトウェアで作成した3Dモデルを取り込むことができます。
そのため、配布されている3Dモデルと物理演算、ゲームのアイデアを組み合わせることで、
アクションゲームをはじめとした、様々なゲームを作成することができそうです。
以上、「cannon.jsの物理演算をthree.jsの3D世界に反映させる」でした。
CONTACT
お問い合わせ
あなたの「想い」に挑戦します。
どうぞお気軽にお問い合わせください。
受付時間:平日9:00〜18:00 日・祝日・弊社指定休業日は除く