本文へスキップ

セマンティッククエリを実行してセマンティック・チャンネルをハイライトする

ARアプリでプレイバックを使ってセマンティック情報を表示している様子

この入門ガイドでは、以下の内容について説明します。

  • セマンティックをクエリして、プレイヤーがタッチしたポイントに何が表示されているかを検出する。
  • プレイヤーが最後にタップしたポイントに基づき、特定のセマンティックチャンネルをハイライトする。
  • セマンティック情報を照会するために利用可能なAPI。

前提条件

ARDKがインストールされたUnityプロジェクトと、セットアップされた基本的なARシーンが必要です。 詳細については、 ARDK 3のインストール および ARシーンの設定 をご覧ください。

手順

UI要素の追加

セマンティッククエリーを処理するスクリプトを実装する前に、ユーザーにセマンティック情報を表示するUI要素を準備する必要があります。 この例では、セマンティックチャンネル名を表示するテキストフィールドと、シェーダー出力を処理する RawImage を作成します。

UIエレメントを作成する:

  1. Hierarchyで、ARシーンを右クリックし、 UI にマウスオーバーし、 Raw Image を選択して、 RawImage をシーンに追加します。
  2. この手順を繰り返しますが、 Text-TextMeshPro を選択してテキスト・フィールドを追加します。 TMP Importerポップアップが表示されたら、Import TMP Essentialsをクリックして、テキストフィールドの追加を完了します。
  3. Hierarchyでテキストフィールドを選択し、 Inspectorで、その位置を (0, 0, 0) に設定します。
  4. InspectorRect Transform メニューで、左上隅の四角をクリックして、 Anchor Presets メニューを開きます。 Shiftを押しながらcenterオプションをクリックすると、テキストが画面の中央に固定されます。
Center Transformボタンを強調したUnity UIのイメージ
  1. RawImageを選択し、 Anchor Presets メニューを再度開きます。 Optionキー(WindowsではAltキー)を押しながら右下の正方形を選択し、 RawImage 、正しい位置に配置し、画面全体を覆うように引き伸ばす。
アンカー・プリセット」メニューの「ストレッチ」オプションを強調するUnity UIのイメージ

Semantic Segmentation Managerの追加

Semantic Segmentation Managerは、セマンティック・サブシステムへのアクセスを提供し、コードの他の部分がアクセスできるセマンティック予測を提供します。 (詳細については、 Semantics機能ページを参照)

シーンにSemantic Segmentation Managerを追加する:

  1. Hierarchy ウィンドウで右クリックし、 Create Empty を選択して、空の GameObject をシーンに追加します。 Segmentation Manager という名前を付けます。
  2. 新しい GameObjectを選択し、 Inspector ウィンドウで、 Add Componentをクリックし、"AR Semantic Segmentation Manager" を検索して選択し、コンポーネントとして追加します。
Semantic Segmentation Managerを含むゲームオブジェクトの例。

Alignment Shaderの追加

意味情報が画面上で正しく配置されるようにするには、ディスプレイ・マトリクスを使い、カメラから返されたバッファを回転させる必要がある。 オーバーレイシェーダーを使って、 ARCameraManager frame update イベントからディスプレイのトランスフォームを取得し、それを使ってUVを正しいスクリーン空間にトランスフォームすることができます。 そして、セマンティック情報を先ほど作成した RawImage にレンダリングし、ユーザーに表示する。

シェーダーを作成する:

  1. シェーダーとマテリアルを作成します:
    1. Project ウィンドウで、 Assets ディレクトリを開きます。
    2. Assets ディレクトリで右クリックし、 Create にマウスオーバーし、 Shader メニューから Unlit Shader を選択します。 SemanticShader という名前を付けます。
    3. この作業を繰り返しますが、 Create メニューから Material を選択し、新しいマテリアルを作成します。 SemanticMaterialと名付け、SemanticShaderを新しいMaterialにドラッグ&ドロップして関連付けます。
  2. シェーダーのコードを追加する:
    1. Assets ディレクトリから SemanticShader を選択し、 Inspector ウィンドウで、 Open をクリックしてシェーダーコードを編集します。
    2. デフォルトのシェーダーをアライメントシェーダーのコードに置き換えます。
