ARKit如何将物体隐藏在墙后面?

32

我如何利用ARKit跟踪的水平和垂直平面,将对象隐藏在墙壁/真实物体后面?当前,当您离开房间和/或在它们应该在后面的物体前面时,可以穿过墙壁看到添加的3D对象。因此,是否可以使用ARKit提供给我的数据,提供更自然的AR体验,而不会让对象穿过墙壁呢?


你能否添加从你所看到的和期望的图片吗?(我对ARKit一无所知,但仍然无法理解你的问题) - mfaani
5个回答

35

您有两个问题。

(而且您甚至没有 使用正则表达式!)

如何为 ARKit/SceneKit 创建遮挡几何体?

如果将 SceneKit 材质的 colorBufferWriteMask 设置为空值(在 Swift 中为 []),使用该材质的任何对象都不会出现在视图中,但它们仍会在渲染期间写入 z 缓冲区,从而影响其他对象的渲染。实际上,您将获得一个形状类似于您的对象的“孔”,通过这个“孔”,可以看到背景(在 ARSCNView 的情况下是相机反馈),但它仍然可以遮挡其他 SceneKit 对象。

您还需要确保遮挡物在它应该遮挡的其他节点之前渲染。您可以使用节点层次结构来实现这一点(我记不清父节点是在子节点之前还是之后进行呈现,但很容易测试)。在层次结构中同级的节点没有确定性顺序,但是您可以使用renderingOrder属性强制指定顺序,无论层次结构如何。该属性默认为零,因此将其设置为-1将在所有内容之前呈现。(或者为了更精细的控制,将几个节点的renderingOrder设置为一系列值。)

如何检测墙壁等障碍物以便知道在哪里放置遮挡几何体?

在iOS 11.3及以上版本(也称为“ARKit 1.5”)中,您可以打开垂直平面检测。 (请注意,当您从中获取垂直平面锚点时,它们会自动旋转。因此,如果您将模型附加到锚点上,则它们的本地“向上”方向与平面法线相同。)另外,在iOS 11.3中还可以获得每个检测到的平面的更详细的形状估计(请参见ARSCNPlaneGeometry),无论其方向如何。

然而,即使您拥有水平和垂直方向,平面的外部限制只是随时间变化的估计值。也就是说,ARKit可以快速检测出墙壁的一部分位置,但是如果没有用户花费一些时间来映射空间,它不知道墙壁的边缘在哪里。即使这样,映射出的边缘可能也不会与真实墙壁的边缘完全对齐。

如果您使用检测到的竖直平面来遮挡虚拟几何体,您可能会发现一些本应隐藏的虚拟对象通过显示出来,可能是因为在墙壁边缘没有完全隐藏或者在ARKit没有映射整个真实墙壁的地方可见。(您可以通过假设比ARKit更大的范围来解决后一个问题。)

是的,但我的意思是,如果您可以将平面检测设置为水平,则应该能够将其设置为垂直。 - Steve
@Steve(在Xcode中),您可以跳转到.horizontal的定义(⌃⌘单击),并且您将在那里找不到其他选项。如果Apple扩展了选项集以包括“vertical”-以及可能在未来的其他平面类型,我也不会感到惊讶。 - PDK
嗨Rick,部分遮挡技术会比这种技术复杂得多吗? - Benjohn
@Benjohn 您所说的部分遮挡是什么意思?只有实际位于遮挡物后面(从相机的角度)的部分将不可见。 - mnuages
抱歉,我的表述不够清晰:我想问的是如何模拟一个部分透明的“遮挡物”,而不是完全不透明的。对象的被遮挡部分可能会以降低的 alpha 值进行渲染。因此,在桌子遮挡物有一个方块推入时,你仍然可以看到桌子下面的方块,但它会变得模糊。 - Benjohn
显示剩余3条评论

2

要创建遮挡材质(也称为黑洞材质或阻塞材质),您需要使用以下实例属性:.colorBufferWriteMask.readsFromDepthBuffer.writesToDepthBuffer.renderingOrder

您可以这样使用它们:

plane.geometry?.firstMaterial?.isDoubleSided = true
plane.geometry?.firstMaterial?.colorBufferWriteMask = .alpha  
plane.geometry?.firstMaterial?.writesToDepthBuffer = true
plane.geometry?.firstMaterial?.readsFromDepthBuffer = true
plane.renderingOrder = -100

可以采用以下方式实现:

func occlusion() -> SCNMaterial {

    let occlusionMaterial = SCNMaterial()
    occlusionMaterial.isDoubleSided = true
    occlusionMaterial.colorBufferWriteMask = []
    occlusionMaterial.readsFromDepthBuffer = true
    occlusionMaterial.writesToDepthBuffer = true

    return occlusionMaterial
}

plane.geometry?.firstMaterial = occlusion()
plane.renderingOrder = -100

1
为了创建遮挡材质,非常简单。
    let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)

    // Define a occlusion material 
    let occlusionMaterial = SCNMaterial()
    occlusionMaterial.colorBufferWriteMask = []

    boxGeometry.materials = [occlusionMaterial]
    self.box = SCNNode(geometry: boxGeometry)
    // Set rendering order to present this box in front of the other models
    self.box.renderingOrder = -1

0

很好的解决方案:

