[visionOS] ARKitで検出したシーンのメッシュを可視化する
本記事でやりたいこと: visionOSで、ARKitのScene Reconstructionで検出したシーンのメッシュを可視化したい。

iOSでScene Reconstructionのメッシュを可視化した例。これをvisionOSでやりたい。
方法のひとつとしては、Xcodeの "Visualizations" 機能を使えば、ポチッとチェックを入れるだけで可視化できる。

が、そうではなくて、プログラムからシーンのメッシュを描画するにはどうするか、という話。
公式サンプルの実装は何が足りないか
visionOSにおけるARKitのScene Reconstructionについて、公式チュートリアルが公開されている:
以前はドキュメントだけだったが、サンプルコードも最近公開された。(※ ARKitなのでVision Pro実機でしか動作確認できない(シミュレータでは動かない))
このチュートリアルおよびサンプルコードでは、次のように MeshAnchor から ModelEntity を生成している:
for await update in sceneReconstruction.anchorUpdates {
let meshAnchor = update.anchor
guard let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor) else { continue }
switch update.event {
case .added:
let entity = ModelEntity()
entity.transform = Transform(matrix: meshAnchor.originFromAnchorTransform)
entity.collision = CollisionComponent(shapes: [shape], isStatic: true)
entity.components.set(InputTargetComponent())
entity.physicsBody = PhysicsBodyComponent(mode: .static)
meshEntities[meshAnchor.id] = entity
contentEntity.addChild(entity)
case .updated:
guard let entity = meshEntities[meshAnchor.id] else { continue }
entity.transform = Transform(matrix: meshAnchor.originFromAnchorTransform)
entity.collision?.shapes = [shape]
case .removed:
meshEntities[meshAnchor.id]?.removeFromParent()
meshEntities.removeValue(forKey: meshAnchor.id)
}
}
複雑に見えるかもしれないが、次のように CollisionComponent と InputTargetComponent と PhysicsBodyComponent を持つ(つまり、インタラクションや物理演算が可能な)ModelEntity を生成してるだけだ。
let entity = ModelEntity()
entity.transform = Transform(matrix: meshAnchor.originFromAnchorTransform)
entity.collision = CollisionComponent(shapes: [shape], isStatic: true)
entity.components.set(InputTargetComponent())
entity.physicsBody = PhysicsBodyComponent(mode: .static)
関連:
ここでのポイントは、CollisionComponent の形状(shape)として、Scene Reconstructionによって検出されたシーンのメッシュ形状を利用している点:
guard let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor) else { continue }
...
entity.collision = CollisionComponent(shapes: [shape], isStatic: true)
衝突判定が効いているので、床やテーブルなどScene Reconstructionによって検出されたシーンの表面上に3Dオブジェクトを設置できる:
func addCube(tapLocation: SIMD3<Float>) {
let placementLocation = tapLocation + SIMD3<Float>(0, 0.2, 0)
let entity = ModelEntity(
mesh: .generateBox(size: 0.1, cornerRadius: 0.0),
materials: [SimpleMaterial(color: .systemPink, isMetallic: false)],
collisionShape: .generateBox(size: SIMD3<Float>(repeating: 0.1)),
mass: 1.0)
entity.setPosition(placementLocation, relativeTo: nil)
entity.components.set(InputTargetComponent(allowedInputTypes: .indirect))
let material = PhysicsMaterialResource.generate(friction: 0.8, restitution: 0.0)
entity.components.set(
PhysicsBodyComponent(
shapes: entity.collision!.shapes,
mass: 1.0,
material: material,
mode: .dynamic)
)
contentEntity.addChild(entity)
}
(スクショは用意できないが、ぜひ実機で公式サンプル "SceneReconstructionExample" を動かしてみてください)
ただ、このコードではシーンのメッシュは可視化されない。
InputTargetComponent や CollisionComponent が与えられているだけで、ModelEntity にメッシュ情報が与えられていないからだ。
MeshAnchor から MeshResource を生成する
SceneReconstructionProvider からは、MeshAnchor という構造体が得られる。
シーンのメッシュを可視化するには、この MeshAnchor が持っているメッシュ情報を ModelEntity に渡す必要があるわけだが、そのためには MeshResource という型にする必要がある。
@MainActor
init(
mesh: MeshResource,
materials: [Material] = []
)
ShapeResource であれば次のようにメソッド一発で MeshAnchor から ShapeResource インスタンスを得られるのだが、
let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor)
MeshResource には残念ながらそのような便利メソッドがない。
ではどうするかというと、
MeshAnchor.Geometry から頂点情報(vertices)や法線情報(normals)を取り出し、MeshDescriptor に詰め直して、それを用いて MeshResource を生成する:
var desc = MeshDescriptor()
let posValues = geometry.vertices.asSIMD3(ofType: Float.self)
desc.positions = .init(posValues)
let normalValues = geometry.normals.asSIMD3(ofType: Float.self)
desc.normals = .init(normalValues)
do {
desc.primitives = .polygons(
(0..<geometry.faces.count).map { _ in UInt8(3) },
(0..<geometry.faces.count * 3).map {
geometry.faces.buffer.contents()
.advanced(by: $0 * geometry.faces.bytesPerIndex)
.assumingMemoryBound(to: UInt32.self).pointee
}
)
}
let meshResource = try MeshResource.generate(from: [desc])
これで MeshAnchor のメッシュを持つ ModelEntity を生成できる。
なお、MeshDescriptor を作成する実装はこちらのリポジトリを参考にした:
サンプル
サンプルコードをGitHubで公開しています。Xcode 15.1 beta 3 で動作確認済み。要実機。
(参考になったらスター、記事へのLikeいただけると嬉しいです)
-
12/21時点で未投稿だったため「代わりに投稿」させていただきました。 ↩︎
Discussion