深度を使って画面上の地点を現実世界の位置に変換する
Lightshipの深度マップ出力を使用すると、平面やメッシュを使用せずにARシーン内にオブジェクトを動的に配置できます。 この入門ガイドでは、画面上の位置を選択し、深度出力を使用して3D空間にオブジェクトを配置する手順を説明します。

前提条件
Lightship ARを有効にしたUnityのプロジェクトが必要です。 詳細については、 ARDK 3 のインストール をご覧ください。
初めて深度を使うのであれば、 Accessing and Displaying Depth Information は、深度のより単純な使用例を提供しており始めやすいです。
手順
メインシーンがAR対応でない場合は、次のように設定します。
-
Main Camera を削除します。
-
ARSession と XROrigin をHierarchyに追加し、 AR Occlusion Manager コンポーネントを XROrigin に追加します。 高精度なオクルージョンを望む場合は、 How to Set Up Real-World Occlusion(現実世界のオクルージョンを設定する) の
LightshipOcclusionExtension
の使用方法をご覧ください。 -
深度ピックとプレハブの配置を処理する
MonoBehaviour
スクリプトを作成します。Depth_ScreenToWorldPosition
という名前を付けます。 -
スクリプトに必要な名前空間を追加します。
using Niantic.Lightship.AR.Utilities;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems; -
更新時に深度画像を取得する
-
AROcclusionManager
のシリアライズされたフィールドと、XRCpuImage
のプライベートフィールドを追加します。[SerializeField]
private AROcclusionManager _occlusionManager;
private XRCpuImage? _depthimage; -
以下の手順で、新しいメソッド(
UpdateImage
)を作成します。XROcclusionSubsystem
が有効かつ実行中であることを確認します。_occlusionManager.TryAcquireEnvironmentDepthCpuImage
を呼び出して、最新の深度画像をAROcclusionManager
から取得します。- 古い深度画像を破棄し、新しい値をキャッシュします。
private void UpdateImage()
{
if (!_occlusionManager.subsystem.running)
{
return;
}
if (_occlusionManager.TryAcquireEnvironmentDepthCpuImage(out var image))
{
// 古いイメージを破棄
_depthImage?.Dispose();
// 新しいイメージをキャッシュ
_depthImage = image;
}
} -
次のように、
Update
コールバック内でUpdateImage
メソッドを呼び出します。private void Update()
{
UpdateImage();
}
-
-
表示マトリックスの計算: 機械学習モデルから生成される際、深度画像はセンサーに向かって配置されるため、現在の画面の向きに合わせてサンプリングする必要があります。 表示変換では、スクリーン空間からイメージ座標系への変換マッピングを利用できます。
Sample(Vector2 uv, Matrix4x4 transform)
メソッドをCPU上で使用できるように、GPUテクスチャではなくXRCpuImage
を使用します。- 非公開の
Matrix4x4
フィールドとScreenOrientation
フィールドを追加します。private Matrix4x4 _displayMatrix;
private ScreenOrientation? _latestScreenOrientation; - 新しいメソッド(
UpdateDisplayMatrix
)を作成します。 - スクリプトに有効な
XRCpuImage
がキャッシュされていることを確認します。 - 画面の向きが変更されたかどうかを確認して、マトリックスの再計算が必要かどうかを判断します。
CameraMath.CalculateDisplayMatrix
を呼び出し、スクリーン座標をイメージ座標に変換するマトリックスを計算します。
private void UpdateDisplayMatrix()
{
// 有効な深度画像があることを確認
if (_depthImage is {valid: true})
{
// 表示マトリックスは画面の向きが変わっている場合にのみ再計算が必要
if (!_latestScreenOrientation.HasValue ||
_latestScreenOrientation.Value != XRDisplayContext.GetScreenOrientation())
{
_latestScreenOrientation = XRDisplayContext.GetScreenOrientation();
_displayMatrix = CameraMath.CalculateDisplayMatrix(
_depthImage.Value.width,
_depthImage.Value.height,
Screen.width,
Screen.height,
_latestScreenOrientation.Value,
invertVertically: true);
}
}
}- 次のように、
Update
コールバック内でUpdateDisplayMatrix
メソッドを呼び出します。
private void Update()
{
...
UpdateDisplayMatrix();
} - 非公開の
-
以下の手順で、タップ入力を処理するコードを設定します。
- 「HandleTouch」というプライベートメソッドを作成します。
- エディターでは、「Input.MouseDown」を使用してマウスクリックを検出します。
- スマートフォンでは、「Input.GetTouch」を使用してタップを検出します。
- 次に、デバイスから、2D screenPosition座標を取得します。
private void HandleTouch()
{
// エディターではマウスクリック、スマートフォンではタップを使用
#if UNITY_EDITOR
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2))
{
var screenPosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
#else
//タップがない場合、またはタップでUI要素を選択している場合
if (Input.touchCount <= 0)
return;
var touch = Input.GetTouch(0);
// タップが始まった瞬間のみカウントする
if (touch.phase == UnityEngine.TouchPhase.Began)
{
var screenPosition = touch.position;
#endif
// タップで何かのアクションを行う
}
}
} -
深度を使ってタップポイントを3D座標に変換します。
-
HandleTouchメソッド内で、タップが検出された際に有効な深度画像があるかどうかを確認します。
// タップで何かのアクションを行う
if (_depthImage.HasValue)
{
// 1. 視線深度をサンプリングする
// 2. ワールド座標を取得する
// 3. 深度マップ上にオブジェクトをスポーンする
} -
screenPositionで深度画像をサンプリングして、z値を取得します。
// 1. Sample eye depth
var uv = new Vector2(screenPosition.x / Screen.width, screenPosition.y / Screen.height);
var eyeDepth = _depthImage.Value.Sample<float>(uv, _displayMatrix);
-