Away3D TypeScriptではじめる3次元表現

第1回Away3D TypeScriptで基本的な3次元の形状をつくる

Away3Dはオープンソースのリアルタイム3Dエンジンだ。もともとはFlashプラットフォーム向けに開発された。そのAway3Dエンジンが「Away3D TypeScript」というJavaScriptライブラリに移植されはじめている。現在アルファリリースが公開されている。本連載では、このAway3D TypeScriptによる3次元表現を解説していきたい。

その初めてのお題は、つぎのサンプル1のような床に置いたボールだ[1]⁠。ドラッグすると、カメラ位置を回すことができる。今回を含めた都合3回で仕上げたい。

サンプル1 Away3D 14/08/26 : Panning and tilting the camera in the 3D space

Away3D TypeScriptライブラリを使う

まずは、ライブラリのダウンロードと読込みから説明しよう。Away3D TypeScriptのサイトには、作例やソースファイルのリンクなどがある。最新のライブラリは、⁠Source Files」の欄に示された「GitHub: AwayJS Core TypeScript」「GitHub: StageGL Core TypeScript」および「GitHub: StageGL Extentions」の3つのリンクからダウンロードすすればよい図1⁠。また、サンプルコードとして「GitHub: StageGL Examples」も掲げられている。

図1 Away3D TypeScriptライブラリのソースファイル
図1 Away3D TypeScriptライブラリのソースファイル

サンプルコードなどAway3D TypeScriptを試してみるには、ソースファイルの3つのリンクからダウンロードしたファイルの中の3つのライブラリ「awayjs-core.next.min.js」「stagegl-core.next.min.js」および「stagegl-extensions.next.min.js」を使う図2⁠。これらのJavaScript(JS)ファイルを、これから書くコードのライブラリ用に定めたフォルダ(本連載では「lib」とする)に配置する。

図2 Away3Dで使う3つのライブラリのJSファイル
図2 Away3Dで使う3つのライブラリのJSファイル

HTMLドキュメントには、まずscript要素にAway3Dの3つのJSファイルawayjs-core.next.min.jsとstagegl-core.next.min.jsおよびstagegl-extensions.next.min.jsを読み込む。つぎに、JavaScriptコードには初めに呼び出す関数(initialize())を設け、body要素のonload属性にその呼出しを加える。これで準備が整った。Away3Dのコードを書いていこう。

<script src="lib/awayjs-core.next.min.js"></script>
<script src="lib/stagegl-core.next.min.js"></script>
<script src="lib/stagegl-extensions.next.min.js"></script>
<script>
function initialize() {
  // 初期設定
}
</script>
<body onload="initialize();">

Away3Dの3次元表現で用意しなければならないのは、つぎの3つだ。第1に、舞台となる3次元空間をつくる。第2に、その舞台に照明を備える。そして第3に、役者となる物体を舞台に登場させる。項を改めて、順に説明しよう。

Away3Dで用意しなければならないもの
  1. 舞台:Viewオブジェクトで3次元空間の表示領域を定める
  2. 照明:光源のオブジェクトで3次元空間を照らす
  3. 役者:物体のひながたからインスタンスをつくって3次元空間に加える

Viewクラスで3次元空間の表示領域を定める

第1につくらなければならないのは、3次元空間の表示領域を定めるViewオブジェクトだ。3次元の座標空間や、それを表示領域に映すカメラもオブジェクトの中にもつ。View()コンストラクタの引数はレンダラだ。ここでは、DefaultRendererオブジェクトを渡すことにする。

new away.containers.View(new away.render.DefaultRenderer())

Viewオブジェクトには、プロパティで設定が加えられる表1⁠。もっとも、プロパティの数は多いものの、ほとんどはデフォルト値が定められている。今回は、ごく基本的なプロパティのみ定めることにしよう。

