見出し画像

Vision Pro で3次元の太陽系作ってみた

この記事はjig.jp Advent Calendar 2023の12月19日(火)の記事です。


こんちには、株式会社 jig.jp でアプリ開発を担当している入社2年目の三宅です。趣味がカメラで、休日には県内外問わず、写真撮影に出かけることが多いです📸
最近は動物撮影の楽しさに気づき水族館や動物園に行くことが多いです。
↓の画像は地元の動物園にいるタンチョウを撮影したものです。

さて、今回は 2023年6月に開催された WWDC23 で発表された Apple 初の空間コンピューターである「Vision Pro」用のアプリを作ってみましたので、それについてコードの解説やツールの使い方を紹介します。

作成したアプリについて

今回作成したアプリは、宇宙空間で太陽系について学ぼう正面から骸骨が迫ってくるゲーム という 2 つのモードが遊べるものになっています。
宇宙空間で太陽系について学ぼうでは実際に宇宙空間にある太陽系を見ながらそれぞれの惑星を見ることができます。
正面から骸骨が迫ってくるゲームでは名前の通り、正面から骸骨がこちらに向かって移動してくるので避けようというゲームです。

今回は宇宙空間で太陽系について学ぼうについて紹介していきます。

作成したアプリのプロジェクトは GitHub のリポジトリとして公開しています。

開発環境

  • PC: MacBook Pro(M1 Max)

  • macOS: Sonoma 14.1.2

  • Xcode: Xcode 15.1 beta3

  • visionOS: 1.0

  • デバイス: Vision Pro(シミュレーター)

Xcode のダウンロードはこちらから。

プロジェクトの作成

Xcode を開き、メニューバーの File > New > Project を選択し visionOS のテンプレートプロジェクトを選択してください。
Product Name 等の設定画面が開きましたら、Initial Scene を Window、Immersive Space RendererをRealityKit、Immersive Space を Mixedにしてください。Initial Scene はアプリの初期 Scene、ImmerSive Space Renderer は Immersive Space の render に何を使うのか、Immersive Space はどのようにオブジェクトを表示するかを決めるものです。
詳しくは Apple 公式のドキュメントをご覧ください。

初期アプリについて

プロジェクト生成後のアプリについて解説します。
まずはアプリを build して実行してみましょう。下記画像のような空間と Window が表示されたかと思います。トラックパッドのピンチイン・アウトで前後に移動したり、2本指でスクロールさせると上下左右に移動できます。

Show Immersive Space のボタンをタップすると球体が3つになります。
では実際に 3D オブジェクトの表示方法や動作について説明していきます。

Reality Composer Pro

Reality Composer Pro とは visionOS と iOS アプリ向けの空間オブジェクトを配置、変換、プレビューすることができるツールです。visionOS を開発するうえでは必須のツールになります。
Reality Composer Pro は、プロジェクト直下の Packages/RealityKitContent/Package.realitycomposerpro を開き、右上の Open in Reality Composer Pro から開くことができます。3D オブジェクトごとに位置や回転、サイズを簡単に変更できるのでかなり便利です。

SwiftUI で3D オブジェクトを表示

まずは ContentView.swift を開きましょう。3D オブジェクトを表示させるにはいくつか方法がありますが初期コードでは Model3D(named:bundle:) を使っています。named に Reality Composer Pro で作成した Scene(.usda ファイル)を指定し、budle にはアプリの main budle を指定します。

Model3D(named: "Scene", bundle: realityKitContentBundle)

ImmersiveSpace の表示

3Dオブジェクトの下に Show Immersive Space と書かれたボタンがあり、タップすると球体が2つ出てきます。これは、ボタンタップによって showImmersiveSpace の状態が変化するので、それを onChange モディファイアで検知して openImmersiveSpace(id:) を呼び出し、球体が2つある Immersive Space を表示させる処理になっています。また、openImmersiveSpace で指定する id(任意)は xxxApp.swift に記述する ImmersiveSpace(id:) の id と一致させる必要があります。

struct ContentView: View {
    @State private showImmersiveSpace = false
    @Enviroment(\.openImmersiveSpace) var openImmersiveSpace
    
    var body: some View {
        VStack {
            // ...
        }
        .onChange(of: showImmersiveSpace) { _, newValue in
            Task {
                if newValue {
                    // id を "ImmersiveSpace" に指定
                    switch await openImmersiveSpace(id: "ImmersiveSpace") {
                        // ...
                    }
                }
            }
        }
    }
}

@main
struct xxxApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        ImmersiveSpace(id: "ImmersiveSpace") {
            // ボタンがタップされたときに表示される View
            // RealityKitView を使って Immersive.usda を表示している
            ImmersiveView()
        }
    }
}

太陽系を表示させる

初期コードの解説と Reality Composer Pro の紹介が終わりましたのでさっそく太陽系を作っていきましよう。以下の動画のような太陽系を3Dで表示します。

