为 visionOS 创建 Environment / 虚拟环境

学习如何使用全景图片或 360° 图片,为 visionOS 创建沉浸式的虚拟环境。

为 visionOS 创建 Environment / 虚拟环境
Photo by Sebastian Svenson / Unsplash

对于开发 visionOS 平台的应用,创建一个逼真且具有沉浸感的 Environment 是至关重要的。

这篇文章将向你介绍,如何使用 RealityKit 结合全景图片或 360° 图片创建一个栩栩如生的虚拟环境,打造出令人惊叹的视觉效果。

使用 RealityKit 创建虚拟环境

创建沉浸式的虚拟环境有多种方法,例如使用Blender等建模软件进行详细的3D建模,或通过 Skybox 技术创建背景和环境效果,还可以利用 Unity 等其他技术栈。

使用 Skybox 技术创建虚拟环境的优势是,你只需要使用 RealityKit 通过编程的方式来实现,这非常的简单和高效,特别适用于快速开发和原型设计。

可以实现什么效果?

可以创建一个 360 度环绕的沉浸式虚拟环境,可以通过加载不同的图片资源,快速切换不同的环境效果:

什么是 Skybox 技术

Skybox 技术是一种在 3D 图形和游戏中经常使用的背景渲染方法,广泛应用于各种图形和游戏引擎中(例如 Unity、Unreal Engine),用于创建逼真的背景和环境效果

10 TROPICAL ENVIRONMENT HD SKYBOX PACK | Unity AssetStore Price ...
Skybox Environment / Unity

想象一下,你站在一个立方体的中央,立方体的每个面都贴着一个大图像,这些图像连接在一起形成一个完整的全景画面。无论你往哪个方向看,看到的都是连续的背景图像,没有边界或空隙。

Skybox 是通过将场景包围在一个立方体内,并在其六个面上贴上预渲染的图像,从而模拟出一个无缝的背景环境,这就是 Skybox 技术的工作原理。

在游戏或虚拟现实中,Skybox 技术常用于创造逼真的天空或远景效果,让你感觉自己处在一个真实的环境中,而不是一个空荡荡的虚拟空间。

在 RealityKit 中使用 Skybox

截止 2024 年 6 月,苹果并未在 RealityKit 中直接提供用于实现 Skybox 的官方 API。因此,我们需要自己实现一个 createSkyBox 方法,好在这并不复杂。

注意:RealityKit 虽然提供了 skybox(_:) 方法,但目前并不适用于 visionOS:

创建 createSkyBox() 方法,该方法返回一个 Entity 对象,它的工作流程如下:

  • 使用 MeshResource 创建一个球体形状
  • 使用 SimpleMaterial 添加材质
  • 最后,通过 Entity 的 .components.set() 方法,将 Mesh 和 Material 组合创建一个 Entity 对象。

首先,你需要理解 RealityKit 中的基本概念:

在 RealityKit 中,Entity 是一个抽象的容器。它本身不具备任何属性,但可以包含各种组件(Component)。这些组件定义了 Entity 的外观、行为以及其他特性。

其中一个组件是 ModelComponent,这个组件定义了 Entity 的几何形状(Mesh)和材质(Material)。为了创建 ModelComponent 组件并添加到 Entity 中,我们首先需要定义 Mesh 和 Material。

我们一起来看看如何实现这个方法。

  1. 创建 Mesh 几何图形
//Mesh
let skyBoxMesh = MeshResource.generateSphere(radius: 1000)

MeshResource 是 RealityKit 提供用于生成几何形状的类,.generateSphere(radius:) 是这个类的方法之一,用于生成一个球体网格。最符合我们创建 skybox 的需求。

为了创建一个尽可能大的空间,因此我们通过 radius: 1000 指定了生成球体的半径为 1000 米。

  1. 创建 Material 材质
//Material
let skyBoxMaterial = SimpleMaterial(color: .systemPink, isMetallic: false)

RealityKit 提供了几种用于创建 Material 的方法,最常用的是 SimpleMaterial() 和 UnlitMaterial()。

上述代码使用 SimpleMaterial() 创建了一个简单的材质,color: .systemPink 指定了材质的颜色为系统粉红色 (systemPink),isMetallic: false 指定材质不具有金属光泽。

  1. 创建 ModelComponent 组件
//ModelComponent     
let modelComponent = ModelComponent(
    mesh: skyBoxMesh,
    materials: [skyBoxMaterial]
)

ModelComponent 是 RealityKit 中的一个组件类,负责定义实体的几何形状和外观。它包含网格(Mesh)和材质(Materials)等属性。通过 mesh 和 materials 参数,我们直接调用了刚才创建的 skyBoxMesh 和 skyBoxMaterial。

注意: materials 参数是一个数组,因此你必须使用 [] 来赋值。

  1. 添加 ModelComponent 到 Entity
//Entity
let skyBoxEntity = Entity()

skyBoxEntity.components.set(modelComponent)

