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

# はじめに

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

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

# 今年の振り返り

　実は昨年あたりから、自作ライブラリ開発に対してやる気の低下が起きており、進捗が思うように出せずにいました。３０代の頃は怖いもの知らずというか、がむしゃらに開発をしておりまして、自分のやる気がいつか衰えるなんて考えもしなかったのですが、老化(?)というのは恐ろしいものですね。代わりに歴史やリベラルアーツみたいな文系の勉強に興味が出てしまい、しばらくCGと疎遠になっていました。最近戻ってきましたけど。

　そんなわけで、今年はあまり進捗が思うように出せなかったのですが、それでも新規に追加した機能などについて、ご紹介したいと思います。

# Rhodoniteライブラリについて

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

![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に新たに追加した機能

## VRM 1.0対応

　昨年まではVRM 0.xまでの対応でしたが、今年のバージョンからVRM 1.0のロード・表示にも対応しました。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/240245f4-6342-6a83-23c8-00365d27cd87.png)

 VRM 1.0対応でてこずったのが、VRM 1.0ではボーンのローカル軸の情報がVRM 0.xと違って初期化されず、保持されるという点です。

　VRM 0.xではアニメーションデータのボーンの回転値と移動値をVRMモデルにそのまま流し込めば良かったのですが、VRM 1.0では同じことをやるとおかしな結果になります。
 結論としては[こちらの記事](https://qiita.com/TokageItLab/items/beaf219762e1e34a6250)で説明されているグローバルリターゲッティングの処理を行うことで対応しました。

　実装にあたって、各ボーンにレスト（アニメーションしていない素の姿勢の状態）とポーズ（アニメーションしているポーズの状態）のそれぞれの情報を持たせる必要があったため、Rhodoniteで姿勢情報を受け持つコンポーネントであるTransformComponentとSceneGraphComponentに、RestとPoseの二つの姿勢情報を持たせるように改良を施しました（以前はPoseに当たる姿勢情報しか持っていなかった）。

　今年の改良で、Rhodoniteのアニメーション対応はかなり改善されたと思います。

## VRMA (VRM Animation)対応

　VRMAにも対応しました。
　こちら、Rhodonite Editorに、VRMファイルをD&Dした後、VRMAファイルもD&Dすると、表示されたVRMモデルがアニメーションします。試してみてね。

 https://editor.librn.com/

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/296fd3fc-d57a-4979-e448-f55e3b0aceab.png)

## アニメーションブレンディング対応

 小ネタですが、アニメーションブレンディングに対応しました。最初は、スキン行列ごと線形補間する必要あるのかしら、と思ったんですが、キー情報のlerpだけでいけましたね。楽、楽。

https://x.com/emadurandal_cgj/status/1683878192329936896?s=20

## glTF PBR拡張の対応を拡充

　今年は新たにKHR_materials_anisotropyに対応し、異方性反射を扱えるようになりました。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/d90d8f71-1504-de43-5e90-1fe457e096f1.png)

## 物理シミュレーションに対応

　といっても、物理シミュまで自作はしておらず、Oimo.jsを利用しています。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12753/6bad42cc-f0de-75f4-8391-628015afbed5.png)

## WebGPU対応（中）

　重い腰を上げてWebGPU対応も始めています。年のせいか、おじいちゃんもうモチベーション上がらねぇよ……(´Д｀)

# 困ったこと

## Puppetterによるビジュアルテストのメンテナンス

　PupetteerとJest、Github Actionsを駆使してビジュアルテストを導入しているのですが、最近のバージョンのPupetteerだと、どういうわけかSwiftShader(Chromeに内蔵されているソフトウェアレンダラー)によるソフトウェアレンダリングを使ったテストができなくなってしまいました。ソフトウェアレンダリングでないと、OS環境が違うとレンダリング結果が微妙に異なってしまい、ビジュアルテストの一貫性を保てません。私はMac環境で開発していましてローカルでビジュアルテストした結果と、Github ActionsのUbuntuインスタンスで結果を一致させたいわけです（ハードウェアレンダリングだと、不一致になります）。

