はじめに
これまでiPhoneだけを用いて、オンデバイスで3Dモデルの作成と自分の目の前に作成した3Dモデルの配置をチャレンジしてきました。
詳細が気になる方はこれまでの記事を見ていただければと思います。
- SwiftUI+RealityKit 超初級編 – AR空間にブラウン管テレビを設置する
- 【iOS 17】オンデバイス完結!オブジェクトキャプチャ→3Dモデル生成の過程を確認してみた
- 【iOS】作成した3Dモデルを画面上にリストで表示させる
しかし、実際に3Dモデルを配置してみると、現実世界にあるオブジェクトの後ろに配置したはずなのに、その物体より前に表示されているように見えたり、手を差し出すと手より手前にあるように表示されていました。
今回はAR体験が現実世界とより一体感を持ち、よりリアルな印象を与えるため、オクルージョンの適用方法について調査しました。
環境
- Xcode 15.2
- iOS 17.3.1
- iPhone 15 Pro
オクルージョンとは
オクルージョンとは、塞ぐを意味する英単語「occlude」で、IT業界では手前にある物体が後ろにある物体を隠す状態のことをいいます。
ARコンテンツにおいてオクルージョン技術が効いていることで、空間認識によって現実世界の物体の前後関係を把握し、物体の場所によってコンテンツの一部を隠したり、表示させたりすることができます。そのためARコンテンツの前を人が通り過ぎるということが可能になり、ARで表現されたものをより本物のように感じさせることができます。
引用: AR用語集 - オクルージョン
現状の体験
画面タップで3Dモデルを配置するARView
protocol ARPlacementViewDelegate: AnyObject {
func arPlacementView(_ arPlacementView: ARPlacementView, didTapAt position: SIMD3<Float>)
}
class ARPlacementView: ARView {
required init(frame: CGRect) {
super.init(frame: frame)
addTapGesture()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
weak var delegate: ARPlacementViewDelegate?
func addTapGesture() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
self.addGestureRecognizer(tapGesture)
}
@objc func handleTap(_ recognizer: UITapGestureRecognizer) {
// タップしたロケーションを取得
let tapLocation = recognizer.location(in: self)
// タップした位置に対応する3D空間上の平面とのレイキャスト結果を取得
let raycastResults = raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .horizontal)
guard let firstResult = raycastResults.first else { return }
// タップした位置をワールド座標系に変換
let position = simd_make_float3(firstResult.worldTransform.columns.3)
delegate?.arPlacementView(self, didTapAt: position)
}
}
ARViewContainer
SwiftUI上でARView
を表示する為のラッパーです。
import SwiftUI
import ARKit
import RealityKit
struct ARViewContainer: UIViewRepresentable {
@Environment(AppDataModel.self) var model
func makeUIView(context: Context) -> ARView {
let arView = ARPlacementView(frame: .zero)
arView.delegate = context.coordinator
let configuration = ARWorldTrackingConfiguration()
configuration.isLightEstimationEnabled = true
arView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, ARPlacementViewDelegate {
let parent: ARViewContainer
init(_ parent: ARViewContainer) {
self.parent = parent
}
// ARViewをタップ時に現在選択中の3DモデルをAR空間上に配置
@MainActor
func arPlacementView(_ arPlacementView: ARPlacementView, didTapAt position: SIMD3<Float>) {
guard let path = parent.model.selectingModelPathToPlace,
let model = try? ModelEntity.loadModel(contentsOf: path,
withName: String(describing: Date.now))
else { return }
let anchor = AnchorEntity(world: position)
anchor.children.append(model)
arPlacementView.scene.anchors.append(anchor)
}
}
}
現状の実装だと配置した3Dモデルが人の手や手前に配置しているオブジェクトとの前後関係が無視された状態になっています。
3Dモデルに人物との前後関係を考慮させる
3Dモデルに人物との前後関係を考慮させる為には、People Occlusion機能を使用します。
実装はとても簡単でARConfiguration
のframeSemantics
プロパティに.personSegmentationWithDepth
を指定するだけです。
configuration.frameSemantics = [.personSegmentationWithDepth]
personSegmentation
でもPeople Occlusionは実現できますが、personSegmentation
では深度を考慮せずに描画されるため、今回はよりリアルな印象を与えるのが目的なので.personSegmentationWithDepth
を指定しています。
func makeUIView(context: Context) -> ARView {
let arView = ARPlacementView(frame: .zero)
arView.delegate = context.coordinator
let configuration = ARWorldTrackingConfiguration()
configuration.isLightEstimationEnabled = true
// People Occlusionを適用
configuration.frameSemantics = [.personSegmentationWithDepth]
arView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
return arView
}
これで人の手をかざすと3Dモデルが見えなくなり、手を退けると3Dモデルが表示されるようになりました。
3Dモデルに現実世界のオブジェクトとの前後関係を考慮させる
現実世界のオブジェクトをAR空間上で認識できるようにメッシュ化を行います。ARConfiguration
のsceneReconstruction
プロパティに.meshWithClassification
もしくは.mesh
を指定することでメッシュ化により物理環境のおおよその形状を検出できるようになります。
.mesh
では、メッシュ化だけですが、.meshWithClassification
を指定すると、その現実世界のオブジェクトが何に該当するのか分類してくれます。
configuration.sceneReconstruction = .meshWithClassification
次にARView.Environment.SceneUnderstanding.Options
にARシーンで利用する設定を加えることができます。そこで.occlusion
を加えると現実世界のオブジェクトとのオクルージョンを適用できるようになります。
arView.environment.sceneUnderstanding.options.insert(.occlusion)
下記のように実装することで現実世界のオブジェクトとの前後関係も考慮できるようになりました。
func makeUIView(context: Context) -> ARView {
let arView = ARPlacementView(frame: .zero)
arView.delegate = context.coordinator
let configuration = ARWorldTrackingConfiguration()
configuration.isLightEstimationEnabled = true
// People Occlusionを適用
configuration.frameSemantics = [.personSegmentationWithDepth]
// メッシュ化とオブジェクトの分類
configuration.sceneReconstruction = .meshWithClassification
// シーンの設定にオクルージョンを適用
arView.environment.sceneUnderstanding.options.insert(.occlusion)
arView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
return arView
}
おわりに
オクルージョンを適用して、3Dモデルと現実世界のオブジェクトや人物との前後関係を考慮することでより良いAR体験になったように感じました。
iPhoneを活用したより良いAR体験をできるようになるため、引き続き調査をしていきたいと思います。