Unity 2018.1 で VR180 動画を作成する

## はじめに

本記事は [Unity 2018.1 でステレオ VR 動画を作成する](https://qiita.com/tan-y/items/ea824684077b0a544421) の続きです。

"VR180" とは Google が提唱している前面視野 180 度のみのステレオ半天球映像規格です。

https://vr.google.com/vr180/

多くの場合、後ろの方を見ることはほとんどないため、前面のみと割り切る事でデーター量を大きく削減できることにメリットがあります。また、全天球のステレオ映像を実際のカメラで撮影するのは難しいのですが、前面のみだったら魚眼レンズのカメラを二台同時に撮影するだけでよいので比較的容易に実現可能なのもよいところです。

映像の形式自体は特段目新しいものではないのですが、 Google が提唱しているだけのことがあり、規格に沿って Metadata を付与してから YouTube にアップロードするとステレオ全天球と同様に YouTube VR 用のステレオ VR 動画として登録、視聴することが可能になります。

しかしこの VR180 の規格は長い間公開されてきておらず、自力で VR180 動画を作成することができなかったのですが、最近になって VR180 Metadata を付与する非公式ツールが作成され、これを用いる事で VR180 動画を作成、 YouTube にアップロードする事が可能になりました。

* [【YouTube】VR180のメタデータを非公式ツールで付与してみたけど良さそう](http://greety.sakura.ne.jp/redo/2018/03/youtubevr180.html)

上記記事がとても参考になりました。ありがとうございます。

「よし、じゃあ自分もちょっとやってみるか」といろいろやってある程度まとまったタイミングで Google が公式な VR180 のフォーマット情報を出してきてしまいまして。

* [How to publish VR180](https://www.blog.google/products/google-vr/how-publish-vr180/)
* [VR180 Video Format](https://github.com/google/spatial-media/blob/master/docs/vr180.md)

といった状況でこの記事を書いているわけですが、とりあえず当初進めていた非公式ツールを前提としますが、公式情報も加味して進めていこうと思います。

[![(3D VR180) Unity VR180 Test](http://img.youtube.com/vi/x23RIxFUg9k/0.jpg)  
(3D VR180) Unity VR180 Test - YouTube](https://www.youtube.com/watch?v=x23RIxFUg9k)

© Unity Technologies Japan/UCL

この動画は YouTube VR で視聴するとステレオで見れます。

## 作成の手順

大きくは全天球の時と同じです。 [前回記事](https://qiita.com/tan-y/items/ea824684077b0a544421) を参照してください。

1. RenderTexture を用意。
2. パノラマ映像を RenderTexture にレンダリングするスクリプトを用意、設定する。
    * 撮影対象の Camera に CubemapToOtherProjection を追加する。
    * CubemapToOtherProjection に 1. で作った RenderTexture を指定する。
    * その他設定をする。
3. [Recorder アセット](https://assetstore.unity.com/packages/essentials/beta-projects/recorder-94079) で 1. の RenderTexture を動画として録画する。
4. Metadata を付与する。

今回はこの内の 2. を VR180 向けの映像をレンダリングするスクリプトにします。

### スクリプトの使い方

* [aosoft/UnityVRVideoRecordingTest](https://github.com/aosoft/UnityVRVideoRecordingTest/releases/tag/20180418)

前回作ったサンプルプロジェクトに CubemapToEquirectangular.cs/.shader をベースに大分書き換えた　CubemapToOtherProjection.cs/.shader を作成しました。このプロジェクトを checkout してください。各自のプロジェクトでは CubemapToOtherProjection.cs/.shader をコピーしていけばよいです。

今回はレンダリング時の投影モードを指定できるようにしています。図の四角で囲んでいるところです。

![UnityRecording3.png](https://qiita-image-store.s3.amazonaws.com/0/211064/ef74d3f9-2ce2-f3a8-e984-1f9ba68dbe17.png)

* Projection Type (投影モードの種類)
    * Equirectangular_360 ( [正距円筒図法 (Equirectangular)](https://ja.wikipedia.org/wiki/正距円筒図法) の全天球 (360 度) 用)
    * Equirectangular_180 (正距円筒図法 の前面半天球 (180 度) 用)
    * FishEye_Circumference (円周魚眼レンズ)
    * FishEye_Diagonal (対角線魚眼レンズ)
* FishEyeType (魚眼レンズの射影)
    * Equidistance ( [等距離射影方式 (正距方位図法)](https://ja.wikipedia.org/wiki/正距方位図法) )
    * EquisolidAngle (等立体角射影方式)
    * Orthogonal (正射影方式)

RenderTexture の割り当ては Recorder アセットと CubemapToOtherProjection のそれぞれに割り当てます。

RenderTexture への描画はアスペクト比の考慮をしていないので、必ず次のようなアスペクト比になるサイズを指定する必要があります。

* 単眼 (Mono) 向けの場合、 Equirectangular_360 は 2:1 、それ以外は 1:1 となるようにする必要があります。
* ステレオ向けの場合 (Render In Stereo にチェック) の場合、 Equirectangular_360 は 1:1 (上下に並ぶ) 、それ以外は 2:1 (左右に並ぶ) となるようにする必要があります。
  ただし、単眼の時と比べ面積としては倍にした方がよいでしょう。

投影モードの設定ですが、先に今回の結論だけ書いておきますと

* 360 度全天球の VR 動画を作成する場合は ProjectionType に "Equirectangular_360" を指定する。
* 180 度の VR180 動画を作成する場合は ProjectionType に **"Equirectangular_180" を指定する。**

## VR180 形式とは

そもそも VR180 動画はフォーマットが不明でしたので、 Metadata の適用とかの前に「どういった形式 (図法、投影法) の映像を作成すればよいか」自体がわかりませんでした。既存の VR180 動画、とうたっているものはいわゆる "魚眼レンズ (fisheye)" 形式だったので "魚眼レンズで撮影したような映像を作るシェーダーの実装" をするところから始めました。ただ一口に「魚眼」といってもさらにバリエーションが存在します。

* [魚眼レンズ - fisheye lens (Wikipedia)](https://ja.wikipedia.org/wiki/魚眼レンズ)
* [FI-085／111](http://fit-movingeye.jp/products/optics/consumer_optics/fi_series/fi_02.html) - レンズの商品紹介ですが、投影方式についてのわかりやすい図が書かれています。
* [射影方式のちがい](http://tin0.blog.fc2.com/blog-entry-49.html)

どれが VR180 仕様に合致するのか確証が得られなかったので適当にいろいろと実装してみた結果、使い方で書いた通りのバリエーションをレンダリングできるようにしました。

~~で、先日公開された [VR180 のフォーマット](https://github.com/google/spatial-media/blob/626f595b7cc56349ee538fa059f83e46cd99cb26/docs/vr180.md) ではこの辺りのことがちゃんと書いていないようなのです。 Equirectanglur ？ Fisheye ？ Fisheye として射影は？~~
(2018/5/7) これ間違いでした。 [Mesh Projection](https://github.com/google/spatial-media/blob/master/docs/vr180.md#2-mesh-projection) が該当します。詳細は後述。

といってももう作ってしまった後なので、実装したものを全て試してみることにしました。

## レンダリング例

|||
|---|---|
|Equirectangular 180 (半天球)|![Equirect.png](https://qiita-image-store.s3.amazonaws.com/0/211064/c3c2121d-19d8-cc35-5303-aea5a1138036.png)|
|Circumference Fisheye - Equidistance|![FishEye_Cir_Equidist.png](https://qiita-image-store.s3.amazonaws.com/0/211064/d31834ea-2e05-5c1d-5c78-a7b14672010d.png)|
|Circumference Fisheye - EquisolidAngle|![FishEye_Cir_EquiSolidAngle.png](https://qiita-image-store.s3.amazonaws.com/0/211064/0223dba9-4ef3-27fa-6d47-8eb8433c1f6a.png)|
|Circumference Fisheye - Orthogonal|![FishEye_Cir_Ortho.png](https://qiita-image-store.s3.amazonaws.com/0/211064/502031f0-029e-29ef-bb3c-2c1affb791ea.png)|
|Diagonal Fisheye - Equidistance|![FishEye_Dia_Equidist.png](https://qiita-image-store.s3.amazonaws.com/0/211064/00b62eff-0d65-7a48-ed7c-c73999d2ea04.png)|
|Diagonal Fisheye - EquisolidAngle|![FishEye_Dia_EquiSolidAngle.png](https://qiita-image-store.s3.amazonaws.com/0/211064/82491718-e208-f161-47b5-463a7cf2a6d5.png)|
|Diagonal Fisheye - Orthogonal|![FishEye_Dia_Ortho.png](https://qiita-image-store.s3.amazonaws.com/0/211064/68fe215a-5cbd-7daf-9b28-41653bddcca4.png)|


## YouTube VR で見れるようにする

とりあえず全て H.264 + MP4 でエンコードし、 Metadata の付与をします。

* [Vargol/spatial-media (VR180 用 Metadata ツール)](https://github.com/Vargol/spatial-media)

[Google 公式](https://github.com/google/spatial-media) から fork して独自拡張で Spherical Video V2 と VR180 対応をされているものです。

付与したファイルを YouTube にアップロードすると VR180 動画と認識され、通常時はパース変換された普通の映像、 YouTube VR では前面のみのステレオ VR 動画として視聴できるようになります。

## 結果と考察

YouTube VR で見て、一番正しいような感じに見えたのは魚眼ではなく **ステレオの Equirectangular_180 (180 度の正距円筒図法)** のものでした。

ただこれはよくよく調べると当然のことでした。

* [Spherical Video V2 RFC (draft)](https://github.com/google/spatial-media/blob/0485eb81e35acfdeb5bcb999425f10edd3335564/docs/spherical-video-v2-rfc.md)

これよく読むと Equirectangular に関する規定はあるのですが、 Fisheye については書かれていません。
((2018/5/7) 正確には Fisheye を直接指定する記述の規定はなく、 Fisheye も表現できる記述は存在しました)

>#### Equirectangular Projection Box (equi)
>##### Definition
>Box Type: `equi`  
>Container: `proj`
>
>Specifies that the track uses an equirectangular projection. The
>[equirectangular projection](
>https://en.wikipedia.org/wiki/Equirectangular_projection) should be arranged
>such that the default pose has the forward vector in the center of the frame,
>the up vector at top of the frame, and the right vector towards the right of the
>frame.

Metadata を付与したファイルをバイナリエディッターで確認すると equi box の存在は確認できるので、ファイルとしては "Equirectangular である" ということになります。ので Equirectanglur で映像を記録しないとおかしなことになるのは当然のことでした。 (最初、ツールオプションの equirectanglur は特に意味がないと思ってました・・・)

[README で解説されてます](https://github.com/Vargol/spatial-media/blob/master/README.md) が、

>This fork is designed to fake googles VR180 camera metadata. It uses Spherical Video V2 metadata with the left and right crop set to 1073741823 which is 0x3FFFFFFF or 1/4 of 0xFFFFFFFF.

そのような形で実際に埋め込まれているのが確認できます。

![meta.png](https://qiita-image-store.s3.amazonaws.com/0/211064/0e9f4998-0bba-e5af-77de-16c6a592e47c.png)

今回使わせていただいている改変版のツール、コミットログを追ってみると 2017/2 とかなり前に fork していて、 Spherical Video V2 の独自対応をされた版をさらに fork して VR180 対応も行った、という形のようです。非公式、とはいっても規格的に問題があるわけでもなさそうですし、 YouTube も適切に処理してくれているようなので、これでよいのではないかなと思います。

(2018/5/7 追記)
後述しますが、 VR180 カメラでは Fisheye で映像を記録し、それに準拠した Metadata を記述しています (本記事の初稿時点では Fisheye を Metadata で記述するフォーマットはないと書いていましたが誤りでした)　。

ただ、 Unity を使用する場合など CG から生成するのであれば特に Fisheye にこだわるメリットもないですし、 Equirectangular ベースでよいように思います。

## VR180 カメラでの Metadata

(2018/5/7 追記)

Lenovo から VR180 の撮影ができる "Mirage Camera" が発売されました。こちらのカメラで撮影した動画ファイルは魚眼x2になっており、これをそのまま YouTube にアップロードすると VR180 動画になります。

ではこれはどういう Metadata が仕込まれているのかというと、 "Mesh Projection Box" という形式で射影情報が記述されていました。

>#### Mesh Projection Box (mshp)
>##### Definition
>Box Type: `mshp`  
>Container: `proj`
>
>Specifies that the track uses mesh projection. A mesh projection describes the
video projection in the form of a 3D mesh and associated metadata.

![180507_1.png](https://qiita-image-store.s3.amazonaws.com/0/211064/d1f48d68-a7ab-3349-c69b-24f905ecfbc6.png)

これは 3D の座標データー (頂点位置と UV 座標) を並べることで表現がされています。確かにこれならどのような形式でも表現が可能ですし、 Metadata はそこそこ大きくなってしまいます ('mshp' の前のサイズに注目) がハードウェアとしては同じデーターを書くだけの簡単な処理になりますし、 Fisheye から Equirectangular へリアルタイムに変換する負荷を考えれば適当な方法と思われます。


## コードの解説

今回結論にコードがあんまり関係なくなってしまいましたが、簡単な解説をします。

* [UnityVRVideoRecordingTest/Assets/CubemapToEquirectangular.shader](https://github.com/aosoft/UnityVRVideoRecordingTest/blob/master/Assets/CubemapToEquirectangular.shader)
* [UnityVRVideoRecordingTest/Assets/CubemapToEquirectangular.cs](https://github.com/aosoft/UnityVRVideoRecordingTest/blob/master/Assets/CubemapToOtherProjection.cs)

### Equirectangular (正距円筒図法) の前面半天球 (180 度)

[正距円筒図法 (Wikipedia)](https://ja.wikipedia.org/wiki/正距円筒図法)

正距円筒図法は前回からベースにさせてもらっている [CubemapToEquirectangular.shader](https://github.com/Mapiarz/CubemapToEquirectangular/blob/master/Assets/Shaders/CubemapToEquirectangular.shader) と基本的には同じですが、 UV 座標の参照の仕方を調整して半天球対応をしています。

正距円筒図法の映像はその UV 座標のうち U (横方向) が経度、 V (縦方向) が緯度そのものなので、 全天球の場合は 0.0 ～ 1.0 を U 方向は 0 ～ 2π 、V 方向は 0 ～ π の角度値 (原点のオフセット調整は別途) として、三次元空間の座標にマッピングすればよいです。  

今回は半天球ですのでテクスチャ全体で半天球分しかないわけです。なので U 方向も 0 ～ π とすればよいです。 

```glsl
v2f vert( appdata_img v )
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.pos.xy = o.pos * _PositionScaleOffset.xy + _PositionScaleOffset.zw;
    o.uv = v.texcoord.xy * _UVScaleOffset.xy + _UVScaleOffset.zw;

    return o;
}
```

o.uv の計算に使用している _UVScaleOffset で UV 座標を調整しています。

```CSharp
switch (ProjectionType)
{
    case ProjectionType.Equirectangular_360:
        {
            SetUVScaleOffset(Mathf.PI * 2.0f, Mathf.PI, 0.0f, 0.0f); 
            // 略
        }
        break;

    case ProjectionType.Equirectangular_180:
        {
            SetUVScaleOffset(Mathf.PI, Mathf.PI, Mathf.PI * 0.5f, 0.0f);
            // 略
        }
        break;
```

### 魚眼レンズ (Fisheye)

* [魚眼レンズ (Wikipedia)](https://ja.wikipedia.org/wiki/魚眼レンズ)

魚眼レンズは先に記載した通り、多様なバリエーションがありますが、一番シンプルな [正距方位図法](https://ja.wikipedia.org/wiki/正距方位図法) から見ていくのがよいです。

魚眼レンズは投影した結果が円形になります。円の画像が地球の北極からみたものとした場合、中心からの任意の点の距離が北極からの経度そのものになるのが正距方位図法の特徴です。

![fisheye01.png](https://qiita-image-store.s3.amazonaws.com/0/211064/6df57095-5534-7b63-1bb4-8a99f231d194.png)

* 中心から距離が表す角度 (Θ / 経度)
* XY 座標系での角度 (φ / 緯度) 

この二つ (と中心からの距離) で三次元空間での座標が表せるので、今回の場合は逆算して三次元空間の座標を求めます。

図の y は正距方位図法の公式では

* y = fΘ

となっています。 f の値は視野角に合わせて設定します。魚眼レンズの等距離射影方式 (正距方位図法)、等立体角射影方式、正射影方式はこの公式が異なるだけですので、シェーダーの実装ではマルチシェーダーバリアントで式を切り替えるようにしています。

#### 円周魚眼と対角魚眼の違い

魚眼レンズは基本的には円周魚眼ですが、対角魚眼というものもあります。対角魚眼は GoPro などが対角魚眼になっていると思います。

ものすごく雑に言うと、対角魚眼は出力映像自体が矩形となり、その矩形の対角線が円周魚眼の径に相当する範囲の映像です。円周魚眼は円の外側に無駄な余白が生じますが、対角魚眼の場合はそれがありません。ですが、対角魚眼は実質的に有効な視野が狭くなる (同視野角の円周魚眼と比較して見切れが生じる) ので一長一短です。

![fisheye02.png](https://qiita-image-store.s3.amazonaws.com/0/211064/698cb309-64db-da21-e9ce-b1f76edffc9b.png)

今回は 1:1 の矩形になるようにしているので図の赤線内側の部分になります。対角魚眼は矩形の対角線が円周魚眼の径に対応していればよいので、青線のようなアスペクト比の矩形でも OK です。実際、デジカメの対角魚眼はそのようになっています。

シェーダーの実装では単に径の長さを調整するパラメーターを追加して対応しています。ちなみに厳密には出力矩形のアスペクト比を考慮する必要があるのですが、このシェーダーはアスペクト比 1:1 決め打ちとしているので、対角魚眼の場合は円の半径を √2 倍になるようにしているだけです。

```CSharp
    case ProjectionType.FishEye_Circumference:
        {
            // 略
            SetFishEyeDiameterScale(1.0f);
        }
        break;

    case ProjectionType.FishEye_Diagonal:
        {
            // 略
            SetFishEyeDiameterScale(1.0f / Mathf.Sqrt(2));
        }
        break;
```

```glsl
fixed4 frag(v2f i) : COLOR 
{
    float4 unit = float4(0, 0, 0, 1);

#if PROJ_FISHEYE

    float len = length(i.uv) * _FishEyeDiameterScale;
    if (len > 1.0)
    {
        return fixed4(0.0, 0.0, 0.0, 1.0);
    }

#if ANGLEFUNC_EQUISOLIDANGLE
    float ang_yz = 2.0 * asin(len * SIN45);
#elif ANGLEFUNC_ORTHGONAL
    float ang_yz = asin(len);
#else
    float ang_yz = len * PIDIV2;
#endif
```

## まとめ

また今回も調べたり実装している過程は紆余曲折ありまくりだったのですが、まとめてみるとたいしたことのない事に落ち着いた感じです。

VR180 動画は 360 度と比べて (非圧縮状態で) 単純に半分にできますし、実写での撮影がステレオ 360 度と比べて圧倒的に容易であるなど、かなり魅力のあるものと思います。 360 度はライブなど臨場感が求められるものではいいと思いますが、メインのコンテンツに集中できればよいケースなどでは VR180 の方が適切でしょう。

個人的には VTuber には向いているかなあと思います。まあステレオ動画は後からテロップを入れたりする編集をどうするかって課題もありますが。

VR180 の映像形式、 Metadata についてはまだ疑問な事もあり、やっぱり Miragae Camera を入手して撮影ファイルを調べるしかないのかなあ。

Fisheye shader の実装などで間違いがありましたらご指摘をお願いします・・・

* 2018/5/7

Mirage Camera での録画データーを元に記事の誤りを訂正、追記しました。

* 2018/4/18

等立体角射影の計算式を間違えていたので修正しました。