高速・高画質で出力する魔改造UnityRecorderと動画出力のベンチマーク

# 全天球映画 オワリズム弁慶 幻夢大演舞 -(un)ExtraVERSE-

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【VRチケット】※3日間限定上映<a href="https://t.co/4W942lk900">https://t.co/4W942lk900</a>…<br><br>1上映あたりの座席数が非常に少ないです。当日お待ちになる可能性がある為、お早めのご予約がお勧めです。</p>&mdash; オワリズム弁慶@VR音楽映画製作中 (@owa_benkei) <a href="https://twitter.com/owa_benkei/status/1591785798856507393?ref_src=twsrc%5Etfw">November 13, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

去年から、[「オワリズム弁慶」](https://twitter.com/owa_benkei)というバンドの[VR音楽映画製作のクラウドファンディング](https://motion-gallery.net/projects/owarhythmbenkei_vrmovie_project)に技術協力をしており、そこで得られた知見について記事を書いていきたいと思います。

今回の記事は第2弾で、第1段の「Unity出力の全天球ステレオ動画をYouTubeへ360度ステレオ動画としてアップロードする方法」は[こちら](https://qiita.com/kotauchisunsun/items/7bc242e3d14130572fac)

今回の魔改造UnityRecorderでは以下のような動画がUnityから出力できます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/vEvrj0NxSpg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 魔改造UnityRecorderのベンチマーク結果

今回は、[ユニティちゃんのCandyRockStarを解像度2048x2048の全天球の両眼の動画として出力](https://qiita.com/kotauchisunsun/items/7bc242e3d14130572fac)しています。設定は60fpsの10秒(画像数600)としてベンチマークをとりました。ProRes(参考)と書いてあるのは、Windows上で処理しており、ProResに関しては再生環境が用意できず、適切に再生できるファイルが出力出来ているか不明だったため、参考程度の情報としています。

| 出力形式 | 処理時間(秒) | 処理時間の実時間比 | ファイル容量(MB) |  データ圧縮率    |
|:-------:|------------:|------------------:|----------------:|----------:|
|mp4      |    30.94    |     3.09          |      19.76      |  0.21%    |
|ProRes(参考) |  43.43  |     4.34          | **2,401.94**    | **25.02%**    |
|png      |  **213.48**  |  **21.35**        |   2,089.21     |  21.76%   |
|jpg      |     79.35   |     7.93          |     346.07     |  3.60%  |
|魔改造    |     88.89  |     8.89          |   1,644.23      | 17.13%   |

「処理時間の実時間比」は、動画の長さが10秒に対し、mp4が処理に30.94秒かかったため、動画の長さに対し、3.09倍かかった。という計算をしています。また、動画の非圧縮のサイズは、解像度2048x2048、1画素RGBAの4Byteと考え、60fps、10秒なので、2048 * 2048 * 4 * 60 * 10 ≒ 10GBが容量となります。「データ圧縮率」は、それに対する、各手法で出力されたファイルサイズの比になっています。そのため、数字が小さければ小さいほど高圧縮となっています。この辺りのデータは、他の動画で出力する際の、動画の出力時間や、ファイルサイズの目安に出来ると思います。
今回の実験結果において、

**魔改造UnityRecorderの出力はpng出力の2.4倍速く、容量は22%小さいです。**

以上の結果を可視化したのが以下のグラフになっています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/b6bb4903-e76d-d89f-85e4-c1d812849e30.png)

魔改造UnityRecorderによる出力はProResとpngより容量が少なく、pngより高速に出力出来ていることが分かります。

# 出力結果の定性調査

この章では、出力結果の定性調査をします。mp4はUnityで動画出力した後、ffmpegによって画像出力をしました。そのため、一度動画エンコードを通しているので、画像の劣化があります。jpg,png,魔改造に関しては、UnityRecorderで画像として出力しています。今回、動画の中の同一フレームを比較していますが、UnityProject内にパーティクルなどランダム要素があるので、厳密に4つの画像が一致しません。したがって、あくまで定性的な評価になります。また、それぞれの画像において白いノイズの様に見えるものは、紙吹雪の演出です。

(クリックして拡大推奨)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/258306a2-0609-9dad-1160-a34d207a67c7.png)

この中では一番pngが綺麗なはずです。pngはそもそも可逆圧縮のため、本来のUnityの出力を一番再現していると考えられます。mp4は元が動画ということもあって、左上のトラス構造の部分に滲みがあります。jpgはトラス構造と背景の間にノイズが乗っています。これはjpgは不可逆圧縮で、そもそもあまりアニメ調の再度変化の激しい絵には向かないため、リンギングと呼ばれる圧縮アーティファクトが見られます。一方で、魔改造版はjpgに見られるようなノイズは無く、pngと似たような絵を出力をしていることが分かると思います。

# 魔改造UnityRecorderとは何か

実はたいしたことをしていないです。今回は、UnityRecorderの2.5.7を対象にしましたが、それ以外でも、コードが読める人なら、それほど難しい内容ではないです。実際の作業は、`\Library\PackageCache\com.unity.recorder@2.5.7\Editor\Sources\Recorders\ImageRecorder\ImageRecorder.cs`の

```
bytes = tex.EncodeToJPG();
```

部分を

```
bytes = tex.EncodeToJPG(100);
```

としているだけです。使い方は、UnityRecorderでUnityRecorderでImageSequenceでjpgを指定して出力するだけです。
どうしてこんなことが起きるのかというと、Unityの[ImageConversion.EncodeToJPG
](https://docs.unity3d.com/jp/current/ScriptReference/ImageConversion.EncodeToJPG.html)に以下のような記述があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/25f272fd-9403-0250-275a-71778883d1c5.png)

デフォルトで75を指定されています。この **jpgの品質レベルを最高の100** にしています。
このjpgの品質レベルですが、どれくらいが適切かというと85にするとよい。という目安があります。

https://qiita.com/miyanaga/items/a616261de490cc342d08

https://developers.google.com/speed/docs/insights/OptimizeImages

少なくともUnityのjpg出力のデフォルトの品質設定は低めに設定されています。そのため、劣化が目に見えるレベルになっているようです。そこで、品質設定を85で設定した出力も試してみました。
以下の画像は品質設定を75,85,100に設定して出力したものです。

(クリックして拡大推奨)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/f59e669b-180c-c79b-a067-87ce01107819.png)

このように品質設定85でもノイズが出ることが分かります。元々jpgはビビッドな彩度変化の激しい絵には向かないのですが、そのような影響も受けていると考えられます。

# 無圧縮出力について

ここまでくると、BMPなど無圧縮のフォーマットでも良いのではないか？となってくると思います。
そこで、TGA形式による出力を試みました。やったことはほぼ魔改造UnityRecorderと同じで、

```
bytes = tex.EncodeToJPG();
```

を

```
bytes = tex.EncodeToTGA();
```

に変えただけです。TGA形式は無圧縮やランレングス圧縮による形式に対応しているそうです。[参考](https://gameprogrammingunit.web.fc2.com/tga/index.html)

ここでUnityから出力されたTGAファイルの出力を16進ダンプを見てみると、以下のような出力を得ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/3060f358-c813-2191-2b44-441bee0f4e49.png)

ファイルの冒頭から3Byte目がTGAに関するフォーマット形式を示すカラムで、この値が0x02となっています。これは、上記した参考文献によると、無圧縮のフルカラーを示しているので、UnityImageConversion.EncodeToTGAを用いると、無圧縮の画像データが得られることが分かります。
この値をベンチマークに含めて可視化します。

| 出力形式 | 処理時間(秒) | 処理時間の実時間比 | ファイル容量(MB) |  データ圧縮率    |
|:-------:|------------:|------------------:|----------------:|----------:|
|mp4      |    30.94    |     3.09          |      19.76      |  0.21%    |
|png      |  **213.48**  |  **21.35**        |   2,089.21     |  21.76%   |
|jpg      |     79.35   |     7.93          |     346.07     |  3.60%  |
|魔改造    |     88.89  |     8.89          |   1,644.23      | 17.13%   |
|tga      |    131.42   |   13.14           |  **9,600.01**   | **100.00%** |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/b10b9e88-4cdd-3c58-3b3d-770afa27dad9.png)

 TGAが無圧縮であるため、データ圧縮率が100%となっています。これはTGA形式の容量と、生データの容量を計算で求めたときの理論的な値がほぼ一致していることを示しています。そして、ファイル容量が9.6GBと非常に大きいことも分かります。また、 **処理時間が131.42秒かかっており、TGAによる無圧縮出力は遅い部類である。** ということが分かります。
 これは1つの見解ですが、出力時間は以下のような関係になっていると考えられます。

