【Unity】超高解像度映像「KNOT ISLAND 2155」をつくりました

# はじめに
長崎にある軍艦島デジタルミュージアムの巨大プロジェクタで上映する
映像作品「KNOT ISLAND 2155」をUnityで製作しました。
カラコレ・編集以外は全部一人で、製作期間は基本日曜に作業して3ヶ月くらい。
編集や音楽のおかげもあって、かなり良い映像になりました。


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">軍艦島デジタルミュージアムで上映される「KNOT ISLAND 2155」をつくりました。点群データをUnityのVFX Graphで映像にしました。 <a href="https://t.co/EFtmeuKgDP">pic.twitter.com/EFtmeuKgDP</a></p>&mdash; ekunish (@ekunish) <a href="https://twitter.com/ekunish/status/1465289558656835584?ref_src=twsrc%5Etfw">November 29, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# 製作について
手順は下記の通りです。

- モデルデータを間引き
- 建物ごとにモデルを切り出し
- 切り出したモデルをクレンジング
- ワイヤーフレーム表示用にモデルデータ書き換え
- Unityに取り込み
- VFX Graphで表示
- TextMeshProでUI表示
- CinemachineとTimelineでカメラワーク調整
- Rendermonsterでpng書き出し
- ffmpegでHapに変換


## モデルデータを間引き
Meshlabを使い、モデルデータのvertexが1000万程度となるように調節しました。
最終的にはUnity上で25fpsくらいとほぼリアルタイムに処理できました。


## 建物ごとにモデルを切り出し
ここは自動でやる手段が思いつかなかったので、手動です。
軍艦島の建物数十棟を目検で分けていくのは辛い作業でしたが、
Meshmixerだとモデルの形状に合わせてある程度自動で範囲選択してくれるので、
思ったより時間は掛かりませんでした。


## 切り出したモデルをクレンジング
手動で切り出すとどうしても細かいポリゴンが残ってしまったため、
再びMeshlabのisolation機能を使い、孤立しているパーツを削除しました。


## ワイヤーフレーム表示用にモデルデータ書き換え
元々、点群っぽい表現にしたいと思い、UnityのVFX Graphの使用を想定していました。
表現の方向性を検討していく上で、ワイヤーフレーム的な表現を使うことになり、
色々調べたのですが、Particle Stripなどはあるものの、
VFX Graphでワイヤーフレーム表示をする機能は見つかりませんでした。

とりあえず、点群データのフォーマットであるplyをasciiコードで見てみると、
全頂点位置と色情報が並び、その次に各ポリゴンを形成する頂点の組み合わせが並んでいる
割とシンプルな構造であることがわかりました。
Particle Stripは、頂点データを上から順番に読んでいくので、
ポリゴンの順に頂点を並び替えたplyを作ってやれば良さそうです。

本当はunityのプラグインを書けば良いのでしょうが作法がよく分からなかったので
pythonでスクリプトを書きました。
二重に被る点が出てきちゃいますが、とりあえず動いたので良し。


```
input_dir = 'インプットディレクトリ'
output_dir = 'アウトプットディレクトリ'

os.makedirs(output_dir, exist_ok=True)

for filename in os.listdir(input_dir):
    base, ext = os.path.splitext(filename)
    if ext == '.ply':
        print(filename)
        f = open(os.path.join(input_dir, filename), 'r')
        linelist = f.readlines()
        f.close()

        is_header = True

        header = []
        vertexes = []
        tri_indexes = []
        sorted_vertexes = []

        for line in linelist:
            if (is_header):        
                header.append(line)
                if (line == 'end_header\n'):
                    is_header = False
            else:
                elem_num = len(line.split())

                if (elem_num == 7):
                    vertexes.append(line)
                elif(elem_num == 4):
                    tri_indexes.append(line)

        # sort            
        for tri_index in tri_indexes:
            indexes = tri_index.split()
            sorted_vertexes.append(vertexes[int(indexes[1])])
            sorted_vertexes.append(vertexes[int(indexes[2])])
            sorted_vertexes.append(vertexes[int(indexes[3])])
            sorted_vertexes.append(vertexes[int(indexes[1])])


        # output
        w = open(os.path.join(output_dir, filename), 'w')

        w.write('ply\n')
        w.write('format ascii 1.0\n')
        w.write('comment python converted\n')
        w.write('element vertex ' + str(len(sorted_vertexes)) + '\n')
        w.write('property float x\n')
        w.write('property float y\n')
        w.write('property float z\n')
        w.write('property uchar red\n')
        w.write('property uchar green\n')
        w.write('property uchar blue\n')
        w.write('property uchar alpha\n')
        w.write('element face 0\n')
        w.write('property list uchar int vertex_indices\n')
        w.write('end_header\n')

        w.writelines(sorted_vertexes)
        w.close()
```


## Unityに取り込み
@keijiro さんのプラグインを使わせていただきました。
読み込んだら、タイプをTextureに変更。

https://github.com/keijiro/Pcx


## VFX Graphで表示
前述の通り、Particle Stripを使いました。
スクリプトで処理するため、Blackboardでパラメータを外に出しておきます。
個人的な注意点はParticleのCapacityです。
建物ごとにVFX Graphデータをつくるにあたり、
最初は、全部のCapacityを1000万とかにしてたのですが、
3棟ほど動かしたあたりでUnityが動かなくなりました。
1日くらい原因が分からず、そもそもVFX Graphの限界なのではと焦ったのですが、
どうやらCapacityが、確保するメモリやらの値のようで、
モデルに合わせてやることで解決しました。


## TextMeshProでUI表示
文字の色やサイズを変更するため、TextMeshProをつかいました。
htmlっぽいタグで書けるので楽チン。


## CinemachineとTimelineでカメラワーク調整
最初はスクリプトでどうにかできるかなと思っていたのですが、
ダイナミックなカメラワークにするためには目で見ながら調整するしかなく、Cimemachineを使いました。
軌道を設計するにあたってはSmooth pathが使いやすかったです。

各種パラメーターの一部はTimelineで処理してます。
グラフィカルに変数を調整できるのが良いです。
もう少しAfterEffectっぽい感覚に近づけるともっと使いやすくなりそうです。


## Render Monsterでpng書き出し
今回のプロジェクタが8K超えの横長超高解像度で、
Unity Recorderでは書き出せませんでした。
また、そもそもmp4も規格として4Kだか8Kまでしか対応していないようで、
Render Monsterを使い、連番のpngで書き出しました。
シーン再生は25FPSくらい出てたんですが、書き出しは1FPSほどでかなり時間掛かりました。

https://assetstore.unity.com/packages/tools/utilities/render-monster-82563?locale=ja-JP


## ffmpegでHapに変換
動画は普通ならProResコーデックで良いと思いますが、
Touchdesignerでは読み込めませんでした。
調べてみると、Hapには対応しているようだったので、ffmpegで変換しました。
最初再生したらどうやってもカクカクで、どうしようかと思いましたが、
Perfome Modeだと滑らかに動いたので良かったです。


# 最後に
点群を扱うのも初めてですが、Unityで初めて本格的な映像をつくってみました。
やることも多かったですが、学ぶことが多かったです。
1000万超えの点群をリアルタイムで処理できるVFX Graphはすごいです。
Cinemachineも初めて使ってみましたが、かなり直感的にカメラワークを設計できました。
20万で作ったWindowsマシンを有効活用できてよかったです。

しばらくは常設で上映されると思うので、軍艦島デジタルミュージアムにぜひ行ってみてください。

https://www.gunkanjima-museum.jp