表1 Viewクラスに備わる基本的なプロパティ
Viewクラスのプロパティプロパティの値
width
height
表示される領域の幅と高さ。
backgroundColor画面の背景色。デフォルト値は黒(0x000000⁠⁠。
camera表示領域を描くために用いられるCamera3Dオブジェクト。
scene表示領域を描くもととなる3次元空間のSceneオブジェクト。

Viewオブジェクトをつくって返す関数(createView())は新たに設ける。引数には、表示領域の幅と高さ、および背景色を渡す。

createView(幅, 高さ, 背景色)

関数(createView())の中身は、つぎの抜書きのように定めた。そして、初期設定の関数(initialize())から呼び出す。できあがりのJavaScriptは、後にコード1としてまとめてある。

var view;

function initialize() {

  view = createView(240, 180, 0x0);

}
function createView(width, height, backgroundColor) {
  var defaultRenderer = new away.render.DefaultRenderer();
  var view = new away.containers.View(defaultRenderer);
  view.width = width;
  view.height = height;
  view.backgroundColor = backgroundColor;
  return view;
}

DirectionalLightクラスで平行光源を定める

第2に、光源を定める。3次元空間をつくっても、光がなければ何も見えない。神と同じく光りあれと、光をつくらなければならないのだ。3次元表現で使われる光源はいくつかある。その中でも基本となるのは平行光源⁠directional light)だ。太陽の光のように同じ向き(平行)に進み、距離によって強さが変わらない図3右上⁠⁠。

図3 3次元表現で使われる光源の種類
図3 3次元表現で使われる光源の種類
Tcpp's fileより引用

平行光源は、DirectionalLight()コンストラクタで定める。引数はなくて構わない。この光で、後からつくる物体の表面を照らす。

new away.entities.DirectionalLight()

もっとも、太陽光だけでは、月のように光の当たっていない部分は暗闇になってしまう。地球上では環境光ambient lightがあることで、陰にも光が及ぶ。そこで、DirectionalLightオブジェクトをつくって返す関数(createDirectionalLight())は、環境光の強さとその光の色を引数に与えてつぎのように定める。

createDirectionalLight(環境光の強さ, 光の色)

DirectionalLightクラスは、LightBaseを継承する表2⁠。そして、DirectionalLightオブジェクトのLightBase.ambientプロパティに強さの数値を与えれば、環境光が加わる。また、LightBase.colorプロパティで光の色を定める。さらに、DirectionalLight.directionプロパティには、Vector3Dオブジェクトの3次元ベクトルで光源の位置を与える。