クリックしてアライメントシェーダーのコードを展開する
Shader "Unlit/SemanticShader"
{
Properties
{
_MainTex ("_MainTex", 2D) = "white" {}
_SemanticTex ("_SemanticTex", 2D) = "red" {}
_Color ("_Color", Color) = (1,1,1,1)
}
SubShader
{
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Blend SrcAlpha OneMinusSrcAlpha
// カリングまたは深度はなし
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float3 texcoord : TEXCOORD1;
float4 vertex : SV_POSITION;

};

float4x4 _SemanticMat;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;

//画像を適切な回転とアスペクトに調整する必要があります。
o.texcoord = mul(_SemanticMat, float4(v.uv, 1.0f, 1.0f)).xyz;

return o;
}

sampler2D _MainTex;
sampler2D _SemanticTex;
fixed4 _Color;

fixed4 frag (v2f i) : SV_Target
{
//座標空間の変換
float2 semanticUV = float2(i.texcoord.x / i.texcoord.z, i.texcoord.y / i.texcoord.z);

float4 semanticCol = tex2D(_SemanticTex, semanticUV);
return float4(_Color.r,_Color.g,_Color.b,semanticCol.r*_Color.a);
}.
ENDCG
}.
}
}
  1. 材料のプロパティを設定します:
    1. シェーダーコードを置き換えると、マテリアルにプロパティが入力されます。 これらにアクセスするには、 Assets ディレクトリから SemanticMaterial を選択し、 Inspector ウィンドウを見てください。
    2. マテリアルのカラーとアルファを設定するには、 InspectorColor プロパティの右にあるカラースウォッチをクリックします。 アルファ値を 128 (およそ50%)に設定し、セマンティックカラーフィルターがその下の現実世界のオブジェクトを見るのに十分な半透明になるようにします。 好きな色を選んでください。
マテリアルインスペクター

Query Scriptの作成

プレーヤーがスクリーンにタッチしたときにセマンティック情報を得るには、セマンティック セグメンテーションマネージャーに問い合わせ、プレーヤーがエリアにタッチしたときに情報を表示するスクリプトが必要です。

Query Scriptを作成する:

  1. スクリプトファイルを作成し、Segmentation Managerに追加する:
    1. Project ウィンドウで、 Assets ディレクトリを選択し、ウィンドウ内で右クリックし、 Createにマウスオーバーし、 C# Scriptを選択する。 新しいスクリプトに SemanticQuerying という名前をつけます。
    2. Hierarchyで、 Segmentation Manager GameObjectを選択し、 Inspector ウィンドウで、 Add Componentをクリックします。 script」を検索し、 New Script を選択し、 SemanticQuerying スクリプトを選択します。
  2. スクリプトにコードを追加する:
    1. Assets ディレクトリの SemanticQuerying スクリプトをダブルクリックしてテキストエディタで開き、次のスクリプトをコピーします。 (スクリプトの各部分の動作の詳細については、 Appendix を参照)。
クリックするとSemanticQueryingスクリプトが表示されます。
using Niantic.Lightship.AR.Semantics;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;

public class SemanticQuerying : MonoBehaviour
{
public ARCameraManager _cameraMan;
public ARSemanticSegmentationManager _semanticMan;

public TMP_Text _text;
public RawImage _image;
public Material _material;

private string _channel = "ground";

void OnEnable()
{
_cameraMan.frameReceived += OnCameraFrameUpdate;
}

private void OnDisable()
{
_cameraMan.frameReceived -= OnCameraFrameUpdate;
}

private void OnCameraFrameUpdate(ARCameraFrameEventArgs args)
{
if (!_semanticMan.subsystem.running)
{
return;
}

//セマンティックテクスチャを取得
Matrix4x4 mat = Matrix4x4.identity;
var texture = _semanticMan.GetSemanticChannelTexture(_channel, out mat);

if (texture)
{
//テクスチャを画面に合わせるために表示行列を取得
//シェーダーを使用して、テクスチャの回転やスケールを行う
_image.material = _material;
_image.material.SetTexture("_SemanticTex", texture);
_image.material.SetMatrix("_SemanticMat", mat);
}
}

private float _timer = 0.0f;

void Update()
{
if (!_semanticMan.subsystem.running)
{
return;
}

//Unityエディターとデバイス上での動作の違い
if (Input.GetMouseButtonDown(0) || (Input.touches.Length > 0))
{
var pos = Input.mousePosition;

if (pos.x > 0 && pos.x < Screen.width)
{
if (pos.y > 0 && pos.y < Screen.height)
{
_timer += Time.deltaTime;
if (_timer > 0.05f)
{
var list = _semanticMan.GetChannelNamesAt((int)pos.x, (int)pos.y);

if (list.Count > 0)
{
_channel = list[0];
_text.text = _channel;
}
else
{
_text.text = "?";
}

_timer = 0.0f;
}
}
}
}
}
}

