見出し画像

Vision Pro の アプリ構築 (2) - アプリへの3Dコンテンツの追加

以下の記事が面白かったので、簡単にまとめました。

Adding 3D content to your app


前回

1. はじめに

ステレオスコピックディスプレイを備えたデバイスは、人々がよりリアルに感じる方法で3Dコンテンツを体験することができます。コンテンツは本当の深みを持っているように見え、人々はさまざまな角度からそれを見ることができ、目の前にあるように見えます。

visionOS用のアプリを構築するときは、アプリのインターフェースに深みを加える方法を考えてください。このシステムは、既存の「Window」「Volume」「Immersive Space」など、3Dコンテンツを表示するいくつかの方法を提供します。アプリと提供するコンテンツに最適なオプションを選択してください。

2. 従来の2Dウィンドウに奥行きを追加

ウィンドウはアプリのインターフェイスの重要な部分です。 visionOSを使用すると、アプリは、visionOSのルック&フィールを備えたマテリアル、目と手の入力に合わせて間隔が調整された完全にサイズ変更可能なウィンドウ、カスタムコントロールのハイライト調整へのアクセスを自動的に取得します。

必要に応じて深度効果をカスタム ビューに組み込み、3Dレイアウトオプションを使用してウィンドウ内のビューを配置します。

・ビューに shadow(color:radius:x:y:) または visualEffect(_:) を適用。
・ビューを見ているときに、hoverEffect(_:isEnabled:) でビューをリフトまたはハイライト。
ZStack でビューをレイアウト。
・ビュー関連の変更を transform3DEffect(_:) でアニメーション化。
rotation3DEffect(_:axis:anchor:anchorZ:perspective:)でビューを回転。

2Dビューに奥行きを与えるだけでなく、静的な3D モデルを2Dウィンドウに追加することもできます。Model3Dビューは、USDZまたはその他のアセットをロードし、それを固有のサイズでウィンドウに表示します。これは、アプリ内にモデルデータがすでに存在する場所、またはネットワークからダウンロードできる場所で使用します。たとえば、ショッピング アプリはこのタイプのビューを使用して、商品の3Dバージョンを表示することができます。

3. RealityKit でダイナミック3D シーンを表示

「RealityKit」は、画面上で動的に更新される3Dモデルとシーンを構築するための Apple のテクノロジーです。visionOSでは、「RealityKit」と「SwiftUI」を併用して、アプリの2Dコンテンツと3Dコンテンツをシームレスに結合します。既存のUSDZをロードするか、コンテンツのアニメーション、物理、照明、サウンド、カスタム動作を組み込んだ「Reality Composer Pro」のシーンを作成します。アプリで「Reality Composer Pro」プロジェクトを使用するには、Swift パッケージをXcodeプロジェクトに追加し、そのモジュールをSwiftファイルにインポートします。
詳しくは「Managing files and folders in your Xcode project」を参照してください。

インターフェイスに3Dコンテンツを表示する準備ができたら、「RealityView」を使用します。この「SwiftUI」ビューは「RealityKit」コンテンツのコンテナとして機能し、使い慣れた「SwiftUI」テクニックでコンテンツを更新できます。

次の例は、「RealityView」で3D球を表示するビューを示しています。ビューのクロージャ内のコードは、球の「RealityKit」エンティティを作成し、球の表面にテクスチャを適用して、ビューのコンテンツに追加します。

 struct SphereView: View {
    var body: some View {
        RealityView { content in
            let model = ModelEntity(
                         mesh: .generateSphere(radius: 0.1),
                         materials: [SimpleMaterial(color: .white, isMetallic: true)])
            content.add(model)
        }
    }
}

「SwiftUI」は「RealityView」を表示するときに、コードを1回実行してエンティティやその他のコンテンツを作成します。エンティティの作成には比較的コストがかかるため、ビューは作成コードを1回だけ実行します。エンティティの状態を更新する場合は、ビューの状態を変更し、更新クロージャを使用してそれらの変更をコンテンツに適用します。次の例では、更新クロージャを使用して、scale プロパティの値が変更されたときに球のサイズを変更します。

struct SphereView: View {
    var scale = false


    var body: some View {
        RealityView { content in
            let model = ModelEntity(
                         mesh: .generateSphere(radius: 0.1),
                         materials: [SimpleMaterial(color: .white, isMetallic: true)])
            content.add(model)
        } update: { content in
            if let model = content.entities.first {
                model.transform.scale = scale ? [1.2, 1.2, 1.2] : [1.0, 1.0, 1.0]
            }
        }
    }
}

「RealityKit」を使用してコンテンツを作成する方法については、「RealityKit」 を参照してください。

4. RealityKitコンテンツとのインタラクション

「RealityKit」シーンのエンティティとのインタラクションの手順は、次のとおりです。

・「RealityView」に「Gesture Recognizer」を追加し、それに targetedToAnyEntity() を追加。
・「InputTargetComponent」をエンティティまたはその親エンティティの1つに追加。
・インタラクションをサポートする「RealityKit」エンティティに衝突形状を追加。

targetedToAnyEntity() は、「Gesture Recognizer」と「RealityKit」コンテンツの間のブリッジを提供します。たとえば、誰かがエンティティをドラッグしたことを認識するには、「DragGesture」を指定し、それに修飾子を追加します。指定されたジェスチャがエンティティ上で発生すると、SwiftUIは提供されたクロージャを実行します。

次の例では、「Tap Gesture Recognizer」を前の例の球に追加します。このコードでは、InputTargetComponentCollisionComponent をシェイプに追加して、相互作用が発生できるようにします。これらのコンポーネントを省略すると、ビューはエンティティとの対話できません。

struct SphereView: View {
    @State private var scale = false


