おもちゃラボ

Unityで遊びを作ってます

【Unity】画面四隅のワールド座標を取得する

モデルが画面外に出たことを検知したり、画面いっぱいにPlaneを広げたりしたい場合、画面四隅のワールド座標がわかると案外便利です。

そこで、この記事では画面四隅のワールド座標を取得する方法を説明します。

ワールド座標変換

画面四隅のワールド座標を取得するには、画面四隅のスクリーン座標をワールド座標に変換します。Unityでスクリーン座標をワールド座標に変換するにはScreenToWorldPointメソッドを使用します。

ScreenToWorldPointは引数にスクリーン座標をとり、これをワールド座標に変換するメソッドです。画面四隅のスクリーン座標はそれぞれ、

  • (0,0)
  • (Screen.Width, 0)
  • (0, Screen.Height)
  • (Screen.Width, Screen.Height)

なので、これをScreenToWorldPointに渡せば、画面四隅のワールド座標が得られます。

ただし、変換するときに、カメラからの奥行きによって四隅のワールド座標が変化するので、引数のz値にカメラからの距離(depth)を指定する必要があります。

f:id:nn_hokuson:20201022203824p:plain:w450

画面右上(Screen.Width, Screen.Height)と左下(0, 0)のワールド座標を取得するスクリプトは次のようになります。ここではdepth=5.0として、カメラから5m先のワールド座標がを取得しています。

float depth = 5.0f;
rightTop = Camera.main.ScreenToWorldPoint(new Vector3(Screen.Width, Screen.Height, depth));
leftBottom = Camera.main.ScreenToWorldPoint(new Vector3(0, 0, depth));

カメラがOrthographicの場合

カメラからの奥行きによってワールド座標が変化するのは、カメラのProjectionがPerspectiveのときです。Orthographicのときは下図のようにカメラからの奥行きに関わらずワールド座標は一定なので、depthには0を指定しておいて問題ありません。

f:id:nn_hokuson:20201022203833p:plain:w450

カメラがOrthographicのときのスクリプトは次のようになります。

rightTop = Camera.main.ScreenToWorldPoint(new Vector3(Screen.Width, Screen.Height, 0));
leftBottom = Camera.main.ScreenToWorldPoint(Vector3.zero);

【Unity】メニューからモデルを配置する

Unityエディタ上でモデルを並べる場合、通常はPrefabをシーンビューにドラッグ&ドロップして並べますね。

ただ、数が多くなると手作業で並べるのは大変なので、スクリプトを使って並べられると便利です。そこで、この記事ではUnityエディタ拡張を使って、Unityエディタ上でPrefabを並べる方法を紹介します。

f:id:nn_hokuson:20201020214646j:plain

今回はメニューから「PlaceCubes」という項目を選択したら自動的にCubeのPrefabを並べるエディタ拡張スクリプトを作ってみます。

エディタ拡張を使ってモデルを並べる流れは次のとおりです。

CubeのPrefabを作る

いつもと同じように、ヒエラルキーウインドウに追加したCubeを
プロジェクトウィンドウにドラッグ&ドロップしてCubeのPrefabを作ります。
CubeのPrefabの名前は、ここでは「CubePrefab」とします。

f:id:nn_hokuson:20201020220333j:plain

ヒエラルキーウインドウのCubeは右クリック→Deleteで削除しておきましょう。

エディタ拡張スクリプトを作る

次に、エディタ拡張のスクリプトを作ります。エディタ拡張スクリプトは通常のC#と同じようにプロジェクトウィンドウで右クリック→Create→C# Scriptで作成します。

今回のファイル名はCubePlacementにしておきましょう。

f:id:nn_hokuson:20201020214839p:plain:w300

ファイルが作成できたら次のスクリプトを入力して下さい。

using UnityEngine;
using UnityEditor;


public class StageEditorWindow : EditorWindow
{
    [MenuItem("Window/PlaceCubes")]
    static void Place()
    {
        GameObject cubePrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Cube.prefab");

        for (int i = 0; i <= 180; i+=5)
        {
            GameObject g = PrefabUtility.InstantiatePrefab(cubePrefab) as GameObject;

            float y = 10 * Mathf.Sin(i * Mathf.Deg2Rad);
            g.transform.position = new Vector3(i / 5.0f, y, 0);
        }

    }
}

Unityエディタ拡張では、MenuItemアトリビュートを使うことで、指定した場所にメニュー項目を追加できます。ここでは[MenuItem("Window/PlaceCubes")]とすることで、メニューバー→WindowのなかにPlaceCubesという項目を追加しています。

このPlaceCubes項目を選択したときに実行されるのがPlaceメソッドです。Placeメソッドの中ではPrefabを読み出し、それをInstantiateしています。

