VR180 Mesh Projection Box の技術解説

## はじめに

大分以前に VR180 ビデオの Metadata の一部である "Mesh Projection Box" のパーサーとそれに基づいて Unity の Mesh を生成するコードを書いてたのですが、これの解説記事書いてなかったなーと思ったので書いてみることにしました。

* [VR180 Mesh Projection Box Parser](https://github.com/aosoft/VR180MeshProjection)

![190211.jpg](https://qiita-image-store.s3.amazonaws.com/0/211064/dd071dbb-b193-4cd4-bb80-3098b581173f.jpeg)

## VR180 (VR ビデオ) の Metadata

VR ビデオには規定の Metadata を設定する必要があります。

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

従来の 360 度全天球では "[Spherical Video RFC](https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md)" でしたが VR180 に合わせて (?) V2 の規格ができました。 Mirage Camera はこの規格に沿ったファイルで MP4 を記録しています。

VR ビデオの Metadata の一番の目的は **どのような形状の映像に変形 (配置) して記録しているか** の情報を定義するものです。これがないと結局はただの 2D の映像ということになり、通常のパースの映像とは大分異なる、ある種とても見づらい映像で表示することになります (あるいは決め打ちかユーザー側に個別に設定してもらう) 。

* 記録している映像の投影方式は？ (equirectanglur, fisheye etc.)
* ステレオか？　ステレオなら side-by-side は左右分割か、上下分割か。左右の目の割り当ては？

これらの情報を元にプレイヤーはレンダリング時に適切な変形を行うことで記録時の想定に沿った形で表示ができるわけです。

V2 では多くの情報を MP4 フォーマットに準拠した Box 形式で格納するようになったのが V1 との大きな違いです。このうち、映像の投影方式の定義が 3 種類存在します。

### Cubemap Projection Box

環境マッピングをする際のおなじみの方式。自分を中心として 6 方向の平面にそれぞれの視野角 90 度のパース映像を配置するやり方。

### Equirectangular Projection Box

デファクトスタンダードとなっている "正距円筒図法 (equirectangular)" で記録されている場合のその詳細情報を定義しているものです。 VR (パノラマ) 映像を扱う場合、一度は必ず見るはずの形式です。

なぜこの方式がよく使われるかというと、コンピューター上でレンダリングする際に都合がよいため (UV 座標が緯度経度に対応しているので取り扱いが容易) というのが主な理由のようです。

### Mesh Projection Box

最後の一つが今回の目的である Mesh Projection Box です。これは驚くことに **Mesh 情報 (頂点座標、 UV 座標、インデックス) がそのまま記録されています。** Mesh に映像を投影 (projection) するわけです。

この形式を採用したのはおそらく **あらゆる投影方式を表現できるから** ではないかと思います。 cubemap や equirectangular ももちろん表現可能ですが、 これらはよく使われている (ほとんどの場合は equirectanglar ではないかと) ので特例扱いしたのではないかと思います。

equirectangular 以外では魚眼レンズ (fish eye) 形式になるかと思います。

* [Unity 2018.1 で VR180 動画を作成する](https://qiita.com/tan-y/items/c4b09d2f92a60fda6113)

fish eye 形式についてはこちらの記事で実例を解説しています。この記事では 3D 空間から fish eye へのレンダリングを実装していますが、この逆 (fish eye から対応する Mesh 情報を構築する) ももちろん可能ではあります。

しかし一言で fish eye と言っても投影方式が複数ありますし、同じ投影方式でも視野角が異なったりするため equirectangular と比べてパラメーターが複雑になりがちです。つまり

* 様々な投影方式を仕様で定義する必要がある。 → 未知の方式が出てきた時に困る
* 実装側は仕様に定義されている投影方式に合わせて処理を個別に実装必要がある。

ということがあるのですが、これを Mesh で定義することにより "Mesh からレンダリングする" という統一的な手法で対応できることになって、仕様定義側も実装側も単一の定義で対応できるというメリットが得られます。 equirectanglar も結局球体の内側にテクスチャーマッピングしたものをレンダリングする、というパターンが多いわけでその場合はやる事同じなわけですし。

デメリットとしては

* データー量がとても大きい。圧縮もできますがそれでも大きい。投影方式と視野角だけだったら 2 項目。
* 論理的にどのような投影方式かがわからない。頂点情報だけで判断するのは難しいでしょう。

前者は映像データー本体と比べれば大きいわけでもないので気にする必要はないかもですが、後者は知りたい事もあると思うのでつけてくれてもよかったのではないかなあと思いました。

Mesh Projection Box は圧縮もしたりするのでリアルタイムに作るには重たい処理になりそうですが、カメラの場合はこれはレンズ特性を表すものなので符号化されたバイナリーデーターをファーム内に保持して撮影時にはそれをコピーしているだけと思います。よって撮影時には特に負荷はないと思います。読み込み時もストリームにつき一つなので、初期化時に作ってあとは使いまわしとなりこちらも特に負荷はないでしょう。

## VR180 Mesh Projection Box Parser の概要

"VR180 Mesh Projection Box Parser" は主に 2 つの機能があって

* MP4 の "Mesh Projection Box" を扱いやすい状態にパース (デシリアライズ) する。
* パースしたデーターを元に Unity の Mesh を構築する。

パース処理は Unity に依存していない C# コードなのでそのまま流用可能なはずです。

また、あくまで Mesh Projection Box のパースが主目的でパノラマ動画の再生を目的としていません (動作確認のため、副次的にそういう実装はしていますが) 。パノラマ動画再生を目的とするなら equirectangular や fisheye の投影対応も必要でしょう。

### MeshProjectionBox クラス

MP4 の MeshProjectionBox を表すクラスです。

MeshProjectionBox では先頭の "encoding_four_cc" に符号化方式の fourcc が書き込まれていて、非圧縮か deflate 圧縮かのどちらかです。 .NET では好都合な事に deflate の実装がされている (DefalteStream クラス) のでこれを利用して復号します。

MeshProjectionBox の中は MeshBox が 1 つ以上存在します。

### MeshBox クラス

MeshProjectionBox 内の MeshBox の内容を表すクラスです。項目は仕様通りにそのまま仕分けしています。加工は何もしません。

```csharp
public struct MeshBoxVertex
{
	public int x_index_delta;
	public int y_index_delta;
	public int z_index_delta;
	public int u_index_delta;
	public int v_index_delta;
}

public enum MeshBoxIndexType : int
{
	Triangles = 0,
	TriangleStrip = 1,
	TriangleFan = 2
}

public class MeshBoxVertexList
{
	public int texture_id;
	public MeshBoxIndexType index_type;
	public int[] index_as_delta;
}

public class MeshBox
{
	public float[] coordinates;
	public MeshBoxVertex[] verticies;
	public MeshBoxVertexList[] vertex_lists;
}
```

MeshBox は一般的な Mesh そのもので

* 頂点リスト (頂点座標と UV)
* インデックスリスト

のセットとなっています。

頂点座標と UV 座標は coordinate という浮動小数点値リストのインデックスで表現されています。通常は球体になるはずで、球体であれば同じ値が頻出する可能性があると考えられるので要素数の削減につながります。さらにインデックス値も直接保持するのではなく、直前インデックス値との相対値となっています (これは理由がよくわからない。相対範囲を絞っているわけでもないのでデーター量削減になってないように思います) 。

インデックスリストも頂点リストと同じように直前のインデックス値との相対値です。

インデックス相対値はその総数に応じて値のビット幅を決定することにより適切なデーター量になるようにしています。 (ccsb, vcsb)

### VR180Mesh クラス

MeshProjectionBox から Unity の Mesh を生成するクラスです。このクラスは Unity 依存です。現実装ではいくつか決め打ちで書いているので対処をした方がよいかもしれません。MeshBox の内容を元に Unity 用の頂点リストとインデックスリストに作り変えます。

## 課題

カメラの姿勢情報に対応したかったのですが、 "Projection Header Box" や "Camera Motion Metadata Track" に有意なデーターが確認できすに対応ができていません。

## おわりに

Mirage Camera は画質的には正直今一つなところはありますが、お手軽に VR180 Video の撮影ができるのでなかなかよいカメラではあると思います。

実際のところ VR180 Creator 等を通して equirectangular に変換した方が扱い的にはしやすいかもしれませんが、それはそれとして生データーでの扱いをできるようにしておくのもよいかなと思います。理屈的に画質劣化もないわけですし。