Rust wgpuで3DCGに挑戦する

この記事は [Rust 2 Advent Calendar 2020 - Qiita](https://qiita.com/advent-calendar/2020/rust2) 20日目の記事です！

RustとWebGPUを扱えるwgpuを使って、3DCGに挑戦しました！ので、その記録になります

![家と雪だるま .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205472/4b9c28c3-82bc-3d85-077d-5cb7a8db78ee.png)

ここにもGif画像をあげたかったのですが大きすぎた...

- リポジトリ: https://github.com/anotherhollow1125/obj-viewer
- お試しページ: https://www.namnium.work/obj-web Firefox Nightlyでのみ動作確認しています。重いです。マウス操作等は受け付けません。
- 姉妹記事(ぜひ読んで！): [3DCGの基礎知識 Objファイルを描画するまで](https://qiita.com/namn1125/items/44356be38b55ceb652fb)

# 動機とRustの良さ

Rustを知るまではUnity C#でゲーム制作を行っていたのですが、Rustの魅力的な機能を知るとC#を書きたくなくなってしまい、Unityアレルギーになってしまいました。Rustには以下のような特徴があります。

- 所有権やライフタイムによるメモリ管理: NullReferenceError回避
- ガベージコレクションを使わないメモリ管理: 上記と被りますが、こちらは速度向上の意味で
- トレイト: 継承とか考えなくてよい上にわかりやすい
- わかりやすいパッケージシステム: Cargo.tomlによる管理は、他の言語の依存ライブラリ管理よりはるかにわかりやすいです
- 幅広いコンパイルターゲット: クロスプラットフォームを実現しやすいといえます。Rustによってゲームボーイアドバンスのソフトを作ったりもできるそうです！

そこで、ゲーム制作の未来を担うモダン言語Rustでゲーム制作を行うために3DCGをやってみました！本記事で、Rust&wgpuによる実装についてまとめます！

3Dゲームは9割が3DCGができるか否かにかかっていると思います(言い過ぎ？)。ともかく、3Dモデルを読み込んで表示できれば、Unityに固執する必要はなくなるでしょう。Unityで3Dモデルをロードする方法(あるいはBlenderで3Dモデルをエクスポートする方法)はいろいろありますが、その中でも扱いやすいObjファイルを使用して3Dモデルを描画できれば、この目標を達成できたことになります。

また、 **今回取り組むにあたり必要となった理論的知識は、[3DCGの基礎知識 Objファイルを描画するまで](https://qiita.com/namn1125/items/44356be38b55ceb652fb)という姉妹記事のほうにまとめました** 。そのためこちらの記事では3DCGの基礎知識に関して詳しくは取り扱いません。適宜こちらの記事を参照していただけると幸いです。( このアドベントカレンダー担当記事を優先するべきところをそっちのけで頑張って書いた力作なので是非読んでマサカリを投げてほしいです() )

# 使用したライブラリ － wgpu

Rustでシェーダーを触れるライブラリは複数ありますが、今回は最終的に[wgpu](https://github.com/gfx-rs/wgpu-rs)というライブラリを使用しました。

Rustで3DCGが行えればなんでも良いと考えていたため、最初は[gfx](https://docs.rs/gfx/0.18.2/gfx/)というライブラリを使用していたのですが、複数の光源を設定しようとしたところ[Shader Storage Buffer Objectが使えない](https://stackoverflow.com/questions/56842153/how-to-bind-a-vertex-buffer-to-a-uniform-array-in-gfx-rs)ために回りくどい書き方しかできないことに気づき、またバックエンドのOpenGLもだんだんとレガシー化しているということを知り、gfxライブラリ以外に使えるライブラリを探していたところ、これから策定されようとしている[WebGPU](https://gpuweb.github.io/gpuweb/)というものがあることを知りました。

WebGPUが登場した背景には各社のGPUの設計が似通ってきたことがあります。従来のOpenGL (WebGL) は「各社グラフィックボードでの共通部分をAPIとしてまとめたもの」であり、抽象度が高くオーバーヘッドが大きいです。OpenGLと比べると、より低レベルから書ける各社のAPIのほうが当然性能が高いのですが、結局低レベルでのAPI設計も似通ってきたために、新たに共通なAPIとしてWebGPUを策定しようという運びになったらしいです。以下の記事の受け売りなので詳しくはそちらを見てください()

- [WebGPUでガチリアルタイムレンダリングの世界が見えてきた - Qiita](https://qiita.com/emadurandal/items/3c0f8e06d443346aad8f)

そしてそのWebGPUのRust用の実装として存在していたのが[wgpu-rs](https://github.com/gfx-rs/wgpu-rs)でした。というわけで、紹介していきます！

ちなみにWebGPUはWebGL同様にブラウザで動かすものですが、wgpuはWebGPU専用のライブラリというわけではなく、ローカルでバックエンドの指定なしでビルドすると使用できるバックエンドを勝手に選ぶそうです。自分の環境では `Vulkan` でした。WebGPUを用いてブラウザで実行するプログラムまで書いてみたかったのですが、アドベントカレンダー担当日には間に合わないので、後日できたら追加しようと思っています。(2021/1/3 追加しました！)

## 2021/1/3追記

https://www.namnium.work/obj-web にてWebGPU版をデプロイしてみましたが、はっきりいって全くまともに動いていない状況です。wgpu版のWebGPUは、Firefox Nightlyのみでしか動作確認ができませんでした。その上、フレームレートがかなり死んでいます。

WebGPU関連のリンク:

- [[忙しい人向け] 100行から始めるWebGPU（WGSL対応版） - Qiita](https://qiita.com/cx20/items/1f2dcfe0e7ac60fc8ce9)
- [WebGPU 実装状況のメモ; Firefox-80.0a1(nightly), Chrome-86.0.4191.0(canary) なう - C++ ときどき ごはん、わりとてぃーぶれいく☆](https://usagi.hatenablog.jp/entry/2020/07/04/075822)
- [Implementation Status · gpuweb/gpuweb Wiki](https://github.com/gpuweb/gpuweb/wiki/Implementation-Status)
- [WebGPU Samples](http://austin-eng.com/webgpu-samples/)
- [WebGPU 対応](https://www.namnium.work/obj-web/webgpu_help.html)

誰かwgpuのWebGPUについてもう少し検証等していただけるとありがたいです(><) (他力本願) 

もといライブラリやブラウザがもう少し成熟するのを待った方が良いかもしれません。

# 以降についてTL;DR

wgpuに関する日本語の情報はほとんどなかったのですが、英語のチュートリアルサイトでとても素晴らしいものを見つけました！wgpuだけではなく3DCGの基礎知識も適宜しっかりと教えてくれるので非常におすすめです。

[Introduction | Learn Wgpu](https://sotrh.github.io/learn-wgpu/#what-is-wgpu)

このチュートリアルを見つける前からObjファイルを描画することが目標だったのですが、なんとネタ被りしており、Objファイルを描画するプログラムが登場しています！チュートリアル終了後にかなり書き直しはしましたが、実装した内容は基本的にこのチュートリアルの内容です。(ぶっちゃけこの記事の目的の9割がこのチュートリアルの紹介です)

最近も更新されている新しいチュートリアルですので、wgpuに興味を持たれた方はぜひこのチュートリアルをやってみると良いと思います。

# 実装した内容

かるーく、今回取り組んだ内容を紹介していきます！

## 1. Objファイルの読み込みと表示

[tobj](https://docs.rs/tobj/2.0.3/tobj/)というライブラリを使用してObjファイルを読み込み、表示させました！

Unityではオブジェクトをインスタンスとして管理するので、それに倣い、オブジェクトファイルを読み込んだうえでインスタンスを作成し表示させる方式にしました。そのため同じ物体を複数置いたりなどもできます。

![サイコロ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205472/fda6137a-a1b6-294f-0664-d8cb179eeeef.gif)

Objファイルを表示させるまでの基礎知識は始めのほうで述べた通り[3DCGの基礎知識 Objファイルを描画するまで](https://qiita.com/namn1125/items/44356be38b55ceb652fb)の方を参照していただければと思います。

## 2. 光源の表現

[Phongの反射モデル](https://ja.wikipedia.org/wiki/Phong%E3%81%AE%E5%8F%8D%E5%B0%84%E3%83%A2%E3%83%87%E3%83%AB)をもとに光源を設置してその影響をオブジェクトに与えるようにしました。

またUnityでは複数の光源を設置することができます。wgpuでは[Shader Storage Buffer Object](https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object)が使用できるので、これで光源オブジェクトを動的配列として渡すことができ、複数の光源を設定することができます。(ちなみにチュートリアルにあるのはSSBOの紹介までです)

![サイコロ2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205472/ad89eeb7-0985-8555-a8b1-10e8d1725aa4.gif)

(実際の実行画面では問題ないのですがなぜかGif画像がうまく行ってない...)

## 3. シャドウマッピング

やはり立体に見せるためには影が必要だろうと思い、今回一番頑張ったところです。

実装は [wgpu-rs/examples/shadow at master · gfx-rs/wgpu-rs](https://github.com/gfx-rs/wgpu-rs/tree/master/examples/shadow) を参考に行いました。テクスチャやシェーダを複数用意して入れ替えて...という作業は、振り返ってみるとwgpuだからこそ楽に行えたように感じます。

![ShadowMapping.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205472/90d981c9-3b27-ca4d-0566-8ebd8e343cfc.gif)

[3DCGの基礎知識 Objファイルを描画するまで](https://qiita.com/namn1125/items/44356be38b55ceb652fb#5-%E5%BD%B1%E3%82%92%E4%BB%98%E3%81%91%E3%82%8B--shadow-mapping)の最後の節でシャドウマッピングに関しても軽く解説しているので、良かったら覗いてみてください。

# wgpuを触ってみていくつか気になった点

## `LayoutDescriptor`

詳しくは述べませんがwgpuが持つ要素はどれも

- `LayoutDescriptor`
- `Layout`
- 実体 (`BindGroup` など)

の三要素からなることがとても印象的でした。

自前で用意した構造体で `new` みたいなコンストラクタの役割を果たす関数に引数を渡す際、設定項目が多いとどの引数がどの引数かわからなくなってしまったのですが、そうなったとき `LayoutDescriptor` の存在は一つの解を与えてくれたように思えます。すなわち、引数を構造体としてまとめて渡せば、設定項目について"ラベル"を明記しなければならないので、結果的に可読性が上がるだろうという解法です。

この方法を使うと、先ほど述べた通り引数項目が何であるのかを明記しなければいけないことに加え、[std::default::Default](https://doc.rust-lang.org/std/default/trait.Default.html)トレイトを用いることで **デフォルト引数に近いものを設定できる** というメリットがあります！

実際にこのような性質を利用し"引数構造体"を定義してみるということは結局しなかったのですが、今後コンストラクタに渡す引数が多くなりそうなときは使ってみたいと思っています。

## `#[repr(C)]` とbytemuck

今回、Rustの構造体をシェーダーに渡すために用いたのが `#[repr(C)]` と[bytemuck](https://docs.rs/bytemuck/1.4.1/bytemuck/index.html)ライブラリでした。

`#[repr(C)]` アトリビュートを指定すると、構造体のメモリレイアウトがC言語準拠になります。C言語準拠にすることでシェーダー側が解釈できるレイアウトになります。

この話題に関しては、[Rustの構造体メモリレイアウト - ryochack.blog](https://ryochack.hatenablog.com/entry/2018/03/23/184943)というページが詳しかったです。

また、これに関連して、シェーダーが解釈できるようにするために "パディング" を構造体に入れなければならなかったのですが、これの仕様がよくわかりませんでした。 [ユニフォームブロックのメモリレイアウト @ゲームプログラマの小話[開発:グラフィックス] - Qiita](https://qiita.com/hoboaki/items/b188c4495f4708c19002) などの記事を読み色々調べたり実験したりした結果、次の図のようなルールなのかと解釈したのですが、合っていますでしょうか...?

![パディング.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205472/1a474845-47f5-3557-b0ce-de53cf8fc389.png)

誰か詳しい人がおりましたら合っているか教えていただきたいです。m(_ _)m

# あとがき

最後まで読んでいただきありがとうございました！本記事でRustでのゲーム制作について可能性を示すことができたならば幸いです。

記事投稿までに間に合いませんでいたが、せっかくWebGPUが使えるライブラリを使用しているので、近いうちにWebAssembly化したデモを作りたいと思います！ (→ [作りました!](#2021/1/3追記) ) 

コメント、編集リクエスト等お待ちしております！