#1、概要
3Dグラフィック・エンジンの一つであるBabylon.jsを使用して、色々な3Dグラフィックを表示してみましたので以下に説明します。 なお、Windows10の環境において Microsoft Edge (Ver. 11.0.17763.379)、 Firefox (Ver. 65.0.2/64 bit) 及び Google Chrome (Ver. 73.0.3683.86/64 bit) で動作を確認しました。 また、Androidの一部及びiOSの一部での動作も確認しました。 ここで使用している3DグラフィックデータでGLTFフォーマットの一部は「Poly」から、OBJフォーマットおよびSTLフォーマットは「Free3D」から、また3Dキャラクター「
デフォルメ人物キャラクター(女の子)は PROJECT-6B」様からダウンロードさせて戴きました。 無料グラフィックを提供されている各位に感謝いたします。
#2、ファイル構成
ここで説明する内容に関するファイルは全て「GitHub
Babylon.js_3D_Graphics」からダウンロード可能です。 また、そのファイル構成は次の通りです。
- css: メニュー表示用スタイルシートを保存したフォルダーです。
- scenes: 「babylon」、「gltf」、「obj」及び「stl」各フォーマットの3Dグラフィック・データを保存したフォルダーです。
- texture: 「Babylon.js on GITHUB」からダウンロードしたテクスチュア・データ、2Dグラフィック・データ等を保存したフォルダーです。
- index.html: 今回作成した3Dグラフィックを描画するHTMLファイルの選択・実行用メニューです。
- Babylon_101_sphere.html - Babylon_502_maze.html: 今回作成した3Dグラフィックを描画するJavaScriptを含むHTMLファイル本体です。
- Maze_01.js: Step 5-2で使用する迷路作成JavaScriptファイルです。
#3、3Dグラフィックの描画
ここでは、単純な球の表示から球にテクスチャーを張り付けて表示、アニメーション及び各種フォーマットの3Dグラフィックの描画ならびに3D迷路ゲームの作成について、ステップ・バイ・ステップで説明します。
「index.html」: ここで説明するプログラム全てをメニューから選択して表示できます。
##Step-1:色々な球の表示
ここでは、表示対象として球を使用しますが、デフォルトで「Box」、「Sphere」、「Plane」、「Disc or Regular Polygon」、「Cilynder」、「Torus」、「Knot」、「Ground」等の形状表示が可能です。 詳細については、次(英語)を参照下さい。
- 「Create Set Shapes - Legacy」: Babylon.js Documentation
- 「Create Set Shapes」: Babylon.js Documentation
###1-1、単純な球の表示
始めに単純な球を表示させてみます。 Babylon.jsでは、3Dグラフィックを表示させるために表示対象(今回は球)、ライト、カメラの設定が必要になります。 これらの設定を行うJavaScriptを含むHTMLファイルを次に示します。
「Babylon_101_sphere.html」: 単純な球の表示を実行/マウスで回転(左ドラッグ)、移動(右ドラッグ)、拡大縮小(ホイール)させることが可能です。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Babylon.js - Shape - 2019/10/18 by T. Fujita</title>
<script src="https://code.jquery.com/pep/0.4.0/pep.min.js"></script> <!-- タッチパネル対応用 -->
<script src="https://preview.babylonjs.com/babylon.js"></script> <!-- Babylon.js本体 -->
<script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script> <!-- ファイル・ローダー(特に無くても良い) -->
<style>
html,body,canvas {
margin: 0; padding: 0; width: 100%; height: 100%; font-size: 0;
}
</style>
</head>
<body>
<canvas id = "renderCanvas"></canvas>
<script type = "text/javascript">
var canvas = document.getElementById("renderCanvas");
var engine = new BABYLON.Engine(canvas, true);
var createScene = function() {
var scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color3(1, 0.8, 0.8); // 背景色の設定
var camera = new BABYLON.ArcRotateCamera("Camera", -90/180*Math.PI, 45/180*Math.PI, Math.PI, new BABYLON.Vector3(0, 0, 0), scene); // カメラの設定
var light0 = new BABYLON.HemisphericLight("light0", new BABYLON.Vector3(1, 1, 0), scene); // ライトの設定
var object = BABYLON.Mesh.CreateSphere("object", 20, 1.0, scene); // 球の設定(Legacy:次のどちらでも良い)/数値の20はセグメント数、1.0は球のサイズ
// var object = BABYLON.MeshBuilder.CreateSphere("object", {}, scene); // 球の設定(前のLegacyとどちらでも良い)/{}内でx,y,zそれぞれのサイズを設定可能
camera.attachControl(canvas); // カメラのコントロールを可能とする
return scene;
};
var scene = createScene();
engine.runRenderLoop(function() {
scene.render(); // 表示の実行
});
</script>
</body>
</html>
###1-2、球の表示に360度の背景をつける
次に背景を付けます。 今回はBabylon.jsのPlayground内にあるskyboxを使用して360度の背景にしました。 追加部分を次に示しまします。
「Babylon_102_sphere_with_BG.html」: 球の表示に360度の背景を実行
// Skybox
var skybox = BABYLON.Mesh.CreateBox("skyBox", 1000.0, scene);
var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("./textures/TropicalSunnyDay", scene);
skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
skyboxMaterial.disableLighting = true;
skybox.material = skyboxMaterial;
###1-3、球を着色する
球を着色してみましょう。 ここではランダムに色を付けましたので、その追加の部分を以下に示します。 なお、色はRGBそれぞれ0.0~1.0の範囲で設定します。
「Babylon_103_color_sphere.html」: 球の着色を実行
var material = new BABYLON.StandardMaterial(scene);
material.alpha = 1;
material.diffuseColor = new BABYLON.Color3(Math.random(), Math.random(), Math.random()); // Set the RGB color to the object.
object.material = material;
###1-4、球に地球のテクスチャーを張り付ける
球と言えば地球ということで、地球のテクスチャーを張り付けてみます。 また、背景を宇宙に変更しました。 追加・変更箇所を以下に示します。
「Babylon_104_earth.html」: 球に地球のテクスチャーを張り付け実行
// Skybox 背景を宇宙に変更
var skybox = BABYLON.Mesh.CreateBox("skyBox", 1000.0, scene);
var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("./textures/nebula/nebula", scene);
skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
skyboxMaterial.disableLighting = true;
skybox.material = skyboxMaterial;
// Create materials 地球のテクスチャーを張り付け
var earthMaterial = new BABYLON.StandardMaterial("ground", scene);
earthMaterial.diffuseTexture = new BABYLON.Texture("./textures/earth.jpg", scene);
earthMaterial.diffuseTexture.uScale = -1;
earthMaterial.diffuseTexture.vScale = -1;
object.material = earthMaterial;
###1-5、地球の自転をアニメーションで表示させる
次に地球を自転させます。 自転のアニメーションに関する追加部分を次に示します。
「Babylon_105_animated_earth.html」: 地球の自転をアニメーション表示
// Create animation
var earthAxis = new BABYLON.Vector3(Math.sin(23.4/180 * Math.PI), Math.cos(23.4/180 * Math.PI), 0); // 自転の傾きを設定
var angle = 0.003; // 回転速度(1フレーム毎の回転角:ラジアン)を設定
scene.registerBeforeRender(function() {
object.rotate(earthAxis, angle, BABYLON.Space.WORLD);
})
##Step-2:色々な背景を表示する
Step-2では、色々な背景を表示させてみます。 なお、使用する球も色々なマテリアルで飾ってみました。
###2-1、360度の背景に色々な「skybox」を使用する
Babylon.jsのPlaygroundにデフォルトで保存されている次表の「skybox」を使用して色々な背景を画面上部のメニューから選択して表示できるようにしました。
「Babylon_201_skybox.html」: 背景「skybox」を選択して表示を実行
背景の選択部分のHTML部とJavaScript部を以下に示します。
<nav id="menu-wrap">
<ul id="menu">
<li><a href="#">Menu</a>
<ul id="information">
<li><a>Please select the Environment.</a></li>
</ul>
</li>
<li><a href="#">Environment</a>
<ul id="scroll">
<li><a><input type = "radio" name ="Env_Layer" value = "0" checked onclick = "javascript: Sel_Env_Layer();">skybox</a></li>
<li><a><input type = "radio" name ="Env_Layer" value = "1" onclick = "javascript: Sel_Env_Layer();">skybox2</a></li>
<li><a><input type = "radio" name ="Env_Layer" value = "2" onclick = "javascript: Sel_Env_Layer();">skybox3</a></li>
<li><a><input type = "radio" name ="Env_Layer" value = "3" onclick = "javascript: Sel_Env_Layer();">skybox4</a></li>
<li><a><input type = "radio" name ="Env_Layer" value = "4" onclick = "javascript: Sel_Env_Layer();">nebula</a></li>
<li><a><input type = "radio" name ="Env_Layer" value = "5" onclick = "javascript: Sel_Env_Layer();">TropicalSunnyDay</a></li>
</ul>
</li>
</ul>
</nav>
// Select the Environment Texture
function Sel_Env_Layer() {
temp = document.getElementsByName("Env_Layer");
temp_Environment = "./textures/skybox";
if(temp[1].checked) { temp_Environment = "./textures/skybox2"; }
if(temp[2].checked) { temp_Environment = "./textures/skybox3"; }
if(temp[3].checked) { temp_Environment = "./textures/skybox4"; }
if(temp[4].checked) { temp_Environment = "./textures/nebula/nebula"; }
if(temp[5].checked) { temp_Environment = "./textures/TropicalSunnyDay"; }
###2-2、「skybox」に別の地面を合成する
上記2-1項に地面のテクスチャーを合成してみます。 地面のテクスチャーを選択する部分と地面のテクスチャー表示を設定する部分を以下に示します。
「Babylon_202_skybox+ground.html」: 「skybox」と別の地面を合成の実行
<li><a href="#">Ground</a>
<ul id="scroll">
<li><a><input type = "radio" name = "Ground_Layer" value = "0" checked onclick = "javascript: Sel_Ground_Layer();">dirt</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "1" onclick = "javascript: Sel_Ground_Layer();">grass_01</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "2" onclick = "javascript: Sel_Ground_Layer();">distortion</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "3" onclick = "javascript: Sel_Ground_Layer();">floor</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "4" onclick = "javascript: Sel_Ground_Layer();">grass_02</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "5" onclick = "javascript: Sel_Ground_Layer();">grass_03</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "6" onclick = "javascript: Sel_Ground_Layer();">ground</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "7" onclick = "javascript: Sel_Ground_Layer();">rock</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "8" onclick = "javascript: Sel_Ground_Layer();">rockyGround_basecolor</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "9" onclick = "javascript: Sel_Ground_Layer();">sand</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "10" onclick = "javascript: Sel_Ground_Layer();">speckles</a></li>
<li><a><input type = "radio" name = "Ground_Layer" value = "11" onclick = "javascript: Sel_Ground_Layer();">wood</a></li>
</ul>
</li>
// Select the Ground Texture
function Sel_Ground_Layer() {
temp_G = document.getElementsByName("Ground_Layer");
temp_Ground = "./textures/customProceduralTextures/land/textures/dirt.jpg";
if(temp_G[1].checked) { temp_Ground = "./textures/customProceduralTextures/land/textures/grass.png"; }
if(temp_G[2].checked) { temp_Ground = "./textures/distortion.png"; }
if(temp_G[3].checked) { temp_Ground = "./textures/floor.png"; }
if(temp_G[4].checked) { temp_Ground = "./textures/grass.jpg"; }
if(temp_G[5].checked) { temp_Ground = "./textures/grass.png"; }
if(temp_G[6].checked) { temp_Ground = "./textures/ground.jpg"; }
if(temp_G[7].checked) { temp_Ground = "./textures/rock.png"; }
if(temp_G[8].checked) { temp_Ground = "./textures/rockyGround_basecolor.png"; }
if(temp_G[9].checked) { temp_Ground = "./textures/sand.jpg"; }
if(temp_G[10].checked) { temp_Ground = "./textures/speckles.jpg"; }
if(temp_G[11].checked) { temp_Ground = "./textures/wood.jpg"; }
// Ground
var ground = BABYLON.Mesh.CreateGround("ground", 1000, 1000, 1, scene, false);
var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
groundMaterial.diffuseTexture = new BABYLON.Texture(temp_Ground, scene);
groundMaterial.diffuseTexture.uScale = 60;
groundMaterial.diffuseTexture.vScale = 60;
groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
ground.position.y = -3;
ground.material = groundMaterial;
地面にデコボコを追加したものと水面(揺らぎ付)の場合
// Grass with HeightMap
if(temp_Ground == "./textures/grass.png") {
var ground = BABYLON.Mesh.CreateGroundFromHeightMap("ground", "./textures/heightMap.png", 200, 100, 200, 0, 10, scene, false);
var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
groundMaterial.diffuseTexture = new BABYLON.Texture(temp_Ground, scene);
groundMaterial.diffuseTexture.uScale = 6;
groundMaterial.diffuseTexture.vScale = 6;
groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
ground.position.y = -2.05;
ground.material = groundMaterial;
object_1.translate(new BABYLON.Vector3(-4, 8, 0), 1, BABYLON.Space.WORLD);
object_2.translate(new BABYLON.Vector3(-2, 8, 0), 1, BABYLON.Space.WORLD);
object_3.translate(new BABYLON.Vector3(0, 8, 0), 1, BABYLON.Space.WORLD);
object_4.translate(new BABYLON.Vector3(2, 8, 0), 1, BABYLON.Space.WORLD);
object_5.translate(new BABYLON.Vector3(4, 8, 0), 1, BABYLON.Space.WORLD);
object_6.translate(new BABYLON.Vector3(1, 10, 0), 1, BABYLON.Space.WORLD);
object_7.translate(new BABYLON.Vector3(-1, 10, 0), 1, BABYLON.Space.WORLD);
object_8.translate(new BABYLON.Vector3(1, 6, 0), 1, BABYLON.Space.WORLD);
object_9.translate(new BABYLON.Vector3(-1, 6, 0), 1, BABYLON.Space.WORLD);
ground.translate(new BABYLON.Vector3(0, -5, 0), 1, BABYLON.Space.WORLD);
camera.setPosition(new BABYLON.Vector3(0, 20, -30));
}
// Water
if(temp_Ground == "./textures/waterbump.png") {
var groundMaterial = new BABYLON.StandardMaterial("groundMaterial", scene);
groundMaterial.diffuseTexture = new BABYLON.Texture("./textures/ground.jpg", scene);
groundMaterial.diffuseTexture.uScale = groundMaterial.diffuseTexture.vScale = 4;
var ground = BABYLON.Mesh.CreateGround("ground", 1000, 1000, 32, scene, false);
ground.position.y = -1;
ground.material = groundMaterial;
var waterMesh = BABYLON.Mesh.CreateGround("waterMesh", 1000, 1000, 32, scene, false);
var water = new BABYLON.WaterMaterial("water", scene);
water.bumpTexture = new BABYLON.Texture(temp_Ground, scene);
water.windForce = -15;
water.waveHeight = 1.3;
water.windDirection = new BABYLON.Vector2(1, 1);
water.waterColor = new BABYLON.Color3(0.1, 0.1, 0.6);
water.colorBlendFactor = 0.3;
water.bumpHeight = 0.1;
water.waveLength = 0.1;
water.addToRenderList(skybox);
water.addToRenderList(ground);
waterMesh.material = water;
object_1.translate(new BABYLON.Vector3(-4, 14, 0), 1, BABYLON.Space.WORLD);
object_2.translate(new BABYLON.Vector3(-2, 14, 0), 1, BABYLON.Space.WORLD);
object_3.translate(new BABYLON.Vector3(0, 14, 0), 1, BABYLON.Space.WORLD);
object_4.translate(new BABYLON.Vector3(2, 14, 0), 1, BABYLON.Space.WORLD);
object_5.translate(new BABYLON.Vector3(4, 14, 0), 1, BABYLON.Space.WORLD);
object_6.translate(new BABYLON.Vector3(1, 16, 0), 1, BABYLON.Space.WORLD);
object_7.translate(new BABYLON.Vector3(-1, 16, 0), 1, BABYLON.Space.WORLD);
object_8.translate(new BABYLON.Vector3(1, 12, 0), 1, BABYLON.Space.WORLD);
object_9.translate(new BABYLON.Vector3(-1, 12, 0), 1, BABYLON.Space.WORLD);
ground.translate(new BABYLON.Vector3(0, -5, 0), 1, BABYLON.Space.WORLD);
camera.setPosition(new BABYLON.Vector3(0, 60, 20));
camera.beta = 60/180*Math.PI;
}
###2-3、360度の背景に色々なDDS(DirectDraw Surface) ファイルを使用する
Step-2の最後は「skybox」の代わりに次表のDDSファイルを使用してみます。 このDDSファイルもBabylon.jsのPlaygroundに保存されていますが、諸般の事情により大きなサイズのファイルは使用していませんので、興味がありましたら御自身でダウンロードし使用してみてください。
また、DDSファイルの選択部分は、上記と同様ですので割愛します。
「Babylon_203_dds.html」: DDSファイルの背景を選択して表示する
var canvas = document.getElementById("renderCanvas");
var temp = null;
var temp_Texture = "./textures/environment.dds";
var createScene = function () {
var scene = new BABYLON.Scene(engine);
var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 4, Math.PI / 2.5, 200, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
camera.minZ = 0.1;
var hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(temp_Texture, scene);
scene.imageProcessingConfiguration.exposure = 0.6;
scene.imageProcessingConfiguration.contrast = 1.6;
// Skybox
var hdrSkybox = BABYLON.Mesh.CreateBox("hdrSkyBox", 1000.0, scene);
var hdrSkyboxMaterial = new BABYLON.PBRMaterial("skyBox", scene);
hdrSkyboxMaterial.backFaceCulling = false;
hdrSkyboxMaterial.reflectionTexture = hdrTexture.clone();
hdrSkyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
hdrSkyboxMaterial.microSurface = 1.0;
hdrSkyboxMaterial.disableLighting = true;
hdrSkybox.material = hdrSkyboxMaterial;
hdrSkybox.infiniteDistance = true;
##Step-3:色々なグラフィック・ファイルを表示する
Babylon.jsでは、拡張子babylonの3Dグラフィック・ファイル以外にGLTF、OBJ、STLの拡張子を持つフォーマットのファイルに対応しています。 ここでは、それぞれの3Dグラフィックを表示させてみます。
###3-1、Babylon.jsのデモを表示してみる
Babylon.jsでは、色々なデモが用意されていますのでその一部を表示させてみます。 残念ながら大きな容量のファイルは保存していませんので、興味がありましたらBabylon.jsからダウンロードしてみて下さい。
「Babylon_301_Sel_babylon.html」: Babylon.jsのデモを表示
var canvas = document.getElementById("renderCanvas");
var engine = new BABYLON.Engine(canvas, true);
var temp_dir = "./scenes/babylon/ActionBuilder/";
var temp_file = "ActionBuilder.babylon";
var createScene = function() {
BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental = true;
BABYLON.SceneLoader.Load(temp_dir, temp_file, engine, function (scene) {
scene.activeCamera.attachControl(canvas);
if (scene.activeCamera.keysUp) {
scene.activeCamera.keysUp.push(90); // Z
scene.activeCamera.keysUp.push(87); // W
scene.activeCamera.keysDown.push(83); // S
scene.activeCamera.keysLeft.push(65); // A
scene.activeCamera.keysLeft.push(81); // Q
scene.activeCamera.keysRight.push(69); // E
scene.activeCamera.keysRight.push(68); // D
}
scene.executeWhenReady(function() {
engine.runRenderLoop(function() {
scene.render();
});
});
});
};
var scene = createScene();
###3-2、Babylonフォーマットの3Dグラフィックを表示する
次にBabylonフォーマットの3Dグラフィック・データと背景を表示させてみます。 背景の表示は前述と同様ですので、Babylonフォーマットの3Dグラフィックを表示する部分を以下に示します。
「Babylon_302_Sel_BG&babylon.html」: Babylonフォーマットの3Dグラフィックを表示
var canvas = document.getElementById("renderCanvas");
var temp_dir = "./scenes/babylon/Dude/";
var babylon_data = "dude.babylon";
var temp = null;
var temp_Texture = "./textures/environment.dds";
var engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true });
var CreateScene = function() {
var scene = new BABYLON.Scene(engine);
BABYLON.SceneLoader.ImportMesh("", temp_dir, babylon_data, scene, function (mashes) {
scene.createDefaultCameraOrLight(true, true, true);
});
var camera = new BABYLON.ArcRotateCamera("Camera", 1, 0.8, 10, new BABYLON.Vector3(0, 0, 0), scene);
camera.attachControl(canvas, true);
var hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(temp_Texture, scene);
scene.imageProcessingConfiguration.exposure = 0.6;
scene.imageProcessingConfiguration.contrast = 1.6;
###3-3、GLTFフォーマットの3Dグラフィックを表示する
GLTFフォーマットの3Dグラフィック・データを表示するために「babylon.glTFFileLoader.js」を使用してみました。 他は、上記3-2項と同様です。
「Babylon_303_gltf.html」: GLTFフォーマットの3Dグラフィックを表示
###3-4、OBJフォーマットの3Dグラフィックを表示する
OBJフォーマットの3Dグラフィックを表示するために「babylon.objFileLoader.js」を使用してみました。他は上記と同様です。
「Babylon_304_obj.html」: OBJフォーマットの3Dグラフィックを表示
###3-5、STLフォーマットの3Dグラフィックを表示する
STLフォーマットの3Dグラフィックを表示するために「babylon.stlFileLoader.js」を使用しました。 他は上記と同様です。 なお、ここでは上記2-3項で使用したテクスチャーを適用してみました。
「Babylon_305_stl.html」: STLフォーマットの3Dグラフィックを表示
##Step-4:カメラワーク
Babylon.jsでは、「Camera Work(英語)」にあるように「Universal Camera」、「Arc Rotate Camera」、「FollowCamera」、「AnaglyphCameras」、「Device Orientation Camera」、「Virtual Joysticks Camera」、「VR Device Orientation Cameras」、「WebVR Free Camera」及び「FlyCamera」のカメラが使用可能です。 このうち幾つかのカメラをサンプルとして使用してみました。
以下にカメラの指定部分を示します。
「Babylon_401_cameras.html」 色々なカメラワークを表示
var createScene = function () {
var scene = new BABYLON.Scene(engine);
if(temp_camera_id == 1) {
var camera = new BABYLON.UniversalCamera("Camera", new BABYLON.Vector3(120, 60, 120), scene);
camera.setTarget(BABYLON.Vector3.Zero());
} else if(temp_camera_id == 2) {
var camera = new BABYLON.FollowCamera("Camera", new BABYLON.Vector3(0, 100, 400), scene);
camera.radius = 30;
camera.heightOffset = 50;
camera.rotationOffset = 0;
camera.cameraAcceleration = 0.005;
camera.maxCameraSpeed = 1;
} else if(temp_camera_id == 3) {
var camera = new BABYLON.DeviceOrientationCamera("Camera", new BABYLON.Vector3(120, 60, -120), scene);
camera.setTarget(new BABYLON.Vector3(0, 0, -10));
camera.angularSensibility = 1200;
camera.moveSensibility = 200;
} else if(temp_camera_id == 4) {
var camera = new BABYLON.FlyCamera("Camera", new BABYLON.Vector3(0, 50, -250), scene);
camera.rollCorrect = 10;
camera.bankedTurn = true;
camera.bankedTurnLimit = Math.PI / 2;
camera.bankedTurnMultiplier = 1;
} else {
var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 4, Math.PI / 2.5, 200, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
camera.minZ = 0.1;
}
var hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(temp_Texture, scene);
scene.imageProcessingConfiguration.exposure = 0.6;
scene.imageProcessingConfiguration.contrast = 1.6;
##Step-5: 歩行アニメーションの表示
Step-5として、3Dキャラクターを歩かせてみます。 ここで使用する3Dキャラクター「
デフォルメ人物キャラクター(女の子)」は PROJECT-6B様からダウンロードさせて戴きました。 また、歩行アニメーション本体は、Blenderを使用してかんたんBlender講座を参考に作成し、GLTFファイルとして保存しました。
###5-1、単純な歩行アニメーション
初めに時計回りにゆっくり歩行する単純なアニメーションを表示させてみます。 Babylon.jsでの表示では、上記「3-3、GLTFフォーマットの3Dグラフィックを表示する」の一部を使用し、加えて3Dキャラクターに影を付けた他カメラでトレースさせてみました。
Babylon_501_walk.htmlにおける歩行並びに影を付ける部分を以下に示します。
「Babylon_501_walk.html」 女の子の歩行アニメーションを表示
var createScene = function() {
var scene = new BABYLON.Scene(engine);
BABYLON.SceneLoader.ImportMesh("", temp_dir, gltf_data, scene, function (newMeshes, particleSystems, skeletons) {
scene.createDefaultCameraOrLight(true);
obj = newMeshes[0];
obj.rotationQuaternion = undefined;
camera.target = obj; // Trace the Object
scene.createDefaultCamera(0, 0, 0);
var objAxis = new BABYLON.Vector3(0, Math.PI/2, 0);
// Shadow
var shadowGenerator = new BABYLON.ShadowGenerator(1024, light);
shadowGenerator.useContactHardeningShadow = true;
shadowGenerator.addShadowCaster(obj);
obj.receiveShadows = true;
// Circle Walk
scene.registerBeforeRender(function() {
walk_dir += walk_step;
obj.position.x = area_radius * Math.sin(walk_dir);
obj.position.z = area_radius * Math.cos(walk_dir);
obj.rotation.y = -90/180 * Math.PI + walk_dir;
});
});
~
// 地面に影を落とす設定
ground.receiveShadows = true;
###5-2、3D迷路ゲームの作成
次に3D迷路ゲームを作成してみます。 迷路本体は、「
5分でできる迷路・自動生成アルゴリズム」に基づき作成し、Babylon.jsでの表示は「Coding4Fun tutorial: creating a 3D WebGL procedural QRCode maze with Babylon.js」を参考にさせて戴きました。 ここでは、画面上部のメニューにより迷路のサイズを選択できる他、画面左側をクリックすると現れるVertual Joystickにより女の子の歩行を制御します。 歩行スピードが速くなってもアニメーションは変わりませんので違和感があると思いますが、ご愛嬌ということでお許しください。 なお、Android端末では動作しませんでした。
「Babylon_502_maze.html」 女の子の歩行アニメーションによる3D迷路ゲームの実行
以下に「Babylon_502_maze.html」における迷路表示部分と歩行制御部分を示します。
// Create a Maze
for (var row = 0; row < Maze_size; row++) {
for (var col = 0; col < Maze_size; col++) {
if(ROOM[row].substr(col, 1) == "W") {
var soloCube = BABYLON.Mesh.CreateBox("mainCube", BLOCK_SIZE, scene);
soloCube.subMeshes = [];
soloCube.subMeshes.push(new BABYLON.SubMesh(0, 0, 4, 0, 6, soloCube));
soloCube.subMeshes.push(new BABYLON.SubMesh(1, 4, 20, 6, 30, soloCube));
soloCube.rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(0, -Math.PI / 2, 0);
soloCube.material = cubeMultiMat;
soloCube.checkCollisions = true;
soloCube.position = new BABYLON.Vector3(BLOCK_SIZE / 2 + (row - (Maze_size / 2)) * BLOCK_SIZE, BLOCK_SIZE / 2, BLOCK_SIZE / 2 + (col - (Maze_size / 2)) * BLOCK_SIZE);
}
if(ROOM[row].substr(col, 1) == "G") {
diamond_01.position = new BABYLON.Vector3(BLOCK_SIZE / 2 + (row - (Maze_size / 2)) * BLOCK_SIZE, BLOCK_SIZE / 2, BLOCK_SIZE / 2 + (col - (Maze_size / 2)) * BLOCK_SIZE);
diamond_01.position.y = 20;
light2.position = new BABYLON.Vector3(BLOCK_SIZE / 2 + (row - (Maze_size / 2)) * BLOCK_SIZE, BLOCK_SIZE / 2, BLOCK_SIZE / 2 + (col - (Maze_size / 2)) * BLOCK_SIZE);
light2.position.y = 20;
plane_01.position.y = -6;
Goal_x = BLOCK_SIZE / 2 + (row - (Maze_size / 2)) * BLOCK_SIZE;
Goal_z = BLOCK_SIZE / 2 + (col - (Maze_size / 2)) * BLOCK_SIZE;
}
if(ROOM[row].substr(col, 1) == "P") {
x = BLOCK_SIZE / 2 + (row - (Maze_size / 2)) * BLOCK_SIZE;
z = BLOCK_SIZE / 2 + (col - (Maze_size / 2)) * BLOCK_SIZE;
diamond_02.position = new BABYLON.Vector3(x, BLOCK_SIZE / 2, z);
diamond_02.position.y = 20;
light3.position = new BABYLON.Vector3(x, BLOCK_SIZE / 2, z);
light3.position.y = 20;
plane_02.position.y = -6;
BABYLON.SceneLoader.ImportMesh("", temp_dir, gltf_data, scene, function (newMeshes, particleSystems, skeletons) {
scene.createDefaultCameraOrLight(true);
var obj = newMeshes[0];
obj.rotationQuaternion = undefined;
obj.scaling = new BABYLON.Vector3(0.05, 0.06, 0.05);
obj.position = new BABYLON.Vector3(x, y, z);
obj.rotation.y = -90/180 * Math.PI + walk_dir;
camera.target = obj;
scene.createDefaultCamera(0, 0, 0);
var shadowGenerator = new BABYLON.ShadowGenerator(512, light0);
shadowGenerator.useContactHardeningShadow = true;
shadowGenerator.addShadowCaster(obj);
obj.receiveShadows = true;
scene.registerBeforeRender(function() {
if((moveX == -1) && (obj.position.x <= (BLOCK_SIZE * (Maze_size + 2) / -2) + 2)) {moveX = 0;}
if((moveX == 1) && (obj.position.x >= (BLOCK_SIZE * (Maze_size + 2) / 2) - 2)) {moveX = 0;}
if((moveZ == -1) && (obj.position.z <= (BLOCK_SIZE * (Maze_size + 2) / -2) + 2)) {moveZ = 0;}
if((moveZ == 1) && (obj.position.z >= (BLOCK_SIZE * (Maze_size + 2) / 2) - 2)) {moveZ = 0;}
x = obj.position.x;
z = obj.position.z;
pos_row_00 = Math.round(((x - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size / 2));
pos_row_01 = Math.round((((x + limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size / 2));
pos_row_02 = Math.round((((x - limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size / 2));
pos_col_00 = Math.round(((z - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size / 2));
pos_col_01 = Math.round((((z + limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size / 2));
pos_col_02 = Math.round((((z - limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size / 2));
if((moveX == 1) && (Temp_Room[pos_row_01 + 2].substr(pos_col_00 + 2, 1) == "W")) { moveX = 0; }
if((moveX == -1) && (Temp_Room[pos_row_02 + 2].substr(pos_col_00 + 2, 1) == "W")) { moveX = 0; }
if((moveZ == 1) && (Temp_Room[pos_row_00 + 2].substr(pos_col_01 + 2, 1) == "W")) { moveZ = 0; }
if((moveZ == -1) && (Temp_Room[pos_row_00 + 2].substr(pos_col_02 + 2, 1) == "W")) { moveZ = 0; }
obj.position.x = x + walk_step * moveX;
obj.position.y = 0;
obj.position.z = z + walk_step * moveZ;
obj.rotation.y = walk_dir;
});
});
}
}
}
// Create joystick and set z index to be below playgrounds top bar
var leftJoystick = new BABYLON.VirtualJoystick(true);
// var rightJoystick = new BABYLON.VirtualJoystick(false);
BABYLON.VirtualJoystick.Canvas.style.zIndex = "4";
// Game: Render loop
scene.onBeforeRenderObservable.add(()=>{
if(leftJoystick.pressed){
if(leftJoystick.deltaPosition.x <= -0.5) {
walk_dir = 0 / 180 * Math.PI;
moveX = 0;
moveZ = 1;
walk_step = walk_org * Math.abs(leftJoystick.deltaPosition.x);
} else if(leftJoystick.deltaPosition.x >= 0.5) {
walk_dir = 180 / 180 * Math.PI;
moveX = 0;
moveZ = -1;
walk_step = walk_org * Math.abs(leftJoystick.deltaPosition.x);
} else {
moveZ = 0;
}
if(leftJoystick.deltaPosition.y <= -0.5) {
walk_dir = -90 / 180 * Math.PI;
moveX = -1;
moveZ = 0;
walk_step = walk_org * Math.abs(leftJoystick.deltaPosition.y);
} else if(leftJoystick.deltaPosition.y >= 0.5) {
walk_dir = 90 / 180 * Math.PI;
moveX = 1;
moveZ = 0;
walk_step = walk_org * Math.abs(leftJoystick.deltaPosition.y);
} else {
moveX = 0;
}
}
// if(rightJoystick.pressed){
//
// }
});
// Create button to toggle joystick overlay canvas
var btn = document.createElement("button");
btn.innerText = "Enable/Disable Joystick";
btn.style.zIndex = 10;
btn.style.position = "absolute";
btn.style.bottom = "50px";
btn.style.right = "0px";
document.body.appendChild(btn);
// Button toggle logic
btn.onclick = ()=>{
if(BABYLON.VirtualJoystick.Canvas.style.zIndex == "-1"){
BABYLON.VirtualJoystick.Canvas.style.zIndex = "4";
}else{
BABYLON.VirtualJoystick.Canvas.style.zIndex = "-1";
}
}
// Dispose button on rerun
scene.onDisposeObservable.add(()=>{
document.body.removeChild(btn);
});
var angle = 0.03;
var plane_Axis = new BABYLON.Vector3(0, 90, 0);
var beforeRenderFunction = function () {
diamond_01.rotate(plane_Axis, angle, BABYLON.Space.WORLD);
diamond_02.rotate(plane_Axis, angle, BABYLON.Space.WORLD);
if((Math.round(x / 8) == Math.round(Goal_x / 8)) && (Math.round(z / 8) == Math.round(Goal_z / 8))) {
var advancedTexture_03 = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
var text1 = new BABYLON.GUI.TextBlock();
text1.text = "GOAL !";
text1.color = "red";
text1.fontSize = 100;
advancedTexture_03.addControl(text1);
}
};
迷路本体を作成するJavaScriptを以下に示します。
// This program is based on "https://matome.naver.jp/odai/2141170552198835001" by Mr. tan23ae.
// by T. Fujita
var Maze = function(X, Y) {
var w = X; // 幅(奇数)
var h = Y; // 高さ(奇数)
if(w < 11) {w = 11;}
if(h < 11) {h = 11;}
if(w % 2 == 0) {w = w - 1;}
if(h % 2 == 0) {h = h - 1;}
var x;
var y;
var maze = new Array();
var results = new Array();
// 準備
for (y = 0; y < h; y++) {
maze[y] = new Array();
for (x = 0; x < w; x++) {
if (y == 0 || y == h -1 || x == 0 || x == w - 1) {
maze[y][x] = "W"; // 外周
} else if (y % 2 == 0 && x % 2 == 0) {
maze[y][x] = "W"; // [偶数][偶数]
} else {
maze[y][x] = "F"; // その他
}
}
}
// 壁位置
for (y = 2; y < h - 2; y += 2) {
for (x = 2; x < w - 2; x += 2) {
var n;
if (y == 2) { // 一番上の段
if (maze[y][x-1] == "W") {
n = rand(0, 2);
} else {
n = rand(0, 3);
}
} else {
if (maze[y][x-1] == "W") {
n = rand(1, 2);
} else {
n = rand(1, 3);
}
}
switch (n) {
case 0: // 上
maze[y - 1][x] = "W";
break;
case 1: // 右
maze[y][x + 1] = "W";
break;
case 2: // 下
maze[y + 1][x] = "W";
break;
default: // 左
maze[y][x - 1] = "W";
break;
}
}
}
var i = 0;
while (maze[0][i] == "W") {
if(maze[1][i + 1] == "F") {maze[0][i + 1] = "P";}
i = i + 1;
}
i = maze[0].length - 2;
var j = maze.length - 1;
while(maze[j][i] == "W") {
if(maze[j - 1][i - 1] == "F") {maze[j][i - 1] = "G";}
i = i - 1;
}
// alert(maze[0] + "\n" + maze[1]);
for (y = 0; y < h; y++) {
results[y] = [];
for (x = 0; x < w; x++) {
results[y] = results[y] + maze[y][x];
}
}
// alert(results[0] + "\n" + results[1]);
return results;
}
// 乱数取得
var rand = function(min, max) {
return Math.floor(Math.random() * (max + 1 - min) + min);
};
#4、Reference
- Babylon.js: Home page of Babylon.js
- Poly: 3D graphincs
- かんたんBlender講座: 高知工科大学のかんたんBlender講座
- PROJECT-6B: デフォルメ人物キャラクター(女の子)のダウンロード・サイト
- Coding4Fun tutorial: creating a 3D WebGL procedural QRCode maze with Babylon.js: Babylon.jsによる迷路作成の解説
- 「
5分でできる迷路・自動生成アルゴリズム」 - Babylon.jsで3Dゲームを作成してみた(その1:迷路編)
- Babylon.jsで3Dゲームを作成してみた(その2:脱出パズル編)
- Babylon.jsで空中散歩はいかが?
以上