【visionOS】visionOS向けアプリ開発の基礎


## 0. はじめに…

WWDC23にて、新たなXRデバイスであるVision ProがAppleから発表されました。それに伴い、Vision Proのアプリケーション開発向けのセッションが数多く公開されました。そのセッションの一つである、「[Get started with building apps for spatial computing](https://developer.apple.com/videos/play/wwdc2023/10260/)」はvisionOSとはどういったOSなのか、どのようにアプリケーションを開発すれば良いのかなどを詳細に説明されています。本記事では、上記のセッションを軸に、visionOS向けアプリ開発の基礎知識などをまとめていくとともに、簡単なvisionOS向けのサンプルアプリを作成していきます。

## 1. 3つの基本要素

visionOS向けのアプリケーションは以下の3つの要素が組み合わさせることで、構築されています。また、継続的な没入感を実現できるアプリを構築するための柔軟なツールセットでもあります。
- Window
- Volumes
- Spaces

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/720bdaa9-ecea-2a21-b489-7a56c64fa7b2.png)


### 1.1 Windowについて

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/9171a3a7-3706-1ce5-62dc-a70186b0b241.png)


アプリケーションは基本的に1つ、あるいは複数のWindowを有しています。visionOSのアプリケーションもまた、同様に1つ、あるいは複数のWindowを有することになります。

Windowを利用することで、2Dと3Dをうまく組み合わせて表示することができ、Windowのサイズや位置などを自由に変更することも可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/f7ac7f7f-9d31-850b-4a65-de7c8d2e0b5c.png)

Windowを実際にコードを呼び出すときは以下のように呼び出してあげる必要があります。

```swift
@main
struct WorldApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

WindowGroupの中のViewでは、3Dコンテンツを追加することができ、2Dと3Dを組み合わせたViewを構築することができます。

3Dコンテンツを導入するためには、Model3Dのビューを活用することが必要です。

```swift
import SwiftUI
import RealityKit

struct GlobeModule: View {
    var body: some View {
        Model3D(named: "Satellite")
    }
}
```

加えて、SwiftUIは様々なジェスチャ認識機能を携えており、WWDC23にて新たに3Dコンテンツ向けのジェスチャ認識機能も追加されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/a30cb14c-77b4-cbc9-26ff-08a68d28ddff.png)


以下のコードのように、空間内でのタップジェスチャを有効にし、衛星を掴んで移動するようにできます。

```swift
struct Globe: View {
    
    @GestureState private var myState: State? = nil
    
