産業分野におけるDX推進の切り札として注目を集めるXR技術。本記事では、KDDIテクノロジーのXRエンジニア・福田陸弥氏を講師に迎え、Unity完全初心者の編集部員が生徒役となって挑んだ「Unityで実践する産業DX ― 3時間で作るハンズフリーARドキュメント」オンラインセミナーの模様をお届けする。
開発環境のセットアップからARマーカーの認識、そして最終的な「視線(ゲイズ)でページをめくる仕組み」の入り口まで、業務にすぐ活かせるARアプリ開発の基本をステップバイステップで徹底解説。ぜひ実際にPCで操作をしながら、AR開発の第一歩を一緒に踏み出してみてほしい。
※2時間38分のセミナー全編から、初期セットアップ〜スクリプト実装までの前編を記事化しています
福田陸弥
株式会社KDDIテクノロジー 開発3部
KDDIテクノロジーではPMやテック営業として活動中。副業としてXRエンジニアやスクール講師としても活動している。フリーランス時代の実戦経験から、ハイスピードなPoC制作や多角的なコンテンツ設計、コンサルティングを得意とする。
x.com/GONBEEE_project
まずはここから!Unity 2022とVisual Studioのインストール
福田陸弥氏(以下、福田):このセミナーでは、AR Foundationを用いた環境構築から、画像認識によるARマーカーの活用、ARグラスと組み合わせたハンズフリー体験まで、丁寧に解説していきます。
▲本セミナーで作成するコンテンツは、A4サイズの紙に印刷されたARマーカーの上にデータ(ドキュメント)を表示し、ハンズフリーで閲覧するというもの。産業DXに置き換えると、現場でARグラス越しに作業支援マニュアルを閲覧するといった活用法が期待できる
CGWORLD(以下、CGW):よろしくお願いします。私は完全な初心者で、今日は初歩の初歩から教えていただけるということで安心しています。
福田:はい、Unityのインストールからステップバイステップで進めていきます。この授業では少し特殊というか、APIの都合で特定のバージョンを入れる必要があります。Unity Hubの「Install Editor」から、「Unity 2022.3.73f1」をインストールしていきます。今回はAndroidスマホ向けにアプリをつくっていくために、インストール時に「Microsoft Visual Studio Community 2022」、「Android Build Support」、「OpenJDK」、「Android SDK & NDK Tools」にチェックを入れてモジュールを追加してください。
※「2022.3.74f1」は現状Unity Industry・PROライセンス限定のバージョンとなります。 ライセンスをお持ちでない方は、以下のダウンロードアーカイブより「2022.3.62f3」をインストールのうえご対応いただけますと幸いです。
CGW:今はUnity 6があるのに、なぜ少し古い2022.3を選ぶのでしょうか?
福田:良い質問ですね。2022系統からUnity 6系統に更新される際、今回使う「AR Foundation」というプラグインがメジャーバージョンアップして、各種APIに大きな変更が入りました。新バージョンでの実装はまだ日本語文献が少なく、授業後に皆さんが復習や独学をするのが難しくなってしまいます。そのため、周辺情報の調べやすさを重視してこのバージョンで解説します。
CGW:なるほど、学習しやすさ優先ですね。
福田:はい。では、Visual Studioの準備について補足します。インストール時の「ワークロード」の画面で、「Unityによるゲーム開発」に必ずチェックを入れてインストールしてください。これがないと上手く連携できません。
CGW:わかりました。次はプロジェクトの立ち上げですね。
福田:はい、Unity Hubの「New project」から新規プロジェクトをつくります。Editor versionを先ほどの「2022.3.73f1」に変更し、テンプレートは「3D(Built-In Render Pipeline)」を選択します。プロジェクト名は任意の名前(日本語はNG)にしてください。
※「2022.3.74f1」はUnity Industryライセンスがないと起動できません。Unity Industryライセンスをお持ちでない方は、以下のダウンロードアーカイブより「2022.3.62f3」をインストールのうえご対応ください。
CGW:テンプレートの選択肢に「AR Mobile」というものがありますが、これは使わないのですか?
福田:実はAR MobileにはスマホAR向けのプラグインやツールがすでに用意されているのですが、逆に言えば「何のプラグインを入れたか」がわかりにくくなってしまいます。Unity初学者はあえて初期状態でプロジェクトをつくり、「何のために」「何のプラグインを入れるか」「どう使うか」を意識したほうが、のちのちの“Unity力”がグッと上がります。
CGW:なるほど。あえてまっさらな状態から始めるわけですね。
福田:プロジェクトが立ち上がったら、一度Unityから離れて「ADB(Android Debug Bridge)」をインストールします。これはPCからAndroidへアプリケーションをインストールするためのツールです。配布サイトからダウンロードして解凍し、環境変数に展開先のパスを登録します。コマンドプロンプトで「adb」と入力して、使い方がズラッと出れば成功です。
AR Foundationの導入と、スマートフォンAR向けシーンの構築
CGW:ADBのパスが無事に通りました。Unityの画面に戻って、何をすればいいでしょうか?
福田:いよいよ各種プラグインのインストールです。上部Windowメニューから「Package Manager」を展開します。左上のカテゴリを「Unity Registry」に変更し、検索窓に「AR」と入力して「AR Foundation」を探してください。今回はバージョン5.2.2をインストールします。
CGW:AR Foundationとはどのようなプラグインですか?
福田:AndroidのARCoreやiOSのARKitなど、OSによって異なるAR機能をひとまとめにしてくれる便利な共通パッケージです。これを使えば、Android向けに作った体験をiPhoneやARグラスに展開するのも非常にスムーズになります。
インストール後、「New Input Systemを有効化するか」という警告ポップアップが出ますが、今回はスモールスタートなので「No」を選択してください。念のため、上部のEditメニュー→「Project Settings」→「Player」→「Other Settings」から「Active Input Handling」を見つけて、ドロップダウンを「Both」にしておきましょう。
CGW:おまじないですね。設定しました。
福田:次はAR用シーンの組み立てです。まずヒエラルキーにある「Main Camera」を削除します。そしてヒエラルキーの中で右クリックして、「XR」→「XR Origin (Mobile AR)」を設置します。もうひとつ、「XR」→「AR Session」も設置します。たったこれだけで、スマートフォンAR体験用のシーンになります。
CGW:あっという間ですね。
福田:次に画像トラッキング用のコンポーネントを準備します。今追加した「XR Origin」オブジェクトを選択して、インスペクターの「Add Component」から「ARTrackedImageManager」を検索して追加してください。このコンポーネントはXR Originを参照する挙動があるため、必ずXR OriginコンポーネントがあるGameObjectに直接Add Componentしてください。
CGW:追加できました。画像トラッキングのターゲットにはどのような画像が良いのでしょうか?
福田:ARマーカーは、特徴点が明確なものが最適です。例えば「色の境界線(コントラスト)」「形状の輪郭」「線と線の交差点」などが検知の目印になります。逆に、グラデーションや輪郭があいまいな画像、同じパターンの繰り返し、点対称・線対称の画像はトラッキングに向いていません。生成AIでマーカーをつくるのもGoodです。
CGW:AIでつくるんですね。プロンプトのコツはありますか?
福田:「UnityのARFoundation環境下でAR画像認識を行う、画像マーカーの生成をお願いします。輪郭線がはっきりしている、コントラストがはっきりしている、線対称・点対称ではない状態など、特徴点が多く画像認識として扱いやすい形式の画像を生成してください」と指示すると上手くいきやすいです。今回は事前に用意した画像を使います。
CGW:画像をUnityに取り込みました。
福田:では、現実空間の画像サイズとデータ上のサイズを連携させる「Reference Image Library」を構築します。Projectビューで右クリックし、「Create」→「XR」→「Reference Image Library」を選択します。インスペクタービューで「Add Image」を押し、画像スロットに画像データを登録して、わかかりやすい名前を設定します。今回はKDDIテクノロジーのロゴ画像と、CGWORLDのロゴ画像を設定しました。
CGW:「Physical Size」という項目はどうすればよいですか?
福田:現実空間のサイズ(メートル)を入力します。画像をA4縦(縦297×横210mm)に印刷すると想定し、横幅(X)を「0.21」に設定するとシンプルでわかりやすいのでオススメです。設定できたら、XR Originの「ARTrackedImageManager」コンポーネントにある「Serialized Library」の枠に、今つくったReference Image Libraryをドラッグ&ドロップして参照渡しをします。
画像の上に何を表示する?トラッキング対象オブジェクトの作成
CGW:これで画像を認識できるようになりましたね。認識した後に表示するものをつくるのでしょうか。
福田:その通りです。画像認識時、その場所に表示するオブジェクトをつくります。ヒエラルキーで空のGameObjectを作成し、名前を先ほどReference Image Libraryに登録した画像と同じ名前にしてください。
CGW:名前を同じにするんですね。
福田:はい、後でスクリプトでひも付けるためです。次に、その空のGameObjectの配下(子オブジェクト)として、サンプルの「Cube」を追加します。そしてインスペクターからCubeのScaleを「X:0.21、Y:0.01、Z:0.3」に設定してください。
CGW:かなり薄っぺらい板のようになりました。
福田:これがA4の紙とほぼ同じ大きさになります。ARで重畳するとマーカーがちょうど隠れるサイズですね。後々、このオブジェクトをベースにドキュメントを表示する仕組みをつくっていきます。
CGW:いよいよプログラミングですか?
福田:はい、トラッキング用スクリプトの実装に入ります。ARTrackedImageManagerで検知した画像座標へ、今つくったGameObjectの座標を重ねる処理を書きます。Projectビューで右クリックして、「Create」→「C# Script」を選択します。名前は「ImageTracker」など、わかりやすいものにしてください。
CGW:作成しました。コードの知識がないのですが大丈夫でしょうか?
福田:全体としては50行程度の小さなコードですし、分割しながら日本語に翻訳するように解説していくので安心してください。作成したスクリプトをダブルクリックして、Visual Studioを起動しましょう。
CGW:Visual Studioが開きました。
福田:準備バッチリですね。まずはスクリプトの上部に `using UnityEngine.XR.ARFoundation;` などの名前空間を追加し、`ARTrackedImageManager` や `Dictionary` を使って、認識した画像の名前と、先ほどつくったGameObjectをひも付ける処理を書いていきます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
public class ImageTracker : MonoBehaviour
{
[SerializeField] private GameObject[] targets;
private Dictionary<string, GameObject> targetPairs = new Dictionary<string, GameObject>();
private ARTrackedImageManager trackedImageManager;
// Start is called before the first frame update
void Start()
{
trackedImageManager = GetComponent<ARTrackedImageManager>();
trackedImageManager.trackedImagesChanged += OnChanged;
for(int i = 0; i < targets.Length; i++)
{
targetPairs.Add(trackedImageManager.referenceLibrary[i].name, targets[i]);
targets[i].SetActive(false);
}
}
private void OnDisable()
{
trackedImageManager.trackedImagesChanged -= OnChanged;
}
void OnChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
foreach(var newImage in eventArgs.added)
{
UpdateImage(newImage);
}
foreach (var updatedImage in eventArgs.updated)
{
UpdateImage(updatedImage);
}
}
private void UpdateImage(ARTrackedImage trackedImage)
{
GameObject g = targetPairs[trackedImage.referenceImage.name];
g.SetActive(true);
Transform t = trackedImage.transform;
g.transform.SetPositionAndRotation(t.position, t.rotation);
}
}
ImageTracker スクリプト
ここまでの手順でARマーカーを認識し、その上にオブジェクトを表示する準備は整った。続いては、C#によるトラッキングスクリプトの完成、ドキュメント表示パネルの作成、ページ素材のインポートへと進む。そして、スマートフォンのカメラ(=ユーザーの視線)を向けるだけでページ送り・戻りができる「ゲイズ」操作を実装し、矢印ボタンも設置。仕上げに、DocumentRoot、ArrowButton、GazeControllerという3つの制御スクリプトを作成していく。
これらの実装を終え、Android端末向けにビルド(apk化)して実機にインストールすると、スマホをかざすだけで空間上にマニュアルが浮かび上がり、視線を矢印に向けるだけでページが切り替わるARアプリが完成する。セミナーではさらにプロジェクトを拡張し、XREAL Air 2 Ultraなどの最新ARグラスへアプリを展開し、両手が完全にフリーな状態で作業支援マニュアルを閲覧する手法を解説している。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DocumentRoot : MonoBehaviour
{
[SerializeField] private Sprite[] docPages;
[SerializeField] private Image docImage;
private int currentPage = 0;
// Start is called before the first frame update
void Start()
{
UpdatePage();
}
public void NextPage()
{
currentPage = (currentPage + 1) % docPages.Length;
UpdatePage();
}
public void PreviousPage()
{
currentPage = (currentPage - 1 + docPages.Length) % docPages.Length;
UpdatePage();
}
void UpdatePage()
{
docImage.sprite = docPages[currentPage];
}
}
DocumentRoot スクリプト
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ArrowButton : MonoBehaviour
{
[SerializeField] private float gazeTimeMax = 2f;
[SerializeField] private Direction direction = Direction.Next;
public enum Direction { Next, Previous }
private float gazeTimer = 0f;
private bool isGazing = false;
private DocumentRoot documentRoot;
private Image progressImage;
// Start is called before the first frame update
void Start()
{
documentRoot = transform.root.GetComponent<DocumentRoot>();
progressImage = GetComponent<Image>();
}
// Update is called once per frame
void Update()
{
if (isGazing)
{
gazeTimer += Time.deltaTime;
progressImage.fillAmount = gazeTimer / gazeTimeMax;
if (gazeTimer >= gazeTimeMax)
{
OnGazeComplete();
// ����
}
}
else
{
progressImage.fillAmount = 0f;
}
}
public void OnGazeStart()
{
isGazing = true;
gazeTimer = 0f;
}
public void OnGazeEnd()
{
isGazing = false;
gazeTimer = -1f;
}
public void OnGazeComplete()
{
switch (direction)
{
case Direction.Next:
documentRoot.NextPage();
break;
case Direction.Previous:
documentRoot.PreviousPage();
break;
}
gazeTimer = 0f;
}
}
ArrowButton スクリプト
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GazeController : MonoBehaviour
{
[SerializeField] private Transform head;
private ArrowButton currentGaze = null;
// Update is called once per frame
void Update()
{
Gaze();
}
private void Gaze()
{
Ray ray = new Ray(head.position, head.forward);
if(Physics.Raycast(ray, out RaycastHit hit))
{
ArrowButton arrowButton = hit.collider.GetComponent<ArrowButton>();
if(arrowButton != null)
{
if(currentGaze != arrowButton)
{
if(currentGaze != null)
{
currentGaze.OnGazeEnd();
}
currentGaze = arrowButton;
currentGaze.OnGazeStart();
}
}
else
{
if(currentGaze != null)
{
currentGaze.OnGazeEnd();
currentGaze = null;
}
}
}
else
{
if (currentGaze != null)
{
currentGaze.OnGazeEnd();
currentGaze = null;
}
}
}
}
GazeController スクリプト
後編はセミナーのアーカイブ動画からご視聴いただけます!
アーカイブ動画視聴の申し込みはこちらTEXT&EDIT___kagaya(ハリんち)