    var body: some View {
        RealityView { content in
            let model = ModelEntity(
                mesh: .generateSphere(radius: 0.1),
                materials: [SimpleMaterial(color: .white, isMetallic: true)])


            // Enable interactions on the entity.
            model.components.set(InputTargetComponent())
            model.components.set(CollisionComponent(shapes: [.generateSphere(radius: 0.1)]))
            content.add(model)
        } update: { content in
            if let model = content.entities.first {
                model.transform.scale = scale ? [1.2, 1.2, 1.2] : [1.0, 1.0, 1.0]
            }
        }
        .gesture(TapGesture().targetedToAnyEntity().onEnded { _ in
            scale.toggle()
        })
    }
}

5. Volume内の3Dコンテンツの表示

「Volume」は、含まれるコンテンツのサイズに合わせて 3 次元に拡大するウィンドウの一種です。「Window」と「Volume」はどちらも2Dコンテンツと3Dコンテンツに対応しており、多くの点で似ています。ただし、「Window」はその表面から遠くまで広がる3Dコンテンツをクリップするため、主に3Dのコンテンツには「Volume」の方が適しています。

「Volume」を作成するには、WindowGroup シーンをアプリに追加し、そのスタイルを「volumetric」に設定します。 このスタイルは、「SwiftUI」に3Dコンテンツ用のウィンドウを作成するように指示します。「Volume」に必要な2Dビューまたは3Dビューを含めます。「RealityView」を追加して、RealityKit を使用してコンテンツを構築することもできます。 次の例では、アプリのバンドルに保存されているいくつかのバルーンの静的3Dモデルを含む「Volume」を作成します。

struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            Model3D("balloons")
        }.windowStyle(style: .volumetric)
    }
}

「Window」と「Volume」は、境界のある2Dおよび3Dコンテンツを表示する便利な方法ですが、アプリは人の周囲でのコンテンツの配置を制御しません。システムは、表示時に各「Window」の初期位置と「Volume」を設定します。 また、システムはウィンドウバーを追加して、「Window」の位置を変更したり、サイズを変更したりできるようにします。

「Volume」ついて詳しくは、「Human Interface Guidelines > Windows」を参照してください。

6. 周囲に3Dコンテンツを表示

アプリのコンテンツの配置をより詳細に制御する必要がある場合は、そのコンテンツを「ImmersiveSpace」に追加します。ImmersiveSpaceは、コンテンツ用の無制限の領域を提供し、空間内のコンテンツのサイズと配置を制御します。ユーザーから許可を得た後、ImmersiveSpaceでARKit を使用して、コンテンツを周囲の環境に統合することもできます。たとえば、ARKitシーンの再構築を使用して、家具や近くのオブジェクトのメッシュを取得し、コンテンツをそのメッシュと対話させることができます。

ImmersiveSpace」は、アプリの他のシーンと一緒に作成するシーンタイプです。 次の例は、ImmersiveSpaceとWindowを含むアプリを示しています。

@main
struct MyImmersiveApp: App {
    var body: some Scene {
        WindowGroup() {
            ContentView()
        }


        ImmersiveSpace(id: "solarSystem") {
            SolarSystemView()
        }
    }
}

ImmersiveSpace 宣言にスタイル修飾子を追加しない場合、システムはmixedスタイルを使用してその空間を作成します。このスタイルでは、ユーザーの周囲を示すパススルーコンテンツとともにコンテンツが表示されます。他のスタイルでは、さまざまな程度でパススルーを非表示にすることができます。

immersionStyle(selection:in:)で、空間がサポートするスタイルを指定します。 複数のスタイルを指定した場合は、修飾子の選択パラメータを使用してスタイルを切り替えることができます。

【注意】
mixedスタイルを使用する没入型シーンにどれだけのコンテンツを含めるかに注意してください。画面の大部分を占めるコンテンツは、たとえそのコンテンツが部分的に透明であっても、周囲にある潜在的な危険をユーザーが認識できない可能性があります。 人物をコンテンツに没入させたい場合は、fullスタイルでスペースを構成します。
詳しくは「Creating fully immersive experiences in your app」を参照してください。

ImmersiveSpace に配置するアイテムの位置を忘れずに設定してください。修飾子を使用して「SwiftUI」ビューを配置し、変換コンポーネントを使用して「RealityKit」エンティティを配置します。「SwiftUI」は、最初は空間の原点を人の足元に配置しますが、他のイベントに応じてこの原点を変更できます。たとえば、空間ペルソナを使用してコンテンツを表示する「SharePlay」アクティビティに対応するために、システムは原点をシフトする場合があります。「SwiftUI」ビューと「RealityKit」エンティティを相互に相対的に配置する必要がある場合は、「RealityView」の content パラメータのメソッドを使用して、必要な座標変換を実行します。

ImmersiveSpace シーンを表示するには、「SwiftUI」環境から取得した openImmersiveSpace アクションを使用してシーンを開きます。このアクションは非同期で実行され、提供された情報を使用してシーンを検索して初期化します。次の例は、solarSystem 識別子を使用してスペースを開くボタンを示しています。

Button("Show Solar System") {
    Task {
        let result = await openImmersiveSpace(id: "solarSystem")
        if case .error = result {
            print("An error occurred")
        }
    }
}

アプリが ImmersiveSpace を表示すると、システムは視覚的な競合を防ぐために他のアプリのコンテンツを非表示にします。空間が表示されている間は他のアプリは非表示のままですが、空間を閉じると元に戻ります。 アプリで複数の空間を定義している場合は、別の空間を表示する前に、現在表示されている空間を閉じる必要があります。表示されている空間を無視しない場合、他の空間を開こうとすると、システムは実行時警告を発行します。

次回



この記事が気に入ったらサポートをしてみませんか?