ARKit 将物体隐藏在墙后

ARKit hide objects behind walls

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

这里有两个问题。

(而你甚至没有use regular expressions!)

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

如果您将 SceneKit material 的 colorBufferWriteMask 设置为空值(Swift 中的 []),任何使用该 material 的对象都将获胜' 出现在视图中,但它们在渲染期间仍会写入 z 缓冲区,这会影响其他对象的渲染。实际上,您会得到一个形状像您的对象的 "hole",通过它可以显示背景(在 ARSCNView 的情况下是相机馈送),但它仍然可以遮挡其他 SceneKit 对象。

您还需要确保被遮挡的节点在它应该遮挡的任何其他节点之前渲染。您可以使用节点层次结构来执行此操作(我不记得父节点是在其子节点之前呈现还是相反,但它很容易测试)。层次结构中的对等节点没有确定的顺序,但您可以使用 renderingOrder 属性 强制执行一个顺序,而不管层次结构如何。 属性 默认为零,因此将其设置为 -1 将在所有内容之前呈现。 (或者为了更好地控制,将多个节点的 renderingOrder 设置为一系列值。)

如何检测 walls/etc 以便知道放置遮挡几何体的位置?

在 iOS 11.3 及更高版本(又名 "ARKit 1.5")中,您可以打开 vertical plane detection. (Note that when you get vertical plane anchors back from that, they're automatically rotated. So if you attach models to the anchor, their local "up" direction is normal to the plane.) Also new in iOS 11.3, you can get a more detailed shape estimate for each detected plane (see ARSCNPlaneGeometry),无论其方向如何。

然而,即使你有水平和垂直,平面的外部限制也只是随时间变化的估计值。也就是说,ARKit 可以快速检测到墙的一部分在哪里,但是如果用户不花一些时间四处挥动设备来绘制出 space,它就不知道墙的边缘在哪里。即使这样,映射的边缘也可能不会与真实墙壁的边缘精确对齐。

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

很好的解决方案:

GitHub: arkit-occlusion

对我有用。

但就我而言,我想通过代码设置墙壁。因此,如果您不想由用户设置墙壁 -> 使用平面检测来检测墙壁并通过代码设置墙壁。

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

要创建遮挡 material(也称为黑洞 material 或阻塞 material),您必须使用以下实例属性:.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

要创建一个遮挡 material 非常简单

    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

ARKit 4 和 LiDAR 扫描仪

您可以将任何对象隐藏在复制真实墙壁几何形状的虚拟隐形墙后面。 iPhone 12 Pro 和 iPad Pro 4th Gen 配备了 LiDAR(光探测和测距)扫描仪,可帮助我们重建周围环境的 3d 拓扑图。 LiDAR 扫描仪大大提高了 Z 通道的质量,允许从 AR 场景中遮挡或移除人类。

LiDAR 还改进了对象遮挡、运动跟踪和光线投射等功能。使用 LiDAR 扫描仪,您甚至可以在没有照明的环境中或在没有任何特征的白墙的房间中重建场景。得益于 sceneReconstruction 实例 属性,ARKit 4.0 中周围环境的 3d 重建成为可能。有了墙壁的重建网格,现在可以非常轻松地将任何物体隐藏在真实墙壁后面。

要在 ARKit 4.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
        }
    }
}