NTTドコモR&Dの技術ブログです。

AppleVisionPro/VisionOSのアプリ開発をやってみた

※ 本記事は 2026/3/31 以前にNTTコノキューにて記載した記事になります

はじめに

今年6月のWWDCで公開されたApple初のXRデバイス「Vision Pro」ですが、まだ実機はほとんどの人が触れない状態です。

しかし開発環境は全て提供されており、シミュレータ上では一部機能を除いて誰でも利用可能になっています!

NTTコノキューで遠隔作業支援サービス「NTT XR Real Support」や新規案件を検討する上で、このデバイスを触らないわけにはいかない!ということでデモアプリ開発を行っているので、その知見を共有したいと思います。

この記事では

  • VisionOSのシミュレータ上でサンプルを使った開発方法の概要
  • 簡単な変更(オブジェクトを手で移動させる、Webブラウザを表示する)を行う
  • ARKitの利用

についてをまとめます。

AppleVisionProとは

Apple Vision Pro外観

Apple Vision Pro外観

  • 今年6月のWWDCでAppleから発表された新デバイス
  • 公式↓

Appleが開発した初の空間コンピュータ Vision Proは、従来のディスプレイの枠を超えて広がるアプリのための無限のキャンバスを作り出し、ユーザーの目と手、声という、最も自然で直感的な操作方法によって、完全に3次元化されたユーザーインターフェイスを実現します。世界初となる空間オペレーティングシステム、visionOSを搭載したVision Proにより、まるでデジタルコンテンツが自分のいる空間に物理的に存在しているかのように楽しむことが可能となります。

https://www.apple.com/jp/newsroom/2023/06/introducing-apple-vision-pro/

つまり、いわゆるパススルー(ビデオシースルー)型のHMDで、VR/AR/MRの体験がこれ1つで出来ます!

AppleVisionProとは:いつ買える?どこで体験できる?

・公式↓

Apple Vision Proは3,499米ドルから、apple.com/storeおよび米国のApple Store直営店では来年初旬より、その他の国や地域では来年の後半より販売を開始します。

https://www.apple.com/apple-vision-pro/

・体験は、AppleDeveloper向けのDeveloperLabでのみ可能(日本は東京でOK)

DeveloperLab

AppleDeveloperProgramに参加しているメンバーなら誰でもエントリー可能
https://developer.apple.com/visionos/labs/

(ちなみに)浅井も参加させていただきました。行ったこと以外は言えないのでこの話は以上です🙏

AppleVisionProのアプリ開発

前置きはこんなもんで、ようやく本題です!

Xcodeの提供

Unityでの開発

  • VisionOS向けのアプリ開発は一部のiOS/Unityエンジニアが取り組んでいる段階
  • Unityではまだ触れないので、Xcodeの純正VisionOSアプリ開発を行ってみました
  • SDKはXcode15(β)と合わせて提供されるため、こちらから「beta」のものをDLすればOK
  • 最新はXcode 15.1 beta 2(October 26, 2023)
  • Xcodeでの開発なので、言語はSwift。画面の考え方などもiOSと近い(ようです)

Xcode画面

  • シミュレータが充実していて、ビルドしなくてもシミュレータを動かせるので、プログラムを書きながら実機での見え方・挙動を確認出来ます
  • シミュレータはiOSと同様、プリインアプリも操作出来ます

Xcodeシミュレータ

AppleVisionProのアプリ開発の流れ

ざっくり流れは以下

1.XcodeでVisionOSを選択し、「App」を選択

XcodeでのOS選択

2.Initial Scene, Immersive Spaceを選択(次ページで説明します)

Scene選択

3.2.の設定に応じてサンプルコードが設定され、プロジェクが立ち上がります

4.3.のサンプルコードを改変して自分なりのVisionOSアプリを作ればOK ContentView, ImmersiveViewの2つの画面が基本 ContentView→アプリ起動時の2Dウィンドウ ImmersiveView→3Dの体験を行う空間

Xcode画面

Initial Scene, Immersive Spaceとは

  • Windows

ブラウザとか、iOS/iPadOS向けアプリとかの、所謂「従来のスマホアプリ」 3Dアプリの中でも操作パネルやステータス表示などで扱うことがあるかも

  • Volumes

3Dオブジェクト Unityで扱うCube, Cylinder等と同様。四角形のオブジェクトとして空間上やUIとの関連も行う。MRTK等でも同様に扱われる「従来の3D(VR/MR)アプリ」

