ios 当虚拟对象附加到其网格时,如何刷新创建ARView的SwiftUI UIViewRepresentable?

zazmityj  于 2023-08-08  发布在  iOS
关注(0)|答案(2)|浏览(82)

我正在尝试一个AR应用程序。我尝试做以下几点:
iOS设备通过ARView显示真实的场景,ARView创建网格。
每当网格更新时,我都想找到最靠近相机的顶点,并将虚拟对象附加到它(在删除可能先前附加的对象之后)。
我不确定我现在的代码是否正确,但它似乎做了我上面描述的事情。如果移动设备,显示的网格将更新。当找到一个新的最近顶点时,它会在MainView中切换一个状态变量refreshToggle,我希望更新后的网格与虚拟对象一起显示。但虚拟对象没有显示,我不明白为什么。
这是我的代码。我很抱歉它这么长,但我不知道该省略什么。任何帮助都欢迎!

struct MainView : View {
    @State private var refreshToggle = false // Toggled, when a new closest anchor is found
    var body: some View {
        ARViewContainer(refreshToggle: $refreshToggle).edgesIgnoringSafeArea(.all)
    }
}

struct ARViewContainer: UIViewRepresentable {
    @Binding var refreshToggle: Bool
    
    func makeUIView(context: Context) -> ARView {
        let arView = ARView(frame: .zero)
        arView.environment.sceneUnderstanding.options = []
        arView.environment.sceneUnderstanding.options.insert(.occlusion) // Turn on occlusion from the scene reconstruction's mesh.
        arView.environment.sceneUnderstanding.options.insert(.physics) // Turn on physics for the scene reconstruction's mesh.
        arView.debugOptions.insert(.showSceneUnderstanding) // Display a debug visualization of the mesh.
        arView.renderOptions = [.disablePersonOcclusion, .disableDepthOfField, .disableMotionBlur] // Disable not required render options
        arView.session.delegate = context.coordinator
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator($refreshToggle)
    }
    
    class Coordinator: NSObject, ARSessionDelegate {
        @Binding var refreshToggle: Bool
        var model: ModelEntity
        
        init(_ refreshToggle: Binding<Bool>) {
            self._refreshToggle = refreshToggle
            
            // Create a cube model
            let mesh = MeshResource.generateBox(size: 0.1, cornerRadius: 0.005)
            let material = SimpleMaterial(color: .gray, roughness: 0.15, isMetallic: true)
            model = ModelEntity(mesh: mesh, materials: [material])
        }
        
        func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
            var closestAnchor: ARAnchor? = nil
            for anchor in anchors {
                if let meshAnchor = anchor as? ARMeshAnchor {
                    let meshGeometry = meshAnchor.geometry
                    let vertices = meshGeometry.vertices

                    // Search for the vertex closest to the camera and place there a virtual marker object
                    let nrVertices = vertices.count
                    var closestVertex = SIMD3<Float>(x: 0, y: .infinity, z: 0) 
                    for i in 0 ..< nrVertices {
                        let nextVertex = meshGeometry.vertex(at: UInt32(i))
                        if nextVertex.y < closestVertex.y {
                            closestVertex = nextVertex
                            if closestAnchor?.identifier != meshAnchor.identifier {
                                // A new closest anchor has been found. Remove a virtual marker object
                                if let closestAnchor = closestAnchor {
                                    let anchor = AnchorEntity(anchor: closestAnchor)
                                    anchor.children.remove(model)
                                }
                            }           
                            closestAnchor = meshAnchor
                        }
                    }           
                    
                    // If a closest vertex was found, attach a virtual object to it
                    if let closestAnchor = closestAnchor {
                        let anchor = AnchorEntity(anchor: closestAnchor)
                        anchor.children.append(model)
                        refreshToggle = !refreshToggle // Let ARViewContainer redisplay the real scene with the mesh and a virtual object attached to the closest anchor
                    }
                } // if an ARMeshAnchor was found
            } // for all anchors
        } // session didUpdate anchors
    } // coordinator
    
}