首先创建一个 skyBoxEntity,这个 Entity 默认不具有任何内容。然后使用.components.set() 方法将 modelComponent 添加到 skyBoxEntity。

完整代码如下:

// create a createSkyBox function which return a Entity object

func createSkyBox() -> Entity? {
    //Mesh
    let skyBoxMesh = MeshResource.generateSphere(radius: 1000)
    
    //Material
    let skyBoxMaterial = SimpleMaterial(color: .systemPink, isMetallic: false)
    
    //ModelComponent     
    let modelComponent = ModelComponent(
        mesh: skyBoxMesh,
        materials: [skyBoxMaterial]
    )
    
    //Entity
    let skyBoxEntity = Entity()
    
    skyBoxEntity.components.set(modelComponent)
    
    //reverse texture surface
    skyBoxEntity.scale *= .init(x: -1, y: 1, z: 1)
    
    return skyBox
    
}

我们还额外添加了一段代码来反转球体的材质表面,使其从内部显示正确的图像:

//reverse texture surface
  skyBoxEntity.scale *= .init(x: -1, y: 1, z: 1)

这段代码将 skyBoxEntity 球体的材质内外反转。由于创建的 Skybox 球体默认情况下材质显示在外部,所以需要反转纹理表面,以确保图像从内部看起来正确。通过这种方式,我们成功创建了一个简单的球体,作为环绕虚拟环境的 Skybox。

在之前的示例中,我们使用 SimpleMaterial 方法将材质设置为简单的粉红色:

let skyBoxMaterial = SimpleMaterial(color: .systemPink, isMetallic: false)

因此,您会看到整个空间都是粉红色的:

下一步,我们将通过使用 UnlitMaterial 和加载实际的纹理图像,创建出更为真实和吸引人的虚拟环境。


使用图片为 Skybox 添加纹理

在上一步的演示代码中,我们通过使用 SimpleMaterial() 创建了一个简单的材质,这个材质仅包含简单的颜色:

let skyBoxMaterial = SimpleMaterial(color: .systemPink, isMetallic: false)

这显然并不如何吸引人,我们可以使用 UnlitMaterial() 来添加更加丰富的 Texture 纹理,从而创建出更加真实的空间场景:

在 RealityKit 中,实体(Entity)的外观由包含网格(Mesh)和材质(Material)的模型组件(ModelComponent)定义。材质可以包含纹理,从而增强视觉效果。

接下来,我们将之前使用的 SimpleMaterial 修改为 UnlitMaterial 实例,UnlitMaterial 支持自定义纹理效果:

// 创建 UnlitMaterial
var skyBoxMaterial = UnlitMaterial()

然后,我们需要创建一个 Texture 资源,并将它添加到 skyBoxMaterial 中。

在 Xcode 项目的 Assets 中,创建一个新的 Image Set,并添加一张全景照片或 360° 照片,这将作为纹理来加载:


如果你还没有全景照片或 360° 照片资源,可以查看我的下面这篇文章,详细介绍了如何获取免费的高清全景照片和 360° 照片:

如何免费下载超清全景照片与 360° 照片 / 8K / 12K
这篇文章会持续更新,如果我发现好的资源和平台会在这里分享出来。

使用 RealityKit 提供了 TextureResource 类来创建纹理资源,TextureResource.load(named:) 方法可以加载一个静态资源(例如图片)来创建纹理,下面代码将加载一个名为 shybox04-360 的 Image Set 作为纹理:

// 加载纹理
let skyBoxTexture = try TextureResource.load(named: "skybox04-360")
skyBoxMaterial.color = .init(texture: .init(skyBoxTexture))

由于 TextureResource.load(named:) 是同步加载,因此需要放在 do-catch 语句中以处理可能的错误:

// 加载纹理
do {
  let skyBoxTexture = try TextureResource.load(named: "skybox04-360")
  skyBoxMaterial.color = .init(texture: .init(skyBoxTexture))
} catch {
  print("Failed to load skybox texture: \(error)")
  return nil
}

最终的完整代码:

可以通过导入不同的纹理图片,创建出不同的环境效果:

在模拟器或 Vision Pro 上运行代码,体验您亲手创建的沉浸式环境效果吧!

图片资源

免费下载高清全景图片/360° 图片

如果你还没有全景照片或 360° 照片资源,可以查看我的下面这篇文章,详细介绍了如何获取免费的高清全景照片和 360° 照片:

如何免费下载超清全景照片与 360° 照片 / 8K / 12K
这篇文章会持续更新,如果我发现好的资源和平台会在这里分享出来。

图片分辨率应当小于 8192

由于硬件性能的限制,Apple 目前允许加载的最大分辨率为 8192 x 8192,所以你在选择图片的时候需要注意这一点。

否则会出现虚拟环境无法加载或闪退,并在 Xcode 会出现 failed assertion `Texture Descriptor Validation 的错误:

参考资源

Construct an immersive environment for visionOS | Apple Developer Documentation
Build efficient custom worlds for your app.