はじめに
この度インターンシップにて、株式会社GENEROSITY様でARコンテンツの開発を行いました。そこでの実習内容を第1回から第4回の全4部に渡って記事にさせていただきます。
第2回では2週間行ったAR開発の内容のモデル生成方法についてをまとめています。ARの技術として今回は、ARマーカーを認識しその上にオブジェクトを出現させるという事を行いました。前回の第1回の内容を踏まえて進んでいきますので第1回をご覧になっていない方はぜひ以下からご覧になってください。
blenderでのモデル作成からプログラムを書いて実装するまですべて1人で進めましたのでぜひ第2回もご覧いただけると嬉しいです!
目次
対象者
企画概要
使用ツール
開発環境
OS
Unity
テストデバイス
制作
Illustrator
Blender
Unity
モデルのインポート
ARマーカーの設定
フラスコの生成スクリプト
TrackedImageRotationFixer.cs
UIの実装
ARマーカー読み取りスキャン枠
枠の非表示設定
ScanUIManager.cs
アイコンのUI
UIの配置
アイコンの出現タイミング
IconDisplayManager
まとめ
対象者
・プログラミング初心者の方
・UnityでのAR開発に興味がある方
企画概要
企画名はAR授業資料 。例えば地理の学習でARを用いて仮想の地球儀を出現させ国の位置を目で見て学んだり、化学では物質の分子構造を視覚化した疑似的なモデルをARを用いて出現させることでより理解度が深まる工夫をした、頭だけでなく視覚的情報を与えることで子どものさらなる学力を向上を目指したコンテンツです。
今回実習では価値の検証に必要な最低限の機能を備えたプロダクト、MVP(Minimum Viable Product)として、水素と酸素の混合気体の爆発実験のシミュレーション映像を制作することにしました。
以下は私の手書きではありますが今回作るコンテンツの一連の流れになります。
1.まずARマーカーを検出する画面が開きます。
2.ARマーカーを認識するとフラスコの3Dオブジェクトがマーカーの上に出現します。
("けむり"とありますが、MVPを作る上で、出現時の演出である煙は最低限必要な機能ではないため実装していません。)
3.フラスコの出現後、右側にアイコンのパネルが出てきます。
4.上から酸素、水素、マッチ棒のアイコンです。元素のアイコンを押すとその3Dオブジェクトが出現し、フラスコの中に入るアニメーションが再生されます。
5.マッチ棒のアイコンを押してマッチ棒を出現させます。火のついたマッチ棒がフラスコに近づきます。
6.水素が火に反応して酸素の働きも加わることから爆発が起きます。フラスコは粉々に粉砕されます。
7.最後になぜその爆発が起こったのか解説が表示されます。
これが私の今回の企画の一連の流れです。この流れを頭に入れて制作を進めます。
使用ツール
Unity
Visual Studio 2022
blender4.3
Adobe Illustrator 2025
ChatGPT
開発環境
OS
Windows 11 Home
Unity
Unity 2022.3.56f1
開発開始時にUnity6がリリースされていましたが、Unity6でのAR開発について情報が少なかったため、過去バージョンで制作しました。
AR Foundation ver. 5.1.5
AR機能を提供するフレームワークです。
テストデバイス
Google pixel 6 (Android 15)
実習にてお借りした端末です。Android端末で設定から開発者向けオプションを開き、USBデバッグをオンにしましょう。
制作
それではいよいよ制作の中身について入っていきます。Unityを開く前にまずは必要な素材を作ります。
Illustrator
Adobe IllustratorでARマーカーのデザインとUIアイコンの作成を行いました。
今回はこのマーカーをAndroid端末のカメラで映し、この上にフラスコを生成させます。
フラスコの絵はIllustratorのベクター生成AIを駆使して短時間でマーカーを作成しました。
マーカーは印刷しても良いですし、png
やjpeg
で保存してスマホなどのデバイスでマーカーの画像を表示させておくのでも良いです。
Blender
出現させる3DモデルはすべてBlenderで自作しました。
マテリアルはUnity内で設定するのでBlenderではモデル作成だけでOKです。
後ほどプログラムで物理演算をこのフラスコにつけて粉砕を実装するのですが、その粉砕する欠片をすべてUnityに持っていく必要があるためこのように作成しています。
フラスコの破片はBlenderのCell Fractureという無料のアドオンでランダムに分割します。
(ここでは詳しい説明は省かせていただきます。)
分割をしすぎてポリゴン数が多くなるとビルドした際に重くなってしまうので分割しすぎないでください。
割れる前のフラスコも忘れずに用意しましょう。
フラスコのモデリングはこちらを参考にしました。
作成したモデルはBlender
でFile
→Export
→FBX
からfbx形式で書き出しをしてください。
書き出す際にBlenderを開いたとき、シーンにデフォルトで存在するライトとカメラは削除してシーンにモデルのみがある状態で書き出しをしてください。fbxで書き出す際にライトとカメラも一緒に書き出されてしまいます。
Unity
モデルのインポート
必要な素材を作り終えたのでまずはUnity
に3Dモデルをインポートしましょう。
制作中様々なアセットをUnity
のProject
内に入れていくのでフォルダを作って整理してください。
Unity画面のAssets
のパネルの上で右クリックをし、Create
→Folder
を選択します。
するとフォルダ名を変更できるのでフォルダ名をPrefabs
と名付けます。
Prefabs
という名前のフォルダが作れていれば問題ありません。
次に、PCのエクスプローラーから先ほど書き出したモデルのfbx
ファイルをドラッグ&ドロップで今作ったPrefabs
フォルダに格納します。
マテリアルはお好みで設定してください。今回はMVP開発のため単色の簡単なマテリアル設定のみ行いました。
ARマーカーの設定
前編をご覧になってそのプロジェクトのまま進めている方は、Hierarchy
にはDirectional Light
とAR Session
とXR Origin
のみがある状態にしてください。オブジェクトを消去する際は消去するオブジェクトを選択してDeleteキー
を押すことでシーンから削除できます。
前編をご覧になっていない方は以下を参照してAR開発の準備をしてください。
マーカーの設定は以下の記事を参考にしましたのでその記事の中の画像認識準備の見出しの内容までご覧いただき設定を行ってください。
フラスコの生成スクリプト
フラスコの生成を私はスクリプトで行いました。開発中、マーカーとフラスコの位置がずれてしまうのが気になり、何とかできないかと思いスクリプトでフラスコがマーカーに追従するように工夫しました。
Prefab
フォルダを作成したときと同様、Assets
の上で右クリックをし、Create
→Folder
を選択します。
フォルダ名をScripts
とします。
ではそのScripts
フォルダの中にフラスコの生成を行うスクリプトを作りましょう。Scripts
フォルダを選択した状態で右クリックをし、Criate
→C# Script
を選択します。
スクリプト作成直後、スクリプト名が選択された状態なのでその状態のままスクリプト名を変更します。私はTrackedImageRotationFixer
としました。
このように作成直後の名前に青い帯がある状態で名前を変更してください。
スクリプト名は自分が分かりやすい名前にしましょう。
TrackedImageRotationFixer.cs
Project
のAssets
でスクリプトをダブルクリックするとエディタが立ち上がります。私の場合はVisual Studio 2022が立ち上がります。
コードはこちらです。
using System.Collections;
using System.Collections.Generic;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
public class TrackedImageRotationFixer : MonoBehaviour
{
[SerializeField] private GameObject normalFlaskPrefab; // 通常のフラスコ
private ARTrackedImageManager trackedImageManager;
// 生成されたフラスコを保持するリストまたは変数
private GameObject spawnedFlask;
private void Awake()
{
trackedImageManager = FindObjectOfType<ARTrackedImageManager>(); // シーン内のARTrackedImageManagerを取得
}
// オブジェクトが有効になる際 = ARトラッキング画像が検出される際に呼ばれる
private void OnEnable()
{
if (trackedImageManager != null)
{
trackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
}
}
// オブジェクトが無効になる際に呼ばれる
private void OnDisable()
{
if (trackedImageManager != null)
{
trackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
}
}
// マーカーが追加されたり、更新されたときに実行される
private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
// foreachは配列やListの要素を順番に読み取り、要素の数だけ処理を繰り返す
// 新しく検出されたマーカーのリスト
foreach (var trackedimage in eventArgs.added)
{
// FixRotationAndSpawn関数の呼び出し
FixRotationAndSpawn(trackedimage);
}
// すでに検出されているマーカーが更新された場合のリスト。
foreach (var trackedImage in eventArgs.updated)
{
FixRotationAndSpawn(trackedImage);
}
}
private void FixRotationAndSpawn(ARTrackedImage trackedImage)
{
// すでにフラスコが生成されている場合は何もしない
if (spawnedFlask != null) return;
//trackedImage.trackingStateがTrackingState.Tracking の場合にのみ処理を行う
if (trackedImage.trackingState == TrackingState.Tracking)
{
// フラスコを生成
spawnedFlask = Instantiate(normalFlaskPrefab, trackedImage.transform.position, Quaternion.identity);
spawnedFlask.name = normalFlaskPrefab.name;
// 回転補正
Quaternion targetRotation = trackedImage.transform.rotation;
Quaternion rotationOffset = Quaternion.Euler(-90f, 0f, 0f); // 必要に応じて調整 //モデルのローカル軸を調整
spawnedFlask.transform.rotation = targetRotation * rotationOffset;
// 画像の子として設定
// マーカーに追従するようにしている
spawnedFlask.transform.SetParent(trackedImage.transform, true);
}
}
}
// 生成されたフラスコを返すメソッド
public GameObject GetSpawnedFlask()
{
return spawnedFlask;
}
}
コードが書けましたらCtrl+S
でセーブし、Unity
に戻ってください。
今書いたスクリプトを選択するとInspector
にNormal Flask Prefab
という項目があると思います。その右側にNone
と書いてある箇所があります。
ここには最初に出現させるフラスコを入れてあげる必要があります。ここがNone
のままだと、ARマーカーを読み取っても何も出現しません。
スクリプトのInspector
を開いた状態で、Prefab
フォルダの中のヒビが入っていないフラスコをNone
の場所までドラッグします。
↑このように青くなったらマウスのボタンを離してドロップさせます。
このスクリプトを実行させるにはシーンにあるGame Object
へアタッチする必要があります。
Hierarchy
で右クリック→Create Empty
を選択して空のオブジェクトを用意します。
オブジェクトの名前はRotationFixer
とします。
このRotationFixer
にTrackedImageRotationFixer
スクリプトをドラッグ&ドロップをしてアタッチをします。RotationFixer
のInspector
にドラッグ&ドロップでも大丈夫です。
オブジェクトのInspector
がこのようになっていればOKです!Normal Flask Prefab
にも、Project
のAsset
からPrefabs
フォルダを開いて、最初に出現させるフラスコをドラッグ&ドロップをしてアタッチしてください。
ここで一旦File
→Build Settings
→Build And Run
でマーカーの上にモデルが表示するか確認してみてください。
UIの実装
制作では先に分子やマッチ棒のモデルを出現させてからUIの操作による動作を実装しましたが、記事での説明上、先にUIの実装から説明します。
ARマーカー読み取りスキャン枠
ARマーカーを探している時に出すフレームを作ります。フレームはIllustratorで画像書き出しまで行って用意しました。
Unity
で画像をシーンに配置する方法を説明します。Hirarchy
で右クリック→UI
→Image
を選択します。するとCanvas
の子にImage
が入った状態でHierarchy
に追加されます。Image
の名前をScanFrame
とします。
Inspector
のSource Image
にフレームの画像をドラッグ&ドロップで埋め込みます。その前にProject
のAssets
で右クリック→Create
→Folder
を選択して、画像を入れておくためのフォルダを用意しましょう。名前はImages
とします。
ARマーカーの画像素材がどこのフォルダにも入っていない状態でAssets
に存在している場合はこの段階で今作ったImage
フォルダにスキャン時のフレーム画像素材とともに入れておきましょう。
Source Image
の部分にフレームの画像をアタッチするのを忘れないでください。
Position
は画面の中央に来るようにし、Scale
は起動したときに適切な大きさになるように何度かBuild And Run
をして調整してください。
シーンはこのようになっていればOKです!
枠の非表示設定
ARマーカーを認識してフラスコが出現したらスキャン時のフレームは必要がないので、マーカーが検出された際にこのフレームを消す処理をスクリプトで作成します。
Project
のAssets
に先ほど作成したScripts
フォルダを選択してCreate
→C# Script
から新しいスクリプトを用意してください。名前はScanUIManager
としました。
ScanUIManager.cs
作成したスクリプトをダブルクリックしてコードを書き入れましょう。
コードはこちらです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
public class ScanUIManager : MonoBehaviour
{
[SerializeField] private Image scanUI; // スキャン枠のUIオブジェクトを設定
private ARTrackedImageManager trackedImageManager;
private void Awake()
{
// ARFoundation の ARTrackedImageManager を取得しマーカーの状態を監視する
trackedImageManager = FindObjectOfType<ARTrackedImageManager>();
}
private void OnEnable()
{
if (trackedImageManager != null)
{
trackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
}
}
private void OnDisable()
{
if (trackedImageManager != null)
{
trackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
}
}
private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
// マーカーが検出された場合スキャン枠を非表示にする
if (eventArgs.added.Count > 0 || eventArgs.updated.Count > 0)
{
HideScanUI();
}
else if (eventArgs.removed.Count > 0)
{
ShowScanUI();
}
}
public void ShowScanUI()
{
if (scanUI != null)
{
scanUI.enabled = (true); // Imageを表示
}
}
public void HideScanUI()
{
if (scanUI != null)
{
scanUI.enabled = (false); // Imageを非表示
}
}
}
コードがが書けたらUnity
に戻り、このスクリプトをGame Object
にアタッチします。
Hierarchy
で右クリック→Create Empty
を選択してシーンに空のゲームオブジェクトを用意します。名前をScanUIManagerObject
とします。
ScanUIManagerObject
にScanUIManager
スクリプトをドラッグ&ドロップをしてアタッチしましょう。
以下のようになっていればOKです!
Scan UI
と書いてあるところには作ったスキャン時に表示されるフレームの画像を入れてください。
スキャン前
スキャン後
ここまで確認できればOKです!
アイコンのUI
ここからはマーカーを読み取ってから少しだけ間をあけてアイコンを表示させる機能を実装します。このアイコンをタップするとそれぞれの3Dモデルが出現して次に進むというものです。
デモ画像
UIの配置
先ほどマーカー読み込み前に表示するフレームを作成したときにCanvas
というものが作られていたと思います。このCanvas
の子にアイコンを入れてあげます。
以下をもう一度ご覧いただき、酸素・水素・マッチ棒・ツールボックスのUIを今回実装するので4つImage
を用意してそれぞれ名前分かる形でを変更してください。
ARマーカー読み取りスキャン枠
Canvas
がこのようになっていれば大丈夫です。
それぞれのImage
を選択し、Inspector
に表示されているSource Image
に画像を入れましょう。Unityではほとんどの一般的な画像ファイルに対応しています。詳しくは以下のリンクからご覧ください。
位置調整
このUIたちは画面が回転しても右端に表示させたいので右端に固定されるように設定します。Hierarchy
でアイコンのImage
を選択し、Inspector
に四角いマスのようなものがあると思います。
画像のように選択をすると右端に寄るのでBuild And Run
をして確認してみてください。(できていなかったらすみません。もしその場合はPosX
を変更したりしてみてください)
Scale
ははみ出ないようにそして見にくくない程度でいい感じに調整してください。
アイコンの出現タイミング
ではスクリプトで、マーカーを認識した2秒後にアイコンのUIを表示させるようにします。
スクリプトを新規作成して名前をIconDisplayManager
とします。(スクリプトの新規作成方法を忘れてしまった方は記事を遡ってみてください。)
IconDisplayManager.cs
以下コードです。
using System.Collections;
using System.Collections.Generic;
using System.Data;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
public class IconDisplayManager : MonoBehaviour
{
[SerializeField] private GameObject[] icons; // 表示するUIアイコン(複数対応)
private ARTrackedImageManager trackedImageManager;
private bool[] isIconHidden; // アイコンが非表示になっているかを記録
private void Awake()
{
trackedImageManager = FindObjectOfType<ARTrackedImageManager>();
// 非表示状態の配列を初期化
isIconHidden = new bool[icons.Length];
// シーン開始時に全アイコンを非表示にする
foreach (GameObject icon in icons)
{
if (icon != null)
{
icon.SetActive(false); // 非表示に設定
}
}
}
private void OnEnable()
{
if (trackedImageManager != null)
{
trackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
}
}
private void OnDisable()
{
if (trackedImageManager != null)
{
trackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
}
}
private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
// ARマーカーが検出された場合
if (eventArgs.added.Count > 0 || eventArgs.updated.Count > 0)
{
StartCoroutine(DisplayIconsWithDelay());
SetupIconButtons();
}
}
private IEnumerator DisplayIconsWithDelay()
{
yield return new WaitForSeconds(2f); // 2秒待機
Debug.Log($"Icons array length: {icons.Length}"); // 配列の長さを表示
// すべてのアイコンを表示 (ただし、非表示にされたものは除外)
for (int i = 0; i < icons.Length; i++)
{
if (icons[i] != null && !isIconHidden[i]) // 非表示にされていないアイコンだけ表示
{
icons[i].SetActive(true);
Debug.Log($"Icon {i} is now active: {icons[i].name}");
}
else
{
Debug.LogWarning($"Icon {i} is null or hidden.");
}
}
}
}
これまでと同様にコードを書いたらUnity
に戻って空のゲームオブジェクトにスクリプトをアタッチします。名前をIconDisplayManagerObject
とします。
スクリプトがアタッチされたIconDisplayManagerObject
のInspector
を見るとIcons
という項目があるので、+
を押して箱を増やし、それぞれのモデルのプレハブをドラッグ&ドロップしてください。
私は水素→酸素→マッチ棒の順番にタップすることを想定して作っているのでIcons
にもこの順番に入れています。順番を誤ると押したUIと出てくるモデルが違ってしまうので注意してください。
デモ画像
ここまで出来たらBuild And Run
で確認してみてください。UIが表示されればOKです!
まとめ
ここまでお読みいただきありがとうございました。モデルの生成はうまく出来たでしょうか。私と同じ初心者の方にも分かるような説明を心掛けましたのでお役に立てていたら嬉しいです!
次回の第3回では生成したモデルにアニメーションをつけていきますので次回もよろしくお願いします。
おすすめ
第1回はこちら
第3回はこちら
第4回はこちら