67.5K Views
December 14, 22
スライド概要
2022/12/18に開催したAR Fukuokaのハンズオン資料
可視化技術や人間計測/空間計測技術を活用した問題解決に関する研究開発。 ARコンテンツ作成勉強会(tryAR)を主催。
MRTKではじめるPortal表現
⾃⼰紹介 ⽒名︓吉永崇 (Takashi Yoshinaga) 所属︓Steampunk Digital株式会社 / エンジニア 九州先端科学技術研究所 / 特別研究員 領域︓AR/VR応⽤に関するR&D。主に医療⽀援 ウェアラブル・モーションキャプチャ技術開発 Volumetric Video遠隔コミュニケーション
⾃⼰紹介 ⽒名︓吉永崇 (Takashi Yoshinaga) 趣味︓ARシステムのプロトタイピングと情報共有 @Taka_Yoshinaga Takashi Yoshinaga コミュニティ︓ARコンテンツ作成勉強会を主催
ARコンテンツ作成勉強会(略称:AR Fukuoka)の概要 [形式] AR/VRコンテンツの作り⽅や関連技術を主にハンズオン形式で体験。 [規模] 参加⼈数はコロナ前は約5~10名/回、現在は約10~20名/回。 [参加条件] AR/VRの開発に興味があれば初⼼者⼤歓迎。専⾨知識は不要。
Twitterと勉強会ページで情報を発信しています @AR_Fukuoka Googleで「AR勉強会」で検索
ハッシュタグ #AR_Fukuoka
今⽇のゴール https://youtu.be/rOwVVCeY_NU https://youtu.be/ps9HG3C5cpM ポータル表現を⽤いたAR/VRアプリを作るための基礎的な部分の実装とUnityEditorでの動作確認
演習素材のダウンロード https://github.com/TakashiYoshinaga/ARFukuoka/raw/main/PortalMRTK_20221218/Sample.zip
UnityHubを起動 Unity Hub
プロジェクトを作成 (1/7) ①Projects ②New project
プロジェクトを作成 (2/7) Editor Versionを開く
プロジェクトを作成 (3/7) 2020.3.xを選択 ※2020以降でも動作するが 本資料ではこのバージョンで解説
プロジェクトを作成 (4/7) 3Dを選択
プロジェクトを作成 (5/7) プロジェクト名 (例:PortalApp) 保存場所 (例:Desktop)
プロジェクトを作成 (6/7) Create project
プロジェクトを作成 (7/7)
UIの概要 Sceneタブ オブジェクトの一覧 オブジェクトの配置を行う プロジェクト内のフォルダやファイルの一覧
シーンを作成 File -> Save As...
シーンを作成 シーン名を設定(例︓PortalApp) Save
今回使⽤するアセットのインストール
移動先空間のアセット 本ハンズオンで利⽤するにはオブジェクトの位置の調整が必要なため、作者の許可のもと調整済みのパッケージを使⽤
移動先空間のアセットの導⼊ (1/5) Assets
移動先空間のアセットの導⼊ (2/5) ImportPackage Custom Package
移動先空間のアセットの導⼊ (3/5) [Sampleフォルダ内] RPGPP_LT.unitypackage Open
移動先空間のアセットの導⼊ (4/5) Import
移動先空間のアセットの導⼊ (5/5) RPGPP_LTが追加されていればOK
MRTKのダウンロード (1/3) https://github.com/microsoft/MixedRe alityToolkit-Unity/releases/tag/v2.8.3 ※今回はMRToolKit v2.8.3を使用
MRTKのダウンロード (2/3) スクロール
MRTKのダウンロード (3/3) Foundationをダウンロード
MRTKのインストール (1/5) Assets -> Import Package -> Custom Package
MRTKのインストール (2/5) [ダウンロードフォルダ内] MicrosoftMixedRealityToolkit.Unity.Foundation.2.8.3 Open
MRTKのインストール (3/5) Import
MRTKのインストール (4/5) Built-in Unity plugins ※OpenXRでもBuilt-inでもOKだが今回は後者
MRTKのインストール (4/5) Skip Setup Until Next Session ※実際の開発ではスキップせず対応させるプラットフォームに合わせて設定を行う
インストール完了
Next Step 移動先の世界をシーンに追加
移動先の世界を追加 (1/3) Assets->RPGPP_LT->Prefabs
移動先の世界を追加 (2/3) Hierarchyに ドラッグ&ドロップ World
移動先の世界を追加 (3/3) 移動先の空間が表⽰される
確認 Worldを開く ⼦要素として各オブジェクトが登録されている 本来は各⾃で各オブジェクトの配置や位置の調整が必要。 今回は事前に調整したプレハブでこの操作を省略。
ゲートを作成 (1/8) 何も選択されていない状態で Hierarchy内を右クリック
ゲートを作成 (2/8) 3D Object Quad
ゲートを作成 (3/8) 板が追加される ⾒づらいのでSceneの視点を調整 (次⾴)
ゲートを作成 (4/8) Quadをダブルクリック
ゲートを作成 (5/8) 板らしきもの (裏⾯のため不可視)
ゲートを作成 (6/8) ⽩い板 (1x1m) Altもしくはoption + ドラッグで視点を回転
ゲートを作成 (7/8) Quad Position X: 0 Y:1 Z:1 Rotation: X: 0 Y:0 Z:0 Scale: X:1.2 Y:2 Z:1
ゲートを作成 (8/8) Quad 名前をPortalGateに変更
動作確認 Play
動作確認 Main Cameraからの視点 (固定視点)
動作確認 再度Playをクリックして停⽌ 動作確認をしたときは編集に戻る前に必ず停⽌︕︕
空間作成はほぼ完了
MRTKの設定 (1/2) Mixed Reality -> Toolkit -> Add to Scene and Configure...
MRTKの設定 (2/2) MRTK関連のオブジェクトが追加される
Ctrl/command + Sで現状を保存
動作確認 Play
動作確認 [A][D]キー︓左右移動 [W][S]キー︓前後移動 [Q][E]キー︓上下移動 右クリック+ドラッグ︓回転 MRTKを使うことで仮想的に動きまわれる
動作確認 再度Playをクリックして停⽌ 動作確認をしたときは編集に戻る前に必ず停⽌︕︕
Next Step ゲートから移動先の世界を覗き込む
今⽇のメインテーマ ステンシルバッファを使ったマスク表現
ステンシルって︖
ステンシルのイメージ (現実世界) ⽳の空いたシートの上からスプレー シートの⽳の部分だけ⾊がつく 引用元: https://jeanssitesame.web.fc2.com/content/column/stencil.html
ステンシルのイメージ (デジタルな世界) 2D画⾯ ゲートのピクセルがシートの⽳に相当 Worldオブジェクトで上書き ⽳の部分だけ描画される
つまり 2D画⾯内で描画する/しない領域を制御するテクニック
シェーダーでの処理 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 ⽐較 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 ⽳の代わりに数値で描画領域を定義 ⽐較⽤の値を割り当て 同じ値(=1)のピクセルに描画
この後やること 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 ⽐較 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 マスク側の各ピクセルの値 マスクされる側の値 それぞれを設定
シェーダーを記述することで実現可能。 でも初学者には敷居が⾼い・・・ MRTKのStandard Shaderを使うと楽できる︕
マスク情報の作成 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 ⽐較 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 マスク側の各ピクセルの値 マスクされる側の値 それぞれを設定
マスク作成⼿順 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 常に 0 上書き 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 描画前の背景(デフォルト値:0) ゲートのピクセルの値を1に設定 マスク完成
これから操作する箇所の確認 PortalGate Default-Materialに注⽬ UnityではMaterial(に紐づいたシェーダー)で そのオブジェクトの⾒た⽬に関する設定を⾏う
これから操作する箇所の確認 Default MaterialはUnityが⽤意した デフォルトのビジュアル設定なので変更不可 設定変更可能なマテリアルを作ろう
マテリアルの作成 (1/7) Assets 空⽩を右クリック
マテリアルの作成 (2/7) Create Material
マテリアルの作成 (3/7) New Materialの名前をPortal Materialに変更
マテリアルの作成 (4/7) PortalGateにドラッグ&ドロップ Portal Material
マテリアルの作成 (5/7) PortalGate Portal Materialに差し代わっていればOK
マテリアルの作成 (6/7) Shaderのドロップダウンを開く
マテリアルの作成 (7/7) Mixed Reality Toolkit Standard
ステンシル関連の設定 (1/7) PortalGate マテリアルの詳細を開く
ステンシル関連の設定 (2/7) Rendering ModeをFadeに変更 Cull ModeをOffにして両⾯描画 Rendering ModeがOpaqueだとステンシルの設定に関係なく 空間的に奥にあるオブジェクトが描画されなくなるため要透過設定
ステンシル関連の設定 (3/7) Albedoの横の□をクリック Aを0(=透明)にする
ステンシル関連の設定 (4/7) Gateが消えるけど気にしない
ステンシル関連の設定 (5/7) Render Queue Overrideを1999 今回は他のオブジェクトのRender Queueが2000なので それより⼩さな値を設定し、まず先にマスク情報を作成。
ステンシル関連の設定 (6/7) Enable Stencil Testingをオン
ステンシル関連の設定 (7/7) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 0 常に 0 上書き 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ステンシル処理(以下の2項⽬)で 使⽤する値を1に設定 ステンシル処理(=描画)をする条件を Always(=常に処理)に設定 条件に合致した場合の処理を Replace(=書き換え)にして1を書き込む
マスクされる側のステンシルバッファの設定 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 ⽐較 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 マスク側の各ピクセルの値 マスクされる側の値 それぞれを設定
ステンシル関連の設定 (1/7) マスクされる側のオブジェクト 数が多いためオブジェクトごとに設定するのは⾯倒。 ただし、実は3つのマテリアルを使い回しているだけなので ⼤元となるマテリアルを変更してしまえばOK。
ステンシル関連の設定 (2/7) Assets->RPGPP_LT->Materials
ステンシル関連の設定 (3/7) ⼀気に設定するため、3つとも選択
ステンシル関連の設定 (4/7) Shaderのドロップダウンを開く
ステンシル関連の設定 (5/7) Mixed Reality Toolkit Standard
ステンシル関連の設定 (6/7) Enable Stencil Testingをオン
ステンシル関連の設定 (7/7) いまここ 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 ⽐較 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 ステンシル処理(以下の2項⽬)で 使⽤する値を1に設定 ステンシル処理(=描画)をする条件を Equal(等しい場合)に設定 条件に合致した場合の処理を Keepにして値の上書きはしない
結果 ゲートの向こうを覗けるようになった
Ctrl/command + Sで現状を保存
動作確認 Play
動作確認 動き回るとさらに奥⾏きを感じられる https://youtu.be/xuC07SmuZj0 [A][D]キー︓左右移動 [W][S]キー︓前後移動 [Q][E]キー︓上下移動 右クリック+ドラッグ︓回転
動作確認 再度Playで停⽌
Next Step 逆パターンも確認してみよう
ゲートの外側にWorldオブジェクトを表⽰ (1/2) RPGPP_LT->Materials ⼀気に設定するため、3つとも選択
ゲートの外側にWorldオブジェクトを表⽰ (2/2) いまここ 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 ⽐較 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 ゲートの値(=1)とNotEqualつまり 異なる値のピクセルに描画するよう設定
結果
表⽰を元に戻す (1/2) ⼀気に設定するため、3つとも選択
表⽰を元に戻す (2/2) Stencil ComparisonをEqualに戻す
Next Step ARモード VRモード カメラ(視点設定オブジェクト)とゲートの接触を検知し、各世界を⾏ったり来たりできるようにする
接触検知をゲートに追加 (1/5) PortalGate Mesh Colliderが 接触検知を担っている 接触判定にMesh Colliderを使うことも可能だが、 判定領域を勝⼿に設定されるので使い勝⼿が良くない
接触検知をゲートに追加 (2/5) Mesh Colliderを右クリックし、 RemoveComponent
接触検知をゲートに追加 (3/5) PortalGate Add Component
接触検知をゲートに追加 (4/5) Box Colliderで検索 Box Collider
接触検知をゲートに追加 (5/5) Box Colliderが追加される Is Triggerをオン
接触検知をカメラに追加 (1/6) MainCamera Add Component
接触検知をカメラに追加 (2/6) Sphere Colliderで検索 Sphere Collider
接触検知をカメラに追加 (3/6) Main Camera Sphere Colliderが追加される Is Triggerをオン Radiusを0.15
補⾜︓Radius=0.15の理由 ここでPortal Gateとの 接触判定をしたい 引用元:https://docs.unity3d.com/ja/2021.3/Manual/UnderstandingFrustum.html Computer Graphicsの世界では描画する奥⾏きの範囲を Near clipping planeとFar clipping planeで設定。 今回Near=0.1[m]と設定されているため、接触半径を少し⼤きめの0.15[m]とした。
接触検知をカメラに追加 (4/6) MainCamera Add Component
接触検知をカメラに追加 (5/6) Rigidで検索 Rigidbody
接触検知をカメラに追加 (6/6) Main Camera Rigidbodyが追加される Use Gravityをオフ Is Kinematicをオン
Ctrl/command + Sで現状を保存
スクリプトで接触検知を取得しよう
スクリプトを追加 (1/4) PortalGate Add Component
スクリプトを追加 (2/4) 検索をクリア PortalManager New script Create and Add
スクリプトを追加 (3/4) PortalGate Portal Managerが追加される
スクリプトを追加 (4/4) Assets スクリプトファイルとしても追加されている
スクリプトを追加 PortalManager.csをダブルクリックで開く
接触開始と終了を検知 public class PortalManager : MonoBehaviour { //最初に⼀回だけ呼ばれる void Start() { } //毎フレーム呼ばれる void Update() { } //他のオブジェクトが接触した時に呼ばれる void OnTriggerEnter(Collider other) { Debug.Log("Portal Entered"); } //接触終了時に呼ばれる void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); } } Lesson01
Ctrl/command + Sでソースを保存
動作確認の前におすすめレイアウト Scene Game SceneとGameウィンドウを横並びに表⽰
動作確認の前におすすめレイアウト Scene Game [Sceneでの視点調整] 回転: Alt/option + ドラッグ 移動: ← → ↑ ↓キー
動作確認 Play
動作確認 Gameウィンドウをクリックして選択 Consoleを開く [A][D]キー︓左右移動 [W][S]キー︓前後移動 [Q][E]キー︓上下移動 右クリック+ドラッグ︓回転
動作確認 PortalGateとカメラが接触開始・終了時にメッセージが表⽰
動作確認 再度Playをクリックして停⽌ Projectに戻す
Next Step ゲート通過後に表⽰を切り替える
ステンシル設定変更スクリプト (1/5)
using System.Collections.Generic;
using UnityEngine;
//ステンシルの⽐較条件を操作する際に必要
using UnityEngine.Rendering;
public class PortalManager : MonoBehaviour
{
//移動先の世界の3Dモデルをまとめたオブジェクト
[SerializeField] GameObject worldObject;
//上記オブジェクトのマテリアル(描画設定ファイル)を保持するために使⽤
List<Material> worldMaterials = new List<Material>();
//現在の表⽰モード
bool isARMode = true;
void Start()
{
}
/*以下略*/
Lesson02
ステンシル設定変更スクリプト (2/5)
//移動先の世界の3Dモデルをまとめたオブジェクト
[SerializeField] GameObject worldObject;
//上記オブジェクトのマテリアル(描画設定ファイル)を保持するために使⽤
List<Material> worldMaterials = new List<Material>();
void Start()
{
//移動先の3DモデルのRendererを取得
Renderer[] renderers =
worldObject.GetComponentsInChildren<Renderer>();
foreach (Renderer renderer in renderers)
{
//マテリアルを取得
Material material = renderer.sharedMaterial;
//既に他のオブジェクトから取得したマテリアルでなければリストに追加
if(!worldMaterials.Contains(material)){
worldMaterials.Add(material);
}
Lesson03
}
}
ステンシル設定変更スクリプト (3/5) //他のオブジェクトが接触した時に呼ばれる void OnTriggerEnter(Collider other) { Debug.Log("Portal Entered"); } //接触終了時に呼ばれる void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); } //引数で受け取った設定でステンシル設定を変更 void SetStencilComparison(CompareFunction mode){ //マテリアルを1つずつ取得してステンシル処理の⽐較条件を変更 foreach(Material material in worldMaterials){ material.SetInt("_StencilComparison",(int)mode); } } Lesson04
ステンシル設定変更スクリプト (4/5) //他のオブジェクトが接触した時に呼ばれる void OnTriggerEnter(Collider other) { Debug.Log("Portal Entered"); } //接触終了時に呼ばれる void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); /*ここにコード追加*/ } //引数で受け取った設定でステンシル設定を変更 void SetStencilComparison(CompareFunction mode){ //マテリアルを1つずつ取得してステンシル処理の⽐較条件を変更 foreach(Material material in worldMaterials){ material.SetInt("_StencilComparison",(int)mode); } }
ステンシル設定変更スクリプト (5/5) void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); if(isARMode){//現在ARモード︓VRモードにしてゲートの外側のみを表⽰ SetStencilComparison(CompareFunction.NotEqual); isARMode=false; } else{//現在VRモード︓ARモードにしてゲートの内側のみを表⽰ SetStencilComparison(CompareFunction.Equal); isARMode=true; } } //アプリ終了時にEditor内の表⽰をARモードに戻しておく void OnDestroy() { SetStencilComparison(CompareFunction.Equal); } Lesson05
Ctrl/command + Sでソースを保存
Worldオブジェクトをスクリプトに登録 (1/2) PortalGate Worldオブジェクトを割り当てる(次⾴)
Worldオブジェクトをスクリプトに登録 (2/2) Worldに注⽬(クリックしない) World Object横の ボックスにドラッグ&ドロップ
動作確認 Play Gameウィンドウをクリックして選択
動作確認 https://youtu.be/o7JWDX7Sbds [A][D]キー︓左右移動 [W][S]キー︓前後移動 [Q][E]キー︓上下移動 右クリック+ドラッグ︓回転
動作確認 再度Playをクリックして停⽌
問題点 接触前 接触中 ARモード 接触後 VRモード ⼀瞬、移動先の 世界が消える
原因 NearPlane 引用元:https://docs.unity3d.com/ja/2021.3/Manual/UnderstandingFrustum.html Computer Graphicsの世界では描画する奥⾏きの範囲を Near clipping planeとFar clipping planeで設定。 Farより奥やNearよりも⼿前のオブジェクトは描画されない︕ PortalGateがNearPlaneの内側
解決のためのアイディア Worldオブジェクト表⽰ カメラのコライダーが 接触した瞬間
没にした実装 //他のオブジェクトが接触した時に呼ばれる void OnTriggerEnter(Collider other) { Debug.Log("Portal Entered"); } //接触終了時に呼ばれる void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); モード切り替えのコードを OnTriggerEnterに移動 if(isARMode){//現在ARモード︓ VRモードにしてゲートの外側のみを表⽰ SetStencilComparison(CompareFunction.NotEqual); isARMode=false; } else{//現在VRモード︓ ARモードにしてゲートの内側のみを表⽰ SetStencilComparison(CompareFunction.Equal); isARMode=true; } }
問題点 (VR -> AR) ゲートをくぐっていないのに 元いた世界が消えてしまう 視線⽅向 後ろ向きに進む
解決⽅法(暫定) 前進 後進 Collider接触中はWorldオブジェクトを表⽰
実装 //他のオブジェクトが接触した時に呼ばれる void OnTriggerEnter(Collider other) { Debug.Log("Portal Entered"); //Alwaysを指定して常に表⽰ SetStencilComparison(CompareFunction.Always); } //接触終了時に呼ばれる void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); if(isARMode){//現在ARモード︓ VRモードにしてゲートの外側のみを表⽰ SetStencilComparison(CompareFunction.NotEqual); } isARMode=false; else{//現在VRモード︓ ARモードにしてゲートの内側のみを表⽰ SetStencilComparison(CompareFunction.Equal); /*以下略*/ Lesson06
Ctrl/command + Sでソースを保存
動作確認 Play Gameウィンドウをクリックして選択
動作確認 [A][D]キー︓左右移動 [W][S]キー︓前後移動 [Q][E]キー︓上下移動 右クリック+ドラッグ︓回転 https://youtu.be/CfGrS9OZZS4 AR->VRの前進とVR->ARの後進は⾃然に切り替わる
動作確認 再度Playで⽌める
更なる問題点 ARモード VRモード 後進 視線⽅向 ゲートをくぐっていないのに これから⼊る世界が⾒えてしまう 同じAR->VRの移動でもWorldオブジェクトを表⽰したい場合と⾮表⽰にしたい場合がある
結局どう解決するのか︖ (例︓AR->VRの場合) Gate接触時にWorldオブジェクトは表⽰。 ただし、Gateの表・裏のどちらか半分を表⽰ つまり Gate接触時は現在いる世界は残しつつ、 Gateの向こうはこれから⾏く世界を表⽰。
Gate接触時の状態のパターン VRモード ARモード カメラの位置がGateの表(図中の左) カメラの位置がGateの裏(図中の右)
シェーダーを操作することでクリッピングも可能。 ただし結構⾯倒なので今回は MRTKが提供するスクリプトに頼る
Clipping Primitive 今回はClipping Planeを使⽤ 引用元: https://learn.microsoft.com/ja-jp/windows/mixed-reality/mrtkunity/mrtk2/features/rendering/clipping-primitive?view=mrtkunity-2022-05
Clipping Planeの追加 (1/9) 右クリック
Clipping Planeの追加 (2/9) 3D Object Plane
Clipping Planeの追加 (3/9) ⼤きな板が表⽰される (10x10m相当)
Clipping Planeの追加 (4/9) 名前をClippingPlaneに変更 Plane
Clipping Planeの追加 (5/9) Position: 0 0 1 Rotation:-90 0 0 Scale: 全て0.05 位置と⾓度はPortalGateに⼀致するように設定。 サイズは⽬視での確認⽤なのでなんでもOK。
Clipping Planeの追加 (6/9) Clipping Plane Add Component
Clipping Planeの追加 (7/9) Clippingで検索 ClippingPlane
Clipping Planeの追加 (8/9) ClippingPlaneが追加される
Clipping Planeの追加 (9/9) Renderers: クリッピングしたいオブジェクトを ここで登録。 今回はオブジェクトが多いので 後ほどスクリプトから設定。 Clipping Side: このPlaneに対して表と裏の どちらをクリッピングするか設定
スクリプトを⽤いたClippingPlane設定 (1/7) PortalManager.csを開く
スクリプトを⽤いたClippingPlane設定 (2/7)
//ステンシルの⽐較条件を操作する際に必要
using UnityEngine.Rendering;
//ClippingPlaneをこのスクリプトで操作するためのMRTKの関連機能を読み込み
using Microsoft.MixedReality.Toolkit.Utilities;
public class PortalManager : MonoBehaviour
{
//Clipping Planeスクリプト
[SerializeField] ClippingPlane clippingPlane;
//移動先の世界の3Dモデルをまとめたオブジェクト
[SerializeField] GameObject worldObject;
//上記オブジェクトのマテリアル(描画設定ファイル)を保持するために使⽤
List<Material> worldMaterials = new List<Material>();
//現在の表⽰モード
bool _isARMode=true;
void Start()
{
//ここでclippingPlaneにworldObject内のオブジェクトを登録
}
Lesson07
スクリプトを⽤いたClippingPlane設定 (3/7)
void Start()
{
//移動先の3DモデルのRendererを取得
Renderer[] renderers =
worldObject.GetComponentsInChildren<Renderer>();
foreach (Renderer renderer in renderers)
{
//clippingPlaneにWorld内のオブジェクトを登録 (必ず先に実⾏すること)
clippingPlane.AddRenderer(renderer);
//マテリアルを取得
Material material = renderer.sharedMaterial;
//既に他のオブジェクトから取得したマテリアルでなければリストに追加
if(!worldMaterials.Contains(material)){
worldMaterials.Add(material);
}
}
}
//デフォルトはクリッピングをオフ
clippingPlane.enabled=false;
Lesson08
スクリプトを⽤いたClippingPlane設定 (4/7) void OnTriggerEnter(Collider other) { Debug.Log("Portal Entered"); SetStencilComparison(CompareFunction.Always); //ゲートに接触したらクリッピング処理をオン clippingPlane.enabled=true; } void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); if(isARMode){//現在ARモード︓ゲートの外側のみを表⽰し、VRモードにする SetStencilComparison(CompareFunction.NotEqual); isARMode=false; } else{//現在ARモード︓ゲートの外側のみを表⽰し、VRモードにする SetStencilComparison(CompareFunction.Equal); isARMode=true; } //ゲートから離れたらクリッピング処理をオフ Lesson09 clippingPlane.enabled=false; }
Ctrl/command + Sでソースを保存
スクリプトを⽤いたClippingPlane設定 (5/7) PortalGate PortalManagerのClippingPlaneに Hierarchy内のClippingPlaneオブジェクトを登録
スクリプトを⽤いたClippingPlane設定 (6/7) Clipping Planeに注⽬ (まだクリックしない) None (Clipping Plane)に ドラッグ&ドロップ
スクリプトを⽤いたClippingPlane設定 (7/7) ClippingPlane Clipping SidをOutside (ゲートの向こう側を表⽰)
動作確認 Play
動作確認 接触前 接触中 [A][D]キー︓左右移動 [W][S]キー︓前後移動 [Q][E]キー︓上下移動 右クリック+ドラッグ︓回転 この進⾏⽅向に関して クリッピングの切り替え完了 接触後
動作確認 再度Playで停⽌
状況に応じたクリッピング処理の実装 (1/3) VRモード ARモード カメラの位置がGateの表(図中の左) カメラの位置がGateの裏(図中の右)
状況に応じたクリッピング処理の実装 (2/3) public class PortalManager : MonoBehaviour { //Clipping Planeスクリプト [SerializeField] ClippingPlane clippingPlane; //移動先の世界の3Dモデルをまとめたオブジェクト [SerializeField] GameObject worldObject; //上記オブジェクトのマテリアル(描画設定ファイル)を保持するために使⽤ List<Material> worldMaterials = new List<Material>(); //現在の表⽰モード bool isARMode = true; //ゲートに表と裏どちらから⼊るか float enteringSide; void Start() { /*省略*/ } Lesson10
状況に応じたクリッピング処理の実装 (3/3)
void OnTriggerEnter(Collider other) {
/* 省略 */
clippingPlane.enabled=true;
//カメラの座標をゲートを原点にしたローカル座標に変換
Vector3 localPos=
transform.InverseTransformPoint(Camera.main.transform.position);
//表か裏かを+-で表現
enteringSide=Mathf.Sign(localPos.z);
//条件に応じたクリッピング設定
if((isARMode&&enteringSide<0)||(!isARMode&&enteringSide>0)){
clippingPlane.ClippingSide=ClippingPrimitive.Side.Outside;
}
else{
clippingPlane.ClippingSide=ClippingPrimitive.Side.Inside;
}
//Clipping情報更新のフラグをOn
clippingPlane.IsDirty=true;
Lesson11
}
Ctrl/command + Sでソースを保存
動作確認 Play
動作確認 https://youtu.be/fZyW3dVI_1Q
動作確認 再度Playで停⽌
Clipping Planeの不可視化 Clipping Plane Mesh Rendererをオフ
問題点 接触前 接触中 接触後 [A][D]キー︓左右移動 [W][S]キー︓前後移動 [Q][E]キー︓上下移動 右クリック+ドラッグ︓回転 ⼊った⾯と同じ⽅向から出ても切り替わってしまう
Enter/Exitの⽅向を考慮 void OnTriggerExit(Collider other) { Debug.Log("Portal Exited"); //カメラの座標をゲートを原点にしたローカル座標に変換 Vector3 localPos= transform.InverseTransformPoint(Camera.main.transform.position); //表か裏かを+-で表現 float exitingSide=Mathf.Sign(localPos.z); } if(isARMode){//現在ARモード︓ゲートの外側のみを表⽰し、VRモードにする SetStencilComparison(CompareFunction.NotEqual); isARMode=false; } else{//現在ARモード︓ゲートの外側のみを表⽰し、VRモードにする SetStencilComparison(CompareFunction.Equal); isARMode=true; } //ゲートから離れたらクリッピング処理をオフ clippingPlane.enabled=false; Lesson12
Enter/Exitの⽅向を考慮 if(isARMode){//現在ARモード︓ if(exitingSide!=enteringSide){ //⼊った⽅向と逆から出たならVRモードに切り替え SetStencilComparison(CompareFunction.NotEqual); isARMode=false; } else{ SetStencilComparison(CompareFunction.Equal); } } else{//現在VRモード︓ if(exitingSide!=enteringSide){ //⼊った⽅向と逆から出たならARモードに切り替え SetStencilComparison(CompareFunction.Equal); isARMode=true; } else{ SetStencilComparison(CompareFunction.NotEqual); Lesson13 } }
動作確認 Play
動作確認
動作確認 再度Playをクリックして停⽌
仕上げ︓Diagnosticsの⾮表⽰化 MixedRealityToolKit Cloneをクリック
仕上げ︓Diagnosticsの⾮表⽰化 Cloneをクリック
仕上げ︓Diagnosticsの⾮表⽰化 Diagnostics Enable Diagnostics Systemをオフ
動作確認 https://youtu.be/ps9HG3C5cpM
完成︕
完成版 https://github.com/TakashiYoshinaga/PortalExpression-Sample-for-Unity
参考 AR Portal Tutorial with Unity - Portal Mask Implementation - Part 4 https://www.youtube.com/watch?v=OBb4iUiXaRA Building a holographic card with the MRTK Standard Shader https://smeenk.com/building-a-holographic-card-with-the-mrtkstandard-shader/ Clipping primitive ̶ MRTK2 https://learn.microsoft.com/en-us/windows/mixed-reality/mrtkunity/mrtk2/features/rendering/clipping-primitive QuestパススルーUnityサンプルプロジェクト (ポータルなし) https://github.com/TakashiYoshinaga/UnityVRStarter
おまけ ARCore/ARKitアプリとしてビルド
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
表⽰オブジェクトをまとめる (1/3) 下記を選択 ・World ・PortalGate ・ClippingPlane
表⽰オブジェクトをまとめる (2/3) MixedRealitySceneContentにドラッグ&ドロップ
表⽰オブジェクトをまとめる (3/3) MixedRealityScene Contentの⼦要素 になっていればOK
⾼さの調整 (1/2) MixedRealitySceneContent
⾼さの調整 (2/2) PosittionのY=-1.5 スマホARの原点はアプリ起動時のスマホの位置。 地⾯はスマホの1.5m下と仮定して全体的に下に下げる。 ちなみにQuestの場合、原点の⾼さ=地⾯なので調整不要。
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
AR Foundationのインストール (1/4) Window ->PackageManager
AR Foundationのインストール (2/4) Packagesのドロップダウンから Unity Registryを選択
AR Foundationのインストール (3/4) ARで検索
AR Foundationのインストール (4/4) AR Foundation Install
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
ARCore and/or ARKitのインストール (1/3) ARCore XR Plugin Install
ARCore and/or ARKitのインストール (2/3) ARKitXR Plugin Install
ARCore and/or ARKitのインストール (3/3) 閉じる
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
ビルドプラットフォームの選択 (1/3) File ->Build Settings...
ビルドプラットフォームの選択 (2/3) AndroidもしくはiOSを選択 Switch Platform
ビルドプラットフォームの選択 (3/3) MRTK Project Configuratorが表⽰ された場合は閉じる。(この作業は後程)
ビルド設定 Player Setting
ビルド設定 for Android (1/6) Other Settings
ビルド設定 for Android (2/6) Vulkan - で削除
ビルド設定 for Android (3/6) Minimum API Level Android 7.0以上を選択
ビルド設定 for Android (4/6) Configuration Scripting BackendをIL2CPP
ビルド設定 for Android (5/6) Target Architecturesで ARM64にチェックを⼊れる
ビルド設定 for Android (6/6) 閉じる
ビルド設定 for iOS (1/3) Other Settings
ビルド設定 for iOS (2/3) Configuration Camera Usage Descriptionに下記を追加 Required for AR
ビルド設定 for iOS (3/3) 閉じる
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
MRTKの設定 Mixed Reality -> Toolkit -> Utilities -> Configure Project for MRTK
MRTKの設定 Built-in Unity Plugin
MRTKの設定 Show Settings
MRTKの設定 (iOSの場合) iOS ARKitのチェックをオン
MRTKの設定 (Androidの場合) Android ARCoreのチェックをオン
MRTKの設定 (共通) Project Settingsを閉じる
MRTKの設定 Next
MRTKの設定 Apply
MRTKの設定 Next
MRTKの設定 Skip This Step
MRTKの設定 Done
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
MRTKプロファイルの設定 MixedRealityToolkit Camera
MRTKプロファイルの設定 DefaultMixedRealityCameraProfileをClone
MRTKプロファイルの設定 Clone
MRTKプロファイルの設定 Add Camera Setting Provider
MRTKプロファイルの設定 New data provider4を開く
MRTKプロファイルの設定 Typeの横のドロップダウン
MRTKプロファイルの設定 Type ->Microsoft中略.UnityAR ->UnityARCameraSettings
MRTKプロファイルの設定 ⻘空が消えてしまう
MRTKプロファイルの設定 Camera Display Settingsを開く
MRTKプロファイルの設定 Far Clipを1000に変更
MRTKプロファイルの設定 Spatial Awareness
MRTKプロファイルの設定 Enable Spatial Awareness Systemをオフ
MRTKプロファイルの設定 Mixed Reality -> ToolKit -> Utilities ->UnityAR ->Update SettingDefines
p オブジェクトの位置調整 p ARFoundationのインストール p ARCore/ARKitのインストール p ビルドセッティング p MRTKのセッティング p ARでスマホカメラを使うセッティング p ビルド
ビルド File -> Build Settings...
ビルド Buildをクリック Android: apkファイルができます iOS: xcodeプロジェクトができます
インストール⽅法等はネットに掲載されていますのでご確認ください