Initial Scene, Immersive Spaceとは

  • .mixed(Passthrough) MR空間上でCGコンテンツを表現する状態。カメラ映像を通した現実世界上にWindows, Volumesを表現する。

  • .progressive(Progressive) Passthrough/Fully Immersiveが複合された状態。この場合はCrownで没入度を変更可能。

  • .full(Fully Immersive) VR/MR空間上でCGコンテンツを表現する状態。VR↔︎MRの体験を制御可能。VR→MRとか、MR→VRとかみたいな体験も可能だし、VRアプリとして提供も可能(たぶん)。 MR=Passthroughだと思うので、VR的な没入度の高いアプリを作る場合に利用すると想定。 この場合は没入度はアプリ側で設定されたものから変更不可。

mixed/progressive/full


https://developer.apple.com/videos/play/wwdc2023/10260/

つまり

.mixed

こっちが.mixed アプリが複数立ち上がっていて、どれでも利用できます。

.full

こっちが.full アプリは1つだけ見える ここから出ないと他アプリは利用不可です。

アプリの考え方はHoloLens/MagicLeapとほぼ同じ、という印象です。

AppleVisionProのアプリ開発:やってみた

長々と書きましたが、

  1. .mixedの空間上でWebサイトにアクセスし
  2. .fullの空間に遷移して空間上にオブジェクトを表現し、オブジェクトをつまんで動かせる

という、簡単なアプリの実装方法をご紹介します。

環境

PC:MacBookPro  Chip:Apple M2 Max  OS:macOS Ventura Version 13.6 Xcode:Version 15.0 beta 8 (15A5229m)

AppleVisionProのアプリ開発:ContentView

ContentView

.mixedの空間上でWebサイトにアクセスし

まずはここからです。WebView自体は別ファイルで用意して、ContentViewでそれを呼び出します。ContentViewはXcodeでプロジェクトを選択すると基本的な画面に関する記述(ボタンクリックでオブジェクトサイズを変更、ボタンクリックでImmersiveSpaceへ遷移/戻る)は既に記載されているので、それをカスタマイズする形で、自分好みに書き換えてください。

私の場合は、

  • Webview表示
  • トグルのオンオフでCGオブジェクト位置を変更

を追加しています。

ContentView.swift
<body>
<pre>
<code>
import SwiftUI
import RealityKit
import RealityKitContent
import WebKit
import UIKit

struct ContentView: View {
    
    @State private var ensmall = false
    @State private var showImmersiveSpace = false
    @State private var immersiveSpaceIsShown = false
    
    @Environment(\.openImmersiveSpace) var openImmersiveSpace
    @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
    
    @State  var changeXAxis:Bool = false
    
    let website = "www.google.com/"
    
    var body: some View {
        
        WebView(website: website)
        
        VStack {
            RealityView { content in
                // Add the initial RealityKit content
                if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) {
                    content.add(scene)
                }
            } update: { content in
                // Update the RealityKit content when SwiftUI state changes
                if let scene = content.entities.first {
                    let uniformScale: Float = ensmall ? 0.1 : 1.0
                    scene.transform.scale = [uniformScale, uniformScale, uniformScale]
                }
            }
            .gesture(TapGesture().targetedToAnyEntity().onEnded { _ in
                ensmall.toggle()
            })
            
            RealityView { content in
                // Add the initial RealityKit content
                if let Cube = try? await Entity(named: "TestCube", in: realityKitContentBundle) {
                    content.add(Cube)
                }
            } update: { content in
                // Update the RealityKit content when SwiftUI state changes
                if let Cube = content.entities.first {
                    let uniformPosition: Float = changeXAxis ? 0.2 : 0
                    Cube.position = [-uniformPosition, 0, 0]
                }
            }
            
            VStack {
                Toggle(isOn: $changeXAxis) {
                    Text(changeXAxis ? "change position left" : "change position right")
                }.padding().glassBackgroundEffect()
                
                Toggle("Enlarge RealityView Content", isOn: $ensmall)
                    .toggleStyle(.button)
                
                Toggle("Show Immersive Space", isOn: $showImmersiveSpace)
                    .toggleStyle(.button)
            }.padding().glassBackgroundEffect()
        }
        .onChange(of: showImmersiveSpace) { _, newValue in
            Task {
                if newValue {
                    switch await openImmersiveSpace(id: "ImmersiveSpace") {
                    case .opened:
                        immersiveSpaceIsShown = true
                    case .error, .userCancelled:
                        fallthrough
                    @unknown default:
                        immersiveSpaceIsShown = false
                        showImmersiveSpace = false
                    }
                } else if immersiveSpaceIsShown {
                    await dismissImmersiveSpace()
                    immersiveSpaceIsShown = false
                }
            }
        }
    }
}