Reality Composer Pro で惑星を並べる

Reality Composer Pro を開いて太陽系の惑星をダウンロードしましょう。ウィンドウの右上のプラスアイコンから Apple が用意している 3D オブジェクトを使うことができます。そこから太陽系の惑星をダウンロードしてください。

つづいて太陽系用の Scene を用意しそこに惑星たちを配置していきましょう。Scene の作成はメニューバーから File>New>Scene でできます。作成した Scene にそれぞれの惑星を配置します。大きさや位置については調べながら実際の太陽系に合わせてください。今回作成する Scene は .usda というファイル形式になります。ソースコードからこの .usda を読み込むことで Reality Composer Pro で作成した Scene を取得し表示させることができます。

RealityKitView を使って3D オブジェクトを表示する

Reality Composer Pro を使わずに、RealityKitView を使って手軽に .usdz の3D オブジェクトを表示することができます。info.plist がある階層に USDZ というフォルダを作成し、Reality Composer Pro で使った sun.usdz をコピーしてフォルダ内にペーストしてください。
RealityKitView を使って追加した .usdz の3D オブジェクトを表示します。

import SwiftUI
import RealityKit

struct Home: View {
    var body: some View {
        RealityKitView { content in
            guard let sun = try? await ModelEntiry(named: "Sun.usdz") else {
                return
            }
            content.add(sun)
        }
    }
}

3D オブジェクトにタップ判定を追加する

初期アプリではトグルボタンをタップすると Immersive Space が表示されました。今回はせっかくですので3D オブジェクトがタップされたら Immersive Space が表示されるようにしましよう

import SwiftUI
import RealityKit

struct Home: View {
    RealityKitView { content in
        guard let sun = try? await ModelEntiry(named: "Sun.usdz") else {
            return
        }
        sun.components.set(InputTargetComponent())
        sun.components.set(CollectionComponent(shapes:
        [ShapeResource.generateBox(size: SIMD3<Float>(repeating:
        0.1))]))
        content.add(sun)
    }
    .gesture(
        SpatialEventGesture()
            .targetedToAnyEntity()
            .onEnded { _ in
                Task {
                    await openImmersiveSpace(id: "planet")
                }
            }
    )
}

宇宙空間を作成

太陽系の Immersive Space を表示するまでの準備はできました。次は実際に太陽系の Immersive Space を作ります。既に Reality Composer Pro で宇宙空間の .usda(太陽系の Scene) は作成済なので RealityKitView を使って表示させましょう。

import SwiftUI
import RealityKit
import RealityContent

struct PlanetSpace: View {
    var body: some View {
        RealityKitView { content in
            guard let planets = try? await Entity(named: "Planet.usda", in: realityContent) else {
                return
            }
            content.add(plants)
        }
    }
}

これだけだと空間に太陽系が表示されるだけです。もっとリアルにするために宇宙空間を作りましょう。
Project 直下に ImageTexure.skybox というフォルダを作り、宇宙空間となる画像をおきます。さらに3D オブジェクトに画像ベースの環境ライティングを与えるために真っ白の画像もおきます。

宇宙空間の画像と環境光の白画像を RealityKitView で使ってみます。

import SwiftUI
import RealityKit
import RealityContent

struct PlanetSpace: View {
    var body: some View {
        RealityKitView { content in
            // ImageTexture.skyboxにspace.pngを置いておく
            guard let resource = try? await TextureResource.load(named: "space") else {
                return
            } 
            var material = UnlitMaterial()
            material.color = .init(texture: .init(resource))
            
            // Immersive Spaceを球体にする
            let entity = Entity()
            entity.components.set(ModelComponent(mesh: .generateSphere(radius: 1000), materials: [material]))
            entity.scale *= .init(x: -10, y: 10, z: 10)
        
            guard let planets = try? await Entity(named: "Planet.usda", in: realityContent),
                  let whiteLighting = try? await EnvironmentResource(named: "whiteLighting") 
            else {
                return
            }
            let imageLight = ImageBasedLightComponent(source: .single(whiteLighting). intensityExponent: 0.5) // ここの値で環境ライティングの明るさを調整
            planets.components.set(imageLight)
            planets.components.set(ImageBasedLightReceiverComponent(imageBasedLight: planets))
            contetn.add(plants)
        }
    }
}

再度実行してみると...
太陽系がちゃんと宇宙空間にあるようにできました👏

まとめ

RealityKit を使った visionOS のアプリを作ってみましたがなかなか楽しかったです。
今回紹介しなかった 正面から骸骨が迫ってくるゲーム はコードが多少複雑になっていますがよりゲーム感があって面白いです。

また、今回は Vision Pro がまだ発売されていないためシミュレーターを使って動作確認をしましたが、シミュレーターだと動作確認に関して限界あるなと感じました(ハンドトラッキングとか)。来年に米国で発売されるらしいのですが、購入して遊んでみてもいいかもしれませんね。


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