お仕事で babylon.js を使うことになったので、勉強したことをまとめます。
承前
babylon.js については 公式のチュートリアル が充実しているので、基本的にそれだけで事足ります。
チュートリアルを一通り読んで基礎事項を押さえれば、あとは必要に応じて HOW TO や API を調べるだけでよい状態になれます。
この記事では、公式のチュートリアルでは少しわかりづらいと感じた箇所について、補足事項を書いていきます。
なお、この記事は随時更新予定です。
Material
Color
3D の物体表面の色は、光に含まれる各色の強度と物体表面の各色の反射率の積で計算されます。
例えば、光の RGB の各色の強度が (1,0,1)
で物体の反射率が (1,0,0)
だとすると、それぞれの要素の値を掛け合わせた (1,0,0)
が最終的に見える物体表面の色になります。同様に、反射率が (0,1,0)
の物体なら、最終的な色は (0,0,0)
になります。
Material では、この計算に使用される物体の各色の反射率を定義できます。
定義は以下のように、Material オブジェクトのプロパティへの設定を通して行います。
var material = new BABYLON.StandardMaterial("material", scene);
material.diffuseColor = new BABYLON.Color3(1, 0, 1);
material.specularColor = new BABYLON.Color3(0, 1, 0);
material.emissiveColor = new BABYLON.Color3(0, 0, 1);
material.ambientColor = new BABYLON.Color3(1, 1, 1);
mesh.material = material;
上記のプロパティは、それぞれが特定の光に対する物体の色の反射率を表しています。
以下、それぞれのプロパティの詳細について説明します。
diffuseColor
光源からの光に対する物体の反射率を設定します。
ここでいう光源とは ~Light
系のコンストラクタを使用して生成されるオブジェクトを指します。HemisphericLight
や SpotLight
などが該当します。
各光源には diffuse
プロパティを介して光に含まれる各色の強度を設定できます。
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.diffuse = new BABYLON.Color3(1, 1, 1);
diffuseColor
に白・赤・緑を設定した Material を作成し、上記のコードで定義した光源下でどのように見えるかを確認してみます。それぞれの Material の定義は以下の通りです。
var white = new BABYLON.StandardMaterial("white", scene);
white.diffuseColor = new BABYLON.Color3(1, 1, 1);
var red = new BABYLON.StandardMaterial("red", scene);
red.diffuseColor = new BABYLON.Color3(1, 0, 0);
var green = new BABYLON.StandardMaterial("green", scene);
green.diffuseColor = new BABYLON.Color3(0, 1, 0);
結果は以下のようになります。
光源からの光が RGB の全ての成分を含んでいるので、箱がそれぞれ白・赤・緑になっているのが確認できます。
See the Pen babylon.js - diffuse sample by dmystk (@dmystk) on CodePen.
ここで光源の diffuse
から緑成分を取り去ってみます。
光源の定義は以下のようになります。
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.diffuse = new BABYLON.Color3(1, 0, 1);
結果は以下のようになります。
光源からの光に緑成分が含まれていないので、白い箱は紫に、緑の箱は黒に変化します。一方で、赤い箱は赤いままになっているのがわかります。
See the Pen babylon.js - diffuse sample 2 by dmystk (@dmystk) on CodePen.
specularColor
反射光の色を定義します。
反射光といっても、物体から反射した光が他の物体に照射されたりするわけではありません。
ここで反射光という言葉が指しているのは、物体が光に照らされた際に、物体表面が光って見えるエフェクトのことです。
言葉で説明してもわかりづらいので、例を示します。
以下の例では specularColor
を (1,1,1)
に設定しています。
視点を動かすと、箱の上の面が光を反射して光る(艶があるように見える)のがわかると思います。
See the Pen babylon.js - specular sample by dmystk (@dmystk) on CodePen.
次の例では specularColor
を (0,0,0)
に設定しています。
視点を動かしても、箱の上の面は特に光りません。
See the Pen babylon.js - specular sample 2 by dmystk (@dmystk) on CodePen.
上の例で見たように、specularColor
では物体表面が光って見えるエフェクトを制御します。
同様に specularColor
を (1,0,0)
に設定すれば、反射光を赤く見せることもできます。
See the Pen babylon.js - specular sample 3 by dmystk (@dmystk) on CodePen.
なお diffuse
と同様、こちらも specular
プロパティを通して、光源が放つ specularColor
に作用する光の色を設定できます。
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.specular = new BABYLON.Color3(1, 1, 1);
emissiveColor
物体表面それ自体が光っている状態を表現できます。
光源がない環境下でも明るく見えます。
以下の例では emissiveColor
に (1,0,0)
を設定しています。
光源は生成していませんが、箱が真っ赤に表示されているのがわかると思います。
See the Pen babylon.js - emissive sample by dmystk (@dmystk) on CodePen.
なお、光源によって emissiveColor
が設定された面が照らされた場合、光源の光の色(厳密には diffuse
と diffuseColor
によって生じる色)と emissiveColor
の色が混色されます。
上の例に青色 (0,0,1)
の光を放つ光源を追加した例を以下に示します。
See the Pen babylon.js - emissive sample 2 by dmystk (@dmystk) on CodePen.
ambientColor
環境光に対する物体の色の反射率を定義します。
babylon.js の scene には環境光という特別な光が設定されています。
環境光には、以下のように ambientColor
プロパティを通して色の設定を行います。
scene.ambientColor = new BABYLON.Color3(1, 1, 1);
Material の ambientColor
の初期値は (0,0,0)
のようです。
そのため ambientColor
プロパティに明示的に値を設定しない限り、環境光は効果を発揮しません。
以下の例では環境光を (1,1,1)
に設定していますが、Material には何も設定していません。
See the Pen babylon.js - ambient sample by dmystk (@dmystk) on CodePen.
一方、Material の ambientColor
に (1,0,0)
を設定した場合は以下のようになります。
See the Pen babylon.js - ambient sample 2 by dmystk (@dmystk) on CodePen.
ShaderMaterial
ShaderMaterial
を利用することでオブジェクトに自作シェーダーを適用することができます。
シェーダーについては以下の babylon.js の公式ページに詳しく書いてあります。
全部を説明すると大変な文量になってしまうため、ここではさわりだけ説明します。
ShaderMaterial
経由で自作シェーダーを使用するための必要最低限のコードは以下の通りです:
Effect.ShadersStore["customVertexShader"] = `
// type your vertex shader here
`;
Effect.ShadersStore["customFragmentShader"] = `
// type your fragment shader here
`;
var shaderPath = {
vertex: "custom",
fragment: "custom",
};
var option = {
attributes: ["position", "normal", "uv"],
uniforms: ["world", "worldView", "worldViewProjection", "view", "projection"],
}
var shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, shaderPath, option);
大まかには shaderPath
に "custom"
を指定して Effect.ShadersStore
にカスタムシェーダーのコードを記述するという構成です。シェーダーのコードは GLSL で記述します。
GLSL は Babylon.js の裏にある WebGL の世界でコンパイルされて動作します。この際、Babylon.js は GLSL のコードと一緒に必要なデータを WebGL の世界に引き渡します。
option
で指定している attributes
と uniforms
は Babylon.js から WebGL の世界に引き渡す変数を定義しています。変数には Babylon.js のエンジンが勝手に用意してくれるものとプログラマが自分で宣言するものの2種類あるのですが、ここでは前者のみが対象です。後者については ShaderMaterial
の setFloat
や setTexture
を使用して引き渡します。
文面だけではわかりづらいと思うので例を示します。以下はカスタムシェーダを使ってランダムな色のテクスチャを持つ Box を作成するサンプルです。
See the Pen babylon.js - shader material by dmystk (@dmystk) on CodePen.
以下は Vertex シェーダーを記述している箇所の抜粋です。Vertex シェーダーと Fragment シェーダーについては、先に掲載した Introduction to Shaders を参照ください。
BABYLON.Effect.ShadersStore["customVertexShader"] = `
precision highp float;
// Attributes
attribute vec2 uv;
attribute vec3 position;
// Uniforms
uniform mat4 worldViewProjection;
// Varying
varying vec2 vUV;
void main(void) {
gl_Position = worldViewProjection * vec4(position, 1.0);
vUV = uv;
}
`;
attribute
と uniform
として宣言されているものが Babylon.js から渡される変数群です。
ここで宣言している uv
・position
・worldViewProjection
は、いずれも先ほど option
で指定されていたことに注意してください。
varying
は Vertex シェーダーから Fragment シェーダーに値を渡す際に使用する変数です。これは以下の Fragment シェーダーのコードを見るとわかりやすいです。
BABYLON.Effect.ShadersStore["customFragmentShader"] = `
precision highp float;
// Uniforms
uniform sampler2D uTextureSampler;
// Varying
varying vec2 vUV;
void main(void) {
gl_FragColor = texture2D(uTextureSampler, vUV);
}
`;
Vertex シェーダーの中で varying
として宣言していた vUV
と同名の変数が宣言されています。この vUV
には Vertex シェーダーから渡された値が格納されます。
Fragment シェーダーの中でも uniform
が宣言されていますが、ここで宣言されている uTextureSampler
は先ほどの option
の中には含まれていない変数(=プログラマの自作変数)です。uTextureSampler
の値は以下の箇所で設定されています。
let material = new BABYLON.ShaderMaterial("smat", this.scene, {vertex: "custom", fragment: "custom"}, {
attributes: ["position", "normal", "uv"],
uniforms: ["world", "worldView", "worldViewProjection", "view", "projection"],
});
// 中略
material.setTexture("uTextureSampler", texture);
今回は GLSL の sampler2D
型の値を設定したいので ShaderMaterial
の setTexture
をコールして値を設定しています。
Camera
Multi Canvases
Engine
の registerView()
を使うことで、1つの Babylon.js インスタンスに対して、複数の canvas を割り当てることができます。
それぞれの canvas で異なるカメラを使用することも可能です。
See the Pen babylon.js - multi canvases sample by dmystk (@dmystk) on CodePen.
Safari だと表示が崩れるようです。
Chrome では正常に表示されることを確認済みです。
Orthographic Camera
通常、我々が babylon.js の世界でカメラを作成した場合、そのカメラは透視投影を使用したカメラです。透視投影では、1点を中心に放射状に光線を投影するため、遠くのものが小さく、近くのものが大きく画面に表示されます。
一方で、掲題の垂直投影カメラ(Orthographic Camera)は光線を平行に投影します。光線が平行に投影されるため、遠くのものも近くのものも一定の寸法で画面に表示されます。
例えば、カメラのごく近くに箱があり、その遥か向こう側にその箱よりも少しだけ大きな箱がある場合、透視投影では奥の箱は手前の箱に隠れてしまいますが、垂直投影では両方の箱が見える状態となります。
文面ではわかりづらいので、こちら にある画像を見ていただくのがよいかもしれません。
babylon.js では作成したカメラの mode
に Camera.ORTHOGRAPHIC_CAMERA
を設定することで垂直投影カメラに切り替えることができます。
var camera = new ArcRotateCamera("camera", 0, 0, 20, new Vector3.Zero(), this.scene);
camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
注意点として、光線を平行に投影する形式に変更されるため、投影面の範囲を自分で設定する必要があります。これは各種 orthoXxx
に値を設定することで行えます。画面のアスペクト比も考慮する必要がありますが、これは Engine
から取得することができます。
var scale = 5; // スケールの適切な選び方はよくわかっていないので誰か教えてほしい ...
var aspect = scene.getEngine().getAspectRatio(camera);
camera.orthoTop = scale;
camera.orthoBottom = -scale;
camera.orthoLeft = -scale * aspect;
camera.orthoRight = scale * aspect;
以下に Multi Canvases を使用して透視投影と垂直投影の2つのカメラを比較するサンプルを示します。左が透視投影、右が垂直投影です。初期状態では表面に凹凸のある地形(リボン)を上から俯瞰していますが、見え方が大きく異なることがわかると思います。
画面のアスペクト比がうまく反映できていない感じがしますが、CodePen のソースコードペインを折り畳むとそれらしい表示になるようです。リサイズイベント等を適切に拾うことで解消できるのかもしれませんが、ここでは対応していません。
See the Pen babylon.js - multi canvases sample by dmystk (@dmystk) on CodePen.
ちなみに左のキャンバスのカメラをマウス操作すると、連動して右のキャンバスのカメラも動くようになっているので、ご活用ください。なお、連動するのは回転のみで、拡大縮小には対応できていません。