```
出力時間 = 圧縮時間 + ファイル書き込み時間
圧縮時間 = f(圧縮アルゴリズムの複雑さ)
ファイル書き込み時間 = g(ファイル容量)
ファイル容量 = h(圧縮アルゴリズムの性能)
```

となっていると考えられます。「出力時間」は、「圧縮にかかる時間」と「ファイル書き込み時間」の和で表されます。「圧縮時間」は、「圧縮アルゴリズム」の関数`f`として表され、「ファイル書き込み時間」は「ファイル容量」の関数`g`として表されます。そして、「ファイル容量」は「圧縮アルゴリズムの性能」の関数`h`として表されます。
pngのように「圧縮アルゴリズム」が複雑になると、「圧縮時間」がかかるようになります。TGAは無圧縮であるため、「圧縮時間」は0になります。しかし、無圧縮であるため、「ファイル容量」が巨大化します。そのため、「ファイル書き込み時間」が大きくなってしまうため、結論として、 **圧縮している形式(png,jpg,魔改造)より、無圧縮の出力の方が「出力時間」がかかってしまう。** と考えられそうです。

# ファイル容量と処理時間の関係

以上の議論より、処理時間は、「ファイル容量」と「圧縮アルゴリズムの複雑さ」、「圧縮アルゴリズムの性能」の3要素が関係していると予測しました。
参考程度のデータですが、ファイル容量と処理時間の関係を示したグラフを書きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/f8fc6c0a-00bf-327a-8f3e-ff496aff0e92.png)

