🏔️

[WebXR] UnityとiPhone LiDAR、THETAを使った崖下を覗ける360°WebGLアプリを作ってみる

2021/12/22に公開

はじめに

こちらはWebXR ( WebVR/WebAR ) Advent Calendar 2021アドベントカレンダー22日目の記事になります。
Unityでお手軽に作ったVRをWebコンテンツに仕立てた現地体験型のデモです。

こちらでプレイアブルです。今の所ブラウザのみ。
https://it33kam0t0.github.io/mtvr/

動機づけというかコンセプト

体力づくりのアウトドアとして登山を嗜んでします。先日紅葉の時期に訪れた荒船山は特徴的な外観から「陸のタンカー」「日本のテーブルマウンテン」とか呼ばれている日本二百名山。
特に艫岩(ともいわ)の絶壁からの眺めが絶景です。


しかし写真や動画に撮っても、きつい坂道の斜度やスケール感などはイマイチ情報が伝わりにくい。これを「体験型メディア」であるVRで再現できれば感動を持ち帰れるのでは…? と思った次第です。

デザイン検討

  • 周囲の風景は360°写真で、足元の地形は3Dスキャンして組み合わせることで「崖っぷち」の臨場感を再現できないか?

  • UIを編集しやすいUnityで作ってWebコンテンツとして出力したい。

  • 崖下を見下ろせること。踏み外したら実際に崖下に落下する?

素材の調達

  • 360°カメラTHETA Vで現地で撮った4K画像を使用。身を乗り出すと非常に危険な場所なので、自撮り棒など組み合わせて崖から突き出して撮っています。

  • LiDAR機能付きのiPhone 12 Proで艫岩の展望台付近の地形を3Dスキャン。3d Scanner Appを使用。※3Dスキャンの際は安全や周囲の方の迷惑にならないように注意しながら行うこと。
    下記はテクスチャ付きで処理したところ。私の影がたくさん写っちゃってますが(笑)。「Share」からファイル形式でOBJを選択するとテクスチャーファイルも含んだzipファイルとしてエクスポートされます。

-歩き回るユーザーアバターを用意します。なんでもいいのですが、今回は3Dファイルのサイトからこちらなど使用
https://free3d.com/3d-model/nathan-animated-003-walking-644277.html

-360°画像の表示のための全球モデルは以下から入手(octahedron-sphere-meshes.unitypackageをダウンロード)
https://catlikecoding.com/unity/tutorials/octahedron-sphere/

こちらのサイトも参考にさせていただきました。
【簡単】Unityで360度の画像を作る方法【球にセットするだけです】

開発・実装

Unityちょっと触ったことある、というくらいの方向けに書いていきます。
当初は Unity 2021.2.6f1を使用。インストール時に「WebGL Build Support」を忘れずに加えます。

新しい3Dプロジェクトを作成して、先程のoctahedron-sphere-meshes.unitypackageをインポートします。画像では「All」で取り込んでいますが使ったのは一番下のOctahedron Sphere 6 R1.assetだけだったのでこれだけにチェックでもよいです。

Octahedron Sphere 6 R1をプロジェクトペインからヒエラルキーペインに放り込むとピンク色の球体が現れました。

360°写真のファイルをプロジェクトペインにドラッグ&ドロップで登録します。ヒエラルキーでcreate - Material としてマテリアルを新規作成します。

用いたシェーダーは UI - Unlit - Transparent など。このマテリアルに360°画像を選択してテクスチャーとします。

マテリアルを球体に適用すると内部で360°画像が見れるようになりました。続けて3Dスキャナーアプリからエクスポートした艫岩の地形のobjファイルとテクスチャーをプロジェクトペインに取り込んでヒエラルキーに放り込むと3Dオブジェクトとして表示されました。
欠けているところやノイズがありますが今回はこのまま使ってみます。

球体と展望台の3Dオブジェクトを位置、向き、スケールなどを調整して崖の展望台とその遠景になるように合わせて行きます。作例では球体を5倍くらいに大きくした後3Dオブジェクトを位置合わせしています。ときどきシーンギズモで上から見た状態にして行うとやりやすいかも。

ヒエラルキーで Create - 3D Object - Cubeを作成し、大きさと向きを調整して展望台の3Dオブジェクトの下に来るようにして展望台~崖の土台部分とします。

