今回から取組むお題は、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次元空間
コード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ファイルをフォルダにまとめた
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次元空間にスカイボックスが定められた
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次元空間にドーナッツ型を加え、その表面に背景を映し出してみたい。