GitHub: arkit-occlusion

对我有用。

但在我的情况下,我想通过代码设置墙壁。因此,如果您不想让用户设置墙壁->使用平面检测来检测墙壁并通过代码设置墙壁。

或者在4米范围内,iPhone深度传感器可以工作,并且您可以使用ARHitTest检测障碍物。


0

ARKit 6.0和LiDAR扫描仪

您可以将任何对象隐藏在虚拟的不可见墙后面,这个墙会复制真实墙壁的几何形状。配备有LiDAR扫描仪的iPhone和iPad Pro帮助我们重建周围环境的三维拓扑地图。 LiDAR扫描仪极大地提高了Z通道的质量,使得可以遮挡或从AR场景中移除人物。

此外,LiDAR还改进了诸如对象遮挡、运动跟踪和射线投射等功能。使用LiDAR扫描仪,即使在没有光源的环境或完全没有特征的白墙房间中,也可以重建场景。在ARKit 6.0中,通过sceneReconstruction实例属性,周围环境的3D重建成为可能。有了重建的墙壁网格,现在可以轻松地将任何对象隐藏在真实的墙壁后面。

要在ARKit 6.0中激活sceneReconstruction实例属性,请使用以下代码:

@IBOutlet var arView: ARView!

arView.automaticallyConfigureSession = false

guard ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh)
else { return }

let config = ARWorldTrackingConfiguration()
config.sceneReconstruction = .mesh

arView.debugOptions.insert([.showSceneUnderstanding])
arView.environment.sceneUnderstanding.options.insert([.occlusion])
arView.session.run(config)


如果你正在使用SceneKit,请尝试以下方法:

@IBOutlet var sceneView: ARSCNView!

func renderer(_ renderer: SCNSceneRenderer, 
          nodeFor anchor: ARAnchor) -> SCNNode? {

    guard let meshAnchor = anchor as? ARMeshAnchor 
    else { return nil }

    let geometry = SCNGeometry(arGeometry: meshAnchor.geometry)

    geometry.firstMaterial?.diffuse.contents = 
                            colorizer.assignColor(to: meshAnchor.identifier)
‍
    let node = SCNNode()
    node.name = "Node_\(meshAnchor.identifier)"
    node.geometry = geometry
    return node
}

func renderer(_ renderer: SCNSceneRenderer,
          didUpdate node: SCNNode,
              for anchor: ARAnchor) {

    guard let meshAnchor = anchor as? ARMeshAnchor 
    else { return }

    let newGeometry = SCNGeometry(arGeometry: meshAnchor.geometry)

    newGeometry.firstMaterial?.diffuse.contents = 
                               colorizer.assignColor(to: meshAnchor.identifier)

    node.geometry = newGeometry
}

这里是关于 SCNGeometrySCNGeometrySource 的扩展:

extension SCNGeometry {
    convenience init(arGeometry: ARMeshGeometry) {
        let verticesSource = SCNGeometrySource(arGeometry.vertices, 
                                               semantic: .vertex)
        let normalsSource = SCNGeometrySource(arGeometry.normals, 
                                               semantic: .normal)
        let faces = SCNGeometryElement(arGeometry.faces)
        self.init(sources: [verticesSource, normalsSource], elements: [faces])
    }
}

extension SCNGeometrySource {
    convenience init(_ source: ARGeometrySource, semantic: Semantic) {
        self.init(buffer: source.buffer, vertexFormat: source.format,
                                             semantic: semantic,
                                          vertexCount: source.count,
                                           dataOffset: source.offset,
                                           dataStride: source.stride)
    }
}

...以及 SCNGeometryElementSCNGeometryPrimitiveType 扩展:

extension SCNGeometryElement {
    convenience init(_ source: ARGeometryElement) {
        let pointer = source.buffer.contents()
        let byteCount = source.count * 
                        source.indexCountPerPrimitive * 
                        source.bytesPerIndex
        let data = Data(bytesNoCopy: pointer, 
                              count: byteCount, 
                        deallocator: .none)
        self.init(data: data, primitiveType: .of(source.primitiveType),
                             primitiveCount: source.count,
                              bytesPerIndex: source.bytesPerIndex)
    }
}

extension SCNGeometryPrimitiveType {
    static func of(type: ARGeometryPrimitiveType) -> SCNGeometryPrimitiveType {
        switch type {
            case .line: return .line
            case .triangle: return .triangles
        }
    }
}

2
只有在拥有ARView而不是ARSCNView的情况下才能正常工作。您知道如何在ARSCNView中实现这一点吗? - Mikael
在这里,您可以了解如何使用遮挡材质而不是彩色材质:https://developer.apple.com/forums/thread/654431。 - Andy Jazz
1
你能否稍微描述一下如何使用ARSCNView完成它?谢谢。 - pavelcauselov
@pavelcauselov,我添加了一个涉及SceneKit LiDAR扫描实现的代码。 - Andy Jazz
1
@AndyFedoroff谢谢! 但是你能否通过GitHub分享工作代码,因为我没有运气,我的“便签”仍然在真实物体的前面... - pavelcauselov
抱歉 @pavelcauselov,我无法分享完整的代码版本,因为我是在智能手机上写作。您可以复制粘贴以下这段代码... - Andy Jazz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接