在Day 4中我们使用了ImmersiveSpace
并在其中添加了一个立方体,但对这个立方体我们只配置了长宽高,并没有做进一步的操作。
本文中我们会通过纹理和材质对这个立方体的六个面分别进行不同的绘制。首先我们将ImmersiveView
分拆出来,先新建一个ImmersiveView.swift
文件,这是一个视图文件,所以请选择User Interface下的Swift View完成创建,其中的内容待我们编写完ViewModel
中的代码后再进行修改。
我们通过点击界面来打开沉浸式视图,所以需要一个ContentView.swift
文件来编写常规的窗口页面,在其中添加一个Toggle开关,用于打开和关闭沉浸式视图。
import SwiftUI
import RealityKit
struct ContentView: View {
@State var showImmsersiveSpace = false
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
var body: some View {
NavigationStack {
VStack {
Toggle("Show ImmersiveSpace", isOn: $showImmsersiveSpace)
.toggleStyle(.button)
}
.padding()
}
.onChange(of: showImmsersiveSpace) { _, newValue in
Task {
if newValue {
await openImmersiveSpace(id: "ImmersiveSpace")
} else {
await dismissImmersiveSpace()
}
}
}
}
}
#Preview {
ContentView()
}
首先我们定义了一个showImmsersiveSpace
变量供Toggle按钮开关时使用。然后要打开和关闭沉浸式空间,我们可以分别使用@Environment
中的openImmersiveSpace
和dismissImmersiveSpace
,通过onChange
修饰符来监控showImmsersiveSpace
变量的变化,在切换到打开时,就打开沉浸式空间。我们需要知道打开哪一个视图,所以使用了id
参数,这个参数应与应用入口文件中所设置的一致。
接下来就编写入口文件:
import SwiftUI
@main
struct visionOSDemoApp: App {
var body: some Scene {
WindowGroup() {
ContentView()
}
ImmersiveSpace(id: "ImmersiveSpace") {
ImmersiveView()
}
}
}
注意这里ImmersiveSpace
中所添加的id
与之前ContentView
中的要保持一致,我们在后面会学到在同一个应用中也可以添加多个WindowGroup
,同样通过配置id
进行区分。
接下来是核心文件ViewModel.swift
,
import SwiftUI
import RealityKit
@MainActor class ViewModel: ObservableObject {
private var contentEntity = Entity()
func setupContentEntity() -> Entity {
return contentEntity
}
func addCube() {
guard
let texture1 = try? TextureResource.load(named: "Number_1"),
let texture2 = try? TextureResource.load(named: "Number_2"),
let texture3 = try? TextureResource.load(named: "Number_3"),
let texture4 = try? TextureResource.load(named: "Number_4"),
let texture5 = try? TextureResource.load(named: "Number_5"),
let texture6 = try? TextureResource.load(named: "Number_6")
else {
fatalError("Unable to load texture.")
}
let entity = Entity()
var material1 = SimpleMaterial()
var material2 = SimpleMaterial()
var material3 = SimpleMaterial()
var material4 = SimpleMaterial()
var material5 = SimpleMaterial()
var material6 = SimpleMaterial()
material1.color = .init(texture: .init(texture1))
material2.color = .init(texture: .init(texture2))
material3.color = .init(texture: .init(texture3))
material4.color = .init(texture: .init(texture4))
material5.color = .init(texture: .init(texture5))
material6.color = .init(texture: .init(texture6))
entity.components.set(ModelComponent(
mesh: .generateBox(width: 0.5, height: 0.5, depth: 0.5, splitFaces: true),
materials: [material1, material2, material3, material4, material5, material6]
))
entity.position = SIMD3(x: 0, y: 1, z: -2)
contentEntity.addChild(entity)
}
}
同样我们创建了setupContentEntity()
方法,但并没有在方法里添加任何模型,而是将添加的工作交给了addCube()
方法,整个方法虽然很长,但有大量重复的代码,分别为六个面设置不同的图片。
TextureResource.load()
方法设置了不同的纹理SimpleMaterial()
创建了六个材质color
属性分别添加前面配置好的纹理Number_1.jpg
等图片请见我们GitHub的演示代码,我们先来说一下visionOS中的材质,常见的材质请见下图:
其中PhysicallyBasedMaterial
也即PBR和SimpleMaterial
是带光照信息的,而UnlitMaterial
和VideoMaterial
都是不带光照信息的。
在ModelComponent
方法中,我们使用了mesh
和materials
参数,mesh
参数我们同样使用了MeshResource.generateBox
来创建一个立方体,不同的是这次我们指定了splitFaces
参数,该参数设为true
表示顶点不进行合并,因为我们要对六个面分别设置颜色或图像,不指定该参数六个面都会使用相同的材质,在本例中也就是都显示为1
。
最后我们配置了position
,这里x, y, z坐标轴方向示意如下:
接下来我们修改ImmersiveView.swift
的内容如下:
import SwiftUI
import RealityKit
struct ImmersiveView: View {
@StateObject var model = ViewModel()
private var contentEntity = Entity()
var body: some View {
RealityView { content in
content.add(model.setupContentEntity())
model.addCube()
}
}
}
这里的代码相对简单,就是在RealityView
中展示ViewModel
中所添加的模型。
在运行应用前将Info.plist文件中的Preferred Default Scene Session Role切换回Window Application Session Role。
点击Show ImmersiveSpace按钮,会得到如下界面:
再次点击按钮就会收起这个模型。
示例代码:GitHub仓库
其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记