自作Web3DライブラリRhodonite ５年目の報告

# はじめに

　この記事は[グラフィックス全般Advent Calender](https://qiita.com/advent-calendar/2024/graphics)２５日目の記事です。
　私、@emadurandalは自作のWeb3Dライブラリを開発しておりまして、毎年アドベントカレンダーで一年の報告をしております。

　今回は、Rhodoniteという自作ライブラリの５年目の報告ということで、記事を書かせていただきます。

# 今年の振り返り

　一昨年以降、やる気の波の上下が激しく、今年もだいぶ怠けてしまった部分がありますが、昨年よりは多少できたことは多かったかな、と思います。

# Rhodoniteライブラリについて

　[Rhodonite](https://github.com/actnwit/RhodoniteTS)はTypeScriptで書かれたWeb3Dライブラリです。
　WebGL2とWebGPUに対応し、物理ベースレンダリングなどを含めた高度なレンダリングに対応しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/4b9d9739-ae3e-d809-03ab-3834a29e9d7a.png)

## インストール方法

　npmで簡単にインストールできます。

```bash
$ npm install rhodonite
```

## 使い方

　[WebGLを生で使うより](https://qiita.com/cx20/items/0597cdbccdd370e05956)、非常に短いコードで３Dを表示できます。

```typescript
import Rn from 'rhodonite';

// Rhodoniteの初期化
await Rn.System.init({
  approach: Rn.ProcessApproach.DataTexture,
  canvas: document.getElementById('world') as HTMLCanvasElement,
});

// キューブの生成
Rn.MeshHelper.createCube();

// レンダリングループ
Rn.System.startRenderLoop(() => {
  Rn.System.processAuto(); // キューブの生成を検知してキューブを描画してくれます。
});
```

　より詳しい使い方は[こちら](https://github.com/actnwit/RhodoniteTS/wiki/%E3%82%A4%E3%83%B3%E3%83%88%E3%83%AD%E3%83%80%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)をご覧ください。

## Rhodonite Editor

Rhodonite Editorというビューアーもあります。npm installして試すのなんてめんどいという方は以下のURLにアクセスして、画面にglTFファイルとかVRMファイルとか放り込んでみてね(´・∀・｀)

https://editor.librn.com/

# 今年追加した機能

## WebGPU対応

WebGPUに9割ほど対応しました。1割まだなのは、影生成とか一部まだ実装できていないところがあるんですが、まぁ大体は動くようになりました。去年は全然モチベ上がらねぇ、とか言ってましたが、今年は少し自分を褒めたい。

肝心のパフォーマンスですが、WebGPU版はWebGL版よりも相当高速（レンダリング処理が3~4倍高速）に動作するようになりました。

## 描画パフォーマンスの大幅な改善

WebGL実装、WebGPU実装ともに大幅な描画パフォーマンスの改善を果たしました。WebGL実装は昨年時と比べて約4~5倍(ドロー時間1/5~1/4)速くなっています。WebGPU実装はさらにその3~4倍ほど速くなっています。

ライブラリ全体に渡って地道な最適化作業を続けた結果です。Three.jsやBabylon.jsと遜色ない描画速度になったと思います。

描画時間が1msecを下回るのを見ると気分が良いですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/3c5a5042-09a5-3093-53e5-2e388e8ee5c9.png)


## ブルームエフェクトをサポート

ブルームエフェクトをサポートしました。光が溢れ出るような効果を演出します。野外シーンなどでだいぶ雰囲気が出るようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/c8c27e2a-f675-29a4-ee89-ddc32d7dbe4b.png)


## glTF拡張、KHR_materials_emissive_strengthをサポート

emissive(自己発光)パラメータで1.0以上の値をサポートするための拡張です。同拡張では、1.0以上が設定されている場合はブルーム処理等の対象とすることなどが示唆されています。
（Rhodoniteの場合は1.0以上でなくてもレンダリングの結果として1.0以上の輝度値になればブルームがかかるようになっていますが）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/615a829f-d9d9-e053-5d20-42b8ff1deecc.png)

## VRのMultiView拡張 (OCULUS_multiview/OVR_multiview2)をサポート

WebVRレンダリングを高速化するMultiView拡張に対応しました。これは、左目・右目の描画処理を一度に行うことによりパフォーマンスを改善するものです。

https://fernandojsg.com/article/multiview-on-webxr/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/3960afca-f275-931e-32b2-6648d8d79845.png)

## ノードベースシェーダーのリファイン

以前からノードベースシェーダーの仕組みはありましたが、UI画面として頂点シェーダーとフラグメントシェーダーが分かれていました。どうしようか迷いましたが、他の多くのエンジン・ライブラリのノードベースシェーダーが同一画面で頂点シェーダ・フラグメントシェーダーを扱っているので、Rhodoniteもそれに合わせることにしました。

この作業が意外と大変で、頂点シェーダー側のノードをフラグメント側のノードに繋げた際、その間をうまくVaring変数で橋渡ししてあげる必要があります。その辺りの内部処理の実装は骨が折れましたが、だいぶ良い仕組みに落ち着いたと思います。

https://x.com/emadurandal_cgj/status/1804127028398166459

## トーンマッピングをサポート

