みなさんこんばんは。@emadurandal (Twitter: @emadurandal_cgj ) です。
Webグラフィックス Advent Calendar 2021の一環として、今年もWeb3D活動について報告します。
私事のような記事で恐縮ですが、ご関心のある方はどうぞお付き合いください。
Rhodoniteとこれまでの経緯
私 @emadurandal は2005年頃からWebGLライブラリの自作活動を始めました。
第一作目はGLBoostというライブラリで、この頃からいくつかの商用案件で採用されました。
現在はRhodoniteという二世代目のライブラリを開発しており、今年でその2年目の報告となります。
毎年、年の終わりにQiitaのAdvent Calendarの一枠をお借りして一年のご報告をさせていただいております。
2015年: ゆるふわ自作WebGLライブラリを作ろう!
2016年: WebGLライブラリ「GLBoost」のご紹介
2017年: オレオレWebGLライブラリだった「GLBoost」が商業案件に採用され、作者が経験したこと
2018年: 趣味から仕事へ。GLBoostの教訓とこれから
2019年: WebGLライブラリを開発する際の心構え
2020年: 自作WebGLライブラリ: GLBoostの次は「Rhodonite(ロードナイト)」
2021年: WebGLライブラリRhodonite2年目の報告 ←本記事
Rhodoniteの概要
RhodoniteはTypeScriptベースのコンポーネント指向WebGLライブラリになります。NPMでインストールが可能です。
$ npm install rhodonite
Rhodoniteはいくつかの大きな特徴を持っています。
コンポーネント指向
RhodoniteではUnityやUnrealなど、近年のゲームエンジンでおなじみのコンポーネント指向の設計アプローチを取っています。
機能部品である「コンポーネント」を対象(エンティティ)に搭載させることで、その対象の3Dオブジェクトの機能を拡張するタイプの設計です。
ブリッタブル・メモリアーキテクチャ
独自のCPU/GPU共通・大容量メモリストレージ設計です。
各コンポーネントのデータやマテリアルのデータはすべて、ライブラリ初期化時に確保したメモリプール(大きいサイズのArrayBuffer)からアサインしています。
ライブラリ独自のベクトルクラスや行列クラスが全てFloat32Array(ArrayBufferに対してFloat型でアクセスするラッパー)を保持しており、このラッパーにメモリプールのArrayBufferをバイトオフセット付きで保持させることでこれを実現しています。
これらのデータを毎フレーム、浮動小数点テクスチャとしてGPUに転送し、シェーダー側からはそのテクスチャを一次元のメモリー領域としてアクセスさせることで、WebGL1(WebGL2もサポートしていますが)動作時でも大量のデータをシェーダーで扱えるという設計です。
詳細はこちらのドキュメント(https://github.com/actnwit/RhodoniteTSDoc/blob/master/ja/architecture/memory_model.md) をご覧ください。
各種モダン機能の搭載
Rhodoniteでは次のようなモダンな機能を搭載しています。
- 物理ベースレンダリング対応(Image-based Lighting対応)
- glTFとVRMフォーマットの再生に対応
- 専用エディター「RhodoniteEditor」
- Effekseer連携によるエフェクト表現
- ノードベースシェーダー編集システム「Shaderity-Graph」(開発中)
- WebXR対応
WebGL library Rhodonite v0.3.21 Released!https://t.co/Sqyh3a2X1F
— emadurandal_cg (@emadurandal_cg) May 14, 2021
Changes:
- Supports WebXR Controllers in VR mode. You can walk through by thumbsticks.
Try a demo at https://t.co/UGgR2SISKN
Oculus Link, Oculus Air Link and Oculus Browser are all supported.#webgl #webxr https://t.co/oZkYPE4RXo pic.twitter.com/iYTG0nrZR7
昨年からのアップデート
Rhodoniteが当初目指した基本設計は、昨年にはすでに出来ていました。
今年は設計上浮かんだ課題の解消と、次の青写真のための改善を行っています。
モノリシック設計からサブライブラリ化へ
昨年は全てのコードがRhodoniteリポジトリ本体に含まれていました。
しかしWebや3Dの状況は日々進化しています。
Rhodoniteでは新しいトレンドに対応するため、今までのモノリシック(一枚岩)の設計からサブライブラリ群による疎結合な設計に移り変わる予定です。
- WebAssemblyへの対応(コア部分をRust等のネイティブ言語で開発し、WebAssemblyでは時期尚早な箇所はTypeScriptでサブライブラリ開発する)
- 疎結合設計にすることで設計を洗練させる。
- 各サブライブラリをRhodonite非依存にして一般利用可能にすることを目指す。
サブライブラリの紹介
Shaderity
https://github.com/actnwit/shaderity
シェーダー開発を行う上での利便性を提供するユーティリティです。
- GLSL ES 1.0(WebGL1) ⇔ GLSL ES 3.0(WebGL2) のコード変換
- GLSLコードへの動的な変数展開
- glslifyライクなインクルード文のサポート
- 専用WebpackローダーによるTypeScript/JavaScriptへのシェーダーコード組み込み
Shaderity-Graph
Rhodoniteではノードベースのサポートをしばらく前から動画等で少しお披露目していますが、それらのコードも最近Shaderityベースのサブライブラリとして切り出しました。
このShaderity-Graph自体ではコーディングによる利用形態ですが、ノードベースのプログラミングを行うことが出来ます。
実用的にはGUIによる操作が必要となるため、実際にはRhodonite EditorというGUIクライアント上でノードベースUIが提供され、それらがShaderity-Graphの機能を呼び出す形になります。
(ただしまだ開発中で、現行のRhodoniteEditorでのバージョンは十分な動作をしませんのでご了承ください)
(通常のテクスチャリング後の色に赤成分を足しているシェーダーノードグラフ)
Command3D
現在のRhodoniteでもWebGL APIに依存する部分はメインコードとモジュールが分離しています。
しかし当時はWebGPUなどの別3DAPIは登場していなかったので、徹底して分離する必要がなく、コード的には両者の間で若干のコード癒着がある状況です。
これを打開すべく、3D API呼び出し部分を完全に切り出す予定で、それがCommand3Dサブライブラリになります。
予定している機能は以下のとおりです。
- WebGL2とWebGPU両対応
- WebGL2/WebGPUの命令列を整数配列による独自のコマンドバッファ
- コマンドバッファの送受による3D API駆動
- WebAssemblyからのコマンド登録に対応
- コマンドバッファのシリアライゼーションと再生
4つ目の特徴は、いずれRhodoniteのコア部分をRustやC++などのネイティブ言語で作り直した後もCommand3Dを利用できるようにするためです。
このCommand3Dはコマンドバッファベースの仕組み故に様々な応用が考えられますが、開発初期でもあるため伏せておきます。
RnMath
Rhodonite内部の数学クラス(https://github.com/actnwit/RhodoniteTS/tree/master/src/foundation/math)を外部パッケージ化しようと思っています。まだ実現できていませんが、RnMathという名前で分離を予定しています。
WebXR
https://github.com/actnwit/RhodoniteTS/tree/master/src/xr
プライオリティは低めで上記の図にもまだ含めていませんが、WebXRサポート部分も将来の分離を考えています。
Effekseer対応の向上
Effekseerはオープンソースの国産エフェクト制作ツール&ランタイムです。
今年はそのEffekseer対応を強化しました。Rhodonite Editorでの読み込みに対応するとともに、Effekseerプロジェクトへのコントリビュートも始めました。
Effekseer本体とWebGL向けランタイムEffekseerForWebGLに以下のプルリクエストを送りマージ頂いています。
- E2Eビジュアルテストで一貫した結果を得るための乱数seed値の指定API追加(Effekseer#705, EffekseerForWebGL#58)
- テクスチャ等の依存データを1ファイルにパッケージングしたEffekseerパッケージ(.efkpkg)形式の読み込み・表示に対応(EffekseerForWebGL#68)
後者のPRにより、.efkpkgファイルをD&DするだけでEffekseerエフェクトを追加するようなGUI環境を開発することができるようになりました。
最近のWeb3D界隈の動き
Web3D界隈の動きも年を追うごとににぎやかになってきました。ネイティブに近いことが求められるようになってきています。
WebGPU
WebGPUがようやくリリースされそうです。Chromeバージョン102から標準で有効になる予定です。
WebGPUは策定とリリースまで、非常に多くの時間がかかり、低レベルAPIの仲間ではありますが今となってはDirectX12やVulkan、MetalなどのネイティブAPIに比べると機能性はだいぶ制限されています。
個人的にはWebGL2と比べて以下4つが大きな特長だと思います。
- GPUの状態を管理するPipelineStateの導入
- コマンドバッファ
- ComputeShaderによるPixelShaderの制約を超えたGPU汎用計算
- WebGL時代の細かいGL制約からの開放(GL系のオブジェクトバインド方式からの脱却、デバイス座標系のDirectXよりへの移行によるReversed-Z対応など)
ネイティブAPIのGPUのレイトレコアの活用(DXR)やMeshShaderなどの最新の機能のサポートは当面先です。WebGPUワーキンググループはまず1.0をきちんとリリースすることを最優先しています。
WebAssembly
WebAssemblyの実用性は高まってきていますが、BabylonJSチームが興味深い記事を出しています。
Why WASM is not the future of Babylon.js
ポイントを要約すると、以下の3点がエンジンコード全体の書き直しという大きなコストをまだ正当化できないということのようです。
- WASM ⇔ JavaScript間の通信オーバーヘッドの懸念
- WASMのデバッグ環境はまだ成熟していない(Webのそれに劣る)
- WASMランタイムのサイズが大きい可能性
彼らはすでにBabylon Nativeというプロジェクトを始めています。これはNode.js + NAPI (C/C++によるNode向けネイティブアドオンの仕組み) + bgfx(マルチプラットフォームAPI)という構成でネイティブ環境でも動作するバージョンのBabylonライブラリです。
当面はWebAssemblyには頼らず、この方式で既存資産を活かしつつ、ネイティブ対応を続けていくようです。
ネット上の反応を見る限り、WebAssemblyに対する現状のこうしたコンセンサスは、他のメジャーなWebGLライブラリのコミュニティでも共有されているように感じます。
しかし、これから新規にライブラリ開発をしたい、という人たちにとってはその限りではありません。最初からWebAssembly対応を意識して開発するのも差別化になりえると思います。
WebXR
今年からにわかにWebXRの技術ミートアップが増えてきたように思います。APIが普及し、ようやくビジネス需要が高まってきたのでしょうか。Rhodoniteでも今年はWebXRに対応しました。
こうしたXRはユーザビリティや性能なども含めてしっかりチューンしなくてはなりません。VR空間におけるUIもサポートが必要でしょうし、UI/UX面含めてリサーチが必要な分野ですね。
- コントローラ周りに表示する独自のUIの実装
- 使いやすい操作体系の確立
- XR向けの描画最適化(VR高速化のためのインスタンスドステレオレンダリングなど)
WebXRについては、今年のWebXRアドベントカレンダーの25日にも記事を書いています。
メタバース on Web??
今年後半はメタバースが俄に注目を集めるようになりました。ネイティブ技術ですらメタバース実現には性能が足りないと言われている中、WebGLの技術制約ではなおさらできることに限界があります。
WebGL拡張を使った描画最適化やWebGPU対応、WebAssemblyや並列化を駆使することで、ネイティブに近い表現ができるのか……?
WebXRベースのメタバースの強みは、Webとの親和性と実行までの心理的敷居の低さです。
ここは多くのビジネスチャンスがあると思いますので、皆さんもチャレンジされてみると良いかもしれません。
今回は、WebXR(特にVR)での速度向上につながるWebGL拡張についてご紹介します。
WebGL_multi_draw
このWebGL拡張は、複数のプリミティブをまとめて一度に描画する描画関数群を提供します。
プリミティブタイプの変更はGPU側のコンテキストスイッチングコストが大きいのでしょうか(WebGPUでもRenderingPipelineObjectを作り直すことになります)。残念ながら異なるプリミティブタイプを同時に、というのは無理なのですが(TriangleListなメッシュとTriangleStripなメッシュを一緒に扱うことは不可)、同じプリミティブタイプであれば、トポロジの異なる複数種類のメッシュの描画指示を一気にできます。
具体的には、ライブラリ内部で複数の異なるトポロジメッシュを大きな同一のVBOに格納するようにし、それぞれ用のインデックスバッファをこれらの関数で指定して描画することになります。シーンに大量の異なるメッシュ群を大量表示するケースで高速化が図れそうですね。
OVR_multiview2
以前、YVTさんがWebGL1.0で動作するインスタンスドステレオレンダリングの実装アプローチを紹介されていました。
これをやるには全ての描画をインスタンスドステレオレンダリングに対応させ、しかも左目右目の境目をピクセルシェーダーでDiscard命令でクリッピングする必要がありました。
WebGL1でも動作する大変興味深い試みですが、実装作業としてはライブラリの修正箇所が多く、そう簡単に導入できるものではありません。
ありがたいことに、現在ではOVR_multiview2という同時レンダリング用の拡張が出ています。
この拡張によって、WebGLとしてのレンダリングコードの変更を最小限に、左目右目の同時レンダリングが可能になりました。
具体的にはフレームバッファを複数(通常は左目・右目用の2つ)、テクスチャ配列として用意して、拡張の作法に則って設定をセットアップすることで、
WebGLコードとしてはインスタンスド描画のコードを書かなくても、同時レンダリングをやってくれる、というものになります。
おそらく、ブラウザ内部のWebGL実装レイヤーで、ユーザープログラマに代わってANGLE(OpenGL ES互換レイヤー)に対しインスタンスドステレオ描画をセットアップしてくれているのだと思われます。
これによって、比較的少ないコード追加でVRの右目・左目同時レンダリングをサポートできるようになりました。
おおむねのケースで、25%~50%程度の速度向上が期待できるようです。CPU側がボトルネックになるケースで速度改善が顕著になります。
なお、この拡張は仕様として左目・右目だけでなく、さらに多くの数のマルチビュー同時レンダリングをサポートしているようです。
苦労したことや悩んだこと
今年も多くの苦労や困難がありました。
知的興味の範囲が広がり、CGが手薄になった
仕事で求められる内容が多様化し、仕事の多くが部下の育成や会計・経営まわりのタスクが増えました。
私自身、気がつけば来年ついに40歳になるような年齢でして、自分の人生を生きる中で、関心の範囲が技術領域のみに留まらなくなったことも影響しています。
今後、何を追求するかを模索するためというモチベーションからですが、人類学や歴史学、哲学、金融、法学などのリベラルアーツ全域を追い求めるようになり、相対的にCGを追究できる時間が大きく減ってしまいました。それはRhodoniteの開発にも少なからず影響を及ぼしたように思います。
開発時間の大幅減は今年の大きな反省点といえます。来年は再びCGへのリソース配分を増やして巻き返しを図りたいと思います。
ドキュメンテーションやロードマップの明示が不十分
こちらは常に課題ですが、来年からはいよいよ本格化しないといけないと感じています。
一応、すでに書き始めてはいるのですが、なかなか追いつきません。ドキュメンテーションは大変ですね。
公式のWebサイトが無いことも課題と思っています。あとは今後のロードマップをきちんと示せていないことでしょうか。
ThreeJSやBabylon.js、PlayCanvasなどの海外のプロダクトを見ていると、やはりこうした面にとても力を入れています。
情報発信やパブリックアピールが手薄なのは、職人気質になりがちな日本人の弱点ですね。来年はもう少しなにか示せるようにできればと思います。
TypeScriptの型指定が不徹底だったことの後悔
GLBoostでの反省を踏まえ、Rhodoniteでは最初からTypeScriptを導入できたのですが、any指定を多用してしまうことが初期は多くありました。
特に、外部ライブラリを扱うときや、glTF(JSON)のローダーなどを書く場合、anyについつい頼ってしまっていたのです。完全に敗北してますね(笑)
TypeScriptはもともと型の概念のないJavaScriptの世界への導入のため、あえて現実的な折り合いをつけるanyやasといった「便利な」指定子が提供されています。しかしこれに頼ると後で多くの誤動作やメンテナンス低下をもたらします。予想しきれなかったのは、「所詮、型の世界での話だし」「後で直せばいいし」と思っていたanyやasを潰すリファクタリングをやってみると、実際はエンバグなしにリファクタリングすることが予想以上に困難になっていました。怠けた代償として、まるで遅効性毒のような怖さがあります。
RhodoniteはOSSプロダクトなのでいずれはやりきれますが、これが商業プロダクトだったらスケジュールの兼ね合いから、リファクタリングを諦めざるを得ない状況すら発生するかもしれません。開発パフォーマンスの低下は企業競争力の低下や業績への悪影響に繋がります。甘く見てはいけません。
TypeScriptの型システムについて、開発チーム全体で継続的に学習を深めていくことは不可欠と考えます。
こちらの記事あたりに目を通すことをおすすめします。
理想を言えば原理主義者くらいを目指した方が、ライブラリ開発の効率性という点では有利です(コントリビュータが辛いかもしれませんが)。
あるいは、先人によって開発された型支援ライブラリを使うのも良いでしょう。次に紹介する2つの支援ライブラリはJSONデータなど、型を当てはめづらいケースにも役立ちます。
私も今後Rhodoniteでいずれかを使おうと検討してます。
よかったこと
Githubの活用度が向上
テストはJestとPuppeteerを用いたビジュアルE2EテストをGithub ActionsでPush/PRごとに回しています。
Chromiumのヘッドレスモードでソフトウェアレンダラーで動作させれば、どのプラットフォームでも一貫した動作にできます。
非ヘッドレス動作時の画面はこちらです。
どこかでその辺りも解説記事を書ければと思います。
Dockerを開発に取り入れた
VSCodeのdevContainerを活用し、Windowsでもセットアップに躊躇することがなくなりました。
このDockerコンテナではChroniumのインストールも自動で行われ、前述のヘッドレスのE2Eビジュアルテストにも対応します。
設定については こちら をご参考ください。
https://github.com/actnwit/RhodoniteTS/tree/master/.devcontainer
コントリビュータが増えた
少しずつ、ぽつりぽつりとですが、有志で増えてきています。私の特徴も欠点も含め、人となりを理解してくれる方が多いです。
大変ありがたいことなので、このとろ火を少しずつ育てて、大きなキャンプファイヤーにして育てていければと思っています。
今年も商業利用事例がある
引き続き使ってくれている会社さんがあります。ありがたいことです。
今年は新規開拓に手が回らなかったのですが、来年はライブラリの熟成度を上げつつ、対応できそうなところにご紹介していければと思います。
最後に
近年はUnityやUnreal Engineなどの商用ゲームエンジンの万能化が進み、結局これらでいいんじゃない? というコンセンサスが増えてきたように思います。実際、そちらの方が実用性は高いのが実情で、少資本のライブラリ勢がなかなか差別点を打ち出すことが難しくなってきています。
では、個人や少資本によるライブラリ開発は下火になっているのかというと、Githubを眺めているとまったくそんなことはなく、興味深いライブラリが雨後の筍のように、ワールドワイドではむしろ活発化している印象すら受けます。
近年ではRust言語によるグラフィックスライブラリ開発の試みが増えているように思います。
とはいえ、技術者マインドで育てているプロダクトの方が多く、商業ベースで大手のエンジンが席巻している市場で居場所を見つけていくことを本気でやろうとしているプレイヤーとなると、やはり限られているように感じます。
大手エンジン(Unreal EngineやUnity)などを手掛ける大手企業は、自社プロダクトを強化するためにM&Aを駆使して他社から技術を買取って強化しているような、ものすごい世界でしのぎを削っています。私たちのような個人・少資本のプレイヤーはもちろんそんな方法はとれず、別のアプローチを試みるしかありません。
弱者の戦術、ではないですが、個人や小規模ならではの戦い方を模索するのも価値ある試みと考えます。
これからもアドベントカレンダーの一席をお借りして、皆様へのご報告を続けさせてください。
ありがとうございました。