Unreal Engine 5を使ってみる その10(Scaniverse、点群データの取り込み)
はじめに
今回はiPhone 15 ProでScaniverseというアプリを使って3Dスキャンしたデータを、Unreal Engine 5(UE5)に取り込んでみました。
データを取り込むにあたって、MeshLabやPlayCanvas、Blenderなどの様々なツールを試しました。しかしファイル形式が特殊なようで、変換を重ねてようやく取り込むことができました。
UE5向けの有料プラグインが公開されているようなので、そちらを利用したほうが早いかもしれません。この記事では有料のツールは使っていません。
▼販売されていたプラグインはこちら。
https://www.unrealengine.com/marketplace/ja/product/3d-gaussians-plugin
▼プラグインを開発している方のページがありました。Unreal Engineで厳密に実装するのであれば、こちらの記事を参考にすると良さそうです。
https://zenn.dev/akiya_souken/articles/render-gaussian-splat-with-niagara
▼Unityなら情報がありました。
https://note.com/oshimu/n/n430a3fc63804
▼データを比較するとこんな感じ。左から写真、Scaniverse上のデータ、UE5上のデータです。

▼以前の記事はこちら
Scaniverseでスキャンする
▼Scaniverseのページはこちら
この記事を書いている間にもアップデートで画面や機能が変わったところがあります。今後のアップデートによっては、やり方が変わるかもしれません。
なお、この後でSplatモードでスキャンしたデータをUE5で取り込みます。
Splatモードで取り込む
SplatモードとMeshモードがあるのですが、まずはSplatモードで取り込んでみました。
▼真ん中の+のボタンでスキャンを開始します。

▼Splatを選択します。

▼今回はこちらのトマトの苗をスキャンします。学会で貰いました。

机の上に置いた物体だと、机に乗っかった部分がスキャンされないのでデータが取れないといったことがあります。周りに遮蔽物が無い状態でスキャンすることをお勧めします。
▼対象物の周りをあらゆる角度からスキャンします。

▼スキャンを終了して、Process Nowを選択すると処理が始まります。

▼処理が終わった状態がこちら。まるで写真のようですが、スキャンしたデータです。スワイプすると、他の角度から見ることができます。

▼セーブすると、LIBRARYに保存されます。

▼スキャンしたデータを選択すると、オプションが表示されます。

EDITでデータを編集し、SHAREでデータをエクスポートします。
▼EDITのCROPで、データの範囲を絞ることができます。余計なデータはここで省いておきましょう。

▼写真を切り取るときのように、視点を変えながら切り取ることができます。

AUTO機能が優秀で、スキャン対象の物体にほとんど絞ってくれます。
▼AUTOを適用した状態がこちら。

▼さらに絞ると、トマトの苗だけになりました。

▼SHAREからExport Modelを選択します。

▼SPZかPLYの選択肢があります。今回はPLYを選択します。

▼iPhoneのファイルに保存しました。後でパソコンに送信します。

Meshモードで取り込む
もう一つのMeshモードも試してみました。こちらの方が、出力できるファイル形式や機能が多いです。
▼今回はSmall Objectを選択しました。

▼対象をスキャンします。RANGEで距離を絞ることができるようです。

▼処理方法を選択できます。今回はDetailを選択しました。

▼処理が終了後、保存しました。

▼保存後、データを見てみました。こちらにはAR VIEWもあります。

▼CROP後のデータがこちら。

▼スキャンしたデータをズームするとこんな感じ。途切れているところがあります。細かくスキャンしたい場合はSplatの方が良いのか、じっくりスキャンする必要があるのか...

▼AR機能を試してみました。現実世界に実物大ぐらいの大きさで表示されました。

▼SHAREのExport Modelでデータを出力します。

▼出力できるデータ形式は、Splatのときよりも多いです。Unreal Engineで取り込むなら、FBXが良さそうです。

ファイルのデータをパソコンに送信する
ファイルアプリに「サーバへ接続」というものがあったので、試してみました。
▼右上の3つの点のボタンに、サーバへ接続という選択肢があります。

▼ここにサーバのアドレスを入力します。

今回はWindowsのPCにデータを送信します。iPhoneとPCは同じWi-Fiに接続しておいてください。
コマンドプロンプトでipconfigを実行し、IPアドレスを確認してください。
▼以下のように表示されるので、IPv4アドレスをサーバに入力してください。

▼WindowsのPC側をローカルアカウントに切り替える必要があるみたいです。以下の記事を参考に変更しました。
https://kakitamablog.com/iphone-windows-connect
▼PCにログインするときのユーザ名とパスワードで接続できました。

サーバに接続出来たら、ファイルを送信します。
▼送信したいファイルを選択します。

▼Windowsのユーザーファイル以下にアクセスできます。

これでコピーすると、PC側にファイルが送信されました。ネットワークの接続先が異なったり再接続したりすると、IPアドレスが変わるのでご注意ください。
Unreal Engineに取り込む
3dgsconverterで変換する
ScaniverseのSplatでスキャンして、出力したplyファイルをVS Codeの拡張機能であるvscode-3d-previewで見てみました。
▼plyファイルもサポートされています。
vscode-3d-preview - Visual Studio Marketplace
▼こんな感じで表示されました。