Placeメソッドはstaticメソッドなので、インスペクタ経由でのPrefab設定ができません。そこで、AssetDatabase.LoadAssetAtPathメソッドを使って、Assetsフォルダ直下にあるCubePrefabをロードしています。LoadAssetAtPathメソッドでロードするには、オブジェクトの拡張子まで指定する必要があるので注意して下さい。

Resourcesフォルダからロードしたい場合は次のように記述しても問題ありません。(今回はAssetsフォルダ直下からロードするためにLoadAssetAtPathメソッドを使用しました)

Resources.Load("CubePrefab")

CubePrefabのインスタンスが作成できたら、アーチ状になるようにSinメソッドを使ってCubeの位置を移動しています。

エディタ拡張はゲームオブジェクトにアタッチする必要はないので、これで完成です!

メニューを選択する

それではCubePlacementメニューを選択して、動作するかを確認してみましょう。

f:id:nn_hokuson:20201020221746g:plain

参考

エディタ拡張に関しては、次のサイトが非常に参考になりました。
http://49.233.81.186/part1-menuitem.html49.233.81.186

【AR Foundation】特徴点群(Point Cloud)の表示と保存

この記事では、AR Foundationで検出される特徴点群(Point Cloud)を表示する方法と、各特徴点の座標を調べたり、保存したりする方法を紹介します。特徴点を保存しておけば、次図のように、後から特徴点群として表示することもできます。

f:id:nn_hokuson:20200903200034j:plain

AR Foundationの基礎になっているARKitについては「swiftで作る ARKit超入門」がオススメです!こちらも、ぜひご参照ください。
booth.pm

今回の目次は次のとおりです。

プロジェクトの作成と準備

AR Foundationを使ったプロジェクトの作り方と、その準備についてはこちらの記事に詳しく説明しています。

nn-hokuson.hatenablog.com

ここではAR SessionとAR Session Originオブジェクトを配置し終えたところからスタートしたいと思います。

特徴点を表示する

AR Foundationを使って特徴点を表示するには、AR Point Cloud Managerを使用します。このAR Point Cloud ManagerのPrefabに特徴点を表示するPoint Cloud Prefabを指定することで、特徴点を可視化できます。

Point Cloud PrefabにはPoint Cloudスクリプトがアタッチされており、このスクリプトのpositions(Vector3の配列)に検出した全ての特徴点の座標が入っています。

f:id:nn_hokuson:20200903205539p:plain

それではまず、Point Cloud Prefabを作成して、Point Cloud Managerに登録しましょう。
ヒエラルキーウインドウの「+」→「XR/AR Default Point Cloud」を選択して下さい。作成したオブジェクトをプロジェクトウィンドウにドラッグ&ドロップします。ヒエラルキーウインドウのAR Default Point Cloudは不要ですので削除しておきましょう。

f:id:nn_hokuson:20200903201757j:plain:w450

次に、作成したPrefabをAR Point Cloud ManagerのPoint Cloud Prefabの欄にドラッグ&ドロップして下さい。

f:id:nn_hokuson:20200903201929j:plain

これで特徴点を表示する準備はできました。ビルドしてカメラを動かすと、黄色い特徴点が検出されます。

f:id:nn_hokuson:20200903204550j:plain:w450

特徴点の座標を保存する

次に、検出した全ての特徴点の座標を保存してみましょう。ここでは画面をタッチすると、検出した特徴点の座標をCVS形式で保存するようにします。

上図で説明したように、検出した特徴点の座標はPoint CLoudスクリプトのpositions配列に格納されています。これを取り出して、ファイルに保存するスクリプトを作りましょう。

SaveControllerというC#ファイルを作成して、次のスクリプトを入力して下さい。

using System;
using System.Collections.Generic;
using UnityEngine.XR.ARSubsystems;
using System.IO;

namespace UnityEngine.XR.ARFoundation
{
    public class SaveController : MonoBehaviour
    {
        ARPointCloud m_PointCloud;
        Dictionary<ulong, Vector3> m_Points = new Dictionary<ulong, Vector3>();

        void Awake()
        {
            m_PointCloud = GetComponent<ARPointCloud>();
        }

        void OnPointCloudChanged(ARPointCloudUpdatedEventArgs eventArgs)
        {
            if (!m_PointCloud.positions.HasValue)
                return;

            var positions = m_PointCloud.positions.Value;

            if (m_PointCloud.identifiers.HasValue)
            {
                var identifiers = m_PointCloud.identifiers.Value;
                for (int i = 0; i < positions.Length; ++i)
                {
                    m_Points[identifiers[i]] = positions[i];
                }
            }
        }

        void SavePoints()
        {
            string filePath = Application.persistentDataPath + "/points.txt";
            Debug.Log("file save at " + filePath);

            using (StreamWriter sr = File.CreateText(filePath))
            {
                foreach (var kvp in m_Points)
                {
                    Vector3 p = kvp.Value;
                    sr.WriteLine(p.x.ToString("F3") + "," + p.y.ToString("F3") + "," + p.z.ToString("F3"));
                }
            }

        }