トーンマッピングをサポートしました。大体のメジャーどころは対応しています。
Khronos PBR Neutralが程よく彩度を保ってくれるため、これをデフォルトにしようかな？　と思っていたのですが、[HDRブルームとの相性が悪そうという意外な特性があった](https://github.com/KhronosGroup/ToneMapping/issues/5)ことがわかり、結果的にGT ToneMapがデフォルトになりました。

- Reinhard
- GT ToneMap
- ACES Filmic Tone Mapping (Narkowicz)
- ACES Filmic Tone Mapping (Hill)
- ACES Filmic Tone Mapping (Hill - Exposure Boost)
- Khronos PBR Neutral

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/7e60f1e2-e7c5-8291-7483-7f6ff5b89302.png)


## IBLプレフィルタリング処理をサポート

IBLプレフィルタリング処理をサポートしました。以前から一応サポートはしていたのですが、それまでは別の方に開発していただいたものを使わせていただいていました。そのバージョンは輝度変化の大きな部分でFireflyアーティファクト（ところどころに明るい粒々が生じるアーティファクト）が生じることもありました。

今回は、新規にIBLプレフィルタリングを自前で実装しました。自前、と言いつつ多くの部分を[glTF Sample Renderer](https://github.com/KhronosGroup/glTF-Sample-Renderer)の実装を参考にしていますが💦

おかげさまで、とても綺麗なプレフィルタリング結果が得られるようになりました。

HDR画像をお持ちの方は、Rhodonite Editorに.hdrファイルをドラッグ＆ドロップしてみてください。HDR画像がプレフィルタリングされ、IBL（ライティング）処理に使われます。

## FinalizationRegistryによる自動破棄

テクスチャクラスのオブジェクトがどこからも参照されなくなり、GCで破棄される場合に、内部で保持しているWebGL/WebGPUテクスチャリソースも自動解放されるようにしました。

https://x.com/emadurandal_cgj/status/1856229026974838813

これには、モダンブラウザでサポートされているFinalizationRegistryという機能を利用しています。

FinalizationRegistryについてはこちらの記事をご覧ください。

https://qiita.com/emadurandal/private/8bf687f74c9ab01cb07a#%E3%82%A2%E3%83%97%E3%83%AD%E3%83%BC%E3%83%81%EF%BC%92gc%E3%82%AC%E3%83%99%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E4%BC%B4%E3%81%86%E8%87%AA%E5%8B%95%E8%A7%A3%E6%94%BE

この仕組みだと、テクスチャクラスがGC対象（どこからも参照されていない）にならないと内部テクスチャリソースが解放されないため、テクスチャクラスオブジェクトをライブラリ内部で参照している箇所の多くをWeakRefによる弱参照に置き換えました。これにより、テクスチャクラスオブジェクトが不要になった際にGC対象になりやすくなります。

### FinalizationRegistryによる自動破棄は吉と出るか凶と出るか

しかしChatGPTに聞いてみたところ、このFinalizationRegistryによるリソースの自動破棄と弱参照によるGCコントロールは、Three.jsやBabylon.jsなどのメジャーなライブラリでは利用されていないようです。GC処理に起因するフレームドロップを避けたい狙いがあるのでしょう。
あとBabylon.jsの場合、リファレンスカウント方式による特別な管理をしているとGPTが言っていますが、本当なんでしょうか。ちょっと気になりますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/1252aa91-dc68-4ee2-f9bb-3e19d7bd77ec.png)

しかし、群衆シミュレーションのような大量のオブジェクトを生成・破棄するようなケースでは、フレームドロップを許容してでもGCによる使用メモリのリダクションコントロールが必要になるケースがあるかもしれません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/c0f01879-9f08-1259-582a-6e22b2523b43.png)

Rhodoniteでは後者のケースも想定してFinalizationRegistryと弱参照による自動解放機構を作ってみました。まぁ、どちらというとそこに釘があったからとりあえず打ちたくなったともいうんですが（爆

## Khronos Fidelity Comparisonへの適応

[Khronos Fidelity Comparison](https://github.khronos.org/glTF-Render-Fidelity/comparison/)という、各種レンダリングライブラリのglTF描画結果を比較するサイトがあります。[このサイトが使っている仕組み](https://github.com/KhronosGroup/glTF-Render-Fidelity)にRhodoniteを適合させ、three.jsやBabylon.jsなどと同じ条件で描画結果を比較できるようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/30acd3df-3108-216a-f1ae-cd174192c9b1.png)


これの利点は、数々の著名なレンダリングライブラリと同じ条件（描画に使う3Dモデル、IBLに使うHDRI、カメラアングル）で描画結果を比較できることです。ライブラリを自作している方はぜひ、自分のライブラリの描画品質のチェックに利用されてみてはいかがでしょうか。

## TypeScriptの循環参照（import循環参照）の解消

昨年までのRhodoniteは、多くのTypeScriptファイルの間でimportの循環参照が起きてしまっていました。だいぶリファクタリングをして、これらの全てを解消しました。

## Loggerクラス導入

コンソール出力のほとんどをLoggerクラスを通して行うようにし、設定したログレベル未満のレベルのログは出力しないようにしました。

# さいごに

ここ数年、モチベの波が激しく、ひどい時は「CGなんも興味無くなった＼(^o^)／」「Rhodoniteもう更新することはないだろうな……」みたいな境地になることまであったのですが、そのうちするとまた戻って来れるので、なんだかんだで私はCG好きなのかもしれません。

ただ、Rhodoniteの将来をどこへ向かわせるか、という部分は少し迷っています。それが定まるまでは、気ままに機能増強を続けるかもしれません。

みんなも自作ライブラリ作ってみようず(´・∀・｀)

ということで、みなさま良いクリスマスを！