23万ポリという数字自体に囚われるのは良くない――かといって重くないわけでもないという話

# 今北産業
- VRChat向けの23万ポリの衣装が「重いのではないか」と騒がれている
- でも正直「23万ポリ」のインパクトで騒いでる人も居る印象を受けますし、実際私も初めは印象論でツイートしました（ごめんなさい）
- 印象論で騒ぐのは良くないので建設的な形で意見をまとめて送りたかったですし、間違った認識の元「重い」という評判が広まるのはよろしくないと思います。重いなら重いで「何が、どのように重いのか」を指摘した方が建設的です
- まとめていたら思いの他知見として有用そうだったので、折角なので公開記事としてまとめます（4行目）

以下、スクリーンショット等に含まれる3Dモデルは以下の製品か、それを私的に加工したものです。
Only4U 「【リアル服】only4Uクラファン開催記念無料配布衣装【セフィラ・幽狐族のお姉様・ここあ用】ver1.01」
https://only4u.booth.pm/items/3140313

2021/8/21時点でのVRChatの指定UnityバージョンであるUnity2018.4.20f1を利用しています。

# 改善されました（2021/09/26）
この記事を執筆した後、先月初旬に色々と私の方から（お仕事として）アドバイスをさせていただいて、それを元にOnly4Uさんでリアルタイムレンダリング向けの最適化手法を色々と模索されたようでした。しばらく音沙汰がなかったので少し心配していたのですが、パワーアップして復活したようです。

https://twitter.com/only4U_official/status/1441960722611662852?s=20

私がさせていただいたのは最初のアドバイスのみなので、その後の手法の選択や実装は全てOnly4Uさんの方でしていただいた形になります（この記事も私が勝手に書いたものです）。
Cloth部分のポリゴン数が2000まで削減されていたり、Cull offして頂点数を増やさずに裏面描画していたり（以前はされていなかった）とかなり色々と改善されています。Solver Frequencyがやはり320でやや高いようにも思えますが、VRChat向けアバター、特に改変されたものについては全然最適化されていないのが常なので、この衣装が特別重いということもなくなったのかなと思います。

まで書きましたが、2K, 4Kテクスチャを結構な枚数使っていることに今気付いたので気にする人はOverrideしてあげてください。ただし、細い縫い目をNormalで表現していたりする形になっていて、解像度を落とすとその縫い目がどうしても太くなってしまいます。
Only4Uさんが大切にされていることとして、現実の服飾と同様の構造をしている、という点があるそうなので、ここはもう方向性の問題としてしまってもいいのかなとも思います。
（※代替の実装としてそれならポリゴンで溝を掘ってしまうのもアリだよねという話は敢えて掘り下げません）

Unity2019 LTSになりNvClothになって挙動も変わりましたので、以下の記事は2018時点での内容であることに留意してください。VRChatのAvatar Performance Rankingは相変わらず見直されずなので、Very Poorではありますがどこまでアテになるのかというとうーん。という状況も加速するばかりです。


# 結論から
- 高負荷なモデルですが、「23万ポリ」の他に、より大きな負荷の原因があります
- 全体的に、Unityでのリアルタイムレンダリングの知見が不足していることに起因するのではないかなあと思っています
- 折角のフォトリアルなお洋服なのに、人の多いインスタンスで使うと使用者が嫌われかねない現状はあまり嬉しくありません
- Clothコンポーネントは本来リアルタイムレンダリングでの使用に耐えうるほど高速ですが、8000ポリゴンはちょっと多いと思います
    - 目安は2000頂点, Solver Frequency 300Hzで12個だそうです。インスタンスに何人居るかにもよりますが、12人以上が居るインスタンスは珍しくないのでこれより低く抑えることが望ましいでしょう
    - この基準がどのような環境を意図したものはわかりませんが、一般にVR向けの表現は通常のデスクトップアプリケーションよりも厳しいパフォーマンス要件を課されます
- その他、いくつか見た目を全く変化させずにパフォーマンスを向上させられる要素があったのでそちらも記載します
    - ただこれらはこのモデルが特に悪いとかではなく、同様の要素は他の作者のモデルでも目にすることがあります
- Marvelousで吐いたモデルをリトポしていないのはちょっとよくわからなかったです（工数の問題？）

#### 2021/8/3追記：**お仕事としてこのモデルの改善に携わることになりました！**

https://twitter.com/only4U_official/status/1422400527304859650?s=20