#Preview {
    ContentView()
}
</code>
</pre>
</body>

WebviewはiOSアプリ向けに用意されるフレームワークをそのまま転記してます。特に手を加えず既存のフレームワークが使えるのは便利ですね。

Webview.swift
<body>
<pre>
<code>
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    var website: String
    
    func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
        let webView = WKWebView()
        if let url = URL(string: "https://" + website) {
            webView.load(URLRequest(url: url))
            webView.allowsBackForwardNavigationGestures = false
        }
        
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
        
    }
}
</code>
</pre>
</body>

AppleVisionProのアプリ開発:ImmersiveView

ImmersiveView

.fullの空間に遷移して空間上にオブジェクトを表現し、オブジェクトをつまんで動かせる

今後は3Dコンテンツの体験を作ります。RealityKit ComposerProで用意したCGオブジェクトをロードし、それをドラッグできるようにします。

RealityKit ComposerProでCGオブジェクトをロード→接触判定を設定

①右上の+ボタンから、②既存のオブジェクトをロードします。 UnityのようにCubeやCapsuleなど標準のオブジェクトでも、自身で作成したオブジェクトでも構いません。

ComposerPro

自分の好みの配置でオブジェクトを設定したら、以下のようにCollisionなどを設定します。この設定はその他のComponentを追加するなどして、好みに設定しても構いません。

Collisionなどを設定

あとはImmersiveViewで以下のようにオブジェクトの呼び出し、ドラッグ操作を記載します。

ImmersiveView.swift
<body>
<pre>
<code>
import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {
    
    @State var earth = Entity()
    
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content     
            earth = try! await Entity(named: "Earth", in: realityKitContentBundle)
            earth.position = [0, 1, -1.5]
            content.add(earth)
            
        }
        .gesture(
            DragGesture()
                .targetedToEntity(earth)
                .onChanged { value in
                    earth.position = value.convert(value.location3D, from: .local, to: earth.parent!)
                })
    }
}

#Preview {
    ImmersiveView()
        .previewLayout(.sizeThatFits)
}

</code>
</pre>
</body>

AppleVisionProのアプリ開発:出来ること

  • Swiftのフレームワークは基本使える(使えないorまだ使えないものもある)ので、iOSアプリ開発の延長で色々できます 今回はWebviewを呼び出しました。

  • 3Dオブジェクトは専用ツール(RealityComposerKit)で作れます Unityに近いツールで、3Dモデルの挙動も設定可能。3Dモデルの接触判定などはこちらで設定し、接触したらどうなるかの記述はXcode側で書く、みたいな感じです。 今回はドラッグで動かせるように設定しました。アニメーションなども定義できるようです。

Xcodeシミュレータ

  • 実行中にVisualizationを選択すると、空間上のメッシュなどを表示出来る シミュレータ上だとあまり意味はないですが…

Xcode上でのシミュレータ

空間上のメッシュ表示

AppleVisionProのアプリ開発:まだ出来ないこと

  • ARKitはシミュレータ上ではまだ使えないようです(ビルドが通りませんでした) iOSでARKitを使わずに使えるジェスチャ操作(ドラッグやスワイプ)はシミュレータ上でも使えるので、CGオブジェクトをドラッグする操作はいけました。 MR的なコンテンツでは手のトラッキングや視線の入力などを使って色々体験を作り込んでいきたいと思いますが、、今はまだ難しいようです。 ただ、ビルドが通らないだけなので、プログラムを書くこと自体はいけます。もしかしたら挑戦している方もいるのかもしれません…

  • カメラ情報へのアクセスが出来ない Appleはプライバシーに配慮し、アプリからは利用出来ないとのことです。 Fourmでは”現時点ではNO”で、最終的にはまだ不明との回答。どうなるのでしょうか…! https://developer.apple.com/forums/thread/736379

  • ということで、ARKit周りは色々リファレンスはありますが、なかなかどう使って良いものか、という状況です。

ARKit

ハンドトラッキング

ハンドトラッキングも可能で、指ごとに呼び出せるようです。実機で試したいですね!

最後に

AppleVisionPro/VisionOS開発はまだまだ序盤です。今のうちから始めればストア公開の頃には何かアプリが出せるかも、、?

NTTコノキューでも、引き続き開発を進めていきたいと思います。

興味がある方はぜひチャレンジしてみてください!