realitykit

realitykit

熱門

使用 RealityKit 與 ARKit 打造 iOS 擴增實境 (AR) 與 3D 體驗。適用於新增 RealityView 內容、載入 Entity 或 USDZ 模型、將物件錨定至平面或世界座標位置、區分 Entity 點擊測試 (hit test) 與 ARKit 真實世界光線投射 (raycast)、處理 AR 相機可用性、世界追蹤、場景更新,以及 RealityKit Entity 手勢與互動。

961星標
48分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
realitykit
描述

使用 RealityKit 與 ARKit 打造 iOS 擴增實境 (AR) 與 3D 體驗。適用於新增 RealityView 內容、載入 Entity 或 USDZ 模型、將物件錨定至平面或世界座標位置、區分 Entity 點擊測試 (hit test) 與 ARKit 真實世界光線投射 (raycast)、處理 AR 相機可用性、世界追蹤、場景更新,以及 RealityKit Entity 手勢與互動。

RealityKit

使用 RealityKit 處理算圖(rendering)並搭配 ARKit 進行世界追蹤,在 iOS 上打造 AR 體驗。涵蓋 RealityView、Entity 管理、光線投射(raycasting)、場景理解(scene understanding)以及手勢互動。適用於 Swift 6.3 / iOS 26+。

目錄

環境設定

專案設定

  1. 在 Info.plist 中新增 NSCameraUsageDescription
  2. 在 iOS 上,RealityViewCameraContent 預設會顯示 AR 相機視圖(iOS 18+、macOS 15+);若需要明確的非 AR 備用方案(fallback),請使用 .virtual 相機模式
  3. 基礎 AR 無需特殊 Entitlement。若 AR 為 App 的核心功能,請新增 arkit 必要的裝置功能要求(required-device capability);否則請使用 isSupported 控管 AR UI 的顯示。

裝置需求

在呈現 AR UI 之前,請先檢查具體 AR 設定(configuration)的 isSupported 值。

import ARKit

guard ARWorldTrackingConfiguration.isSupported else {
    showUnsupportedDeviceMessage()
    return
}

關鍵型態

型態 平台 角色
RealityView iOS 18+, visionOS 1+ 承載 RealityKit 內容的 SwiftUI 視圖
RealityViewCameraContent iOS 18+, macOS 15+ 在 iOS 上透過 AR 相機視圖顯示的內容,在 macOS 上則為非 AR 模式
Entity 所有平台 所有場景物件的基底類別 (Base class)
ModelEntity 所有平台 帶有可見 3D 模型的 Entity
AnchorEntity 所有平台 將 Entity 繫結至真實世界的錨點

RealityView 基礎

RealityView 是 RealityKit 在 SwiftUI 中的進入點。
RealityViewCameraContent 則是 iOS/macOS 專用的內容型態。在 iOS 上,預設會使用 AR 相機視圖;當有需求或 AR/相機存取權不可用時,可設定 content.camera = .virtual 切換為非 AR 模式。

import ARKit
import SwiftUI
import RealityKit

struct ARExperienceView: View {
    var body: some View {
        RealityView { (content: RealityViewCameraContent) in
            if !ARWorldTrackingConfiguration.isSupported {
                content.camera = .virtual
            }

            let sphere = ModelEntity(
                mesh: .generateSphere(radius: 0.05),
                materials: [SimpleMaterial(
                    color: .blue,
                    isMetallic: true
                )]
            )
            sphere.position = [0, 0, -0.5]  // 相機前方 50cm
            content.add(sphere)
        }
    }
}

Make 與 Update 模式

使用 update 閉包(closure)來回應 SwiftUI 的狀態變更:

struct PlacementView: View {
    @State private var modelColor: UIColor = .red

    var body: some View {
        RealityView { content in
            let box = ModelEntity(
                mesh: .generateBox(size: 0.1),
                materials: [SimpleMaterial(
                    color: .red,
                    isMetallic: false
                )]
            )
            box.name = "colorBox"
            box.position = [0, 0, -0.5]
            content.add(box)
        } update: { content in
            if let box = content.entities.first(
                where: { $0.name == "colorBox" }
            ) as? ModelEntity {
                box.model?.materials = [SimpleMaterial(
                    color: modelColor,
                    isMetallic: false
                )]
            }
        }

        Button("Change Color") {
            modelColor = modelColor == .red ? .green : .red
        }
    }
}