Qiitaを出した後即日お声掛け頂き、色々ご説明をさせていただいておりました。いかんせんまだ1営業日しか経っていないのでどういった関わり方になるかはちょっと不確定でございます。お話しした感じやはりと言うべきかリアルタイムレンダリング環境でのクオリティの上げ方に対して認識違いがあっただけでクオリティに対する誠意は非常に感じましたので、これから良くなっていくのではないかなと思っています。

# 前提
- 出来るだけソースを当たり正確性の担保に努めていますが、不正確な内容が含まれるかもしれません
    - Twitterまで教えて頂けると大変助かります([@AomeeVR](twitter.com/AomeeVR))
- そもそも私は最適化がそんなに好きではありません
    - 私のアバターは大体Poorで、殆ど最適化されていません
    - 面倒ですよね
        - Avatar Performance Rankingの指標自体が必ずしも適切ではない現状もありますから、適切な設定が報われるわけでもない……

- 私は行列や線形代数を理解していないため、Unityのレンダリングに関する知識は表層的なものに留まります
- アバター改変が初めての読者の方でも読めるよう、基本的な内容から解説を行うよう気を付けています
- パラレルマーケットというバーチャル展示会を主催している人間なので、たまにその入稿ルールの話が出てきます。何故かというと、各種バーチャル展示会の入稿ルールは要は会場を重くしないために最低限必要な内容が設定されるものだからです。

# Profiler
簡単に言うと、「何に、どれだけ」計算時間がかかっているのかがわかるUnityの便利機能です

https://docs.unity3d.com/ja/2018.4/Manual/Profiler.html

# Profilerを活用したパフォーマンス確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/6a2f676e-36fb-2c45-6ed1-038c1d3a4e54.png)

こんな感じでMain Cameraの前に当該衣装を置き、Realtime Directional Light1灯で照らしただけのSceneを用意しました。
既に画面にいくつか専門用語が出てきているので特に重要な二つについて簡単に説明すると、
「Batches」は「描画命令が1F(フレーム)に何セット飛んだか」
「SetPass calls」は「描画環境の切り替えが1Fに何回行われているか」
という数値です。これらは描画負荷に関する簡便な指標で、ざっくり言えばこれらの数値が大きい程重いわけです。
といっても数値の感覚がわからないと思うので例を出すと、パラケットやVket、クロマケのブースの制限はBatches:30、SetPass calls:20です。このお洋服は大体ブース1/3~1/2個分ということですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/0403a8aa-9ae5-280b-e787-3754070c7c96.png)

早速Editor上で実行して、Profilerの表示のうち一部を切り出して張り付けてみました。CPU処理についての部分です。
重要なのはこれらの処理に何ms掛かったのかという絶対値ではなく、他と比べてその処理がどのくらい長いのか短いのかという相対値です。本質的には「何がボトルネックなのか」が重要なのであり、その項目を改善するのが効果的なわけですね。

右側の「EditorLoop」はUnityエディタ自体の負荷なので実際の環境では消滅します。ですから、「PlayerLoop」の中で何に時間がかかっているの？　ということを見れば良いようです。

もうお分かりと思いますが、`Physics.UpdateCloth`がPlayerLoopのうち約82%を占めています。これはClothコンポーネントの処理です。じゃあ「Clothは重い」ということで済ませてしまっても良いのでしょうか？

# Clothは重い？

本当でしょうか。Clothには様々な設定項目がありますが、試しにこれを変化させてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/650625a7-8af0-2264-8d83-40a778f02b57.png)

たくさんの項目がありますね。それぞれの項目についての解説は以下の記事がとても参考になりました。

https://qiita.com/esnya/items/8f5020d350f63b8d262a

記事を読む限り、

- Use Continuous Collision
- Solver Frequency
- Self Collision

これらの3項目が特にパフォーマンスに影響しやすいようです。
まずSelf Collisionは編集画面の時点でUnity Editorがカクついてまともに操作できませんでした。
上記記事によれば
> 計算の大部分を占める可能性がある

だそうです（孫引き）。少なくともこのようなハイポリゴンなClothでは利用しない方がいいでしょう。

Use Continuous Collisionはオンにすることで計算量が2倍になる項目です。
高速で移動する物体の計算精度が向上するようですが、「高速」の定義がよくわかりませんね。
切ると平均で0.5ms程度計算時間が短縮されました。試しに衣装を高速移動させてみましたが、特に見た目の違いがあったようには見えませんでした。衣装の形状にもよるのかもしれません。