extension ARMeshGeometry { // See https://developer.apple.com/documentation/arkit/armeshgeometry/3516924-vertices
    func vertex(at index: UInt32) -> SIMD3<Float> {
        assert(vertices.format == MTLVertexFormat.float3, "Expected three floats (twelve bytes) per vertex.")
        let vertexPointer = vertices.buffer.contents().advanced(by: vertices.offset + (vertices.stride * Int(index)))
        let vertex = vertexPointer.assumingMemoryBound(to: SIMD3<Float>.self).pointee
        return vertex
    }
}

字符串

zbsbpyhn

zbsbpyhn1#

编辑:下面代码的解释
我很高兴解决方案为您工作!
对于那些可能不熟悉这些代码的人来说,这是使用Apple的ARKit,RealityKit和SwiftUI生成增强现实视图。ARKit是Apple用于创建增强现实体验的框架,RealityKit是基于Swift的框架,用于创建增强现实的3D内容,SwiftUI是Apple用于声明式用户界面构建的框架。
这里的主要任务是在所有检测到的ARAchor中定位“最近的”ARAchor(增强现实空间中的3D点)。最近的ARAhonor是具有最大z坐标的顶点(3D空间中的点)的ARAhonor(因为在ARKit的基础SceneKit中,z轴从相机指向场景)。
一旦找到最接近的ARAhonor,它就被用来创建AnchorEntity,这是一个RealityKit实体,它将虚拟内容与现实世界的对象或位置连接起来。ARAchor用于在增强现实空间中放置虚拟ModelEntity(在本例中为立方体)。
AnchoringComponent(.world(transform:transform))用于相对于AnchorEntity定位和定向ModelEntity。该变换矩阵中的特定值将影响AR场景中模型的位置和方向。
因此,当您在具有AR功能的设备上运行此代码并四处移动时,您应该会看到一个小立方体出现在您的环境中,位于ARKit能够检测到的“最近”(最前方)表面上。
第一部分
SwiftUI不会像常规SwiftUI视图那样刷新UIViewRepresentable类型。换句话说,当一个状态被切换时,SwiftUI会重新呈现它的主体,但它不会对UIViewRepresentable类型做同样的事情。这就是为什么您的ARView无法刷新的原因。
您需要直接在ARView示例中处理更改,并在必要时手动触发更新。
1.每次找到一个新的锚点时,似乎都在创建一个新的AnchorEntity。跟踪单个AnchorEntity示例并在需要时更新其锚点可能更好。
1.将模型附加到锚点时,应将锚点添加到ARView的scene.anchors,而不是设置refreshToggle。
1.当找到新的最近锚点时,还应该从ARView的场景中删除旧锚点。
...

func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {

        // existing code...
    
        // Create a single AnchorEntity instance
        var anchorEntity: AnchorEntity?
    
        for anchor in anchors {
            // existing code...
    
            if let closestAnchor = closestAnchor {
                // If an anchorEntity already exists, remove it from the ARView's scene
                if let existingAnchor = anchorEntity {
                    existingAnchor.removeFromParent()
                }
    
                // Create a new AnchorEntity and attach the model to it
                anchorEntity = AnchorEntity(anchor: closestAnchor)
                anchorEntity?.children.append(model)
    
                // Add the anchorEntity to the ARView's scene
                DispatchQueue.main.async {
                    arView.scene.anchors.append(anchorEntity!)
                }
            }
        }
    }

字符串
现在,只要找到新的最近锚点,我们就直接更新ARView。它不会尝试刷新整个ARViewContainer,而是只更新AR场景的 * 相关 * 部分,这样效率更高,也避免了SwiftUI不刷新UIViewRepresentable类型的潜在问题。

kpbpu008

kpbpu0082#

问题解决了,虽然我不明白(没有计算机图形学的经验)。
找到最近的网格锚点后,必须创建AnchorEntity
在这个实体中,必须设置anchoring属性,并使用适当的AnchoringComponent.Target初始化AnchoringComponent。只有这样,才在场景中渲染虚拟对象。
下面的代码对我来说很有用,并且基于一些有价值的信息,KFDoom(+1)的答案,blog of Ethan Saadiatutorial of Ralf Ebert
下面是更新后的代码,以防有人想玩它。
.world(transform: transform)中的转换取自不同的版本,结果证明是有用的。

import ARKit
import RealityKit
import SwiftUI

struct MainView: View {
    var body: some View {
        ARViewContainer()
            .edgesIgnoringSafeArea(.all)
    }
}