載入與建立 Entity

從 USDZ 檔案載入

非同步載入 3D 模型,避免阻礙主執行緒(main thread):

RealityView { content in
    if let robot = try? await ModelEntity(named: "robot") {
        robot.position = [0, -0.2, -0.8]
        robot.scale = [0.01, 0.01, 0.01]
        content.add(robot)
    }
}

新增 Component

Entity 採用 ECS(Entity Component System,實體組件系統)架構。透過新增 Component 來賦予 Entity 行為:

let box = ModelEntity(
    mesh: .generateBox(size: 0.1),
    materials: [SimpleMaterial(color: .red, isMetallic: false)]
)

// 使其對物理效果產生反應
box.components.set(PhysicsBodyComponent(
    massProperties: .default,
    material: .default,
    mode: .dynamic
))

// 新增碰撞形狀以供互動使用
box.components.set(CollisionComponent(
    shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
))

// 啟用輸入目標以支援手勢
box.components.set(InputTargetComponent())

錨定與放置

AnchorEntity

使用 AnchorEntity 將內容錨定至偵測到的平面或世界座標位置:

RealityView { content in
    // 錨定至水平平面
    let floorAnchor = AnchorEntity(.plane(
        .horizontal,
        classification: .floor,
        minimumBounds: [0.2, 0.2]
    ))

    let model = ModelEntity(
        mesh: .generateBox(size: 0.1),
        materials: [SimpleMaterial(color: .orange, isMetallic: false)]
    )
    floorAnchor.addChild(model)
    content.add(floorAnchor)
}

錨定目標 (Anchor Targets)

目標 說明
.plane(.horizontal, ...) 水平平面(地板、桌面)
.plane(.vertical, ...) 垂直平面(牆面)
.plane(.any, ...) 任何偵測到的平面
.world(transform:) 固定的世界空間位置

光線投射 (Raycasting)

請明確區分 RealityKit 場景查詢與 ARKit 的真實世界光線投射(raycast):

  • RealityViewCameraContent.ray(through:in:to:) 會在 RealityKit 座標空間中傳回一條相機光線。它將螢幕點投影至虛擬場景中;這並不代表偵測到了真實物理表面。
  • RealityViewCameraContent.hitTest(point:in:query:mask:) 用於命中已透過 CollisionComponent 形狀設為可命中的虛擬 Entity。請將這些形狀用於 Entity 選取與目標手勢,而非 ARKit 平面偵測。
  • 若要在偵測到的平面上進行簡單放置,請使用 AnchorEntity(.plane(...))
  • 當任務需要與真實世界表面進行一次性交集計算時,請結合使用 ARKit 的 ARRaycastQueryARSession.raycast(_:),隨後再透過 AnchorEntity(raycastResult:) 進行錨定。
let results = session.raycast(query)
if let result = results.first {
    let anchor = AnchorEntity(raycastResult: result)
    anchor.addChild(model)
    content.add(anchor)
}

請勿將 Entity 的點擊測試(hit test)混淆或替代 ARKit 的表面光線投射。

手勢與互動

若要實現基於手勢的 Entity 互動,請新增 CollisionComponent 以定義可點擊範圍形狀,並新增 InputTargetComponent 以支援輸入目標指定。

Entity 拖拽手勢

struct DraggableARView: View {
    var body: some View {
        RealityView { content in
            let box = ModelEntity(
                mesh: .generateBox(size: 0.1),
                materials: [SimpleMaterial(color: .blue, isMetallic: true)]
            )
            box.position = [0, 0, -0.5]
            box.components.set(CollisionComponent(
                shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
            ))
            box.components.set(InputTargetComponent())
            box.name = "draggable"
            content.add(box)
        }
        .gesture(
            DragGesture()
                .targetedToAnyEntity()
                .onChanged { value in
                    let entity = value.entity
                    guard let parent = entity.parent else { return }
                    entity.position = value.convert(
                        value.location3D,
                        from: .local,
                        to: parent
                    )
                }
        )
    }
}