Script Variablesの割り当て

スクリプトを実行する前に、スクリプトとUIエレメントが互いに会話できるように、Unityで変数を割り当てる必要があります。

変数を割り当てる:

  1. Hierarchyで、 Segmentation Manager GameObjectを選択します。
  2. Inspector ウィンドウで、 SemanticQuerying スクリプトコンポーネント内の変数を、各項目をそれぞれのフィールドにドラッグアンドドロップして割り当てます:
    1. シーンの MainCamera から Camera Man フィールドへ;
    2. SegmentationManager オブジェクトを Segmentation Man フィールドに渡す;
    3. Text-TMP オブジェクトから Text フィールドへ;
    4. RawImage オブジェクトを Image フィールドに渡す;
    5. SemanticMaterial Assets ディレクトリから Material フィールドへ。
スクリプト変数を設定した後のSemantic Queryingスクリプト

ビルドとテスト

スクリプトを追加してコードを設定したら、プレイバックのデータセットを使ってテストしてみましょう。 録画したデータセットをUnityエディタで再生するための設定方法については、再生設定方法を参照してください。 Lightship GithubのPlaybackデータセットを使う場合、出力は次のようになるはずだ:

ARアプリでセマンティック情報を見る

また、デバイスにビルドし、実際の環境でテストできるようになりました。

ARアプリでセマンティック情報を見る

Appendix: クエリースクリプトの仕組み(How Does the Query Script Work?)

スクリーンには何が映っていますか?

クエリスクリプトで重要なのは、ユーザーがタップ、またはクリックしているかどうかをフレームごとにチェックすることです。 タップやクリックの位置とフレーム数を確認し、適切であることを確認した後、ユーザーが選択した地点でセマンティックチャンネルを取得し、結果をテキストボックスに表示します。

チャンネル名のスニペットを表示するにはここをクリック
using Niantic.Lightship.AR.ARFoundation;
using TMPro;
using UnityEngine;

public class SemanticQuerying : MonoBehaviour
{
public ARSemanticSegmentationManager _semanticMan;
public TMP_Text _text;

void Update()
{
if (!_semanticMan.subsystem.running)
{
return;
}

//Unityエディターと実際のデバイスでの入力処理
if (Input.GetMouseButtonDown(0) || (Input.touches.Length > 0))
{
var pos = Input.mousePosition;

if (pos.x > 0 && pos.x < Screen.width)
{
if (pos.y > 0 && pos.y < Screen.height)
{
_timer += Time.deltaTime;
if (_timer > 0.05f)
{
var list = _semanticMan.GetChannelNamesAt((int)pos.x, (int)pos.y);

if (list.Count > 0)
{
_channel = list[0];
_text.text = _channel;
}
else
{
_text.text = "?";
}

_timer = 0.0f;
}
}
}
}
}
}

セマンティック・クラスのハイライト

GetSemanticChannelTextureを使って、見ているセマンティックチャンネルのテクスチャを取得し、結果を RawImage UI要素に出力することができます。 この関数は Matrix4x4 テクスチャを出力するので、 アライメント・シェーダの追加で説明したように、シェーダで変換します。

クリックするとテクスチャー出力のスニペットが表示されます
using Niantic.Lightship.AR.ARFoundation;
using TMPro;
using UnityEngine;

public class SemanticQuerying : MonoBehaviour
{
public ARSemanticSegmentationManager _semanticMan;
public TMP_Text _text;
public RawImage _image;

void Update()
{
if (!_semanticMan.subsystem.running)
{
return;
}

var list = _semanticMan.GetChannelNamesAt(Screen.width / 2, Screen.height / 2);
_text.text="";
foreach (var i in list)
_text.text += i;

//これは見つけたクラスをハイライトする
if (list.Count > 0)
{
//最初の1つのみを表示する。
_image.texture = _semanticMan.GetSemanticChannelTexture(list[0], out mat);
}
}
}