struct ARViewContainer: UIViewRepresentable {
    
    func makeUIView(context: Context) -> ARView {
        let arView = ARView()
        
        // Configure the ARView to generate a mesh
        arView.environment.sceneUnderstanding.options = []
        
        // Turn on occlusion from the scene reconstruction's mesh.
        arView.environment.sceneUnderstanding.options.insert(.occlusion)
        
        // Turn on physics for the scene reconstruction's mesh.
        arView.environment.sceneUnderstanding.options.insert(.physics)
        
        // Display a debug visualization of the mesh.
        arView.debugOptions.insert(.showSceneUnderstanding)
        
        // For performance, disable render options that are not required for this app.
        arView.renderOptions = [.disablePersonOcclusion, .disableDepthOfField, .disableMotionBlur]
        arView.session.delegate = context.coordinator
        
        // Handle ARSession events via delegate
        context.coordinator.arView = arView
        arView.session.delegate = context.coordinator
        
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator: NSObject, ARSessionDelegate {
        var model: ModelEntity
        weak var arView: ARView?
        
        override init() {
            // Create a cube model
            let boxMesh = MeshResource.generateBox(size: 0.1, cornerRadius: 0.005)
            let material = SimpleMaterial(color: .gray, roughness: 0.15, isMetallic: true)
            model = ModelEntity(mesh: boxMesh, materials: [material])

            super.init()
        }

        func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
            var closestAnchor: ARAnchor? = nil
            guard let arView = arView else { return }
            
            // Create a single AnchorEntity instance
            var anchorEntity: AnchorEntity?
            
            for anchor in anchors {
                if let meshAnchor = anchor as? ARMeshAnchor {
                    let meshGeometry = meshAnchor.geometry
                    let vertices = meshGeometry.vertices
                    
                    // For debugging, we search for the vertex closest to the camera and place there a virtual marker object

                    let nrVertices = vertices.count
                    var closestVertex = SIMD3<Float>(x: 0, y: .infinity, z: 0) 
                    for i in 0 ..< nrVertices {
                        let nextVertex = meshGeometry.vertex(at: UInt32(i))
                        // The frontmost vertex has the largest z value, see https://developer.apple.com/documentation/scenekit/organizing_a_scene_with_nodes
                        if nextVertex.z > closestVertex.z {
                            closestVertex = nextVertex
                            if closestAnchor?.identifier != meshAnchor.identifier {
                                // A new closest anchor has been found. Remove the virtual marker object if it exists.
                                // If an anchorEntity already exists, remove it from the ARView's scene
                                if let existingAnchor = anchorEntity {
                                    existingAnchor.removeFromParent()
                                }
                            }           
                            closestAnchor = meshAnchor
                        }
                    }           
                    
                } // if an ARMeshAnchor found
            } // for all anchors
            
            // If a closest vertex was found, attach a virtual object to it
            if let closestAnchor = closestAnchor {
                // Create a new AnchorEntity and attach the model to it
                anchorEntity = AnchorEntity(anchor: closestAnchor)
                let transform = simd_float4x4([[0.96475136, 0.0, 0.26316252, 0.0], [0.0, 1.0, 0.0, 0.0], [-0.26316252, 0.0, 0.9647514, 0.0], [0.16189954, -0.25364277, -0.22894737, 1.0]])
                let anchoring = AnchoringComponent(.world(transform: transform))
                anchorEntity!.anchoring = anchoring
                anchorEntity!.addChild(model)
                arView.scene.anchors.append(anchorEntity!)
            }
        } // session didUpdate anchors
    } // coordinator
}

extension ARMeshGeometry { // See https://developer.apple.com/documentation/arkit/armeshgeometry/3516924-vertices
    func vertex(at index: UInt32) -> SIMD3<Float> {
        assert(vertices.format == MTLVertexFormat.float3, "Expected three floats (twelve bytes) per vertex.")
        let vertexPointer = vertices.buffer.contents().advanced(by: vertices.offset + (vertices.stride * Int(index)))
        let vertex = vertexPointer.assumingMemoryBound(to: SIMD3<Float>.self).pointee
        return vertex
    }
}

#Preview {
    MainView()
}

字符串

相关问题