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

第5回スカイボックスで3次元空間に背景をつくる

今回から取組むお題は、Away 3D TypeScriptサイトから頂戴する。⁠Examples」の作例Skybox and environment mappingだ。スクリプトの組立ては、わかりやすさを考えて書き改める。次回を含めた都合2回で仕上げたい。

スカイボックスの仕組み

今回つくるのは、3次元空間を取り巻く背景だ。スカイボックスという仕組みで、3次元空間の物体やカメラを、6つのテクスチャの面で囲んでしまう図1⁠。スカイボックスの中をカメラで見回すと、もちろん角や縁が見えることなく、テクスチャはパノラマのように滑らかにつながる。

図1 スカイボックスをつくる6面のテクスチャ

上:y軸正

左:x軸負

奥:z軸正

右:x軸正

手前:z軸負

下:y軸負

スカイボックスの前に、まずは3次元空間のViewオブジェクトを定めなければならないコード1⁠。Viewオブジェクトをつくる関数(createView())の中身は、これまでと同じだ(第1回「Away3D TypeScriptで基本的な3次元の形状をつくる」Viewクラスで3次元空間の表示領域を定める参照⁠⁠。なお、後でアニメーションさせるつもりなので、RequestAnimationFrameクラスでコールバック(render())を定めて描画View.render()メソッド)している。

まだ3次元空間には何もない。しかも、Viewオブジェクトの背景色View.backgroundColorプロパティ)に黄色を与えたので、空間は一面黄色く染まる図2⁠。これで、3次元空間はつくられた[1]⁠。

図2 一面黄色く染まった3次元空間
図2 一面黄色く染まった3次元空間
コード1 3次元空間のViewオブジェクトを定める
var RequestAnimationFrame = require("awayjs-core/lib/utils/RequestAnimationFrame");
var View = require("awayjs-display/lib/containers/View");
var DefaultRenderer = require("awayjs-renderergl/lib/DefaultRenderer");
var view;
var timer;
var centerX = 200;
var centerY = 150;
function initialize() {
  view = createView(centerX * 2, centerY * 2, 0xFFFF00);
  timer = new RequestAnimationFrame(render);
  timer.start();
}
function createView(width, height, backgroundColor) {
  var defaultRenderer = new DefaultRenderer();
  var view = new View(defaultRenderer);
  view.width = width;
  view.height = height;
  view.backgroundColor = backgroundColor;
  return view;
}
function render(timeStamp) {
  view.render();
}

スカイボックスの素材を読込む

スカイボックスは、前述のとおり6つのテクスチャからつくられる。だからといって、北欧発祥の大手家具販売店で買ったボックスのように、自分の手で組立てるには及ばない。どのテクスチャをどこに使うのかという指示さえ与えれば、Away3Dのライブラリがスカイボックスをつくってくれる。

指示はつぎのようなJSONファイルに定める。テクスチャの位置を"id"、ファイルを"image"というプロパティに与えたオブジェクト6面分を、"data"というプロパティ名の配列に納める。テクスチャの位置を示す"id"の名前は、xyz軸と正負の方向の組合せから成立つ図3⁠。

{
  "data":[
     {
        "id":"posX",
        "image":x軸正のテクスチャ
     },
     {
        "id":"negX",
        "image":x軸負のテクスチャ
     },
     {
        "id":"posY",
        "image":y軸正のテクスチャ
     },
     {
        "id":"negY",
        "image":y軸負のテクスチャ
     },
     {
        "id":"posZ",
        "image":"z軸正のテクスチャ
     },
     {
        "id":"negZ",
        "image":z軸負のテクスチャ
     }
  ]
}
図3 スカイボックスの6面のテクスチャとid

y軸正:posY

x軸負:negX

z軸正:posZ

x軸正:posX

z軸負:negZ

y軸負:negY

