
こんにちは!金融ソリューション事業部WEB3グループの山下です。
本記事は「SpatialComputing入門」シリーズのPart3となります。
前回のPart1およびPart2にて紹介したWindowやVolumeとは別の、現実世界とインタラクションが可能なImmersiveViewを用いて現実世界の平面に対してオブジェクトを配置します。
「SpatialComputing入門」シリーズ:
- Part1:3Dオブジェクトを生成して空間に配置する
- Part2:3DオブジェクトにGestureを追加する
- Part3:現実空間の平面に3Dオブジェクトを配置する
WorldTrackingについて
実装に入る前に、今回のデモ実装で利用するWorldTracking機能、およびARKitのプライバシーについて解説します。
画像参考:Meet ARKit for spatial computing
iOSなどと異なりvisionOSでは、カメラや加速度センサーのデータがアプリに対して直接開放されていません。
その代わり、ARKit daemonsによって適切に処理された結果をアプリが利用できるようになっています。
また基本的に、ARKitを用いた空間認知やハンドトラッキングにアクセスする為にはユーザーのアクセス許可が必要になります。
画像参考:Meet ARKit for spatial computing
ただ今回用いるAnchorEntityに関してはWorldTrackingのデータを用いており、ユーザーによるアクセス許可は不要になります。
動作環境
実装手順
- App.swiftにImmersiveViewを追加
- ContentViewにButtonを追加
- ImmersiveView.swiftを新規作成
- VisionPro動作確認
1. App.swiftにImmersiveViewを追加
Part2との変更点のみ記載します。
ModelEntityTestApp.swiftのbody内に、以下を追記します。
ImmersiveSpace(id: "ImmersiveSpace") { ImmersiveView() }
Part1でVolumeを追加した際と同様、ImmersiveSpaceでもidを指定して呼びだす際に利用します。
2. ContentViewにButtonを追加
ContentView.swiftのbody内に、ImmersiveViewを開く為のButtonを追加します。
Button(action: { showImmersiveSpace = true }) { Text("Show ImmersiveSpace") }
今回は、既にVolumeを開く為のButton Viewがあった為、VStack内で上記Buttonを追加しました。
3. ImmersiveView.swiftを新規作成
新規にImmersiveView.swiftを作成して、以下を記載します。
import SwiftUI import RealityKit import RealityKitContent struct ImmersiveView: View { @State private var contentReference: RealityViewContent? @Environment(\.dismiss) private var dismiss var body: some View { RealityView { content in self.contentReference = content setupTimer() } .ignoresSafeArea() } private func setupTimer() { Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in DispatchQueue.main.async { addAnchor() } } } @MainActor private func addAnchor() { guard let content = contentReference else { return } let anchorEntity = AnchorEntity(plane: .vertical) let model = ModelEntity( mesh: .generateCone(height: 0.01, radius: 0.01), materials: [SimpleMaterial(color: .yellow, isMetallic: true)] ) anchorEntity.addChild(model) content.add(anchorEntity) removeAnchor(after: 3, anchor: anchorEntity) } private func removeAnchor(after seconds: Double, anchor: AnchorEntity) { DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { anchor.removeFromParent() } } } #Preview { ImmersiveView() }
概要は、以下のとおりです。
- 変数宣言
@State private var contentReference: RealityViewContent?
- RealityViewのcontent参照を保持するための変数
- 外部に定義した関数でRealityViewのcontentにアクセスする際に利用します
@Environment(\.dismiss) private var dismiss
- ビューを閉じるための環境変数
- addAncor関数
AnchorEntity(plane: .vertical)
で垂直な面を検出して、アンカーを取得します- RealityViewでは、画面に描画されるUIやEntityの変更はメインスレッドで行う必要があります。その為、addAnchor関数に@MainActorを付与しています
- setupTimer関数
- Timerクラスを用いて、0.01秒毎にaddAncor()を呼び出します
- VisionProのフレームレートは90FPXの為、描画回数の限界を試すために約0.01を指定してみました
- DispatchQueue.main.asyncを用いることで、メインスレッドで行う必要のあるAddAnchorの呼び出しを、非同期にQuereとして追加しています
- Timerクラスを用いて、0.01秒毎にaddAncor()を呼び出します
- removeAnchor関数
- 引数で受け取ったAnchorの削除処理を、同じく引数で受け取った秒数を経過後に実行します
- DispatchQueue.main.asyncAfterにより、非同期で削除処理を予約しています
4. VisionPro動作確認
実行すると、以下のような挙動になります。
ヘッドセットの動きに合わせて、AnchorEntity(plane: .vertical)
で壁面が自動的に検出され、3Dオブジェクトが配置されることを確認できました。また天井には描画がされないことからも、正しく垂直面を取得できていることが分かります。
終わりに
今回はSpatialComputingの入門編Part3として、現実空間の平面に3Dオブジェクトを配置しました。
今回はシンプルなWorldTrackingを利用しましたが、ARKitを活用することでより詳細な空間の認知やハンドトラッキングを用いることも可能です。
SwiftUI, RealityKit, ARKitといった馴染みのある開発フレームワークを利用することで、驚くほど簡単にVisionProおよびvisionOSのパワフルな機能にアクセス可能です。
今週のWWDC2024でもvisionOS 2が発表されていますので、引き続き今後のvisionOSの進化にも注目していきたいと思います。
現在、電通総研はweb3領域のグループ横断組織を立ち上げ、web3およびメタバース領域のR&Dを行っております(カテゴリー「3DCG」の記事はこちら)。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください!
私たちは一緒に働いてくれる仲間を募集しています!
参考文献
執筆:@yamashita.yuki、レビュー:@takami.yusuke
(Shodoで執筆されました)