ユーザーアバター用にダウンロードした人間の3Dファイルをプロジェクトに取り込んでヒエラルキーに配置します。展望台の上に来るようにしてサイズも調整します。
実際にこの上を歩くことになるCubeは表面色を岩の質感に合わせて調整しました。(テクスチャーを貼ると なおそれっぽい)

ユーザーアバターに重力や当たり判定の設定をするためにインスペクターペインでAdd ComponentしてRigidbodyとCapsuleColliderを追加します。移動時の転倒を防ぐためにRigidbodyのCostraintsでFreezeRotationのX、Y、Zにチェックを入れます。この辺はキャラクターを動かすチュートリアルなどで定番の設定ですね。

CapsuleColliderでEditColliderを有効にして コライダーの形状をユーザーを包み込むように上に引き上げます。

この時点で上の三角のボタンを押してプレビュー再生モードにし、ユーザーが地面(Cube)に立つか確認します。突き抜けて落下するようならCubeと重なっている可能性があるのでユーザーアバター本体やCapsuleColliderをCube表面より上に来るように調整します。
あ、saveはこまめにしましょう。

ユーザーの動きを書きます。Create - C# Scriptとしてスクリプトファイルを作成します。ここではUserMovementというファイル名(=クラス名)にしました。

コードはこんな感じです。キーボードの入力を受け付けてユーザーアバターの向きで前後左右に移動するようにしています。QとEのキーで体の向きも回転します。

UserMovement.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UserMovement : MonoBehaviour
{
    private float movement = 20f;
    private float moveX = 0f;
    private float moveZ = 0f;

    public float target_rotate = 100;


    void Start()
    {

    }

    void Update()
    {
        // 向きの取得、前後キーで進む
        var vertical = transform.forward * Input.GetAxis("Vertical");
        var horizontal = transform.right * Input.GetAxis("Horizontal");
        var direction = vertical + horizontal;
        // 右回転・左回転:5°ずつ
        if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey("e"))
        {
            transform.Rotate(new Vector3(0, 5, 0));
        }
        if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey("q"))
        {
            transform.Rotate(new Vector3(0, -5, 0));
        }
        // ジャンプ
        if (Input.GetKey(KeyCode.Space))
        {
            direction.y = 5;
        }

        moveX = direction.x * Time.deltaTime* movement * 0.2f;
        moveZ = direction.z * Time.deltaTime * movement * 0.2f;
        transform.Translate(moveX, 0f, moveZ, Space.World);

        // 重力計算
        direction.y -= 2 * Time.deltaTime;
        // 動かす処理
        transform.Translate(direction * Time.deltaTime);
    }
}

スクリプトはドラッグ&ドロップでヒエラルキーのユーザーアバターに適用します。

カメラをユーザー目線で一緒に動くようにします。
MainCameraをユーザーアバターのツリー配下になるように移動し、位置も目か肩の付近でユーザーの前を向くように調整します。

首の代わりにカメラの向きを上下に動かして空や地面を見れるようにします。崖下を覗き込むのに大事。 同じようにCameraAngleというスクリプトを作って次のようなコードを書き、
ヒエラルキーのMainCameraにドラッグアンドドロップで適用します

CameraAngle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraAngle : MonoBehaviour
{
    Vector3 angle;
    Vector3 worldAngle;

    void Start () {

    }
 

    void Update() {
        worldAngle = transform.eulerAngles;

         // 上下向き
        if ((Input.GetAxis("Mouse Y") > 0) || (Input.GetKey("r")))
        {
            angle.x -= 1.5f;
            transform.eulerAngles = new Vector3(angle.x, transform.eulerAngles.y, 0);
 
            // 角度に制限を加える。
            if(angle.x < -80)
            {
                angle.x = -80;
            }
        }
        else if ((Input.GetAxis("Mouse Y") < 0) || (Input.GetKey("f")))
        {
            angle.x += 1.5f;
            transform.eulerAngles = new Vector3(angle.x, transform.eulerAngles.y, 0);

            if (angle.x > 80)
            {
                angle.x = 80;
            }
        }
    }
}

R・F キーか、マウスの前後で視野を上下に傾けられます。

プレビューしてキーボードで操作できるか確認してみます。ユーザーやカメラ視野がいい感じに動いているかな?

おまけで、3Dスキャンで現場に写りこんでいる、展望台によくある眺望の説明プレートがはめこまれた台を、当たり判定あるものにしてみます。Cubeをもう一つ別にCreateして大きさ・位置が台に重なるように調整します。