　結局どうしたのかというと、自分のMacによるSelf-HostedのGithub-Actionに切り替え、ハードウェアレンダリングで行くことにしました。これなら、ローカルのテスト実行とGithub-ActionによるCIテストも、両方自分のMacで実行されるので、ハードウェアレンダリングでも結果は一致します。

　公開リポジトリでのSelf-Hostedはセキュリティ上の観点からあまり推奨されないようなのですが、どうせ私の弱小リポジトリ、そんな変なプルリクもそうそう来ないだろうということで、割り切っています。

 思わぬメリットとして、WebGPUのビジュアルテストも問題なくできるようになりました。SwiftShaderはまだWebGPUに対応していないらしく、ソフトウェアレンダリングだとWebGPUがテストできなかったので、ハードウェアレンダリングに切り替えてちょうど良かったです。

## 非同期の扱い

　WebGPUでは非同期のAPIがいくつかあります。そのため、ライブラリ設計として非同期処理をどう扱うかが重要になってきます。

　非同期プログラミングをやったことのある方はなんとなくわかると思いますが、async修飾子は連鎖していくので、API設計上かなり悩ましいです。扱いやすいAPIを指向するなら、できる限りasyncのないAPIを提供したいのですが、それには非同期処理をライブラリの外の責任にするか、内部でどうにか抱え込んで吸収するかしなくてはいけません。

　Rhodoniteではその点、まだ設計が乱雑としている部分があるので、今後の要改善ポイントですね。

# そういや、Kairosはどうなった？

　[昨年のご報告](https://qiita.com/emadurandal/items/9a091c117464fab26921#%E6%96%B0%E3%81%97%E3%81%84%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E9%96%8B%E7%99%BA%E3%81%AB%E7%9D%80%E6%89%8B%E3%81%97%E3%81%9F)でKairosというC++のライブラリも作り始めたと言いましたが、そちらの進捗もそれほど進んでいません（汗）

　Vulkan、Direct3D12、Metal、WebGPU全てのAPIに対応しようとしているのですが、志が高すぎる（？）のか、各APIの勉強でだいぶ時間が溶けました。
　ただ、それでも少しずつは進捗していまして、Kairosの中でも3DAPIに関わる部分は、Hermes（ヘルメス）という名前を付けました。Meta社の[igl](https://github.com/facebook/igl)みたいな3DAPI抽象化ライブラリです。

　このKairosについては当面、ソース公開の予定はありませんが、経過は都度、X(旧Twitter)の方で報告させていただこうと思います。

# 教育用？ライブラリ「Spinel」

　また、最近は[Spinel](https://github.com/emadurandal/spinel)という小さなライブラリも開発しています。Rhodoniteの経験を活かして、最適化を度外視し、できるだけシンプルなコードで綺麗にライブラリを書いたらどうなるか、という個人的な挑戦です。

「[WebGLライブラリを作ってみよう](https://cg-rnd.growi.cloud/5d998f48a477e200509a3122)」という企画の一環としてやっているのですが、私本当に面倒くさがりなもので、各回の解説が非常に雑になっています。時間がある時に書き直してZennにまとめ直しますので、お待ちください。

　このSpinelのソースコードを追っていって、ライブラリ開発の楽しさを発見してくれる人が出てくるといいな、と思っています。

# 来年以降はどうするか

　今年１１月頃から、ようやくライブラリ開発のモチベーションが回復してきたところです。良かったよかった。
　流石に10年近く開発を続けていますし、私がここまで３D経験・ノウハウを得られたのも自作ライブラリ開発の経験によるところが大きいので、これからもどうにか辞めずに続けたいと思います。

　ここまでお付き合いくださいましてありがとうございました。皆様、良いクリスマスを！