はじめに
この記事はグラフィックス全般Advent Calender 202525日目の記事です。
私、@emadurandal は自作のWeb3Dライブラリを開発しておりまして、毎年アドベントカレンダーで一年の報告をしております。
今回は、Rhodoniteという自作ライブラリの6年目の報告ということで、記事を書かせていただきます。
Rhodoniteライブラリについて
RhodoniteはTypeScriptで書かれたWeb3Dライブラリです。
WebGL2とWebGPUに対応し、物理ベースレンダリングなどを含めた高度なレンダリングに対応しています。
インストール方法
npmで簡単にインストールできます。
$ npm install rhodonite
使い方
WebGLやWebGPUを生で使うより、非常に短いコードで3Dを表示できます。
import Rn from 'rhodonite';
// Rhodoniteの初期化
const engine = await Rn.Engine.init({
approach: Rn.ProcessApproach.DataTexture,
canvas: document.getElementById('world') as HTMLCanvasElement,
});
// キューブの生成
Rn.MeshHelper.createCube(engine);
// レンダリングループ
engine.startRenderLoop(() => {
engine.processAuto(); // キューブの生成を検知してキューブを描画してくれます。
});
Rhodonite Editor
Rhodonite Editorというビューアーもあります。npm installして試すのなんてめんどいという方は以下のURLにアクセスして、画面にglTFファイルとかVRMファイルとか放り込んでみてください。
今年の主な更新
公式サイトの公開
今年の大きな変化といえば、これです。公式サイトの公開です。
導入方法からチュートリアル、APIドキュメントに至るまで、一通りの情報を揃えました。
メジャーどころのライブラリの公式サイトに比べたらまだしょぼいですが、徐々に強化していけたらと思っています。
公式サイトあると、一応いっぱしのライブラリっぽくなるよね(´・∀・`)(個人の感想です)
点光源による全方位影への対応
ようやくですが、点光源を配置した場合に影がつくようになりました。
実装アプローチとしては、キューブマップにシャドウマップを形成する方法ではなく、Dual Paraboloid方式にしました。前者ではシーンジオメトリを6回描画する必要がありますが、後者では2回で済みます。
Dual Paraboloidについては、以下のサイト様の記事で詳しく説明されていますのでご参照ください。
KHR_animation_pointerのサポート
標準のglTF2では、アニメーションできる対象がノードのTransformやBlendShapeウェイトに限られていました。
KHR_animation_pointerに対応すると、アニメーションの対象をJSON Pathで示すことで、glTFのマテリアルパラメータやカメラパラメータ、ライトパラメータなど、ほぼ全ての要素についてアニメーションさせることができます。
RhodoniteでもこのKHR_animation_pointerをサポートしました。これ、ライブラリ内のアニメーション機構をだいぶ拡張する必要があるので、一般的にサポートするのはかなり面倒かもしれませんね。Rhodoniteでは数学クラスをアニメーション対応させることでうまく対処しています。
KHR_materials_dispersionのサポート
KHR_materials_dispersionは物理ベースマテリアルに色収差の要素を加える拡張です。
KHR_materials_diffuse_transmissionのサポート
KHR_materials_diffuse_transmissionは光が薄い素材を透過するときに伴う拡散現象を表現する拡張です。
AssetLoaderクラスを追加
型安全なAssetLoaderクラスを追加しました。
特徴
- 型安全性: TypeScriptのジェネリクスを使用して、読み込むアセットの型が正しく推論されます
- 並列制御: 同時読み込み数を制限して、ネットワーク負荷を制御できます
- エラーハンドリング: タイムアウトとリトライ機能を内蔵
- 読み込み状況の監視: 現在の読み込み状況を取得可能
- 直感的なAPI: オブジェクト形式でPromiseを渡し、同じ構造で結果を取得
例えばCubeTextureにHDRIを読み込ませるコードは従来こうでしたが:
// 型安全性がなく、any型として扱われる
const promises = [];
promises.push(Rn.CubeTexture.loadFromUrl({
baseUrl: '/assets/ibl/environment/environment',
mipmapLevelNumber: 1,
isNamePosNeg: true,
hdriFormat: Rn.HdriFormat.HDR_LINEAR,
}));
promises.push(Rn.CubeTexture.loadFromUrl({
baseUrl: '/assets/ibl/specular/specular',
mipmapLevelNumber: 10,
isNamePosNeg: true,
hdriFormat: Rn.HdriFormat.RGBE_PNG,
}));
const [envTexture, specTexture] = await Promise.all(promises);
// envTexture, specTextureの型はanyになってしまう
// 配列として返ってくるので、どちらがenvironmentでどちらがspecularか区別しにくい
AssetLoaderを使うとこう書けます。
// 型安全で、適切な型が推論される
const assets = await assetLoader.load({
environment: Rn.CubeTexture.loadFromUrl({
baseUrl: '/assets/ibl/environment/environment',
mipmapLevelNumber: 1,
isNamePosNeg: true,
hdriFormat: Rn.HdriFormat.HDR_LINEAR,
}),
specular: Rn.CubeTexture.loadFromUrl({
baseUrl: '/assets/ibl/specular/specular',
mipmapLevelNumber: 10,
isNamePosNeg: true,
hdriFormat: Rn.HdriFormat.RGBE_PNG,
})
});
// assets.environment, assets.specularの型はCubeTexture
// 名前でアクセスできるため、わかりやすい
console.log(assets.environment); // CubeTexture
console.log(assets.specular); // CubeTexture
WebGPUモードでのWebXR(VR)サポート
WebXR(VR)体験をWebGPUモードでもサポートするようになりました。
glTFエクスポーターの大幅改良
glTFエクスポーターが以下の拡張情報も出力できるようになりました。
KHR_animation_pointer
KHR_lights_punctual
KHR_materials_anisotropy
KHR_materials_clearcoat
KHR_materials_dispersion
KHR_materials_emissive_strength
KHR_materials_ior
KHR_materials_iridescence
KHR_materials_sheen
KHR_materials_specular
KHR_materials_transmission
KHR_materials_unlit
KHR_materials_variants
KHR_materials_volume
KHR_texture_transform
ボーン表示のサポート
ボーン表示をサポートしました。
インスタンス描画によって、ボーン数が幾つであっても一瞬で描画します。
VRMSpringBoneのコライダー表示のサポート
VRMフォーマットのSpringBoneのコライダー表示をサポートしました。スフィア・カプセル両対応です。
スフィアコライダーについてはインスタンス描画対応です。
回転ギズモの追加
これまで、並行移動ギズモと拡大縮小技ギズモはあったんですが、回転ギズモについてはギズモの形状を作るのが面倒くさそうで、避けていたんですが、ようやっと実装しました。地味に面倒ですよね(´・∀・`)
WebXR(VR)におけるEffekseerエフェクトの描画をサポート
WebXR(VR)体験中のEffekseerエフェクトの描画をサポートしました。右目左目で表示を少しずらす必要があるので、Effekseerエフェクトを描画する際に、そこらへんちゃんと面倒見ないといけないんですよね。
ちなみに、WebGLのみの対応です。WebGPUはEffekseer自体がまだWebGPU版がないので……。
どなたかEffekseerForWebGPU版、コントリビュートしてあげてください(´・∀・`)
Rhodonite Binary(.rnb)フォーマットとマテリアルノードファイル(.rmn)の策定
以前からシェーダーノードエディタは存在したのですが、作ったノードシェーダーを再利用する手段がこれまで限定的でした。
今回、シェーダーノードエディタから出力したマテリアルノードファイル(.rmn)をglTFに含むことができる独自glTF拡張「RHODONITE_materials_node」を策定し、さらにこの拡張をサポートするglTF(glb)ファイルをRhodonite Binary(.rnb)フォーマットと名付けました。一般的なglTFと混ざらないようにするための配慮です。
RhodoniteEditorからはこの.rnbファイルと.rmnファイルを保存することができ、この2ファイルをRhodoniteで開くと、ノードベースシェーダーをマテリアルとして適用したglTFモデルを表示することができます。
// RHODONITE_materials_nodeの例
{
"extensionsUsed": [
"RHODONITE_materials_node"
],
"materials": [
{
"name": "my-cool-node-based-material",
"extensions": {
"RHODONITE_materials_node": {
"uri": "./foo.rmn", // required
"uniforms": { // optional
"foo": [0, 0, 0, 1],
"boo": 1.0,
"bar": [0, 1, 2]
}
}
}
}
]
}
シェーダーノードエディタの大幅強化
シェーダーノードエディタも大幅強化しました。ノードの種類を大幅に増やし、PBR Shader等、シェーディング系のノードも追加しました。
PBR Shaderノードを適用することで、物理ベースレンダリングによるリアルな陰影や影などを表現しつつ、追加のノードでさらに特殊効果をつけることもできます。
メモリ管理機構の大幅改良
今年一番大きな意味を持つ改良です。
これまでのRhodoniteは、エンティティ数やボーン数などの上限をあらかじめ設定し、実際は少ししか使っていなくても、必ずその上限分のメモリを確保する設計になっていました。そのためメモリ使用量がやたら多く、またシーンにデータを投入し続けて、各上限を超えてしまうとメモリ内容が領域をはみ出して描画が破綻してしまっていました。
今回、このメモリ管理機構を大幅に改良しました。
必要になった段階で、少しずつメモリを確保するようにしたことで、メモリ使用量が最小化されたほか、エンティティ数やボーン数などの上限を設定する必要がなくなりました。つまり、メモリ残量が許す限り、いくらでも要素を増やすことができます。
今までのメモリ管理の弱点を克服したことで、Rhodoniteは今後も長期にわたって戦えるライブラリになったと思っています。
アニメーション処理の最適化
アニメーション処理を最適化しました。
同一ボーン構造、同一アニメーションが設定されているスケルタルモデル間については、アニメーション処理を共有する設計にしました。例えば以下のシーンでは、3体のVRMモデルが同じアニメーションをしていますが、実際アニメーション処理を行っているのは1体分のみで、他2体はその1体目のアニメーション結果を流用することで計算量を抑えています。
キャラクターを複製して、インスタンス描画している際はこの仕組みがかなり効いてきます。以下の2番目の例では257体のVRMモデルを動かしていますが、実際アニメーション処理しているのは1体分のみです。従来と比べて大きなFPS向上を実現することができました。
複数Canvasのサポート
これまで、Rhodoniteは1つのCanvasにしか描画できなかったのですが、Engineのインスタンスを複数作ることで、複数のCanvasへ描画できるようになりました。
各Engineではあらゆるリソースが独立して管理されます。
const engine1 = await Rn.Engine.init({
approach: Rn.ProcessApproach.DataTexture,
canvas: document.getElementById("world1") as HTMLCanvasElement,
});
const engine2 = await Rn.Engine.init({
approach: Rn.ProcessApproach.WebGPU,
canvas: document.getElementById("world2") as HTMLCanvasElement,
});
これは、将来的にReact対応などを考える上で非常に重要な設計改善でした。Reactでの利用の場合、複数箇所で使われることは当然ありえますからね。
Engineリソースの完全破棄
これまでのRhodoniteはリソースを確保したらそのまんまで、きちんと破棄をすることができていませんでした。最新版のRhodoniteでは、Engineクラスのdestoryメソッドを呼ぶことで、そのEngineで利用していたリソースはすべて破棄されます。
engine1.destory() // engine1の管理下にあるリソースを全て破棄する
この改善により、RhodoniteEditorの使い勝手も良くなりました。
Editorのリセットをかけるとき、従来はもうブラウザリロードするしかなかったのですが、現在はEngineを破棄してまた新しいEngineを作る、という流れで、ブラウザリロードなしにEditorの状態リセットをすることができています。
最後に
今年もそれなりにRhodonite更新できたので、満足です。
特に、長らく放置していた設計面の弱点の大半を克服できたことは、今後もRhodoniteを育てていく上で大きな自信につながりました。
最近、生成AIのコーディング・デバッグ能力が格段に進歩したので、開発にあたってはAIがかなりアシストしてくれた点も大きかったですね。
今後はどうしましょう。
ユーザーが依然少なく、特に海外ではまだ知られていないライブラリというステータスなので、もっと使ってくれる人が増えるような施策を打ちたいですね。
ドキュメントの充実? 思わず使いたくなるような機能の追加? 何が現状欠けているんでしょう。来年はそこら辺の状況の改善が目標となってきそうです。
また、レンダリングライブラリのステージから脱皮して、そろそろゲームエンジンのステージに上がっていきたいところでもあります。
一人で頑張るの寂しいので、みんなもライブラリ開発しようず(´・∀・`)
というわけで来年も頑張ります。みなさま良いお年をお迎えください。










