SDK
- iOS 13.4+
- Xcode 11.4+
フレームワーク
- ARKit
概要
iPad OS 13.4以降を搭載する第4世代のiPad Proで、ARKitは、LiDARスキャナを使って周囲の物理的形状のポリゴンモデルを作成します。LiDARスキャナは、ユーザーの前にある広い範囲の深度情報を瞬時に取得します。そのため、ARKitは、ユーザーが動かなくてもリアルワールドの形状を測定できます。ARKitは深度情報を連続する頂点(vertex)に変換し、これらの頂点が相互に接続されてメッシュを形成します。情報を区分するために、ARKitは複数のアンカーを作成し、個々のアンカーにメッシュの一意の部分が割り当てられます。これらのメッシュアンカーの集合体が、ユーザーを取り巻くリアルワールドのシーンを表します。
メッシュを利用すると、次のことが可能になります。
-
現実世界の面上の位置をより正確に特定する。
-
ARKitが認識できる現実世界のオブジェクトを分類(classify)する。
-
アプリの仮想コンテンツを、その前にある現実世界のオブジェクトで遮蔽する。
-
仮想コンテンツと実環境の間のリアルな相互作用を実現する(たとえば、仮想のボールを現実世界の壁にバウンドさせた後、ボールが物理法則に従うようにする)
ここで紹介するサンプルアプリではRealityKitを使ってAR体験を提供します。下の図は、ユーザーがこのアプリを実行し、デバイスを現実世界の椅子に向けたときにRealityKitがARKitからのリアルワールド情報を利用して、デバッグ表示を行う方法を示しています。