Solver Frequencyは通常は120~300で設定されるようで、320という値はそう離れていないように思えます。
単位がHzということは、一秒間に何度計算が走るかというパラメーターなのでしょう。
試しに120まで落としてみたところ、平均して2msの改善が見られました。元が大体3~8msの処理ですから、中々の改善です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/56ac0150-4d8e-840a-4411-fc7dadf76b56.png)
ただ、このモデルの場合はそれなりに見た目への影響がありました。左が変化前(320Hz)、右が変化後(120Hz)です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/e45d23ea-a990-518f-e90b-d50cda49330a.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/b2e4dd46-93af-7830-d135-2a8a27178660.png)
右側の見た目はちょっと頂けないですね。計算頻度が変わったことにより、前開きの広さが変わってしまっています。
この値を下げるにはメッシュを編集する必要がありそうです。

なので、どうせならということでリトポをしてみました。
リトポロジーとは、メッシュをより立体構造に沿った形状にすることで、より効率的なデータとすることを言います。
左半分をリトポしてミラーしただけなので向かって右半分が破綻していますが、左半分だけでも負荷検証には事足りるでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/c760f1ba-9680-39f5-8d69-c9ee5670ca78.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/2069f2d2-10c4-d51d-4d3b-2011e2d05793.png)
左がリトポ前、右がリトポ後のパフォーマンスです。今回は頂点数、ポリゴン数共におよそ1/4まで削減を行いました。
Solver Frequencyは120を設定しています。

この例では頂点数を一般的な水準まで減少させた反面、Clothで表現されていたヨレが消えてしまいました。リトポをするならばその際に一緒にメッシュで表現してあげるべきでしたね。反省。

#### 番外編:リトポロジーってどうやってやるの？
https://github.com/CGCookie/retopoflow/releases

私は今回このアドオンをBlenderに入れてリトポを行いました。Blenderのバージョンは2.83を利用。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/efa1b7ce-917f-25f0-6882-33343fb34eae.png" width="500">
今回掛かったのはアドオン探しから通算で3時間くらいでしょうか。私はモデラーではないのですが、慣れたモデラーさんならもっと早く（そしてハイクオリティに）行えるのかもしれません。気質もあると思うのですが結構楽しかったです。

閑話休題。
ここで提示したかったのは以下の二点です。

- ポリゴン数=ハイクオリティ、ではない
    - 特にトポロジー（四角面の流れ）はレンダリング結果にも影響を及ぼすので、リトポした方がクオリティが高くなる場合も多い
- Clothコンポーネントは、適切に使えば計算負荷を大幅に抑えることができる

Unity2019以降はPxClothからNvClothに変更されるので、細かな変更点はあるかもしれません。ただ「2019に持っていくと壊れる」現象はFBXインポーターに対する変更によるものでNvClothへの変更によるものではないので、この記事で記載した点についてはさほど変わらないのかなと思っています。

ちなみに、ClothはVRChatのAvatar Performance Rankingで不当に低く評価されたままだったりします。これはUnity2017までの間にClothが異常に重くなるバグがあったからなのですが、これが2018になって修正されたにも関わらずAvatar Performance Rankingの基準が低いままになっているんですね。以下のcannyから詳しく見ることが出来ます（VRChatアカウントでのログインが必要です/英語）

https://feedback.vrchat.com/feature-requests/p/redo-performance-rank-values-for-cloth

#### 2020/8/1 16時補足（Graphics Jobs, Normalの利用による利点, 欠点, 注意点）
izmさん[（@izm）](https://twitter.com/izm)より大変素晴らしいご指摘をいただきましたので補足します。

https://twitter.com/izm/status/1421721772601184265?s=20

https://twitter.com/izm/status/1421722878156828678?s=20

ツイートの内容が非常によくまとまっているのですが、Graphics Jobsについて聞いたことが無い方もいるかと思うので説明します。英語に抵抗が無ければ以下のリンク先で専門的な内容ながらわかりやすく説明されています。以下、本項の画像は全て下記の記事から引用します。

https://learn.unity.com/tutorial/optimizing-graphics-in-unity#5c7f8528edbc2a002053b5ad

一言で言ってしまえば、これは「マルチスレッドを活用してグラフィック関連の命令をGPUに出そう」という仕組みのうち一つです。以下の図は最もシンプルな形で、メインスレッド（CPUのスレッドのうち一つ）からGfxDevice（GPU）に描画の命令が出されています。しかし、メインスレッドはその計算をしている間他の処理ができません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/a302981f-dacc-8e2a-8fb2-3c7a588fa2cc.png)
ちょっとこれは非効率的だということで、Unityはデフォルトで一つのRenderthread（CPUのスレッドのうち一つ）にその計算を投げています。でも、最近のパソコンのCPUは基本的にマルチコアですよね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/4dd7219b-ca00-b115-9a81-7a3a815f67e6.png)
なので、それをもっと効率的に使おうというのがGraphics Jobsです。メインスレッドから命令を出す先が増えていますね。これで効率的に描画できそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/ba34d975-4eea-dff7-76cd-e89a80cc8ab5.png)
でも実はこれ、Renderthreadが消えてWorkerthreadが増えていることにお気付きでしょうか。Unity Learnの当該記事が執筆された時点（2020/11/27）ではUnityのGraphics JobsにはRenderthreadがなく、Workerthreadに命令を出す分の負荷が少しだけMainthreadに掛かります。また、Experimentalな機能の為デフォルトでは有効化されないようです。
念のためこの記事で利用したProjectで確認したところGraphics Jobsは有効、Graphics APIはDirect3D11を指定していました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/aa639dc7-c77e-72ea-882a-2752d3f59a36.png)
補足終わり。ありがとうございます！

