【Swift】Vision OS で自作のオブジェクトを配置してみる
初めに
最近はずっと Flutter を使っていたため、Swift、SwiftUI の復習を兼ねて Vision OS を動かしてみたいと思います。今回はオブジェクトを配置するまで実装していきたいと思います。
記事の対象者
- Swift, SwiftUI 学習者
- Vision OS に触れてみたい方
実装
今回は Blender で制作した3Dモデルを取り込むところからやってみたいと思います。
具体的な手順は以下の通りです。
- Blender から Reality Converter へエクスポート
- Reality Converter からプロジェクトへエクスポート
- Vision OS プロジェクトの作成
- App の設定
- ContentView の作成
- モデルを表示させる画面の作成
- 実行結果
Blender から Reality Converter へエクスポート
まずは Vision OS で表示させたい3Dモデルの Blender のモデルのプロジェクトを開きます。今回は以下の飛行機のモデルを取り込んでみます。
次にモデルのエクスポートを行います。
画像のように、File > Export > glTF2.0(.glb/.gltf) を選択してエクスポートします。なお、この際ウィンドウの右側でエクスポートの設定が可能です。エクスポートする際にモディファイアが含まれないことがあるので、Geometry > Mesh > Apply Modifiers の部分にチェックを入れておいた方が良いかと思います。
次に Reality Converter へのインポートを行います。
以下のような表示の部分に先程エクスポートしたファイルをドラッグ&ドロップするだけでインポートは完了します。
完了すると以下のようにインポートしたモデルが表示されるようになります。
Reality Converter からプロジェクトへエクスポート
モデルが正常にインポートされていることが確認できたら以下の画像のように、右上のボタンから「書き出す」を選択して、USDZ形式でエクスポートします。
次に先程エクスポートしたファイルをプロジェクト内にドラッグ&ドロップしてプロジェクトでモデルを使用できるようになります。
Vision OS プロジェクトの作成
ここから Xcode を用いた実装に移っていきます。
Xcode の「Create Project」で以下の画面に移り、「visionOS」タブを選択して「App」を選択して「Next」を押します。
次に以下の画面に移るのでプロジェクトの名前を設定します。
なお、Initial Screen は「Window」に設定しておきます。
保存先のパスを指定してプロジェクトの作成は完了です。
App の設定
App のコードの全文は以下のようになります。
import SwiftUI
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup(id: "Main") {
ContentView()
}
}
}
VisionOS のプロジェクトは基本的に SwiftUI に基づいているため、SwiftUI をインポートしてビューを構築していきます。
この辺りのコードの記述方法は通常の iOS のアプリケーションと全く同じであると言えると思います。
ContentView の作成
次に ContentView
の実装を行います。
コードの全文は以下のようになります。
import SwiftUI
import RealityKit
import RealityKitContent
struct ContentView: View {
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
var body: some View {
NavigationSplitView {
List {
Text("Item List")
}
.navigationTitle("Hello World !")
} detail: {
ScrollView() {
LazyVGrid(
columns: Array(repeating: .init(.flexible()), count: 3),
alignment: .center,
spacing: 4
) {
Image("airplane")
.resizable()
.scaledToFill()
.onTapGesture {
Task {
await openImmersiveSpace(id: "airplane")
}
}
}
}
.navigationTitle("Content")
.padding()
}
}
}
#Preview {
ContentView()
}
ContentView の見た目は以下のようになります。
以下の部分では @Environment
を用いて、ImmersiveSpace を開く、閉じるための処理をそれぞれ変数として定義しています。
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
以下の部分では NavigationSplitView
を実装しています。
NavigationSplitView {
List {
Text("Item List")
}
.navigationTitle("Hello World !")
} detail: {
ScrollView() {
LazyVGrid(
columns: Array(repeating: .init(.flexible()), count: 3),
alignment: .center,
spacing: 4
) {
Image("airplane")
.resizable()
.scaledToFill()
.onTapGesture {
Task {
await openImmersiveSpace(id: "airplane")
}
}
}
}
.navigationTitle("Content")
.padding()
}
NavigationSplitView
では detail
に要素を置くことで、左右に分かれた表示をさせることができます。
detail
の要素の一つとして画像を表示しており、それをタップすると openImmersiveSpace
が非同期処理で発火するようになっています。
なお、今はまだ id が airplane
の ImmersiveSpace を設定していないため、エラーが出力されます。
airplane
の ImmersiveSpace は以下のように App のコードに追加することでボタンをタップした際に開けるようになります。
import SwiftUI
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup(id: "Main") {
ContentView()
}
+ ImmersiveSpace(id: "airplane") {
+ AirplaneView()
+ }
}
}
モデルを表示させる画面の作成
最後にモデルを表示させるための AirplaneView
を作成します。
コードは以下のようになっています。
import SwiftUI
import RealityKit
import RealityKitContent
struct AirplaneView: View {
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissWindow) var dismissWindow
var body: some View {
VStack {
RealityView { content in
if let immersiveContentEntity = try? await Entity(named: "AirplaneScene", in: realityKitContentBundle) {
content.add(immersiveContentEntity)
}
}
.onAppear {
dismissWindow(id: "Main")
}
}
}
}
#Preview {
AirplaneView()
}
RealityView
は Reality Composer Pro で作成された RealityKit コンテンツなどを visionOS アプリで 3D コンテンツを表示するために使用します。
RealityView
の中でコンテンツを定義し、content.add
としてコンテンツを追加していくことができます。
なお、airplane
というモデルをインポートしましたが、ここで読み込んでいるのは AirplaneScene
というモデルです。
この AirplaneScene
は先程インポートした airplane
モデルの座標を変更したものです。
以下で詳しく解説します。
AirplaneScene
を Reality Composer Pro で開くと以下のようになっています。
position
の部分が 0, 150, -150
になっています。これは Vision OS の座標の原点(0, 0, 0) がユーザーの足元にあることに起因しています。
y軸は3D空間における高さ、z軸は奥行きを示しているため、0, 150, -150
という座標は、ユーザーの足元から高さ 150cm、奥側に 150cm の位置になります。
実行結果
最後に実行してみた結果は以下のようになります。
GIF に変換してアップロードしようとしましたが、容量が大きすぎたので、Dropbox で共有します。
こちらからご覧ください。
なお、画像のみだと以下のようになります。
ContentView
AirplaneView
他の角度から観察
まとめ
最後まで読んでいただいてありがとうございました。
今回は単にオブジェクトを表示させるだけでしたが、これからどんどんオブジェクトを追加したり、オブジェクトを動かせるようにしていきたいと思います。
あと、個人的に重たい動画を Zenn に掲載する際の良い方法があればご教授いただけると嬉しいです。
Discussion