今回、6つのテクスチャとJSONのファイルは、awayjs-examplesでダウンロードした中から前掲作例「Skybox and environment mapping」で使われている以下のものを選んだ。これらのファイルは、assetsフォルダの中にさらにskyboxというフォルダをつくってまとめた図4)。

図4 6つのテクスチャとJSONファイルをフォルダにまとめた
図4 6つのテクスチャとJSONファイルをフォルダにまとめた

JSONファイルは拡張子が「cube」で、テクスチャをつぎのように定めている。

{
  "data":[
     {
        "id":"posX",
        "image":"snow_positive_x.jpg"
     },
     {
        "id":"negX",
        "image":"snow_negative_x.jpg"
     },
     {
        "id":"posY",
        "image":"snow_positive_y.jpg"
     },
     {
        "id":"negY",
        "image":"snow_negative_y.jpg"
     },
     {
        "id":"posZ",
        "image":"snow_positive_z.jpg"
     },
     {
        "id":"negZ",
        "image":"snow_negative_z.jpg"
     }
  ]
}

JSONファイルは、AssetLibraryクラスを用いて読込む。読込み方は、以下のように基本的にテクスチャファイルのときと同じだ(第4回「床の追加とカメラのパン・チルト」「テクスチャが貼られた床を加える」参照)。ただ、スカイボックスのファイルはすべてひとつのフォルダにまとめた。そのフォルダをURLの起点にしたい。このとき使うのがAssetLoaderContextクラスだ。AssetLoaderContext.dependencyBaseUrlプロパティにそのURLを与えればよい。AssetLoaderContextオブジェクト(assetLoaderContext)は、AssetLibrary.load()メソッドの第2引数に渡す。

LoaderEvent.RESOURCE_COMPLETEイベントのリスナー関数(onResourceComplete())は、JASONファイルの読込みを確かめたら、LoaderEvent.assetsプロパティの配列(assets)から素材(asset)を取出して、別に定めるスカイボックスをつくる関数(setupSkybox())に引数として渡した。

var AssetLoaderContext = require("awayjs-core/lib/library/AssetLoaderContext");

var baseUrl = "assets/skybox/";
var imageSkybox = "snow_texture.cube";

function initialize() {

  AssetLibrary.addEventListener(LoaderEvent.RESOURCE_COMPLETE, onResourceComplete);
  loadAssets(baseUrl, imageSkybox);

}

function loadAssets(base, image) {
  var assetLoaderContext = new AssetLoaderContext();
  assetLoaderContext.dependencyBaseUrl = base;
  AssetLibrary.load(new URLRequest(base + image), assetLoaderContext);
}
function onResourceComplete(eventObject) {
  var assets = eventObject.assets;
  var count = assets.length;
  var url = eventObject.url;
  for (var i = 0; i < count; i++) {
    var asset = assets[i];
    switch (url) {
      case (baseUrl + imageSkybox):
        setupSkybox(asset);
        break;
    }
  }
}

3次元空間にスカイボックスを定める

いよいよスカイボックスをつくろう。クラスは文字どおりSkyboxで、コンストラクタの引数は素材の(MaterialBase)オブジェクトだ。そして、スカイボックスの素材は、AssetLibraryクラスで読込んだ素材データを、SkyboxMaterialクラスのコンストラクタに渡してつくる。この素材データはキューブマップと呼ばれる。なお、ふたつのクラスのコンストラクタについて、詳しくはAway3D 14/11/05: スカイボックスをつくるをご覧いただきたい。

new Skybox(素材オブジェクト)

new SkyboxMaterial(キューブマップ)

スカイボックスをつくる関数(setupSkybox())は、つぎのように定めた。LoaderEvent.RESOURCE_COMPLETEイベントのリスナー関数(onResourceComplete())が渡すキューブマップのデータ(cubeTexture)からSkyboxMaterialオブジェクトをつくる。そのオブジェクトを引数としてSkyboxインスタンス(skybox)をつくって、3次元空間に加えたScene.addChild()メソッド)。