# ポリゴン数について
今回よく騒がれたポリゴン数ですが、わかりやすい指標ではあるものの、Clothコンポーネントの負荷と比べるとそんなに大したことはないです。というより**不適切な**Clothは超重い。（**適切に設定すれば大丈夫だよ！**　というか揺れ物全般が元々重い処理で、Dynamic Boneも不必要に多く使うのはあまり良くありません）
Skinned Mesh Rendererなので、純粋な描画処理に加えてボーンの計算も見る必要があります。ちょっといい方法が思いつかなかったのでここでは具体的な結果をもとに語ることが出来ません。すみません。
古いUnityのドキュメンテーションには目安の値として「1500~4000ポリゴン」みたいな値がデスクトップ向けに書いてあったりするんですが、5年以上前のものであまり参考になりません。

ただ、最新のUnity Manualに非常に良い文章があったので引用します。

>以下2つの競合する事実は等しく当てはまります。
>
メッシュで使用するポリゴンが少ないほど、アプリケーションの実行が速くなります。これは、すべての頂点、エッジ、面が計算リソースを必要とするためです。
メッシュのポリゴン数が多いほど、より細かく有機的なゲームオブジェクトの外観が得られます。なぜなら、ポリゴンが小さいほど形状をより良く制御できるためです。

https://docs.unity3d.com/ja/current/Manual/ModelingOptimizedCharacters.html

当然これは無駄なポリゴンを割かないための努力を放棄してよいという意味ではないですが、その努力には当然工数が掛かります。
もちろん、そのような努力の為されたモデルの方が高品質であることを疑う人はいないでしょう。ただ高品質なものには工数がかかるという当たり前の事実と、ビジネスとして考えた時に無限に品質を追求することはできないことを忘れてはいけませんね。

# Skinned Mesh Renderer
Skinned Mesh Renderer（ボーンと紐付いたメッシュ。キャラクターモデルは原則そう）は、一部の例外を除いて原則として1キャラクターに1つであるべきです。
Unityのレンダリングにおいて、Skinned Mesh Rendererを分割することのメリットはほとんどありません。
> 1つのメッシュの代わりに2つのスキンメッシュを使用すると、モデルのレンダリング時間がおよそ2倍になる可能性があります。

https://docs.unity3d.com/ja/current/Manual/ModelingOptimizedCharacters.html

今回取り扱った製品は2つのSkinned Mesh Rendererを利用しています。「メイン」と「cloth部分」がそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/9ca88543-ba7c-b393-2fb8-9a36e254e0ea.png)
不必要にClothコンポーネントの頂点数を増やすことが致命的であることは前述した通りなので、この場合はCloth部分のみSkinned Mesh Rendererを分割するべきです。つまり、適切な形での結合/分割が行われていることになると思います。
VRSNS向けアバターの多くはUnity上での改変の利便の為にSkinned Mesh Rendererを分割しています（実際とても便利で、私も助かっています）。もちろんその方が改変はしやすいのですが、計算量は非常に多くなります。
今回取り扱った製品の非常に最適化された部分だと思います。

また、ここで当該キャラクターのClothコンポーネントのついたSkinned Mesh Rendererを見てみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/3bb359fd-4d79-042a-b5ec-0665c853c876.png)
「Update When Offscreen」という項目がオンになっているのがわかるかと思います。
これは「画面外でも更新する」という機能で、この服は画面外でも処理が走るということになります。これはちょっとイケてないですね。各種バーチャル展示会では負荷対策のためにオフを必須にしていることが多いです。
特に理由も見当たらないので、オフにした方が良いかと思います。Clothの場合はBounds（画面内なのか画面外なのかを計算するための値）が自動で計算されるので、特に調整も必要ないようです。