周囲の物理的形状の視覚化
シーンメッシュを有効にするために、サンプルではworld-configurationのscene
(英語)プロパティをメッシュオプションの1つに設定します。
arView.automaticallyConfigureSession = false
let configuration = ARWorldTrackingConfiguration()
configuration.sceneReconstruction = .meshWithClassification
サンプルでは、RealityKitのARView
(英語)を使ってグラフィックスをレンダリングします。実行時にメッシュを視覚化するために、ARView
(英語)にはscene
(英語)デバッグオプションがあります。
arView.debugOptions.insert(.showSceneUnderstanding)
メモ
サンプルでは、メッシュ機能を実証するためにのみメッシュ表示を有効にしています。同様に、メッシュ表示は通常、デバック目的でのみ有効にします。
AR体験を開始するにあたって、サンプルではメインビューコントローラのview
コールバックを使ってアプリの初回起動時のセッションを設定して実行します。
arView.session.run(configuration)
平面検出機能の追加
アプリのシーン再構築で平面検出機能が有効化されると、ARKitはメッシュの作成時にその情報を考慮します。LiDARスキャナが現実世界の面上で生成したメッシュがやや歪んでいる場合も、ARKitはその面上で平面と判断した部分は、そのメッシュを平坦化します。
平面検出がメッシュにもたらす変化を確認できるように、このアプリには切り替えボタンが表示されます。サンプルでは、ボタンハンドラで平面検出設定を調整し、変更を反映させるためセッションを再起動します。
func togglePlaneDetectionButtonPressed(_ button: UIButton) {
guard let configuration = arView.session.configuration as? ARWorldTrackingConfiguration else {
return
}
if configuration.planeDetection == [] {
configuration.planeDetection = [.horizontal, .vertical]
button.setTitle("Stop Plane Detection", for: [])
} else {
configuration.planeDetection = []
button.setTitle("Start Plane Detection", for: [])
}
arView.session.run(configuration)
}
オブジェクトの面上の位置の特定
メッシュを使用して面上の位置を取得すると、かつてないほど優れた正確性を実現できます。レイキャストは、メッシュを考慮に入れることで、平坦でない面や特徴がほとんどない面(白い壁など)との交差する位置を得られます。
正確なレイキャストの結果を確認できるように、このアプリは、ユーザーが画面をタップしたときに光線(レイ)をキャストします。サンプルでは、対象とするターゲットとしてARRaycast
(英語)を、向きのオプションとしてARRaycast
(英語)を指定します。これらはメッシュ化された実世界のオブジェクト上の位置を取得するために必要です。
let tapLocation = sender.location(in: arView)
if let result = arView.raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .any).first {
// ...

ユーザーのレイキャストの結果が返されると、このアプリは交点に小さな球面を配置して視覚的フィードバックを提供します。
let resultAnchor = AnchorEntity(world: result.worldTransform)
resultAnchor.addChild(sphere(radius: 0.01, color: .lightGray))
arView.scene.addAnchor(resultAnchor, removeAfter: 3)

実世界のオブジェクトの分類
ARKitが備える分類機能は、リアルワールドのメッシュモデルを分析して、特定の実世界のオブジェクトを識別します。ARKitはメッシュ内部にある床面、テーブル、椅子、窓、天井を識別できます。完全なリストについては、ARMesh
(英語)を参照してください。
ユーザーが画面をタップし、レイキャストがメッシュ化された実世界のオブジェクトと交差すると、メッシュの分類結果を示すテキストが表示されます。

ARView
(英語)のautomatically
(英語)プロパティがtrue
に設定されている場合、RealityKitはデフォルトで分類を無効化します。これは、分類がオクルージョンや物理演算で不要なためです。メッシュ分類を有効にするために、サンプルではscene
(英語)プロパティをmesh
(英語)に設定してデフォルト値をオーバーライドします。
arView.automaticallyConfigureSession = false
let configuration = ARWorldTrackingConfiguration()
configuration.sceneReconstruction = .meshWithClassification
このアプリはメッシュとの交点部分の分類結果を取得しようとします。
nearbyFaceWithClassification(to: result.worldTransform.position) { (centerOfFace, classification) in
// ...
メッシュの3つの頂点ごとに、フェイスと呼ばれる三角形が形成されます。ARKitはフェイスごとに分類結果を割り当てるため、サンプルではメッシュ内を検索して交点近くのフェイスを見つけます。そのフェイスに分類結果が存在する場合、このアプリは画面上にその分類結果を表示します。このルーチンでは広範囲にわたる処理が必要になるため、サンプルの処理は、レンダラが停止状態にならないように非同期で実行されます。
DispatchQueue.global().async {
for anchor in meshAnchors {
for index in 0..<anchor.geometry.faces.count {
// Get the center of the face so that we can compare it to the given location.
let geometricCenterOfFace = anchor.geometry.centerOf(faceWithIndex: index)
// Convert the face's center to world coordinates.
var centerLocalTransform = matrix_identity_float4x4
centerLocalTransform.columns.3 = SIMD4<Float>(geometricCenterOfFace.0, geometricCenterOfFace.1, geometricCenterOfFace.2, 1)
let centerWorldPosition = (anchor.transform * centerLocalTransform).position
// We're interested in a classification that is sufficiently close to the given location––within 5 cm.
let distanceToFace = distance(centerWorldPosition, location)
if distanceToFace <= 0.05 {
// Get the semantic classification of the face and finish the search.
let classification: ARMeshClassification = anchor.geometry.classificationOf(faceWithIndex: index)
completionBlock(centerWorldPosition, classification)
return
}
}
}
分類結果を入手したら、サンプルではその分類結果を表示する3Dテキストを作成します。
let textEntity = self.model(for: classification)
メッシュによってテキストが部分的に隠れないように、サンプルではテキストの位置をわずかにオフセットして、読みやすさを確保しています。光線の負方向にオフセットを計算することで、テキストをカメラ方向(面から遠ざかる方向)に少しだけ効果的に移動させます。
let rayDirection = normalize(result.worldTransform.position - self.arView.cameraTransform.translation)
let textPositionInWorldCoordinates = result.worldTransform.position - (rayDirection * 0.1)
テキストが画面上に常に同じサイズで表示されるように、カメラからのテキストの距離に基づいて縮尺を適用します。
let raycastDistance = distance(result.worldTransform.position, self.arView.cameraTransform.translation)
textEntity.scale = .one * raycastDistance
テキストを表示するために、調整済みの交点にあるアンカー付きのエンティティ内にテキストを配置します(向きはカメラ方向)。
var resultWithCameraOrientation = self.arView.cameraTransform
resultWithCameraOrientation.translation = textPositionInWorldCoordinates
let textAnchor = AnchorEntity(world: resultWithCameraOrientation.matrix)
textAnchor.addChild(textEntity)
self.arView.scene.addAnchor(textAnchor, removeAfter: 3)
分類結果の取得元であるフェイスの頂点の位置を視覚化するために、サンプルでは現実世界の頂点の位置に小さな球面を作成します。
if let centerOfFace = centerOfFace {
let faceAnchor = AnchorEntity(world: centerOfFace)
faceAnchor.addChild(self.sphere(radius: 0.01, color: classification.color))
self.arView.scene.addAnchor(faceAnchor, removeAfter: 3)
}

メッシュによる仮想コンテンツのオクルージョン
オクルージョンは、リアルワールドに存在する物体が、カメラの視点からアプリの仮想コンテンツの一部分を覆い隠す機能です。この錯覚を実現するために、RealityKitは、ユーザーに表示される仮想コンテンツの手前にあるすべてのメッシュをチェックし、それらのメッシュによって見えなくなっている仮想コンテンツの部分の描画を省略します。サンプルでは、オクルージョンを有効にするためにocclusion
(英語)オプションを環境のscene
(英語)プロパティに追加します。
arView.environment.sceneUnderstanding.options.insert(.occlusion)
実行時に、このアプリはメッシュ化されたリアルワールドの物体によって隠された仮想テキストの部分の描画を省略します。

物理演算を利用した実世界のオブジェクトとの相互作用
シーンメッシュでは、仮想コンテンツと実環境の間のリアルな相互作用を実現できます。これは、RealityKitの物理演算エンジンがメッシュを利用してリアルワールドの正確なモデルを作成できるためです。サンプルでは、物理演算を有効にするために、physics
(英語)オプションを環境のscene
(英語)プロパティに追加します。
arView.environment.sceneUnderstanding.options.insert(.physics)
仮想コンテンツがメッシュ化された実世界のオブジェクトと接触するのを検出するために、add
Scene
(英語)のextensionで、Collision Shapesを使ってテキストの形状を定義します。
if model.collision == nil {
model.generateCollisionShapes(recursive: true)
model.physicsBody = .init()
}
このアプリは、オブジェクトを分類し、テキストを表示すると、3秒待ってから仮想テキストを落下させます。サンプルでテキストのphysics
(英語)のmode
(英語)をPhysics
(英語)に設定すると、テキストは重力の作用で落下します。
Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { (timer) in
model.physicsBody?.mode = .dynamic
}
テキストが落下して、メッシュ化された実世界のオブジェクトと衝突する(床面に着地するなど)ときに相互作用が働きます。
