Babylon.jsで空中散歩はいかが？

#１．概要
3Dグラフィック・エンジンの一つであるBabylon.jsを使用して、空中散歩のデモを作成してみました。　なお、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グラフィックの多くは「[RIG Models](https://rigmodels.com/)」様からダウンロードさせて戴きました。　無料グラフィックを提供されている各位に感謝いたします。

「[完成した空中散歩デモの実行](https://to-fujita.github.io/Babylon.js_3D_Graphics/Cruising_in_the_sky_004.html)」　  
空中散歩デモの画像（例）は次の通りです。　
![Cruising_in_the_Sky.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/1f5c6485-f32c-721f-09e6-c200a018acdb.png)

#２．ファイル構成
ここで説明する内容に関するファイルは全て「[GitHub Babylon.js_3D_Graphics](https://github.com/To-Fujita/Babylon.js_3D_Graphics)」からダウンロード可能です。　また、そのファイル構成は次の通りです。
- scenes: 3Dグラフィック・データを保存したフォルダーです。
- texture: 「Babylon.js on GITHUB」からダウンロードしたskybox等のテクスチュア・データ等を保存したフォルダーです。
- index_Cruising.html: 今回作成した空中散歩デモ用HTMLファイルの選択・実行用メニューです。（注：本メニューは「safari」では動作しませんので悪しからず！）
- Cruising_in_the_sky_001.html - Cruising_in_the_sky_004.html: 今回作成した空中散歩デモ用JavaScriptを含むHTMLファイル本体です。

#３．空中散歩デモの作成
ここでは、周囲の環境と夕暮れ時の空のグラデーション変化、雲の追加表示と途中に配置した３Dグラフィックについて、ステップバイステップで説明します。　なお、Babylon.jsの使い方については、「[Babylon.jsで3Dアニメーションを含むグラフィックを描画してみる](https://qiita.com/halboujp/items/c18daf72019b09a94d44)」で説明していますのでそちらを参照ください。
「[index Cruising in the sky](https://to-fujita.github.io/Babylon.js_3D_Graphics/index_Cruising.html)」： ここで説明するプログラム全てをメニューから選択して表示できます。 但し、このメニューは「Safari」では動作しませんので悪しからず。

## Step-1: 周囲の環境を表示する
[Cruising_in_the_sky_001.html](https://to-fujita.github.io/Babylon.js_3D_Graphics/Cruising_in_the_sky_001.html)：次のような周囲の環境を表示
![Cruising_in_the_Sky_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/f65a5101-6241-6dd5-dfaf-f90ccabec884.png)
周囲の環境として、「skybox」により晴れた海上を表示させました。　また、ライトとカメラも設定してありますので、マウスでカメラの視点を変更できます。　ソースコード全てを以下に示します。


~~~HTML:Cruising_in_the_sky_001.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>Babylon.js - Cruising in the sky with Vertex Shaders #001</title>
<style>
     html,body,canvas { margin: 0; padding: 0; width: 100%; height: 100%; font-size: 0; }
</style>
<script src="https://code.jquery.com/pep/0.4.1/pep.min.js"></script>
<script src="https://preview.babylonjs.com/babylon.js"></script>
<script>

var Camera_Control = "ON";
var CreateVertexDataScene = function(engine) {
    var scene = new BABYLON.Scene(engine);
    var start_time = Date.now();
    var skybox_data = "./textures/TropicalSunnyDay";
    var pos_x = 0;
    var pos_y = -128;
    var pos_z = 0;
    var Limit_z = 5000;

// Set the skybox
    var skybox0 = BABYLON.MeshBuilder.CreateBox("skyBox", {size: Limit_z * 2.0}, scene);
	skybox0.rotation.x = Math.PI * 1.0;
	skybox0.rotation.y = Math.PI * 0.14;
    var skybox0_material = new BABYLON.StandardMaterial('skybox', scene);
	skybox0_material.backFaceCulling = false;
	skybox0_material.reflectionTexture = new BABYLON.CubeTexture(skybox_data, scene);
	skybox0_material.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
	skybox0_material.diffuseColor = new BABYLON.Color3(0, 0, 0);
	skybox0_material.specularColor = new BABYLON.Color3(0, 0, 0);
	skybox0_material.disableLighting = true;
	skybox0.material = skybox0_material;

// Set the light
    var light_1 = new BABYLON.DirectionalLight('light', new BABYLON.Vector3(100, -1000, 100), scene)
	light_1.intensity = 1.0;

// Set the camera
    var camera = new BABYLON.FreeCamera("camera", new BABYLON.Vector3(0, -128, 0), scene);
	camera.fov = 30;
	camera.minZ = 1;
	camera.maxZ = Limit_z * 2.0;

    return scene;
}

var demo = {
    constructor: CreateVertexDataScene,
    onload: function (scene) {
	if (Camera_Control == "ON") {
	    scene.activeCamera.attachControl(canvas);
	}
	else {
	    scene.activeCamera.detachControl(scene.getEngine().getRenderingCanvas());
	}
    }
};

</script>
</head>
<body>
    <canvas id="renderCanvas" touch-action="none"></canvas>
    <div id="notSupported" class="hidden">Sorry but your browser does not support WebGL...</div>
    <script src="loaderCustoms.js"></script>
</body>
</html>
~~~


## Step-2: 時間経過を表現する
[Cruising_in_the_sky_002.html](https://to-fujita.github.io/Babylon.js_3D_Graphics/Cruising_in_the_sky_002.html)：次のような朝方／夕暮れ時に変化するグラデーションを表示
このデモでは、時間と共に朝方から昼、夕暮れ時に徐々に変化するようにcolor gradingを使用しました。
![Cruising_in_the_Sky_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/461a48d6-450d-a35d-3588-0951d80dc298.png)
追加した「color grading」の箇所を以下に示します。　なお、「color grading」については、[Babylon.jsのサンプル](https://www.babylonjs-playground.com/#17VHYI#15)を参照下さい。


~~~Javascript:Cruising_in_the_sky_002.htmlの一部
// Added the color grading
    var colorGrading = new BABYLON.ColorGradingTexture("./textures/LateSunset.3dl", scene);
	skybox0_material.cameraColorGradingTexture = colorGrading;
	skybox0_material.cameraColorGradingEnabled = true;


// Control the color grading
    var j = 0;
    scene.registerBeforeRender(function () {
        colorGrading.level = Math.sin(j++ / 240) * 0.5 + 0.5; 
    });
~~~

## Step-3: 追加で雲を表示させる
[Cruising_in_the_sky_003.html](https://to-fujita.github.io/Babylon.js_3D_Graphics/Cruising_in_the_sky_003.html)：次のように下部に雲を表示
Shaderを使用して雲をたくさん表示しています。　この雲のシェーダーについては[Babylon.jsのShader Material](https://playground.babylonjs.com/#ATDL99#0)を参考に作成しました。
![Cruising_in_the_Sky_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/0fc334a0-d696-c7f5-4b35-fefb58924bd3.png)
追加した雲のシェーダーの箇所は、以下の通りです。


~~~Javascript:Cruising_in_the_sky_003.htmlの一部
// Instrumentation
    var instrumentation = new BABYLON.EngineInstrumentation(engine);
    instrumentation.captureGPUFrameTime = true;
    instrumentation.captureShaderCompilationTime = true;

// Create clouds
    var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, {
        vertexElement: "vertexShaderCode",
        fragmentElement: "fragmentShaderCode"
    },
    {
        needAlphaBlending: true,
        attributes: ["position", "uv"],
        uniforms: ["worldViewProjection"],
        samplers: ["textureSampler"]
    });
    var temp = Math.random() * 3.0;
    var url = "./textures/smoke_15.png";
    if(temp >= 1) {
	url = "./textures/cloud.png";
    } 
    if(temp >= 2) {
	url = "./textures/cloud_01.png";
    }

    cloudMaterial.setTexture("textureSampler", new BABYLON.Texture(url, scene));
    cloudMaterial.setFloat("fogNear", -100);
    cloudMaterial.setFloat("fogFar", Limit_z);
    cloudMaterial.setColor3("fogColor", BABYLON.Color3.FromInts(69, 132, 180));

// Create merged planes
    size = 128;
    var globalVertexData;

    for (var i = 0; i < Limit_z; i++) {
        var planeVertexData = BABYLON.VertexData.CreatePlane({ size: 128 });
        delete planeVertexData.normals;		// We do not need normals

// Transform
	var randomScaling = Math.random() * Math.random() * 1.5 + 0.5;
	var transformMatrix = BABYLON.Matrix.Scaling(randomScaling, randomScaling, 1.0);
	    transformMatrix = transformMatrix.multiply(BABYLON.Matrix.RotationZ(Math.random() * Math.PI));
	    transformMatrix = transformMatrix.multiply(BABYLON.Matrix.Translation(Math.random() * 1000 - 500, -Math.random() * Math.random() * 100, Limit_z - i));

        planeVertexData.transform(transformMatrix);

// Merge
	if (!globalVertexData) {
		globalVertexData = planeVertexData;
	} else {
		globalVertexData.merge(planeVertexData);
	}
    }

    var clouds = new BABYLON.Mesh("Clouds", scene);
    globalVertexData.applyToMesh(clouds);

    clouds.material = cloudMaterial;

    var clouds2 = clouds.clone();
    clouds2.position.z = -500;
~~~

シェーダー本体（vertexShader及びfragmentShader）については、次の通りです。

~~~HTML:Cruising_in_the_sky_003.htmlの一部
<script type="application/vertexShader" id="vertexShaderCode">
	#ifdef GL_ES
	precision highp float;
	#endif

	// Attributes
	attribute vec3 position;
	attribute vec2 uv;

	// Uniforms
	uniform mat4 worldViewProjection;

	// Normal
	varying vec2 vUV;

	void main(void) {
	    gl_Position = worldViewProjection * vec4(position, 1.0);

	    vUV = uv;
	}
</script>

<script type="application/fragmentShader" id="fragmentShaderCode">
	#ifdef GL_ES
	precision highp float;
	#endif

	varying vec2 vUV;

	uniform vec3 fogColor;
	uniform float fogNear;
	uniform float fogFar;

	// Refs
	uniform sampler2D textureSampler;

	void main(void) {
	    float depth = gl_FragCoord.z / gl_FragCoord.w;
	    float fogFactor = smoothstep(fogNear, fogFar, depth);

	    gl_FragColor = texture2D(textureSampler, vUV);
	    gl_FragColor.w *= pow(abs(gl_FragCoord.z), 20.0);
	    gl_FragColor = mix(gl_FragColor, vec4(fogColor, gl_FragColor.w), fogFactor);
	}
</script>
~~~


## Step-4: 船団と行き交う船等を表示させる
[Cruising_in_the_sky_004.html](https://to-fujita.github.io/Babylon.js_3D_Graphics/Cruising_in_the_sky_004.html)：次のように船団と途中の行き交う等を表示
空と雲だけでは寂しいので、次のように船団と行き交う船等を表示させました。　今回は、全てGLTFフォーマットの3Dグラフィックを使用しています。
![Cruising_in_the_Sky_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/9ef0f33f-9d92-1ec4-04a9-9237a7076175.png)
3Dグラフィックの表示用Javascript例を次に示します。　3Dグラフィックそれぞれのサイズや向きを合わせる他、それぞれに動作を設定しています。

~~~Javascript:Cruising_in_the_sky_004.htmlの一部
    var ship = new Array();
    var ship_dir = "./scenes/crusing/";
    var ship_data = new Array();
    var ship_direction = new Array();
    var ship_scale = new Array();

    ship_data[5] = "Sailing_Ship_004_Whale.glb";
    ship_scale[5] = 100;

	BABYLON.SceneLoader.ImportMesh("", ship_dir, ship_data[5], scene, function (newMeshes, particleSystems, skeletons) {
		ship[5] = newMeshes[0];
		ship[5].rotationQuaternion = undefined;
		ship[5].rotation.z = 180/180 * Math.PI;
		ship[5].scaling = new BABYLON.Vector3(ship_scale[5], ship_scale[5], ship_scale[5]);
	    scene.registerBeforeRender(function() {
		ship[5].position = new BABYLON.Vector3( pos_x + 350, pos_y - 0, pos_z * 0 + 3000);
		ship[5].rotation.x = (Math.sin(ship_direction[5].x) * 20 + 0) / 180 * Math.PI;
		ship[5].rotation.y = (Math.sin(ship_direction[5].y) * 20 + 180) / 180 * Math.PI;
		ship[5].rotation.z = (Math.sin(ship_direction[5].z) * 10 + 180) / 180 * Math.PI;
		ship_direction[5].x = ship_direction[5].x + 0.01;
		ship_direction[5].y = ship_direction[5].y + 0.005;
		ship_direction[5].z = ship_direction[5].z + 0.01;
	    });
	});
~~~


#４、Reference
- [Babylon.js](https://www.babylonjs.com/): Home page of Babylon.js  
- [RIG Models](https://rigmodels.com/): 3D Graphics  
- [Babylon.jsで3Dアニメーションを含むグラフィックを描画してみる](https://qiita.com/halboujp/items/c18daf72019b09a94d44)   
- [Babylon.jsで3Dゲームを作成してみた（その１：迷路編）](https://qiita.com/halboujp/items/8b4b07136420c5defb13)　　
- [Babylon.jsで3Dゲームを作成してみた（その２：脱出パズル編）](https://qiita.com/halboujp/items/751c42f3bda10adb7674)　　

以上