表2 DirectionalLightおよびLightBaseクラスに備わる基本的なプロパティ
DirectionalLightオブジェクトのプロパティプロパティの値
ambient環境光の強さを示す0以上1以下の数値。デフォルト値は0。
color光のカラー値。デフォルト値は白(0xFFFFFF⁠⁠。
direction光源の位置を示す3次元ベクトルのVector3Dオブジェクト。デフォルト値は(0, -1, 1)。

DirectionalLightオブジェクトをつくる関数(createDirectionalLight())は、インスタンスをつくった後、LightBase.ambientおよびLightBase.colorプロパティに引数値を与えて返した。DirectionalLight.directionプロパティはデフォルト値のまま用いる。

function initialize() {
  var directionalLight = createDirectionalLight(0.25, 0x00FFFF);

}

function createDirectionalLight(ambient, color) {
  var light = new away.entities.DirectionalLight();

  light.ambient = ambient;
  light.color = color;
  return light;
}

PrimitiveSpherePrefabクラスから球体のオブジェクトをつくって3次元空間に加える

舞台と照明が整ったので、ようやく第3の、役者となる物体のインスタンスを登場させる。そのためには、まずPrefabBaseのサブクラスから物体のひながた(プレハブ)をつくる。そして、そのひながたの参照に対してPrefabBase.getNewObject()メソッドを呼び出すと、物体のDisplayObjectインスタンスが得られる。

今回は球体をつくるので、ひながたにはPrimitiveSpherePrefabクラスを用いる。コンストラクタの引数は、球の半径、および曲面の滑らかさを決める水平と垂直の分割数だ。デフォルト値は、半径が50、分割数は水平16で垂直12だ。続けて、PrefabBase.getNewObject()メソッドは引数なしに呼び出せる。

new away.prefabs.PrimitiveSpherePrefab(半径, 水平分割数, 垂直分割数)
.getNewObject()

ひながたからPrefabBase.getNewObject()メソッドでつくる物体は、DisplayObjectのサブクラスのMeshオブジェクトだ。その表面素材となるテクスチャをMesh.materialプロパティに定める。今回は、DefaultMaterialManager.getDefaultTexture()で得られるデフォルトのテクスチャを使った。また、表面素材にはそれを照らす光源を、MaterialBase.lightPickerプロパティにStaticLightPickerオブジェクトで与える。StaticLightPicker()コンストラクタに渡す引数は、光源の配列だ。

new away.materials.StaticLightPicker(光源の配列)

球体のMeshオブジェクトをつくって返す関数(createSphere())は、以下のように定めた。引数は球の半径(radius⁠⁠、および水平と垂直の分割数(segmentsHとsegmentsV⁠⁠、そして素材を照らす光源(light)だ。この関数を初期設定の関数(initialize())から呼出した。返される球体のMeshオブジェクトは、View.sceneプロパティで得られる3次元空間のSceneオブジェクト(前掲表1Scene.addChild()メソッドで加える。

var view;
var sphere;
function initialize() {
  var directionalLight = createDirectionalLight(0.25, 0x00FFFF);
  view = createView(240, 180, 0x0);
  sphere = createSphere(300, 32, 24, directionalLight);
  view.scene.addChild(sphere);

}

function createSphere(radius, segmentsH, segmentsV, light) {
  var defaultTexture = away.materials.DefaultMaterialManager.getDefaultTexture();
  var material = new away.materials.TriangleMethodMaterial(defaultTexture);
  var sphere = new away.prefabs.PrimitiveSpherePrefab(radius, segmentsH, segmentsV)
  .getNewObject();
  sphere.material = material;
  material.lightPicker = new away.materials.StaticLightPicker([light]);
  return sphere;
}

ここまで解説したJavaScriptコードをまとめれば、3次元空間に球体をひとつ置くにはあと一歩だ。何が足りないのかというと、このまま試したのでは物体が表れない。Away3D TypeScriptは内部的につくったCanvasに描くため、必ず描画のメソッドView.render()を呼び出さなければならないのだ。

View.render()メソッドの呼出しを加えてまとめたのが以下のコード1になる。これで、3次元空間に球体がひとつ描かれる図4⁠。注意したいのは、コード1View.render()メソッドを2回呼び出していることだ。これは誤りではない。なぜか1回の呼出しだけでは、物体が描かれないために加えてある[2]⁠。確認用に、コード1をjsdo.itにもサンプル2として掲げた[3]⁠。

図4 3次元空間に描かれたひとつの球体
図4 3次元空間に描かれたひとつの球体
コード1 3次元空間に球体をひとつ置く
var view;
var sphere;
function initialize() {
  var directionalLight = createDirectionalLight(0.25, 0x00FFFF);
  view = createView(240, 180, 0x0);
  sphere = createSphere(300, 32, 24, directionalLight);
  view.scene.addChild(sphere);
  view.render();
  view.render();
}
function createView(width, height, backgroundColor) {
  var defaultRenderer = new away.render.DefaultRenderer();
  var view = new away.containers.View(defaultRenderer);
  view.width = width;
  view.height = height;
  view.backgroundColor = backgroundColor;
  return view;
}
function createSphere(radius, segmentsH, segmentsV, light) {
  var defaultTexture = away.materials.DefaultMaterialManager.getDefaultTexture();
  var material = new away.materials.TriangleMethodMaterial(defaultTexture);
  var sphere = new away.prefabs.PrimitiveSpherePrefab(radius, segmentsH, segmentsV)
  .getNewObject();
  sphere.material = material;
  material.lightPicker = new away.materials.StaticLightPicker([light]);
  return sphere;
}
function createDirectionalLight(ambient, color) {
  var light = new away.entities.DirectionalLight();

  light.ambient = ambient;
  light.color = color;
  return light;
}
サンプル2 Away3D 14/08/26: Creating a sphere in the 3D space

球体以外の基本的なかたちをつくる

Away3Dでは球体以外にも、基本的なかたちがつくれるひながたのクラスはいろいろある。3つほど紹介して、今回は締めよう。つぎの表3は、平面と直方体およびドーナッツ型をつくるひながたのクラスのコンストラクタだ。引数として渡す幾何学的な数値に違いがある。だが、ひながたオブジェクトにPrefabBase.getNewObject()メソッドを呼出してMeshオブジェクトが得られることは変わらない。つまり、コンストラクタを差し替えるだけで、これらのかたちの物体がつくれる。

表3 平面・直方体・ドーナッツ型のひながたをつくる
ひながたをつくるコンストラクタ引数の値
PrimitivePlanePrefab
(幅, 高さ)
平面のひながたをつくる。
ー 平面のx軸方向の長さ。デフォルト値は100。
高さ ー 平面のz軸方向の奥行き。デフォルト値は100。
PrimitiveCubePrefab
(幅, 高さ, 奥行き)
直方体のひながたをつくる。
ー 直方体のx軸方向の長さ。デフォルト値は100。
高さ ー 直方体のy軸方向の長さ。デフォルト値は100。
奥行き ー 直方体のz軸方向の長さ。デフォルト値は100。
PrimitiveTorusPrefab
(半径, 太さの半径, 水平分割数, 垂直分割数)
ドーナッツ型のひながたをつくる。
半径 ー ドーナッツ型の大きさを示す半径。デフォルト値は50。
太さの半径 ー ドーナッツ型の太さを示す半径。デフォルト値は50。
水平分割数 ー 曲面の水平方向の分割数。デフォルト値は16。
垂直分割数 ー 曲面の垂直方向の分割数。デフォルト値は8。

ひとつ練習として、前掲コード1の球体をつくる関数(createSphere())を下敷きにして、ドーナッツ型をつくる関数に書き替えてみよう。この関数(createTorus())はつぎのように呼び出して、3次元空間にドーナッツ型を加えたい。呼出す関数を差し替えるだけで、前掲コード1の仕組みは変えない。

var torus = createTorus(300, 32, 24, directionalLight);
view.scene.addChild(torus);
図5 3次元空間に描かれたひとつのドーナッツ型
図5 3次元空間に描かれたひとつのドーナッツ型

前述のとおり、基本的にはコンストラクタと引数だけ、つぎのように書き替えればよい。物体の変数名(torus)は関数名(createTorus())に合わせて変えただけだ。もうひとつ、かたちを真横から見たのでは、ドーナッツ型かどうかわからない。そこで、DisplayObject.rotationXプロパティにより角度を少し傾けた(前掲図5参照⁠⁠。

function createTorus(radius, segmentsH, segmentsV, light) {
  var defaultTexture = away.materials.DefaultMaterialManager.getDefaultTexture();
  var material = new away.materials.TriangleMethodMaterial(defaultTexture);
  // var sphere = new away.prefabs.PrimitiveSpherePrefab(radius, segmentsH, segmentsV).getNewObject();
  var torus = new away.prefabs.PrimitiveTorusPrefab(radius, radius / 3, segmentsH, segmentsV)
  .getNewObject();
  torus.material = material;
  torus.rotationX = -30;
  material.lightPicker = new away.materials.StaticLightPicker([light]);
  return torus;
}

さて次回は、3次元空間に加えた球体の表面にビットマップ、いわゆるテクスチャを貼ってみよう。そして、物体に回転のアニメーションを加えるつもりだ。

おすすめ記事

記事・ニュース一覧