    var body: some View {
        ZStack(alignment: .bottom) {
            Model3D(named: "Satellite")
                .gesture(
                    DragGesture()
                        .TargetedToAnyEntity()
                        .updating($myState)
                    { value, state, _ in
                            ...
                    }
                )
        }
    }
}
```

### 1.2 Volumeについて

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/e06991cc-9989-4443-1e65-897fca2fb023.png)

Volumeは、仮想空間内の決められた領域内で3Dコンテンツを表示することができ、他のアプリケーションとスペースを共有することが可能です。
表示された3Dコンテンツは様々な角度からコンテンツを確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/12f36515-6b0c-a80c-2218-3ae50589174e.png)

また、VolumesはWindowの拡張機能のため、コード上ではWindowGroupを用います。

```swift
@main
struct WorldApp: App {
    var body: some Scene {
        WindowGroup {
            Model3D(named: "Globe")
        }
        .windowStyle(.volumetric)
        .defaultSize(width: 0.6, height: 0.6, depth: 0.4, in: .meters)
    }
}
```

ただ、Windowと異なるのは、windowStyleで.volumetricを指定し、defaultSizeで領域を決定している点です。
また、Volumeのビューに対して、より詳細にカスタムしたいときはRealityViewを用いるのが良いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/cd225e6c-eeeb-363e-4daf-b3b603eeda5a.png)

RealityViewは以下のような特徴があります。
- Volumesに対して、多くのコンテンツやさまざまなジェスチャを追加することができる。
- シーンに追加できる新たなビューである、RealityViewを使用することで、任意の数のエンティティをSwiftUI内で直接管理することができる。
- RealityViewが提供する変換関数を利用することで、座標空間間の変換を簡単に行えることができる。
- RealityViewは以下のようなコードで呼び出すことができる。
    - makeクロージャ：エンティティを作成し、それらをルートエンティティにアタッチする。
    - updateクロージャ：いつでも呼び出され、ビューの状態を更新する。
    - attachmentsクロージャ：RealityViewがビューをエンティティに変換できるようにするタグプロパティを用いて、SwiftUIビューをシーンに追加する。
    
    ```swift
    // 3Dコンテンツ上にピンを刺す
    @State private var pinLocation: GloleLocation?
    
    RealityKit {
    		// makeクロージャ
        ...
    } update: { content, attachments in
    		// updateクロージャ
        ...
        if let pin = attachments.entity(for: "pin") {
            content.addChild(pin)
            placePin(pin)
        }
    } attachments: {
    		// attachmentクロージャ
        if let pinLocation {
            GlobePin(pinLocation: pinLocation)
                .tag("pin")
        }
    }
    ```
    
### 1.3 Spaceについて
   
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/e87d06e5-343c-f5e7-f408-345fa8c0cd1e.png)

Spaceは、アプリケーションへの没入度を制御したり、複数のWindowやVolumeなどを画面に表示したりすることができます。セッション内では大きく2つのSpaceが紹介されていました。
- Shared Space
- Full Space

まず、Shared Spaceは、複数のアプリケーションを同一の空間で開くことを可能とします。
一方、Full Spaceは、システムが提供するジェスチャに加えて、より詳細なスケルトン ハンド トラッキングを取得し、アプリの没入度をより細かく制御することができます。
また、アプリの没入度に関しても、以下の2つの概念が紹介されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/2e840e82-0e70-d4f5-ae73-f0294672e651.png)

**① PassThroughについて**
PathThroughは以下の特徴があります。
- パススルーを使用することで、コンテンツを現実世界に固定し、ユーザーは周囲の環境を確認することできる。
- 空間オーディオを活用し、RealityKitを介して3Dをレンダリングすることで、デバイスが部屋の空間情報を継続的に取得・更新し、ビジュアルとサウンドをユーザーの環境に溶け込ますことができる。
- また、仮想オブジェクトが本当に自分の部屋に属していると感じさせる現象を自動的に利用している。

**②Fully Immersiveについて**
Fully Immersiveは以下の特徴があります。

- 視野全体を覆いこむFully Immersive Spaceにレンダリングすることも可能である。
- Fully Immersive Spaceにより、仮想オブジェクトの照明をカスタマイズしたり、オーディオ特性を選択したりすることで、アプリの創造的な意図を柔軟に実現することができる。


PassThroughとFully Immersiveは対比関係にあります。
また、没入度をコントロールするのに、Immersive Styleというパラメータも追加されました。
Immersive Styleは以下の3種類が存在します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/978a6756-56dd-cc38-9510-5517e42b2bb3.png)

**① .mixed**
.mixedは現実空間と仮想空間を組み合わせた状態を表し、最もPathThroughな状態です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/cc788edb-5aa4-1d87-a0fd-fa629f43bffd.png)


**② .progressive**
.mixedと.fullの中間に位置し、視界のほとんどを仮想空間に包み込む状態にはしますが、部分的に現実空間も取り入れた状態です。Vision Pro搭載のDigital Crownを回転させることで、.mixedの状態に近づけたり、.fullの状態に近づけたりすることができます。

![スクリーンショット 2023-12-13 10.01.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/b7051d05-4bf3-f29b-b8d0-54b35262750f.png)


**③ .full**
.fullはユーザーの視界を完全に仮想空間に置き換え、没入感を最大限にした状態となります。.fullの状態時は、現実空間は全く見えないようになります。

![スクリーンショット 2023-12-13 10.01.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/2aafa661-f1d3-115b-a775-7f07d797ad4a.png)

## 2. サンプルアプリの作成
### 2.0 サンプルアプリの概要
今回作成するアプリは、空間上に3Dオブジェクトを配置、表示するアプリを作成していきます。コードとしても、非常にシンプルなものとなりますので、ぜひ試しに実践してみてください。

### 2.1 プロジェクトの作成
まずXcodeのプロジェクト作成画面を開き、「visionOS > App」を選択し、Nextをクリックします。
![スクリーンショット 2023-12-13 17.18.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/4db58bc2-f485-7d74-70df-f3d859eb7813.png)

つぎに、以下の項目を設定し、保存場所を指定して、プロジェクトを作成します。
- Product Name：任意の名前
- Team：任意
- Organization Identifier：任意
- Bundle Identifier：任意 (自動生成)
- Initial Scene：Window
- Immersive Space Renderer：None
- Immersive Space：None

![スクリーンショット 2023-12-13 18.11.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/df162856-6299-a5ee-4640-2724b66218de.png)


### 2.2 表示したいオブジェクトデータの取得
以下のApple Developer公式のサイトにて、様々な3Dオブジェクトのサンプルを提供しています。.usdzという3Dオブジェクトを扱う拡張子のデータをダウンロードすることもできます。
今回のサンプルアプリでは、以下のサイトから「toy_drummer_idle」という3Dオブジェクトを仮想空間上に配置していきます。

https://developer.apple.com/augmented-reality/quick-look/

(ちなみに「toy_drummer_idle」はこの子↓)
![スクリーンショット 2023-12-13 18.23.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/773915/e3364773-74c3-3dc6-2d3f-c3b7f64a1360.png)

### 2.3 いざ実装！
プロジェクト生成時のContentView.swiftファイルのコードは以下のようになっていると思います。

```swift
import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView: View {
    var body: some View {
        VStack {
            Model3D(named: "Scene", bundle: realityKitContentBundle)
                .padding(.bottom, 50)

            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview(windowStyle: .automatic) {
    ContentView()
}
```

上記のコードを以下のように、置き換えます。
```swift
struct ContentView: View {

    // 配置したいオブジェクトのダウンロードURLを設定する。
    private let url = URL(string: "https://developer.apple.com/augmented-reality/quick-look/models/drummertoy/toy_drummer_idle.usdz")!
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("↓ Toy Drummer Idle")
            }
            
            Model3D(url: url) { model in
                model.resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 200, height: 200)
            } placeholder: {
                ProgressView()
            }
        }
        .padding()
    }
}
```
コードは以上です！

あとは、実行ビルドを行い、シミュレータで起動するだけです。(これだけ短いコードで仮想空間上に3Dオブジェクトを配置できることに非常に驚きました！)

## 3. 最後に...

今回紹介したものは、基礎部分となり、RealityKitやARKitを駆使することで、より没入感のある、面白いアプリケーションを作成することができます。
私自身も、初めはiOSアプリ開発に興味があり、Swiftを触り始めましたが、まさかXRの領域まで手を伸ばせるとは思いもしませんでした。継続的にvisionOS向けの技術もインプットしていきたいと思います。
2024年の春には、アメリカ本土でVision Proが発売予定とされていますが、早く日本にも上陸してほしいです！

## 4. 参考記事

https://developer.apple.com/jp/visionos/learn/

https://developer.apple.com/augmented-reality/

https://github.com/satoshi0212/visionOS_30Days