环境反射
在使用 iOS AR中 渲染虚拟物体时,RealityKit 默认使用了一个简单的天空盒(Skybox,即IBL环境资源贴图),所有带反射材质的物体默认会对天空盒产生反射。
但在AR 中,使用IBL 技术实现的天空盒反射有一个很大的问题,那就是不真实,因为天空盒由开发者在开发时设置,不能实时地反映用户使用时的真实环境。为解决这个问题,ARKit 提出了环境探头(Environment Probe)的概念,环境探头在一个特定点模拟了一个全向相机,向6个方向以90°视场角拍摄6张照片(这6个方向分别是十X、 X、+Y、 Y、+Z、一Z),因为视场角是90°,这6张照片捕提到了它周围各个方向的球面图,然后将捕获的图像存储为一个 Cubemap(立方体贴图),这样就可供具有反射材质的对象使用。
利用环境探头捕捉的立方体贴图环境信息,在PBR渲染的基础上,我们就可以实现动态环境的实时反射,而且可以通过调整 PBR 中的 Metallic 和 Smoothness 值实现不同程度的反射效果以便更好进行虚实融合。
环境探头
环境探头理论上能够实时地反映动态的环境,但在AR中还存在一个问题,其原因在于AR 中需要反射的环境是用户的真实环境,而这些真实环境信息只能来自于用户的设备摄像机,在用户 720°扫描其周围环境之前,环境探头无法获取其所需要的所有环境信息,因此无法生成立方体贴图。这是一个需要权衡折中的问题,我们无法强制要求用户必须先720°扫描其周边环境,那只能采用另外一种不精确的以用户已经扫描过的环境信息推测用户所在环境的技术来补充所需信息。
设置多环境探头的方式对静态的、范围有限的场景是一种很好的解决方案,但在大场景中,这就需要设置非常多的环境探头,这显然也不是最理想的解决方案。而且,静态布置的环境探头无法反映动态的内容。以动态物体所在位置实时动态地生成立方体贴图就能解决这个问题。
每一个反射探头的实质是不断拍摄其所在位置6个方向的纹理制作成立方体贴图供反射物体使用,这是一个性能消耗很大的操作,实时的反射探头每帧都会生成一个立方体贴图,在提供对动态物体良好的反射时也会对性能造成很大的影响,为了降低性能消耗,常用的做法有烘焙(Bake)、手动更新(Manually),如对一个大厅的反射,可以预先烘焙到纹理中,但这种方式不能反映运行过程中的环境变化,手动更新可以根据需要在合适的时机人工更新,为达到比较好的性能与表现均
环境探头有位置(Probe Origin)和尺寸(Size)属性,ProbeOrigin 即为反射探头的原点,Size 定义了从其原点出发可以抓取的图像范围,Origin 和 Size 构成了反射探头的反射盒和所在位置,只有在反射盒里的物体才能被拍摄捕获,才能被利用该反射探头的物体所反射。
在 RealityKit 中,使用 ARWorldTrackingConfiguration配置类的 environmentTexturing 属性设置生成环境探头的方式,支持3种方式:None、Manual、Automatic,分别表示不使用环境探头,手动更新环境探头、ARKit 自动使用环境探头,具体如下表所示。
|----------------------------------------------------------------|----------------------|
| 名称 | 描述 |
| ARWorldTrackingConfiguration. Environment Texturing. none | 不使用环境探头,不使用环境反射 |
| ARWorld TrackingConfiguration. Environment Texturing. manual | 由开发人员手动设置,手动更新环境探头 |
| ARWorldTrackingConfiguration. Environment Texturing. automatic | 由ARKit 自动设置,自动更新环境探头 |
environmentTexturing 属性所描述的环境信息也是 ARKit 基于图像估计光照算法的基础,RealityKit会根据environmentTexturing属性的设定白动调整虚拟元素渲染。在AR 中使用环境探头采集真实环境信息并用于虚拟元素反射渲染能极大地提高虚拟元素的可信度和真实感,如图6-13所示,采用了这种技术的圆球能反射真实的白色纸杯,这对反射率很高的材质可以极大地增强其真实感,营造虚实难辨的自然感。
为进一步增强反射的真实性,ARKit 还支持环境探头配合 wantSHDREnvironmentTextures 属性使用,该属性为布尔值,用于设置是否使用 HDR(High Dynamic Range,高动态范围)反射,HDR图像能更真实地反映现实环境的亮度差,高保真还原真实环境信息。在RealityKit 中使用环境探头反射需要使用 AREnvironmentProbeAnchor 类,该类继本目 ARAnchor,用于在指定位置生成环境探头并采集环境信息。
- 提示:ARKit 只在配置 ARSession 为 ARWorldTrackingConfiguration 或者 ARBodyTrackingConfiguration 时才能使用环境反射,在其他配置情况下不支持环境反射。
环境反射操作
RealityKit 在渲染场景时默认不开启环境反射,在使用时,我们也可以手动将配置中 environmentTexturing属性设置为 none 来强制关闭环境反射,代码如下:
Swift
//
// EnvironmentTexturing.swift
// ARKitDeamo
//
// Created by zhaoquan du on 2024/1/30.
//
import SwiftUI
import ARKit
import RealityKit
struct TextWidthtKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
struct EnvironmentTexturing: View {
@State var automatic: Bool = true
@State var textWidth: CGFloat = 0
var body: some View {
GeometryReader { ge in
EnvironmentTexturingContainer(automatic: $automatic)
.overlay(content: {
VStack {
Spacer()
HStack{
Text(automatic ? "自动环境探头" : "不使用环境探头")
.background(GeometryReader {_ in
Color.white
//.preference(key: TextWidthtKey.self, value: $0.frame(in: .local).size.width)
})
// .onPreferenceChange(TextWidthtKey.self, perform: { width in
// print("----------------w: \(ge.size.width), tw: \(width)")
// self.textWidth = width
// })
.padding(10)
.offset(x: 0 )
Toggle(isOn: $automatic) {}
.frame(width: 50)
.offset(x: 0)
}
Spacer().frame(height: 40)
}
})
.edgesIgnoringSafeArea(.all)
.navigationTitle("环境探头")
}
}
}
struct EnvironmentTexturingContainer : UIViewRepresentable{
@Binding var automatic: Bool
func makeUIView(context: Context) -> ARView
{
let arView = ARView(frame: .zero)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
if automatic {
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
config.environmentTexturing = .automatic
config.wantsHDREnvironmentTextures = true
context.coordinator.arView = uiView
uiView.session.delegate = context.coordinator
uiView.automaticallyConfigureSession = false
uiView.session.run(config, options: [.resetTracking,.removeExistingAnchors])
return
}else{
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
config.environmentTexturing = .none
context.coordinator.arView = uiView
uiView.session.delegate = context.coordinator
uiView.session.run(config, options: [.resetTracking,.removeExistingAnchors])
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject,ARSessionDelegate {
var arView: ARView? = nil
var parent: EnvironmentTexturingContainer
init(parent: EnvironmentTexturingContainer) {
self.parent = parent
}
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
guard let anchor = anchors.first as? ARPlaneAnchor else {
return
}
let mesh = MeshResource.generateSphere(radius: 0.1)
let meterial = SimpleMaterial(color: .blue, isMetallic: true)
let modelEntity = ModelEntity(mesh: mesh, materials: [meterial])
let planAnchor = AnchorEntity(anchor: anchor)
//放在正上方
modelEntity.transform.translation = [0, planAnchor.transform.translation.y + 0.05,0]
planAnchor.addChild(modelEntity)
arView?.scene.addAnchor(planAnchor)
//只添加一次
session.delegate = nil
session.run(ARWorldTrackingConfiguration())
}
}
}
我们手动将 environmentTexturing 属性设置为 none来关闭环境反射功能,这样创建的球体将不会对用户真实的环境进行反射效果如下左图所示。但在前文的学习中我们已经知道,RealityKit 默认会使用其内置的天空盒进行反射,这也就是我们能在光滑的球体表面看到模糊光斑的原因。
对代码进行修改,当我们设置 environmentTexturing 为 automatic 时,RealityKit 将自行决定在用户的环境中放置一个或者多个环境探头,并利用这些环境探头采集的环境信息渲染虚拟元素的反射,效果如上图右图所示。
在代码中我们加入了 arView.automaticallyConfigureSession = false 这一行语句,如果不设置禁止 automaticallyConfigureSession,自动环境反射不会起作用,因为 ARKit 的自动配置会覆盖开发者关于环境反射的设置。另外,我们还加人了config.wantsHDREnvironmentTextures = true这一行语句,这是允许 ARKit使用HDR 环境图进行反射,使反射效果更真实。