[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