点群のデータは取れているのですが、色のデータが失われています。この状態で、後述するCloud Compareで変換しても、色情報が失われたままでした。
Unityの記事で紹介されていたデータ変換ツールである3dgsconverterを試してみました。
▼GitHubのリポジトリはこちら
https://github.com/francescofugazzi/3dgsconverter
以下のコマンドで仮想環境を作成して、3dgsconverterをインストールしました。
python -m venv pyenv
cd pyenv
.\Scripts\activate
pip install git+https://github.com/francescofugazzi/3dgsconverter.git
引数でplyファイルのパスを指定し、実行ディレクトリに同名の変換したものを保存したかったので、Pythonのプログラムを用意しました。
▼プログラムはこちら
import subprocess
import sys
from pathlib import Path
import os
input = sys.argv[1]
output = os.path.join(Path.cwd(), os.path.basename(input))
subprocess.run(f'3dgsconverter -i {input} -o {output} -f cc --rgb')
以下のコマンドを実行すると、変換できました。
python convert.py <plyファイルのパス>
▼上下が反転していますが、それっぽい見た目になっています。

Cloud Compareで変換する
Unreal Engineでデータを取り込むために、さらにデータを変換するためのソフトウェアをいくつか試しました。最終的にCloud Compareで変換すれば取り込むことができました。
▼Cloud Compareのダウンロードページはこちら
▼フォルダボタンを押して、先程変換したファイルを取り込みます。

▼キュウリのデータですが、取り込むとこんな感じで表示されます。

▼出力したいオブジェクトを選択した状態で、保存ボタンを押します。

▼保存する際にファイルの形式を選択できます。今回はtxtファイルとして保存しました。

▼ちなみにフォーラムにも寄せられていたのですが、e57ファイルとして保存すると、取り込めはするのですが色がおかしいことがありました。
https://forums.unrealengine.com/t/lidar-point-cloud-plugin-imported-cloud-colors/903981
LiDAR Point Cloud Pluginでメッシュ化する
▼UE5の編集→プラグインから、LiDAR Point Cloud Supportを追加しておいてください。

点群データを扱うプラグインではありますが、利用できるファイル形式の中にplyファイルがありません。Cloud Compareでtxtファイルとして保存したのはそのためです。
▼LiDAR Point Cloud Pluginで対応しているファイル形式の一覧はこちら。
txtファイルをドラッグ&ドロップすると、LiDAR点群インポートオプションが起動します。
このとき、Red、Green、Blueがどの列データなのかを指定する必要があります。
▼RedにRf、GreenにGf、BlueにBfを指定しました。

▼画像のように取り込めました。点群が疎なのか、薄いですね。

選択モードからLiDARモードにすると、プラグインの機能を利用できます。
▼LiDARモードにするとオブジェクトの移動ができなくなるようなのでご注意ください。

▼Create Static Meshを選択すると、スタティックメッシュを作成できます。

▼スタティックメッシュを作成した状態がこちら。トマトの苗はきれいに表示されていますが、トマトは薄いままです。

データが無いところはどうしても薄くなるようなので、スキャンをする際にデータを細かくとる必要がありそうです。
光源を移動させてみる
点群データとして取り込んだ場合に気になっていたのが、光源を移動させたときの見え方です。
Unreal Engineのグラフィック処理を活かしたシミュレーションをしたいのに、点群データに反映されないのでは困ります。どのように反映されるのか確認してみました。
▼光源の位置を変えると、画像のように変わります。左側が点群データ、右側がメッシュ化したデータです。




ちゃんと影も反映されているようでした。点群のデータは結構隙間ができてしまっています。機材を変えたりスキャンの仕方を変えると、隙間が埋まったりするのでしょうか。
▼UE5内で動いた時の様子。
なお、物理シミュレーションを行えばトマトが落下するのではないかと思ったのですが、Simulate Physicsは設定できないようになっていました。
ハニワを3Dスキャンして取り込む
これまでの経験を踏まえ、時間をかけてしっかりスキャンすることで点群を密にできるだろうと考え、私のハニワで試してみました。
▼ScaniverseでExportしたデータはこちら。練習用にでも使ってください。
▼こちらのハニワです。今ではLEDが目に入っています。

10分以上かけてスキャン後、3dgsconverterとCloud Compareで変換しました。
▼vscode-3d-previewで表示した時点で、点群が密になっている気がします。

▼Cloud Compareで取り込んだ状態。ムンクの叫びみたいです。

Unreal Engineに取り込んでメッシュ化してみました。点群は先程よりも密ですが、どうしても表面は荒れてしまうみたいです。
▼左側が点群データ、右側がメッシュ化したデータです。光源の位置を移動すると、点群データはなぜか発光することがありますね。




最後に
もっと良い点群のデータの扱い方があるような気がしますが、ひとまず取り込むことはできました。ただし表面が荒いので、細部にこだわるなら他の方法も検討する必要がありそうです。
ThirdPerson Characterぐらいの少し遠い視点であれば気にならないのかな?という感じです。用途次第ですね。
▼LiDAR Point Cloud Pluginのドキュメントはこちら。もっときれいに表示するための機能が、どこかにあるのかもしれません。