【问题标题】:ARKit hide objects behind wallsARKit 将物体隐藏在墙后
【发布时间】:2017-12-07 04:19:00
【问题描述】:

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

【问题讨论】:

  • 你能根据你看到的和你期望的添加图片吗? (我对ARKit一无所知,但仍然无法理解你的问题)

标签: ios swift scenekit augmented-reality arkit


【解决方案1】:

这里有两个问题。

(你甚至没有use regular expressions!)

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

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

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

如何检测墙壁/等,以便知道在哪里放置遮挡几何?

在 iOS 11.3 及更高版本(又名“ARKit 1.5”)中,您可以开启vertical 平面检测。 (请注意,当您从那里获得 vertical 平面锚点时,它们会自动旋转。因此,如果您将模型附加到锚点,它们的本地“向上”方向与平面垂直。)在 iOS 11.3 中也是新的,您可以为每个检测到的平面获得更详细的形状估计(参见ARSCNPlaneGeometry),无论其方向如何。

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

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

【讨论】:

  • 那么文档中的示例代码可以选择configuration.planeDetection =.horizontal 什么都不做?
  • 该代码开启了平面检测。没有它,ARKit 根本不会报告飞机。
  • 是的,但我的意思是,如果您可以将平面检测设置为水平,您应该可以将其设置为垂直
  • @Steve(在 Xcode 中)你可以跳转到 .horizontal 的定义(⌃⌘click),你会在那里找不到其他选项。如果Apple 将选项集扩展为“垂直”,我不会感到惊讶——不过未来可能还会有其他类型的飞机。
  • 嗨 Rick,部分遮挡会比这种技术复杂得多吗?
【解决方案2】:

要创建一个遮挡材质真的很简单

    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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      很好的解决方案:

      GitHub: arkit-occlusion

      为我工作。

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

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

      【讨论】:

        【解决方案5】:

        ARKit 4 和激光雷达扫描仪

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

        激光雷达还改进了对象遮挡、运动跟踪和光线投射等功能。使用 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
                }
            }
        }
        

        【讨论】:

        • 这仅适用于您有 ARView 而不是 ARSCNView 的情况。您知道如何使用 ARSCNView 来实现吗?
        • 在这里您可以了解如何操作:developer.apple.com/forums/thread/654431。使用遮挡材质而不是彩色材质。
        • 您能否描述一下如何使用 ARSCNView 完成它?谢谢。
        • @pavelcauselov,我添加了一段代码,涉及 SceneKit 的 LiDAR 扫描实现。
        • @AndyFedoroff 谢谢!但是您能否通过 github 分享工作代码,因为我没有运气,我的“便签”仍然在真实物体前面的墙上...
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-20
        • 2018-04-03
        • 1970-01-01
        相关资源
        最近更新 更多