# 単色テクスチャの解像度
このモデルファイルでは、単色のテクスチャが2枚使われています。
1枚目はHSV(0,0,0)の黒のAlbedoとして、2枚目はHSV(0,0,75)の薄いグレーでMetalicのテクスチャです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/1c944f08-255a-23de-fb99-3f7955b19bb4.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/ef3ddda3-5992-432d-1717-146c986819ec.png)
それぞれに2Kの解像度が割かれていますが、その必要はないでしょう。
まずAlbedoに単色のテクスチャを入れる場合、代わりにAlbedoのColorにその色を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/f9fcb087-1345-76fe-1871-a4440d51abe2.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/46d0e32d-dfc5-397c-52f7-dbef52ca873a.png)
左：変更前　右：変更後
またColorの設定ができない箇所に単色のテクスチャを入れる場合、その解像度は出来るだけ下げておくと良いと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665842/64d86b82-efff-dbdc-1127-1641e2a7177b.png)
これらの設定はレンダリングの見た目を一切変えないので、純粋に無駄を省くテクニックとして有用です。
不必要なテクスチャはVRAMに乗る無駄データになってしまうので気を付けたいですね。

# Materialの結合
同じシェーダーで同じ設定値を採用したマテリアルが複数ある場合、「アトラス化」という工程を挟むことにより、上の方で述べたBatchesとSetPass callsを削減することができます。
このモデルの場合、`New Material`と`New Material 1`がどちらも全く同じ設定値を利用しているため、この恩恵を受けることが出来ます。

# 2バイト文字の利用
一般論として、2バイト文字（全角文字）は思わぬバグの原因になりやすいので避けた方が良いと思います。
ただまあ分かりやすいですよね。

# その他、見かけた検証
本記事はあくまでもUnityのEditor上で見た値や各種ドキュメンテーションに立脚した言ってしまえば机上論なので、本番環境での検証とは異なる可能性があります。いくつかVRChatでの検証を見かけたのでリンクを貼っておきます。

https://twitter.com/hikka06/status/1421658907659833348?s=20

公式も検証するらしいです

https://twitter.com/only4U_official/status/1421639625303420928?s=20

# この記事に不足している内容
ライティング/シェーダーについての知見が私では不足しているため、その点については触れていません。この衣装はStandardシェーダーを利用していますが(Autodesk InteractiveはRoughness付きStandardです)、VRSNSのような環境ではライティングが各ワールドにより大幅に異なるので、必ずしも「リアル」な描画結果は得られません。例えばUTS2にはVRChat向けの項目があったりUnlitWFは白飛びし辛いようになっていたり等その辺を考慮しているシェーダーもあるようなのですが、それらの内容の理解には至っていないので「そういうものもあるようだ」という提示のみ行います。

# おわりに
よくある誤解として、「最適化とは、クオリティを下げることだ」というものがあります。そして実際そのような最適化手法を必須かのように語るエンジニアも存在します。
ですが実際のところそんなことはなく、最適化とはクオリティを最大化する為の手法であるべきです。それはレンダリング結果の美麗さもそうですし、必要なフレームレートを確保することも含まれるでしょう。カクカクの画面では折角の作り込みも台無しです。

このモデルで言えば、よく言われた「ポリゴン数が多い」ことは本質的では無く、正確な指摘とするには「リトポロジーが行われていない」「しかもそのメッシュにClothが適用されている」とするべきでしょう。またテクスチャファイルを見ると単色の他単純な図柄にも2Kテクスチャが割り当てられていたり、しかし「Only 4U」のロゴ部分にはそんなに大きなUV領域を割いておらず、結果ピクセルが目立つ等なんだかちぐはぐとも思える部分もあります。
一方、「Skinned Mesh Rendererがまとめられている」ことは他のVRSNS向けダウンロード商品と比べて最適化された要素です。こういったことから個人的には最適化の意思が無いわけではなく、単にリアルタイムレンダリング向けの知識や技術力が追い付いていないだけではないかなと考えています。まだ始まったばかりのブランドのようですし、これからの発展が楽しみです。

あと個人的にちょっと気になっているのは利用規約の個別条項にある「品位を著しく害する～」の項なのですが、「5.1.aに基づく」ということは5.1.aに含まれる行為だけが規制されるということなんでしょうか？　一方2.2.には「個別条件の定めと基本条項の定めに矛盾や抵触がある場合、前者が優先するものとします」とあり、5.1.aは基本条項ですからなんだか循環参照感があります。解釈次第で広く取れる文章ではありますがバスケット条項は5.1.gに既に定めがあることもあり、ちょっとよくわからないので法務に自信ニキは教えてくださると助かります。