VisionPro开发 - 轻松实现天空盒并添加光照

30 阅读4分钟

VisionPro开发系列首页:漫游Apple Vision Pro

系列项目全部代码:github.com/xuchi16/vis…


大家好,我是 xChester。

当我们希望给用户提供沉浸式体验时,常需要利用环境贴图构造天空盒。而 visionOS 本身不提供天空盒的功能,本文将实现一个简单的天空盒效果。

本文主要包含以下内容:

  • 简单的天空盒效果(Skybox),提供沉浸的体验

  • 为其中的物体添加基于图形的光照(Image Based Lighting,IBL),提供反射效果。

最终效果如下,可以看到沉浸式的天空和,同时其中的物体有相应的反射效果。

基本概念

HDRI(High Dynamic Range Imaging,高动态范围成像)环境贴图是一种提供详细光照信息的全景图像技术,用于在 3D 渲染和视觉效果中创建更加真实的光照和反射效果。

简单来说,当我们使用一张普通的全景图片作为环境贴图时,由于其中仅包含了拍摄时的曝光信息,当后续 3D 场景中渲染光照发生变化时图片中无论亮部还是暗部都会统一变化,如当光照变暗时,普通的图片会显得更“灰”。而相比于普通的全景图像,HDRI 图像在拍摄时就会对环境进行多次曝光,生成一系列曝光度不同的照片,并将它们合并成一个动态范围更广的图像。其中包含了更丰富的光照信息,包括色彩、亮度和反射细节等,在 3D 场景和模型的构建中能够营造更为真实的效果。

左。 HDR / 右。 JPG

IBL(Image Based Lighting)是一种在三维图形和视觉效果中使用的照明技术,通过使用 HDRI 图片来模拟光照,从而创造出更加真实和动态的视觉效果。RealityKit 就使用了 IBL 技术来模拟真实的反射效果。

主要步骤

  1. 获取 HDRI 资源并创建材质

  2. 创建模拟天空盒的球体并将 HDRI 贴图材质应用到天空盒上

  3. 加载 HDRI 资源并创建环境资源

  4. 创建环境中的物体并使用 IBL 光照

基本实现

获取 HDRI 资源并创建材质

PolyHeaven 中找到想要的 HDRI 图片并下载,我们这里使用了一张上海外滩的夜景图。下载该图并导入 Xcode 项目中。利用如下代码

  1. 创建TextureResource
  2. 创建UnlitMaterial并使用上述贴图

UnlitMaterial是一种简单的材料,不会对环境中的其他光源做出响应。

guard let resource = try? await TextureResource(named: "shanghai_bund_4k") else {
    fatalError("Unable to load texture.")
}
var material = UnlitMaterial()
material.color = .init(texture: .init(resource))

创建天空盒球体

创建一个半径为 1000 的巨大球体,并将上述贴图应用到该球体上。另外,需要指定其 scale 的 x 方向为负,这样可以将贴图材质应用到球体的内表面。此时运行程序,我们仿佛已经置身上海外滩。

skybox.components.set(ModelComponent(
    mesh: .generateSphere(radius: 1000),
    materials: [material]
))

// Reverse x to let the picture applied to the inner side of skybox
skybox.scale = .init(x: -1 * abs(skybox.scale.x), y: skybox.scale.y, z: skybox.scale.z)

加载 HDRI 资源并创建环境资源

但如果此时在场景中添加物体,会发现物体并没有反射天空盒中的景色,呈现出黑色的无光照效果。

为了给物体添加 IBL 光照,仍需要使用刚刚的 HDRI 环境贴图。在 RealityKit 中,想要将该图用作 IBL 资源,需要

  1. 创建一个文件夹,并将其后缀改为.skybox

  2. 将 HDRI 文件放入上述文件夹中

  3. 将该文件夹拖到 Xcode 项目中,并选择“Create folder reference”,将其添加到项目中

这样 Xcode 就能够将这张图片编译为环境资源,可以在代码中获取到该资源。

guard let environment = try? await EnvironmentResource(named: "shanghai_bund_4k") else {
    return
}

创建环境中的物体并使用 IBL 光照

创建一个球体,并将球体颜色设置为白色,isMetallic设置为ture,模仿光滑的金属表面,从而获得更好的反射效果。

let sphere = ModelEntity(
    mesh: .generateSphere(radius: 0.25),
    materials: [SimpleMaterial(color: .white, isMetallic: true)]
)

然后,创建一个ImageBasedLightComponent,通过该组件引用上述环境资源。再创建一个ImageBasedLightReceiverComponent作为关键光照的接收组件。将上述两组件应用到球体上,即可获得逼真的光照反射效果。

sphere.components.set(ImageBasedLightComponent(source: .single(environment)))
sphere.components.set(ImageBasedLightReceiverComponent(imageBasedLight: sphere))

最终效果

关注我

欢迎在掘金上关注我和我的专栏VisionOS Workshop,以及各种收藏/围观/评论/反馈/批评/Star/点歌