この結果を見ると、決定係数R^2=0.121と、ファイル容量と処理時間がそれほど相関しているとは思えません。ここで、pngを除外した状態でグラフを書いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/dbcc0d68-db17-5228-f322-ea9e1e76683f.png)

結果、決定係数R^2=0.622とまぁまぁ相関係数が高くなります。ここで最初のグラフから、外れ値と考えられるtgaを除外したグラフを書いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/149c0cda-1d7d-c87e-15ea-c85d20f5bb13.png)

今回の場合は、決定係数R^2=0.183とそれほど高くありません。
これらの傾向から、断定するほどではありませんが、**png出力における処理時間は「圧縮アルゴリズムの複雑さ」がボトルネックで、それ以外の出力は「ファイルの容量」の方がボトルネックになってそうです。**
したがって、tgaの出力で何が時間がかかっているか？というと、やはり「ファイル書き込み」に時間がかかっている。と言えそうです。また、tgaは出力サイズが9,600.01MB、pngが2,089.21MBで、圧縮にかかるコストを考えない純粋なファイル出力ととらえた場合、ざっくりとpngの方が1/4程度の出力時間で出力できそうです。しかし、tgaの出力が131.42秒、pngの出力が213.48秒であることを考えると、pngの方が圧倒的に出力に時間がかかっています。したがって、この差は、pngの圧縮による時間だと類推できそうです。したがって、ここからもpng出力における処理時間は「圧縮アルゴリズムの複雑さ」がボトルネックである。といえそうです。
　したがって、 **「無圧縮であれば早く出力できる。」とは、一概に言えなさそうです。** jpgの方が出力時間自体は短いです。逆に言ってしまえば、ファイルの書き込み時間さえ早くできればよいので、(実現可能性を別にすれば)10GB級のRAMディスクを用意できれば高速化は出来そうです。

# 感想

もともとはUnityで高画質の出力をしている人に、「動画のためにUnityでpngで出力したら1週間レンダリングしつづけたよ。」という話を聞いて、さすがにそれはやっておれんな。と思っていました。しかし、自分が動画制作をお手伝いするようになると、自分でもそれは問題だなぁ。と思い始めました。そして、最初は、無圧縮での出力を試みたのですが、正直容量を食いすぎるため、さすがに無い。という判断をしました。そのあと、[DeflateStream](https://learn.microsoft.com/ja-jp/dotnet/api/system.io.compression.deflatestream?view=net-6.0)という逐次でzip圧縮をする仕掛けを作りこんでみたのですが、これをしてしまうと、最速圧縮設定にしても、pngの出力速度とそこまで変わらない。という結果になりました。そこで、「そういえば、pngとjpgだとどちらが出力が速いのだろう？」と疑問を抱き、ネットではpngへの変換よりjpgへの変換の方が処理速度が速い。という実験結果が出ていました。というわけで、やってみました。
今でもそうだと思うんですが、Windowsの標準のペイントで適当なjpg画像開き、上書き保存すると、すごく画質が劣化する。ということを経験していました。なんでだろう？と思っていたのですが、ある日、GIMPを触っていると、出力設定でjpgの出力の品質設定を変えられることを知りました。そこで、jpgには品質設定というパラメーターがあることに気づきました。その時、Windowsのペイントはjpgの品質設定というパラメーターが適当な値のため劣化する設定なんだろうなぁ。ということを思っていました。
そんなことを思っていたので、UnityRecorderがjpg出力した画像にノイズが出たのを見たとき、これはjpgの品質設定が悪いんやろ。と思ったので、ソースコード覗いて無理矢理書き換えたら、なんかそこそこイイ感じになっちゃった。というのがこれです。
まぁこれはUnityRecorderでmp4出力の画質では満足いかず、GB単位の画像出力をする狂人向けの設定の話ですが、何か参考になれば幸いです。