動的ローディング可能なバーチャルオブジェクトのための仕様と実装の提案
Project Spirare とは
Project Spirare[1] (プロジェクト スピラーレ) とは、プログラムを埋め込むことができるバーチャルオブジェクトのための汎用フォーマットの定義と実装を目指しているプロジェクトです。
Spirare では、3D モデルとプログラムを組み合わせたバーチャルオブジェクトの定義、そしてアプリでのバーチャルオブジェクトの動的ローディングが可能です。
例として、以下のオブジェクトの3D モデルや "使用すると弾がとぶ" というプログラムはアプリ内埋め込みではなく外部ファイルからロードしています。
Spirare が目指すもの
どのアプリケーションでも利用できる汎用フォーマット
VRChat などのソーシャル VR アプリケーションでは、ユーザーがワールドと呼ばれる空間情報(空間の 3D モデルやスクリプトなど)をアップロードし、アプリケーション内で利用することができるようになっています。
ただし、ワールドの仕様というのはアプリケーションごとに別々となっています。
特にプログラム部分が大きな課題です。Unity で利用される C# のスクリプトは実行時に動的に追加して利用することができません (Asset Bundle という仕組みを使えばビルド時に埋め込まれたスクリプトは利用できる)。そのため、アプリケーションで利用可能なスクリプトをいくつか提供していたり、アプリケーションごとの独自の仕組みによってプログラムを記述可能だったりと、別々の仕様となっています。
フォーマットがアプリケーションごとに別々だと、オブジェクトやワールドをアプリケーションごとに作りなおす必要がありますし、アプリケーションのサービスが終了してしまうとオブジェクトは利用できなくなってしまいます。
サービスの同時起動
現在の AR/VR デバイスでは、3D アプリケーションは排他起動になるものが多いです。つまり、同時に 1 つのアプリケーションしか起動できません。
そのため、1 つ 1 つのアプリケーションを小さく作り、それを組み合わせて利用するということができません。業務での利用や日常的な利用を考えると、これは非常に不便です。
そこで、バーチャルオブジェクトによってサービスの UI や機能を実装できれば、1 つのアプリ内で複数のサービスを利用することができ、実質的にアプリケーションの同時起動を実現できます。
アプリケーションの機能を事前にビルドされたネイティブアプリ側に持たせるのではなく、ロードするコンテンツ側に持たせることになるので、Web ブラウザと Web アプリのような仕組みです。
Spirare の仕組み
バーチャルオブジェクトの定義は以下の要素で構成されています。
- オブジェクトの定義 (xml ファイル)
- 3D モデル(GLB ファイル)
- プログラム (WebAssembly ファイル)
オブジェクトの定義 (xml ファイル)
<scene>
<model src="./gun.glb" position="1 0 0.1">
<script src="../wasm/fire_object.wasm"/>
</model>
</scene>
<resource>
<model id="bullet" src="./bullet.glb"/>
</resource>
オブジェクト定義ファイルでは、3D モデルデータや WebAssembly スクリプトを組み合わせたオブジェクト定義を行います。
<scene>
タグでは表示するオブジェクト、<resource>
タグではスクリプトから利用するためのリソース定義を行います (Unity でいうシーンとプレハブのようなものです)。
ここのフォーマットを xml にしたのは、HTML と記法を合わせるためです。
ある程度見慣れたフォーマットになるというのと、Web の仕組みやフレームワークを活用しやすそうというのが理由です。SSG (Static Site Generator) と相性が良さそうだと思っています。
3D モデル (GLB ファイル)
3D モデル部分は GLB 形式のファイルをそのまま利用しています。
プログラム (WebAssembly ファイル)
WebAssembly (wasm) はブラウザ上で動作する低水準言語です。もともとはブラウザ上が対象でしたが、同じプログラムを様々な環境で安全・高速に実行できるという利点からブラウザ外でも利用されています。
WebAssembly では直接スクリプト外へのアクセス(ファイルアクセス、ネットワークアクセスなど)はできず、WebAssembly の実行を行うホスト側のコードを経由して行います。
WebAssembly 側のコードと WebAssembly を実行するホスト側のコードで関数名や引数を合わせておくことで、ホストから WebAssembly の関数を呼び出したり、逆に WebAssembly からホストの関数を呼び出したりできます。
Spirare では、ゲームエンジン側のオブジェクトの操作を行うためのプリミティブな関数を定義しました。
例えば transform_get_local_positon_x
はオブジェクトの位置の x 座標を取得する関数、transform_set_local_positon
はオブジェクトの位置を設定する関数です。ゲームエンジン側でこの挙動を実装しておけば、WebAssembly 側からこれらの関数を使ってオブジェクトの操作が行えます。現在は Unity でのみ実装していますが、Unreal Engine など他のプラットフォームでも関数の実装を行っておけば WebAssembly 側からはプラットフォームに関係なく操作が行えます。
また、ホスト側から WebAssembly の関数を呼びだす例としては、毎フレームよばれる update
関数やオブジェクトを選択した際に呼ばれる on_select
関数などを定義しています。
これにより、WebAssembly 側のコードで特定のイベントに反応するコードを記述することができます。
実装
サンプルコンテンツ
サンプルコンテンツです。
GitHub Pages を使ってコンテンツファイルを公開していて、例えば以下の URL からコンテンツのダウンロードが可能です。
Unity 用ライブラリ
バーチャルオブジェクトの動的ロード、WebAssembly スクリプトの実行を行う Unity 用ライブラリです。
WebAssembly 用 Rust ライブラリ
ホストと WebAssembly の間の関数はプリミティブなものなので、直接利用してしまうと記述量が多くなり煩雑なコードになってしまいます。
そこで、プリミティブな関数をラップしてより使いやすくするためのライブラリを作成しています。
例えば、以下は「オブジェクトを使用する」というイベントがトリガーされると弾を発射するというコードです。Unity の C# のスクリプトと似たような書き心地になっています。
#[no_mangle]
unsafe fn on_use() {
let transform = Transform::myself();
let gun_position = transform.world_position();
let gun_rotation = transform.world_rotation();
// spawn bullet
let result = element::spawn_object_by_id("bullet");
if result.is_err() {
return;
}
let bullet_element = result.unwrap();
// set position and rotation
let offset = 0.5;
let forward = transform.forward();
let bullet_position = gun_position + offset * forward;
let bullet_transform = bullet_element.transform();
bullet_transform.set_world_position(bullet_position);
bullet_transform.set_world_rotation(gun_rotation);
// set velocity
let speed = 4.0;
let velocity = speed * forward;
bullet_element.physics().set_world_velocity(velocity);
}
おわりに
Project Spirare は現状自分一人のプロジェクトなので、今後どこまでできるかはわかりませんが、応援していただけると嬉しいです。
意見やアドバイスなどがあればぜひ Twitter (@tarukosu) にメッセージを送ってください。
-
Spirare はラテン語で "息をする" という意味です。バーチャルオブジェクトにプログラムを埋め込むことで命を吹き込みたいという願いをこめています。 ↩︎
Discussion