ハイライト
Apple は visionOS 向けに ARKit を全面的に書き直し、世界追跡、シーン再構築、平面検出、画像追跡、手部追跡をサポートする新しい C/Swift API を提供し、プライバシーを保ちながら没入型空間アプリを構築できます。
主要内容
iOS から visionOS へ: ARKit の変貌
2017 年の iOS 11 で ARKit は初めてスマホ上の AR 体験を可能にしました。6 年後、ARKit はスマホのフレームワークから visionOS のシステムレベルサービスへ成長しました。
visionOS では ARKit は OS に深く統合されます。ウィンドウ操作から没入ゲームまで、基盤は ARKit が駆動します。API も全面再設計され、モダンな Swift インターフェースと古典的な C インターフェースの両方を提供します。
プライバシー優先のアーキテクチャ
ARKit は世界を理解するためにカメラとセンサーデータが必要ですが、生データはシステムデーモンから出ません。センサーデータは ARKit デーモンに入り、Apple のアルゴリズムで安全に処理された後、キュレーションされた結果だけがアプリへ転送されます。
ARKit データ取得には 2 条件が必要: Full Space に入ること(Shared Space では取得不可)、ユーザー認可をリクエストすること。
3 つの中核概念
ARKit API は 3 つの基本部品で構成されます:
- Anchor: 実世界の位置と向きを表し、一意 ID と変換行列を持つ。一部は追跡可能(TrackableAnchor)で、追跡喪失時は関連仮想コンテンツを隠す。
- Data Provider: 単一の ARKit 機能を表し、ポーリングまたは更新の観察が可能。種類ごとに異なるデータを提供。
- Session: 複数の Data Provider を組み合わせて実行。起動後、各 Provider は非同期でデータを受け取り、更新頻度はデータ型により異なる。
世界追跡: 仮想コンテンツを現実に固定
WorldTrackingProvider は仮想コンテンツ配置の基盤で、2 つの中核能力を提供します:
WorldAnchor: 開発者が追加した WorldAnchor は自動永続化され、アプリ起動やデバイス再起動後も有効。永続化されるのは ID と変換行列のみで、仮想コンテンツとの対応は開発者が維持します。
WorldAnchor の永続化は位置ベースです。自宅からオフィスへ移ると自宅マップはアンロードされオフィスマップがロードされます。戻ると ARKit が自動再定位し以前の Anchor を復元します。
デバイス姿勢(Device Pose): アプリ原点に対するデバイスの位置と向きを照会。Metal と CompositorServices でカスタムレンダリングする際に必須です。
シーン理解: 周囲環境の知覚
- 平面検出: PlaneDetectionProvider が水平・垂直の PlaneAnchor を提供。整列、形状、意味分類(床、テーブルなど)を含む。
- シーンジオメトリ: SceneReconstructionProvider が環境を細分化メッシュ(MeshAnchor)として再構築。頂点、法線、面、面ごとの意味分類を含み、高忠実度物理シミュレーションに使える。
- 画像追跡: ImageTrackingProvider が実世界の 2D 画像を検出し、ImageAnchor と推定スケール係数を提供。
手部追跡: 新しいインタラクション次元
HandTrackingProvider は visionOS 向けの新機能で、HandAnchor を提供します:
- Chirality: 左手または右手
- Skeleton: 全関節を含む手の骨格
- Transform: アプリ原点に対する手首の変換行列
各関節には親関節、名前、localTransform(親相対)、rootTransform(根相対)、isTracked フラグがあります。
手部追跡で可能な操作は 2 種: 手相対位置への仮想コンテンツ配置、カスタムジェスチャ検出。
TimeForCube 例: 総合活用
デモ App TimeForCube は ARKit 機能の組み合わせを示します:
- シーン再構築で衝突体を生成し物理とジェスチャターゲットに使う
- 手部追跡で指先に不可視衝突体を作る
- SpatialTapGesture でタップを検出し、タップ位置上に立方体を生成
- 物理システムで立方体をシーン衝突体上に落下させる
- 手の衝突体でユーザーが立方体を「押せる」
詳細
認可 API
(05:20)
ARKit データ取得前にユーザー認可をリクエストします。複数種類を一括リクエストできます:
session = ARKitSession()
Task {
let authorizationResult = await session.requestAuthorization(for: [.handTracking])
for (authorizationType, authorizationStatus) in authorizationResult {
print("Authorization status for \(authorizationType): \(authorizationStatus)")
switch authorizationStatus {
case .allowed:
// All good!
break
case .denied:
// Need to handle this.
break
// ...
}
}
}
キーポイント:
ARKitSession()でセッションインスタンスを作成requestAuthorization(for:)は非同期認可。配列で複数種類を一括リクエスト- 結果は認可タイプごとに列挙。
.denied時はアプリ側のフォールバックが必要 - 事前リクエストしない場合、セッション実行時に ARKit が自動プロンプト
世界追跡とデバイス姿勢(C API)
(10:20)
Metal カスタムレンダリングの没入 App では C API でデバイス姿勢を照会します:
#include <ARKit/ARKit.h>
#include <CompositorServices/CompositorServices.h>
struct Renderer {
ar_session_t session;
ar_world_tracking_provider_t world_tracking;
ar_pose_t pose;
// ...
};
void renderer_init(struct Renderer *renderer) {
renderer->session = ar_session_create();
ar_world_tracking_configuration_t config = ar_world_tracking_configuration_create();
renderer->world_tracking = ar_world_tracking_provider_create(config);
ar_data_providers_t providers = ar_data_providers_create();
ar_data_providers_add_data_provider(providers, renderer->world_tracking);
ar_session_run(renderer->session, providers);
renderer->pose = ar_pose_create();
// ...
}
キーポイント:
ar_session_create()で ARKit セッション作成ar_world_tracking_provider_create()で世界追跡 Provider 作成ar_data_providers_create()とar_data_providers_add_data_provider()で Provider 集合へ追加ar_session_run()でセッション開始ar_pose_create()で姿勢オブジェクトを事前確保しレンダーループでの割り当てを回避
レンダー関数での姿勢照会:
(10:21)
void render(struct Renderer *renderer,
cp_layer_t layer,
cp_frame_t frame_encoder,
cp_drawable_t drawable) {
const cp_frame_timing_t timing_info = cp_drawable_get_frame_timing(drawable);
const cp_time_t presentation_time = cp_frame_timing_get_presentation_time(timing_info);
const CFTimeInterval target_render_time = cp_time_to_cf_time_interval(presentation_time);
simd_float4x4 pose = matrix_identity_float4x4;
const ar_pose_status_t status =
ar_world_tracking_provider_query_pose_at_timestamp(renderer->world_tracking,
target_render_time,
renderer->pose);
if (status == ar_pose_status_success) {
pose = ar_pose_get_origin_from_device_transform(renderer->pose);
}
// ...
cp_drawable_set_ar_pose(drawable, renderer->pose);
// ...
}
キーポイント:
cp_drawable_get_frame_timing()で CompositorServices からフレームタイミング取得cp_frame_timing_get_presentation_time()で目標提示時刻取得ar_world_tracking_provider_query_pose_at_timestamp()で指定時刻のデバイス姿勢照会ar_pose_get_origin_from_device_transform()でアプリ原点相対のデバイス変換を抽出cp_drawable_set_ar_pose()で drawable に姿勢を設定し合成器にレンダリング姿勢を通知- 姿勢照会は比較的高コスト。コンテンツ配置など非レンダリング用途には非推奨
手部追跡の骨格構造
(16:00)
Swift API の Skeleton 構造:
@available(xrOS 1.0, *)
public struct Skeleton : @unchecked Sendable, CustomStringConvertible {
public func joint(named: SkeletonDefinition.JointName) -> Skeleton.Joint
public struct Joint : CustomStringConvertible, @unchecked Sendable {
public var parentJoint: Skeleton.Joint? { get }
public var name: String { get }
public var localTransform: simd_float4x4 { get }
public var rootTransform: simd_float4x4 { get }
public var isTracked: Bool { get }
}
}
キーポイント:
joint(named:)で関節名照会(例:.handIndexFingerTip)parentJointで親関節参照、階層を構成localTransformは親関節相対変換rootTransformは手首(根)相対変換isTrackedは現在追跡されているか
手部追跡 C API の初期化:
(17:00)
struct Renderer {
ar_hand_tracking_provider_t hand_tracking;
struct {
ar_hand_anchor_t left;
ar_hand_anchor_t right;
} hands;
// ...
};
void renderer_init(struct Renderer *renderer) {
// ...
ar_hand_tracking_configuration_t hand_config = ar_hand_tracking_configuration_create();
renderer->hand_tracking = ar_hand_tracking_provider_create(hand_config);
ar_data_providers_t providers = ar_data_providers_create();
ar_data_providers_add_data_provider(providers, renderer->world_tracking);
ar_data_providers_add_data_provider(providers, renderer->hand_tracking);
ar_session_run(renderer->session, providers);
renderer->hands.left = ar_hand_anchor_create();
renderer->hands.right = ar_hand_anchor_create();
// ...
}
レンダーループでの手部データポーリング:
(17:25)
void render(struct Renderer *renderer, ... ) {
// ...
ar_hand_tracking_provider_get_latest_anchors(renderer->hand_tracking,
renderer->hands.left,
renderer->hands.right);
if (ar_trackable_anchor_is_tracked(renderer->hands.left)) {
const simd_float4x4 origin_from_wrist
= ar_anchor_get_origin_from_anchor_transform(renderer->hands.left);
// ...
}
// ...
}
キーポイント:
ar_hand_tracking_provider_create()で手部追跡 Provider 作成ar_hand_anchor_create()で手部アンカーを事前確保ar_hand_tracking_provider_get_latest_anchors()で最新手部データをポーリングar_trackable_anchor_is_tracked()で追跡状態確認ar_anchor_get_origin_from_anchor_transform()でアプリ原点相対のアンカー変換取得
TimeForCube App 構造
(18:00)
@main
struct TimeForCube: App {
@StateObject var model = TimeForCubeViewModel()
var body: some SwiftUI.Scene {
ImmersiveSpace {
RealityView { content in
content.add(model.setupContentEntity())
}
.task {
await model.runSession()
}
.task {
await model.processHandUpdates()
}
.task {
await model.processReconstructionUpdates()
}
.gesture(SpatialTapGesture().targetedToAnyEntity().onEnded({ value in
let location3D = value.convert(value.location3D, from: .global, to: .scene)
model.addCube(tapLocation: location3D)
}))
}
}
}
キーポイント:
ImmersiveSpaceが必須。ARKit データは Full Space のみRealityViewで 3D コンテンツ表示- 3 つの
.taskでセッション、手部更新、シーン再構築更新をそれぞれ実行 SpatialTapGestureで空間タップ検出
ViewModel 構造:
(18:50)
@MainActor class TimeForCubeViewModel: ObservableObject {
private let session = ARKitSession()
private let handTracking = HandTrackingProvider()
private let sceneReconstruction = SceneReconstructionProvider()
private var contentEntity = Entity()
private var meshEntities = [UUID: ModelEntity]()
private let fingerEntities: [HandAnchor.Chirality: ModelEntity] = [
.left: .createFingertip(),
.right: .createFingertip()
]
// ...
}
キーポイント:
ARKitSessionがすべての ARKit Data Provider を管理HandTrackingProviderとSceneReconstructionProviderは具体的 ProvidermeshEntitiesは MeshAnchor ID を ModelEntity に辞書マップfingerEntitiesは左右それぞれ指先エンティティを作成
手部更新の処理
(20:00)
func processHandUpdates() async {
for await update in handTracking.anchorUpdates {
let handAnchor = update.anchor
guard handAnchor.isTracked else { continue }
let fingertip = handAnchor.skeleton.joint(named: .handIndexFingerTip)
guard fingertip.isTracked else { continue }
let originFromWrist = handAnchor.transform
let wristFromIndex = fingertip.rootTransform
let originFromIndex = originFromWrist * wristFromIndex
fingerEntities[handAnchor.chirality]?.setTransformMatrix(originFromIndex, relativeTo: nil)
}
}
キーポイント:
handTracking.anchorUpdatesは手部アンカー更新を継続生成する非同期シーケンス- まず
handAnchor.isTrackedを確認。喪失時はスキップ - 人差し指先
.handIndexFingerTipを取得 handAnchor.transformはアプリ原点相対の手首変換fingertip.rootTransformは手首相対の指先変換- 乗算で指先のアプリ原点相対完全変換を得る
handAnchor.chirality(.left/.right)に応じて対応指先エンティティを更新
シーン再構築更新の処理
(21:20)
func processReconstructionUpdates() async {
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.transform)
entity.collision = CollisionComponent(shapes: [shape], isStatic: true)
entity.physicsBody = PhysicsBodyComponent()
entity.components.set(InputTargetComponent())
meshEntities[meshAnchor.id] = entity
contentEntity.addChild(entity)
case .updated:
guard let entity = meshEntities[meshAnchor.id] else { fatalError("...") }
entity.transform = Transform(matrix: meshAnchor.transform)
entity.collision?.shapes = [shape]
case .removed:
meshEntities[meshAnchor.id]?.removeFromParent()
meshEntities.removeValue(forKey: meshAnchor.id)
@unknown default:
fatalError("Unsupported anchor event")
}
}
}
キーポイント:
sceneReconstruction.anchorUpdatesがメッシュアンカー更新を非同期生成ShapeResource.generateStaticMesh(from:)で MeshAnchor から衝突形状生成.added: 新エンティティ作成、変換・衝突・物理・InputTarget を設定isStatic: trueはシーンジオメトリが静的衝突体であることを示すInputTargetComponent()でジェスチャ受信可能に.updated: 既存エンティティの変換と衝突形状を更新.removed: 親から除去し辞書をクリーンアップ
立方体の追加
(22:20)
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)
}
キーポイント:
- 配置位置はタップ点の 20 cm 上
ModelEntityでメッシュ、マテリアル、衝突形状、質量を同時作成InputTargetComponent(allowedInputTypes: .indirect)は間接入力(注視+ピンチなど)のみ許可PhysicsMaterialResource.generate(friction:restitution:)で物理マテリアルカスタマイズPhysicsBodyComponent(mode: .dynamic)で重力の影響を受ける動的物理体- シーン衝突体上に落下後、手の衝突体で押せる
重要ポイント
-
やること: 実際の部屋で家具をプレビューできる仮想家具配置 App を開発する。
-
価値: ARKit の WorldAnchor は自動永続化され、複数回の装着後も同じ位置に留まる。PlaneDetectionProvider で机や床を識別し精密配置に使える。
-
始め方:
PlaneDetectionProviderで平面検出、WorldTrackingProviderでWorldAnchorを追加して家具を固定し、RealityKit で USDZ を読み込む。 -
やること: ジェスチャで曲切り替え・音量調整する音楽プレイヤーを作る。
-
価値: HandTrackingProvider は 26 関節を精密追跡し、ピンチ・開く・振るなどのカスタムジェスチャを認識。追加ハードウェア不要。
-
始め方:
HandTrackingProvider.anchorUpdatesを監視し、Skeleton.JointのrootTransform変化パターンを分析して再生制御のジェスチャ状態機械を定義。 -
やること: 実際の本のページに 3D モデルとアニメーションを重ねる教育 App を構築する。
-
価値: ImageTrackingProvider が特定参照画像を検出し ImageAnchor を提供。ReferenceImage はアセットカタログやコードから作成可能。
-
始め方:
ImageTrackingProviderとReferenceImageを設定し、検出時に ImageAnchor 位置へ RealityKit エンティティを読み込み、ImageAnchor.estimatedScaleFactorでサイズ補正。 -
やること: ユーザー動作を追跡してフィードバックする没入型フィットネス App を開発する。
-
価値: 手部追跡とシーンジオメトリの組み合わせで、手が仮想ターゲットに触れたか、身体が正しい空間位置にあるかを判断できる。
-
始め方:
HandTrackingProviderとSceneReconstructionProviderを併用し、シーンメッシュ上に仮想ターゲットを配置、手関節とターゲットの衝突を検出。
関連セッション
- Build spatial experiences with RealityKit — visionOS 上の RealityKit 基礎。ARKit データで没入シーンを構築
- Bring your ARKit app to spatial computing — 既存 iOS ARKit App を visionOS へ移行するガイド
- Create immersive Unity apps — Unity で visionOS 没入 App を作成。ARKit データ統合を含む
- Optimize your 3D assets for spatial computing — visionOS でのパフォーマンス向上のため 3D アセットを最適化
コメント
GitHub Issues · utterances