        private void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                SavePoints();
            }
        }

        void OnEnable()
        {
            m_PointCloud.updated += OnPointCloudChanged;
        }

        void OnDisable()
        {
            m_PointCloud.updated -= OnPointCloudChanged;
        }
    }
}

このスクリプトでは、特徴点が新たに追加された場合にOnPointCloudChangedメソッドが呼ばれるようにしています。OnPointCloudChangedメソッドではPoint Cloudスクリプトの持つpositionsから座標を取り出して、一旦、m_Pointsに追加しています。

画面をタップするとSavePointsメソッドが実行されます。SavePointsメソッドではm_Pointsから特徴点の座標を取り出して、ファイルに保存しています。保存先はApplication.persistentDataPathを指定しています。これにより、iPhone内の/var/mobile/Applications/XXXXXXXX/Documentsフォルダにデータが保存されます。

作成したSaveControllerスクリプトをAR Default Point Cloud Prefabにドラッグ&ドロップしてアタッチして下さい。

f:id:nn_hokuson:20200903202110j:plain:w600

これで、特徴点を保存するアプリができました。ビルドしてある程度特徴点を検出したら、画面をタップして特徴点の座標をファイルに保存して下さい。

保存した特徴点を可視化する

保存したファイルを取り出す

保存したファイルはiPhoneの/var/mobile/Applications/XXXXXXXX/Documentsフォルダに入っています。iPhoneのアプリ内に保存したデータを取り出すにはXcodeを使う必要があります。

MacとiPhoneを接続してXcodeを起動し、メニューバーからWIndow→Devices and Simulatorsを選択します。今回作成したアプリを選択して、画面したの歯車ボタンから「Download Container 」を選択します。

f:id:nn_hokuson:20200903202358j:plain:w600

○○○.xcappdataという名前で保存されるので右クリック→「パッケージの内容を表示」を選択して下さい。

f:id:nn_hokuson:20200903202502p:plain:w400

「パッケージの内容を表示」を選択するとFinderが開きます。AppData/Documents/の中にあるpoints.txtが保存したデータになります。

f:id:nn_hokuson:20200903202513p:plain:w600

保存したデータを可視化する

保存した特徴点のデータを可視化するため、新しくシーンを作っておきましょう。シーンができたら、ヒエラルキーウインドウから「+」→「XR/AR Default Point Cloud」を選択して下さい。

今回はこのオブジェクトのParticle Systemだけを使って特徴点を表示するので、AR Point CloudとAR Point Cloud Particle Visualizerのチェックは外しておきます。

f:id:nn_hokuson:20200903202937j:plain

次に特徴点を可視化するスクリプトを作成します。DataVisualizerという名前のC#スクリプトを作成して、次のスクリプトを入力して下さい。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class DataVisualizer : MonoBehaviour
{
    ParticleSystem m_ParticleSystem;
    ParticleSystem.Particle[] m_Particles;

    [SerializeField] TextAsset data;
    List<Vector3> positions = new List<Vector3>();

    void Start()
    {
        m_ParticleSystem = GetComponent<ParticleSystem>();

        StringReader reader = new StringReader(data.text);
        while (reader.Peek() != -1)
        {
            string line = reader.ReadLine();
            string[] str = line.Split(',');
            float x = float.Parse(str[0]);
            float y = float.Parse(str[1]);
            float z = float.Parse(str[2]);
            positions.Add(new Vector3(x, y, z));
        }

        int numParticles = positions.Count;

        m_Particles = new ParticleSystem.Particle[numParticles];
        for (int i = 0; i < positions.Count;i++)
        {
            m_Particles[i].startColor = m_ParticleSystem.main.startColor.color;
            m_Particles[i].startSize = m_ParticleSystem.main.startSize.constant;
            m_Particles[i].position = positions[i];
            m_Particles[i].remainingLifetime = 1f;
        }

        m_ParticleSystem.SetParticles(m_Particles, numParticles);

    }
}

このスクリプトでは、CVS形式で記述された座標を読み出し、パーティクルを使って可視化しています。座標のリストをパーティクルとして表示するには、保存された座標の個数ぶん、ParticleSystem.Particle型の配列(m_Particles)としてパーティクルを生成して、それぞれのパーティクルの座標や色を設定しています。

スクリプトを保存できたらAR Default Point CloudオブジェクトにDataVisualizerスクリプトをアタッチして下さい。

f:id:nn_hokuson:20200903203153j:plain

最後に、先程取り出したデータをUnityのプロジェクトウィンドウに追加し、それをDataVisualizerのData欄にドラッグ&ドロップします。

f:id:nn_hokuson:20200903203252j:plain:w500

実行して保存した特徴点が表示されていることを確認して下さい。下図はPost Processing StackのBloomを使ってちょっときれいに加工しています(笑)

f:id:nn_hokuson:20200903200034j:plain

booth.pm