場景理解

每幀更新 (Per-Frame Updates)

若需要持續處理場景工作,請訂閱 SceneEvents.Update,而非使用 SwiftUI Timer 來驅動 RealityKit。請維持訂閱運作並善用 event.deltaTime;詳情請參考 Entity Animations

平台邊界

在 visionOS 上,ARKit 提供了不同的 API 架構,包含 ARKitSessionWorldTrackingProviderPlaneDetectionProvider。這些 visionOS 專用的型態在 iOS 上並不可用。在 iOS 上,RealityKit 會透過 RealityViewCameraContent 自動處理世界追蹤。

關於 iOS 架構或轉移(migration)注意事項:請使用 ARWorldTrackingConfiguration.isSupported 控制 AR 開關、透過 RealityViewCameraContent 承載內容,並使用以 AnchorEntity 放置的 Entity/ModelEntity 來建立場景。

職責劃分(Handoffs): CollisionComponent + InputTargetComponent 負責處理 RealityKit 的互動;AccessibilityComponent 負責處理 Entity 的無障礙元資料(metadata);而詳細的 SwiftUI 手勢與 VoiceOver/切換控制(Switch Control)策略則歸屬於相鄰元件。

請將現有的 SCNView/SCNNode 程式碼視為獨立的 SceneKit 路徑或明確轉移至 RealityKit 的項目,切勿建立混合的場景圖(scene graph)。

常見錯誤

錯誤作法:忽略 AR 功能支援檢查

在呈現 AR 介面之前,請務必先執行環境設定中針對特定設定的支援度檢查。若不支援,請顯示非 AR 內容或明確的不可用狀態。

錯誤作法:同步載入大型模型

在主執行緒上載入大型 USDZ 檔案會導致掉幀與畫面卡頓。RealityViewmake 閉包支援 async——請務必善用非同步載入。

// 錯誤——同步載入會阻礙主執行緒
RealityView { content in
    let model = try! Entity.load(named: "large-scene")
    content.add(model)
}

// 正確——非同步載入
RealityView { content in
    if let model = try? await ModelEntity(named: "large-scene") {
        content.add(model)
    }
}

錯誤作法:互動式 Entity 漏掉碰撞與輸入目標 Component

具備互動功能的 Entity 必須同時具備手勢與互動章節中提到的兩個 Component;若缺少它們,點擊與拖拽手勢將會直接穿透。

錯誤作法:在 update 閉包中建立新的 Entity

每當 SwiftUI 狀態發生變更時,update 閉包都會執行一次。若在其中建立 Entity,將導致每次算圖時重複產生相同的內容。

// 錯誤——每次狀態變更時都會重複建立 Entity
RealityView { content in
    // 空白
} update: { content in
    let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
    content.add(sphere)  // 每次更新都會再次新增
}

// 正確——在 make 中建立,在 update 中修改
RealityView { content in
    let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
    sphere.name = "mySphere"
    content.add(sphere)
} update: { content in
    if let sphere = content.entities.first(
        where: { $0.name == "mySphere" }
    ) as? ModelEntity {
        // 修改既有的 Entity
        sphere.position.y = newYPosition
    }
}

錯誤作法:忽略相機權限

iOS 上的 RealityKit 需要相機存取權限。若使用者拒絕授權,畫面將直接呈現黑屏且沒有任何說明。

// 錯誤——未處理相機權限
RealityView { content in
    // 若相機權限被拒絕將顯示黑屏
}

// 正確——檢查並請求相機權限
struct ARContainerView: View {
    @State private var cameraAuthorized = false

    var body: some View {
        Group {
            if cameraAuthorized {
                RealityView { content in
                    // AR 內容
                }
            } else {
                ContentUnavailableView(
                    "Camera Access Required",
                    systemImage: "camera.fill",
                    description: Text("Enable camera in Settings to use AR.")
                )
            }
        }

<!-- truncated for translation batch; full body continues in source -->