重なったらCubeのインスペクターでMeshRendererだけチェックを外します。これで、透明だが実体はあるので、ぶつかって突き抜けないオブジェクトになります。

さきほどのUserMovementスクリプトにはSpaceキーでジャンプできるようにしてあるので、台の上に乗れたりします。まぁ、行儀悪い。

要件的にはこれで十分ですが、崖から落下したときの受け皿用に Create - 3D Object - Terrainで地面を崖の下の方に配置しておきました。

さらに、周囲に3D地図の図形が組み合わさっていると、落下して球体を抜けたときの興ざめ感が和らぐかも、と探したところ、国交省の国土地理院の3Dデータがいい感じでした。3Dプリンター用に3Dオブジェクトとして切り出せるようなので試してみます。
サイトはこちら
https://maps.gsi.go.jp/3d/

「地理院地図」で今回の舞台 荒船山を検索して上空に移動します。ツール - 3D - カスタムと進みます。

マウスで艫岩の展望台を含む崖部分を範囲指定します。

いい感じに切り取れました。ちょっと広すぎたかも?これをVRMLファイルを選んで「ダウンロード」します。

Unityにインポートできる形式に変換します。ここでは以下のサイトを用いてOBJ形式に変換しました。
https://imagetostl.com/jp/convert/file/vrml/to/obj

Unityに取り込んで、展望台の位置が崖上にくるようにスケール・位置・向きを調整します。

いよいよWebコンポーネントとしてビルドします。File - Build Settings を選択します。

Add Open Scenesをクリックして現在のシーンをビルド対象に追加し、プラットフォームでWebGLを選択します。現在のプラットフォームがWebGLでないときはSwitchPaltformをクリックする必要があります。

ボタンがBuildに変わったらビルドできます。ファイル名とファイルの書き出し先を指定します。

Webページとして公開

書き出されたzipファイルを展開するとindex.htmlとBuildフォルダとコンテンツが含まれています(Unityのバージョンで構成は異なる)。
これをWebサーバーにアップしてコンテンツとすればブラウザで実行できます。
今回はGitHubのWebページ機能で公開してみます。

GitHubでリポジトリを切ったらそこにコンテンツをアップロードします。WebのUIからフォルダごとアップロードもできますが、各ファイルサイズが25MBまでである必要があり、超えるときはGitコマンドでPushすることになります。
さらにこのコマンド転送もファイルサイズ100MBの制限があるので超えるファイルがあるときはGit LFSで個別にPushする必要があります……!

参考:
https://qiita.com/takish/items/4b397caa5549a39a8194

ファイル一式がアップできたらリポジトリのSettings - PagesでPublishする階層を選ぶと指定されたURLでアクセスできるようになります。

参考:
https://prog-8.com/docs/github-pages
再掲:
https://it33kam0t0.github.io/mtvr/
カメラ視野の上下は[R][F]キーまたはマウスの前後操作でできます。

FireFox、Chromeでは正常に動きました。MacのSafariだとちょっとカクつくかな……?

ここに来てトラブル!

ここまでUnity2021 2.6の画面で紹介してきましたが、次のようなメッセージでロードに失敗することがあるようです。
Unable to parse Build/xxx.framework.js.gz! ...

これはUnityのPlayerSetting - Publishing Settings でCompression Format をDisabledにし、gzip圧縮しないようにすることで回避できたが、次のエラー:
HTTP Response Header "Content-Type" configured incorrectly...

こちらはWebサーバー側の .htaccessを編集して、Buildフォルダに含まれている wasmなどのファイル形式を MIME typesに登録しないといけないので難易度高い。
そこで上記のリンクでは、Unity 2019.4.21f1で改めて今回のアプリを作成してWebGL出力したものに切り替えています。
当時はまだwasmファイルは使われていなかったようです。

参考:
Unity for WebGL Gzip,Brotli圧縮ビルドアプリの実行 - Picosy

おわりに

以上、ざくっとではありますがアウトドアの現地で調達していた素材を用いて体感できるWebVRとして仕立ててみました。
写真や動画もいいですが、こんな感じで残せるようになると体験の共有が捗るかも。

今回時間が無かったですがOculus Questのドライバを仕込んで中をコントローラー操作で動き回れるようにしたいです。

次回はLimeさんによるBabylon.jsのWebXR形式で掴む機能を実装する方法です!

Discussion