var Skybox = require("awayjs-display/lib/entities/Skybox");
var SkyboxMaterial = require("awayjs-renderergl/lib/materials/SkyboxMaterial");

function setupSkybox(cubeTexture) {
  var skybox = new Skybox(new SkyboxMaterial(cubeTexture));
  view.scene.addChild(skybox);
}

これで3次元空間に、6面のテクスチャでつくられたスカイボックスが定められる図5)。もっともこのままでは、テクスチャがきれいにつながっているかどうか確かめられない。そこで、RequestAnimationFrameオブジェクトに定めたコールバック(render())で、以下のようにカメラをy軸で水平に回してみることにした。

図5 3次元空間にスカイボックスが定められた
図5 3次元空間にスカイボックスが定められた
function render(timeStamp) {
  var camera = view.camera;
  camera.rotationY += 0.5;
  view.render();
}

以上をまとめたのがつぎのコード2だ。3次元空間に定めたスカイボックスの中を、水平に回るカメラが映し出す。テクスチャは滑らかにつなぎ合わされていることが確かめられる。今回はテクスチャをひとつのフォルダにまとめて読込むという構成になったため、サンプルコードがjsdo.itに掲げられなかった。筆者のサイトにコード2のサンプルを上げておくので、そちらをご覧いただきたい。

コード2 3次元空間に定めたスカイボックスの中で水平にカメラを回す
var LoaderEvent = require("awayjs-core/lib/events/LoaderEvent");
var AssetLibrary = require("awayjs-core/lib/library/AssetLibrary");
var AssetLoaderContext = require("awayjs-core/lib/library/AssetLoaderContext");
var URLRequest = require("awayjs-core/lib/net/URLRequest");
var RequestAnimationFrame = require("awayjs-core/lib/utils/RequestAnimationFrame");
var View = require("awayjs-display/lib/containers/View");
var Skybox = require("awayjs-display/lib/entities/Skybox");
var SkyboxMaterial = require("awayjs-renderergl/lib/materials/SkyboxMaterial");
var DefaultRenderer = require("awayjs-renderergl/lib/DefaultRenderer");
var view;
var timer;
var baseUrl = "assets/skybox/";
var imageSkybox = "snow_texture.cube";
var centerX = 200;
var centerY = 150;
function initialize() {
  view = createView(centerX * 2, centerY * 2, 0xFFFF00);
  AssetLibrary.addEventListener(LoaderEvent.RESOURCE_COMPLETE, onResourceComplete);
  loadAssets(baseUrl, imageSkybox);
  timer = new RequestAnimationFrame(render);
  timer.start();
}
function createView(width, height, backgroundColor) {
  var defaultRenderer = new DefaultRenderer();
  var view = new View(defaultRenderer);
  view.width = width;
  view.height = height;
  view.backgroundColor = backgroundColor;
  return view;
}
function loadAssets(base, image) {
  var assetLoaderContext = new AssetLoaderContext();
  assetLoaderContext.dependencyBaseUrl = base;
  AssetLibrary.load(new URLRequest(base + image), assetLoaderContext);
}
function onResourceComplete(eventObject) {
  var assets = eventObject.assets;
  var count = assets.length;
  var url = eventObject.url;
  for (var i = 0; i < count; i++) {
    var asset = assets[i];
    switch (url) {
      case (baseUrl + imageSkybox):
        setupSkybox(asset);
        break;
    }
  }
}
function setupSkybox(cubeTexture) {
  var skybox = new Skybox(new SkyboxMaterial(cubeTexture));
  view.scene.addChild(skybox);
}
function render(timeStamp) {
  var camera = view.camera;
  camera.rotationY += 0.5;
  view.render();
}

次回は、3次元空間にドーナッツ型を加え、その表面に背景を映し出してみたい。

おすすめ記事

記事・ニュース一覧