0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザ3Dゲーム入門③:UnityやUnreal EngineはThree.jsを使っているのか?

0
Posted at

この記事について

前回の記事は3Dアセットの圧縮・配信・保存の仕組みを見てきました。今回は、少し視点を変えて「UnityやUnreal Engineでブラウザ向けに書き出したゲームは、Three.jsのようなJavaScriptライブラリを使っているのか?」という疑問に答えていきます。

結論から言うと、答えはNoです。UnityやUnreal Engineは、Three.jsとはまったく異なるアプローチでブラウザに3Dゲームを持ち込んでいます。

Three.jsとUnity/Unrealのアプローチの違い

まず全体像を整理します。

Three.js Unity WebGL / Unreal Web
実体 JavaScriptで書かれたWebGLラッパーライブラリ C++/C#で書かれたゲームエンジン本体を丸ごとブラウザ実行形式に変換したもの
動作原理 ブラウザのJSエンジン上でシーングラフを構築し、WebGLコマンドを発行する エンジンとゲームロジックをまとめて「WebAssembly」という形式に変換し、それを(ほぼネイティブに近い速度で)ブラウザ上で実行する
想定する開発者 Web開発者がJavaScriptで直接3D表現を組み立てる Unity/Unrealのエディタと言語(C#/C++)で普段通り開発したものを、そのままWeb向けに出力する

つまりThree.jsは「はじめからWeb向けに設計されたライブラリ」であるのに対し、Unity/Unrealは「本来PCやコンソール向けのネイティブアプリを、変換技術でブラウザに持ち込んでいる」という、成り立ちが根本的に違うものです。

そもそも、ブラウザでバイナリなんて実行できるのか?

ここで素朴な疑問が浮かびます。「ブラウザは元々JavaScriptを実行する場所のはずなのに、C++で書かれたコード(バイナリ)なんて動かせるのか?」という点です。

答えは、「WebAssembly」というブラウザに内蔵された専用の実行エンジン(サンドボックス化された仮想マシン)の上でなら動く、というものです。

WebAssembly(略してWasm)は、x86やARMのようなCPU固有の機械語ではなく、ブラウザ内蔵のWasm実行エンジン専用のバイトコードです。ブラウザのJavaScriptエンジン(ChromeのV8やFirefoxのSpiderMonkeyなど)には、このWasmバイトコードを安全に、かつ高速に実行するための仕組みが組み込まれています。

MDNの解説では、WebAssembly自体はDOM(Webページの構造)に直接アクセスすることができず、整数や浮動小数点数のようなプリミティブ型を渡してJavaScriptを呼び出すことしかできない、と説明されています。つまりWasmは「隔離された計算エンジン」であり、画面に何かを表示したり、GPUを操作したりするには、必ずJavaScriptを経由する必要があります。

UnityがWebAssemblyに変換されるまでの流れ

UnityのC#スクリプトが、最終的にブラウザで動くWasmになるまでには、いくつかの変換ステップがあります。Unity公式マニュアルの説明を整理すると、次のような流れになります。

  1. IL2CPP:Unityが開発した技術で、C#の.NETバイトコードを対応するC++のソースコードに変換します
  2. Emscripten:C/C++のコードをWebAssemblyやJavaScriptにコンパイルできるツールチェイン(コンパイラ群)です。MDNのチュートリアルでも、Emscriptenを使うとWasmモジュールと、それをブラウザ上で使うために必要なJavaScriptの「グルーコード(橋渡し用コード)」を一緒に生成できると説明されています

Unreal EngineはもともとC++で書かれたエンジンなので、Unityのような「IL2CPP変換」のステップは不要で、エンジンのC++コードを直接Emscriptenに通してWasm化する、というよりシンプルな経路になります。とある技術解説記事では、EmscriptenはC/C++コードをJavaScriptやWebAssemblyへ変換するトランスパイラであり、Unreal Engineもこの技術をWebビルドに利用していると紹介されています。

呼び出し元はやっぱりJavaScript

ここが今回の核心です。「Wasmというバイナリ相当のコードが動く」と言っても、それを起動する主体は依然としてJavaScriptです

Emscriptenでコンパイルすると、.wasmファイルと必ずセットで.jsファイル(グルーコード)が生成されます。このJSファイルが行っている処理を簡略化すると、次のようなイメージになります。

// Emscriptenが生成するグルーコード(簡略化イメージ)
fetch('game.wasm')
  .then(response => WebAssembly.instantiateStreaming(response, importObject))
  .then(result => {
    const instance = result.instance;
    // Wasm側の関数(C++のmain関数やUnityのメインループ)をJSから呼び出す
    instance.exports._main();
  });

流れを整理すると:

  1. JavaScriptがfetch.wasmファイルをダウンロードする
  2. WebAssembly.instantiate(またはストリーミング版のinstantiateStreaming)でWasmをコンパイル・初期化する
  3. JavaScriptからinstance.exports.xxx()という形で、Wasm内部の関数を呼び出す

つまり、Wasmの読み込み・起動は常にJavaScriptが主導している、ということです。

Wasm側からWebGLを呼ぶには?

もう一つの疑問として、「C++側で書かれたOpenGL/WebGLの描画命令は、どうやって画面に反映されるのか」という点があります。

Wasm自体はブラウザのcanvasやWebGLのコンテキストに直接触れることができません。そこでEmscriptenは、SDLやOpenGL、OpenALといったよく使われるC/C++ライブラリを、Web APIを使って再実装しており、それぞれについてWasmとWeb APIをつなぐJavaScriptのグルーコードを用意しています。

イメージとしては、こういう対応関係になります。

C++側でglDrawArrays()のようなOpenGL関数を呼んでいるように見えても、コンパイル時にその呼び出しは「JS側のWebGL呼び出しに変換するためのインポート関数への参照」に置き換えられています。実行時には、Wasm → JS → ブラウザの本物のWebGL API、という経路をたどって初めて画面に絵が出ます。

Unityの実際のファイル構成

Unity WebGLビルドを実際にブラウザで開くと、次のようなファイル群が読み込まれます。

<script src="Build/game.loader.js"></script>
<script>
  createUnityInstance(canvas, {
    dataUrl: "Build/game.data",             // アセットデータ(第2回で扱った部分)
    frameworkUrl: "Build/game.framework.js", // Emscripten生成のグルーコード
    codeUrl: "Build/game.wasm",              // Unityエンジン本体+ゲームロジック
  });
</script>
  • game.loader.js:全体の読み込みを統括するJavaScript
  • game.framework.js:Emscriptenが自動生成したグルーコード
  • game.wasm:UnityエンジンとC#のゲームロジックがまとめてコンパイルされたバイナリ
  • game.data:シーンやアセットのリソースファイル(第2回で説明した「アセットの塊」がここに入る)

つまり第2回で触れた「HTML/JSとは別に置かれる3Dアセットの塊」は、Unityの場合はこの.dataファイルとして具体的に実体化している、ということになります。

この記事のポイントまとめ

  • UnityやUnreal EngineはThree.jsを使っておらず、エンジン本体とゲームロジックを丸ごとWebAssemblyにコンパイルするという全く別のアプローチを取っている
  • WebAssemblyはブラウザ内蔵の専用実行エンジン上で動く、隔離されたバイトコードであり、DOMやWebGLに直接アクセスすることはできない
  • Wasmの読み込み・起動・WebGL呼び出しは、すべてEmscriptenが自動生成する「JavaScriptグルーコード」が仲介している
  • UnityではC# → (IL2CPP) → C++ → (Emscripten) → WebAssembly、という変換の流れをたどる

次回は、この仕組みの先にある「GPUの種類やドライバによる相性問題」を掘り下げます。せっかくWebGL/WebGPUで動かしても、GPUベンダーやドライババージョンによって挙動が変わってしまう理由を見ていきます。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?