RealityKit –在检测到的平面上可视化网格

发布于 2025-01-11 10:46:55 字数 198 浏览 1 评论 0原文

我想在检测到的平面上绘制网格点,如 ARCore 中所示视频链接,我不知道如何实现它。

你能帮我实现这个目标吗?提前致谢。

I want to draw the mesh point on the detected plane as shown in the ARCore video link and I don't know how to achieve it.

Can you help me out to achieve this? Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心意如水 2025-01-18 10:46:55

ARKit|RealityKit 中的经典网格可视化

在这篇文章中,我想向您展示如何使用 ARKit/RealityKit 框架启用平面检测过程的“经典”可视化。但在这种情况下,我不保证您能够以视觉上令人愉悦的方式可视化网格,因为它是在 Google ARCore 中实现的。

但是,为了可视化检测水平和垂直平面的过程(类似于您在 ARCore 中看到的行为),您应该使用场景重建方法,即使用 LiDAR 扫描仪。但这是一个不同的故事,因为您必须使用金属来实现程序纹理,并为前缘设置软蒙版。

输入图像此处描述

这是代码:

import ARKit
import RealityKit

class Grid: Entity, HasModel, HasAnchoring {        
    var planeAnchor: ARPlaneAnchor
    var planeGeometry: MeshResource!
    
    init(planeAnchor: ARPlaneAnchor) {
        self.planeAnchor = planeAnchor
        super.init()
        self.didSetup()
    }
        
    fileprivate func didSetup() {            
        self.planeGeometry = .generatePlane(width: planeAnchor.extent.x,
                                            depth: planeAnchor.extent.z)
        var material = UnlitMaterial()
        material.color = .init(tint: .white.withAlphaComponent(0.999),
                            texture: .init(try! .load(named: "grid.png")))
        let model = ModelEntity(mesh: planeGeometry, materials: [material])
        model.position = [planeAnchor.center.x, 0, planeAnchor.center.z]
        self.addChild(model)
    }
    
    fileprivate func didUpdate(anchor: ARPlaneAnchor) {            
        self.planeGeometry = .generatePlane(width: anchor.extent.x,
                                            depth: anchor.extent.z)
        let pose: SIMD3<Float> = [anchor.center.x, 0, anchor.center.z]
        let model = self.children[0] as! ModelEntity
        model.position = pose
    }        
    required init() { fatalError("Hasn't been implemented yet") }
}

ViewController.swift

class ViewController: UIViewController {
    
    @IBOutlet var arView: ARView!
    var grids = [Grid]()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        arView.session.delegate = self
        
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = [.horizontal, .vertical]
        arView.session.run(config)
    }
}

ARSessionDelegate

extension ViewController: ARSessionDelegate {
    
    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        
        guard let planeAnchor = anchors.first as? ARPlaneAnchor else { return }
        let grid = Grid(planeAnchor: planeAnchor)
        grid.transform.matrix = planeAnchor.transform
        self.arView.scene.anchors.append(grid)
        self.grids.append(grid)
    }
    
    func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
        
        guard let planeAnchor = anchors[0] as? ARPlaneAnchor else { return }
        let grid: Grid? = grids.filter { grd in
            grd.planeAnchor.identifier == planeAnchor.identifier }[0]
        guard let updatedGrid: Grid = grid else { return }
        updatedGrid.transform.matrix = planeAnchor.transform
        updatedGrid.didUpdate(anchor: planeAnchor)
    }
}

仅可以更新共面检测到的平面。

PS

如果您有兴趣在 RealityKit 中可视化Canonical Face Mask看看这篇文章

Classic grid visualization in ARKit|RealityKit

In this post, I want to show you how to enable the "classic" visualization of the plane detection process with ARKit/RealityKit frameworks. But in this case, I don't promise you to visualize a grid in such a way that is visually pleasing, as it's implemented in Google ARCore.

However, in order to visualize the process of detecting horizontal and vertical planes similar to the behavior you can see in ARCore, you should use the scene reconstruction methodology, i.e. using a LiDAR scanner. But that's a different story, because you'll have to use Metal to implement a procedural texture with a soft mask for the front edge.

enter image description here

Here's a code:

import ARKit
import RealityKit

class Grid: Entity, HasModel, HasAnchoring {        
    var planeAnchor: ARPlaneAnchor
    var planeGeometry: MeshResource!
    
    init(planeAnchor: ARPlaneAnchor) {
        self.planeAnchor = planeAnchor
        super.init()
        self.didSetup()
    }
        
    fileprivate func didSetup() {            
        self.planeGeometry = .generatePlane(width: planeAnchor.extent.x,
                                            depth: planeAnchor.extent.z)
        var material = UnlitMaterial()
        material.color = .init(tint: .white.withAlphaComponent(0.999),
                            texture: .init(try! .load(named: "grid.png")))
        let model = ModelEntity(mesh: planeGeometry, materials: [material])
        model.position = [planeAnchor.center.x, 0, planeAnchor.center.z]
        self.addChild(model)
    }
    
    fileprivate func didUpdate(anchor: ARPlaneAnchor) {            
        self.planeGeometry = .generatePlane(width: anchor.extent.x,
                                            depth: anchor.extent.z)
        let pose: SIMD3<Float> = [anchor.center.x, 0, anchor.center.z]
        let model = self.children[0] as! ModelEntity
        model.position = pose
    }        
    required init() { fatalError("Hasn't been implemented yet") }
}

ViewController.swift

class ViewController: UIViewController {
    
    @IBOutlet var arView: ARView!
    var grids = [Grid]()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        arView.session.delegate = self
        
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = [.horizontal, .vertical]
        arView.session.run(config)
    }
}

ARSessionDelegate

extension ViewController: ARSessionDelegate {
    
    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        
        guard let planeAnchor = anchors.first as? ARPlaneAnchor else { return }
        let grid = Grid(planeAnchor: planeAnchor)
        grid.transform.matrix = planeAnchor.transform
        self.arView.scene.anchors.append(grid)
        self.grids.append(grid)
    }
    
    func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
        
        guard let planeAnchor = anchors[0] as? ARPlaneAnchor else { return }
        let grid: Grid? = grids.filter { grd in
            grd.planeAnchor.identifier == planeAnchor.identifier }[0]
        guard let updatedGrid: Grid = grid else { return }
        updatedGrid.transform.matrix = planeAnchor.transform
        updatedGrid.didUpdate(anchor: planeAnchor)
    }
}

Only coplanar detected planes may be updated.

P. S.

If you're interested in visualizing the Canonical Face Mask in RealityKit, take a look at this post.

无人问我粥可暖 2025-01-18 10:46:55

扩展安迪的答案:

由于我真的很想使用金属纹理,而且安迪提供的答案很遗憾没有为我更新平面边界,我想出了这个解决方案:

首先,我使用自定义金属表面着色器:

#include <metal_stdlib>
#include <RealityKit/RealityKit.h>

using namespace metal;
using namespace realitykit;

[[visible]]
void PlaneGridShader(surface_parameters params)
{
    // Constants for grid parameters. bigGrid is for the thicker lines, smallGrid for the finer grid. The bigger these values are, the more grid rows you get.
    constexpr float bigGridSize = 2.0;
    constexpr float smallGridSize = 10.0;
    constexpr float gridThickness = 0.005;
    constexpr float borderThickness = 0.003;
    
    //uv1 gets a global reference, whereas uv0 gets a local UV representation. This means that uv0 has coordinates from 0 to 1 for any grid size and is useful to create a border around the plane. uv1 is dependant on the grid size and offers a great way to create evenly spaced grid rows for differently sized planes
    float2 uv = params.geometry().uv1();
    float2 absoluteUV = params.geometry().uv0();
    
    //convenience values for grid size
    float bigU = uv.x * bigGridSize;
    float bigV = uv.y * bigGridSize;
    
    float smallU = uv.x * smallGridSize;
    float smallV = uv.y * smallGridSize;
    
    //Grid color in rgb values
    //half3 gridColor = half3(1,0.71,0);
    half3 gridColor = half3(1,1,1);
    
    //Make material appear unlit. If you use an unlit material, all grid lines appear black, so you need to dial all the pbr properties down
    params.surface().set_metallic(0);
    params.surface().set_specular(0);
    params.surface().set_clearcoat(0);
    params.surface().set_ambient_occlusion(1);
    
    //Sets the grid color. Can be moved down into if statements if multiple colors are needed for different parts of the grid
    params.surface().set_emissive_color(gridColor);
    params.surface().set_base_color(gridColor);
    
    //Inverse of Outer Border selection
    if (absoluteUV.x > borderThickness && absoluteUV.x < (1.0 - borderThickness) && absoluteUV.y > borderThickness && absoluteUV.y < (1.0 - borderThickness)) {
        //Thicker inner Grid (big)
        if (fract(bigU) < gridThickness || fract(bigV) < gridThickness || fract(bigU) > 1.0 - gridThickness || fract(bigV) > 1.0 - gridThickness) {
            params.surface().set_opacity(0.7);
        }
            else{
                //Small grid
                if (fract(smallU) < gridThickness || fract(smallV) < gridThickness || fract(smallU) > 1.0 - gridThickness || fract(smallV) > 1.0 - gridThickness) {
                    params.surface().set_opacity(0.25);
                }
                //Discard all fragments that do not meet grid criteria
                else{
                    discard_fragment();
                }
            }
    }
    //Constant Outer Border
    else{
        params.surface().set_opacity(0.8);
    }
    
}

然后,我创建了一个与 Andy 类似的平面,所以感谢这部分:)
值得注意的是,它对 didUpdate() 函数和平面范围的检索方式进行了一些更改,因为 Andy 的代码使用了 Apple 已弃用的函数。另外,我将其重命名为“ARGrid”,这样它就不会与 SwiftUI 的 Grid() 冲突。

import ARKit
import RealityKit

class ARGrid: Entity, HasModel, HasAnchoring {
    //Use identifier to later match the ARPlaneAnchor with the Grid
    var identifier: UUID
    var planeAnchor: ARPlaneAnchor
    var planeGeometry: MeshResource!
    
    
    init(id: UUID, planeAnchor: ARPlaneAnchor) {
        self.identifier = id
        self.planeAnchor = planeAnchor
        super.init()
        self.didSetup()
    }
    
    func didSetup() {
        self.planeGeometry = .generatePlane(width: planeAnchor.planeExtent.width, height: planeAnchor.planeExtent.height)
        
        var model = ModelEntity(mesh: planeGeometry, materials: [gridMaterial])
        model.position = [planeAnchor.center.x, 0, planeAnchor.center.z]
        // Rotate 90 degrees around the X-axis so plane is aligned to surface
        model.orientation = simd_quatf(angle: -Float.pi / 2, axis: [1, 0, 0])
        self.addChild(model)
    }
    
    func didUpdate(anchor: ARPlaneAnchor) {
        self.planeGeometry = .generatePlane(width: anchor.planeExtent.width, height: anchor.planeExtent.height)
        let pose: SIMD3<Float> = [anchor.center.x, 0, anchor.center.z]
        let model = self.children[0] as! ModelEntity
        model.model?.mesh = self.planeGeometry
        model.position = pose
    }
    required init() { fatalError("Hasn't been implemented yet") }
}

最后,我在 ARView 代理函数中实现了一些更改:

 func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        let filtered = anchors.compactMap({$0 as? ARPlaneAnchor})
        for anchor in filtered{
            let grid = ARGrid(id: anchor.identifier, planeAnchor: anchor)
            grid.transform.matrix = anchor.transform
            self.arView.scene.anchors.append(grid)
        }
    }
    
    func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
        let filtered = anchors.compactMap({$0 as? ARPlaneAnchor})
        let gridAnchors = arView.scene.anchors.compactMap({$0 as? ARGrid})
        for anchor in filtered{
            let updateAnchor = gridAnchors.first(where: {$0.identifier == anchor.identifier})!
            updateAnchor.transform.matrix = anchor.transform
            updateAnchor.didUpdate(anchor: anchor)
        }
    }

我对 Metal 还很陌生,这是我的第一个项目。因此,如果我在某个地方搞砸了,我会非常高兴收到改进这一点的建议。
希望这有帮助

Expanding on Andy's answer:

As I really wanted to use a Metal texture and also the answer provided by Andy sadly didn't update the plane bounds for me, I came up with this solution:

Firstly, I used a custom Metal Surface Shader:

#include <metal_stdlib>
#include <RealityKit/RealityKit.h>

using namespace metal;
using namespace realitykit;

[[visible]]
void PlaneGridShader(surface_parameters params)
{
    // Constants for grid parameters. bigGrid is for the thicker lines, smallGrid for the finer grid. The bigger these values are, the more grid rows you get.
    constexpr float bigGridSize = 2.0;
    constexpr float smallGridSize = 10.0;
    constexpr float gridThickness = 0.005;
    constexpr float borderThickness = 0.003;
    
    //uv1 gets a global reference, whereas uv0 gets a local UV representation. This means that uv0 has coordinates from 0 to 1 for any grid size and is useful to create a border around the plane. uv1 is dependant on the grid size and offers a great way to create evenly spaced grid rows for differently sized planes
    float2 uv = params.geometry().uv1();
    float2 absoluteUV = params.geometry().uv0();
    
    //convenience values for grid size
    float bigU = uv.x * bigGridSize;
    float bigV = uv.y * bigGridSize;
    
    float smallU = uv.x * smallGridSize;
    float smallV = uv.y * smallGridSize;
    
    //Grid color in rgb values
    //half3 gridColor = half3(1,0.71,0);
    half3 gridColor = half3(1,1,1);
    
    //Make material appear unlit. If you use an unlit material, all grid lines appear black, so you need to dial all the pbr properties down
    params.surface().set_metallic(0);
    params.surface().set_specular(0);
    params.surface().set_clearcoat(0);
    params.surface().set_ambient_occlusion(1);
    
    //Sets the grid color. Can be moved down into if statements if multiple colors are needed for different parts of the grid
    params.surface().set_emissive_color(gridColor);
    params.surface().set_base_color(gridColor);
    
    //Inverse of Outer Border selection
    if (absoluteUV.x > borderThickness && absoluteUV.x < (1.0 - borderThickness) && absoluteUV.y > borderThickness && absoluteUV.y < (1.0 - borderThickness)) {
        //Thicker inner Grid (big)
        if (fract(bigU) < gridThickness || fract(bigV) < gridThickness || fract(bigU) > 1.0 - gridThickness || fract(bigV) > 1.0 - gridThickness) {
            params.surface().set_opacity(0.7);
        }
            else{
                //Small grid
                if (fract(smallU) < gridThickness || fract(smallV) < gridThickness || fract(smallU) > 1.0 - gridThickness || fract(smallV) > 1.0 - gridThickness) {
                    params.surface().set_opacity(0.25);
                }
                //Discard all fragments that do not meet grid criteria
                else{
                    discard_fragment();
                }
            }
    }
    //Constant Outer Border
    else{
        params.surface().set_opacity(0.8);
    }
    
}

Then, I created a Plane similarly to Andy, so thanks for that part :)
Notably, it has some changes to the didUpdate() function and the way the plane extent is retrieved, as Andy's code is using functions that Apple has since deprecated. Also, I renamed it to "ARGrid", so it wouldn't conflict with SwiftUI's Grid().

import ARKit
import RealityKit

class ARGrid: Entity, HasModel, HasAnchoring {
    //Use identifier to later match the ARPlaneAnchor with the Grid
    var identifier: UUID
    var planeAnchor: ARPlaneAnchor
    var planeGeometry: MeshResource!
    
    
    init(id: UUID, planeAnchor: ARPlaneAnchor) {
        self.identifier = id
        self.planeAnchor = planeAnchor
        super.init()
        self.didSetup()
    }
    
    func didSetup() {
        self.planeGeometry = .generatePlane(width: planeAnchor.planeExtent.width, height: planeAnchor.planeExtent.height)
        
        var model = ModelEntity(mesh: planeGeometry, materials: [gridMaterial])
        model.position = [planeAnchor.center.x, 0, planeAnchor.center.z]
        // Rotate 90 degrees around the X-axis so plane is aligned to surface
        model.orientation = simd_quatf(angle: -Float.pi / 2, axis: [1, 0, 0])
        self.addChild(model)
    }
    
    func didUpdate(anchor: ARPlaneAnchor) {
        self.planeGeometry = .generatePlane(width: anchor.planeExtent.width, height: anchor.planeExtent.height)
        let pose: SIMD3<Float> = [anchor.center.x, 0, anchor.center.z]
        let model = self.children[0] as! ModelEntity
        model.model?.mesh = self.planeGeometry
        model.position = pose
    }
    required init() { fatalError("Hasn't been implemented yet") }
}

Lastly, I implemented a few changes in my ARView Delegate Functions:

 func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        let filtered = anchors.compactMap({$0 as? ARPlaneAnchor})
        for anchor in filtered{
            let grid = ARGrid(id: anchor.identifier, planeAnchor: anchor)
            grid.transform.matrix = anchor.transform
            self.arView.scene.anchors.append(grid)
        }
    }
    
    func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
        let filtered = anchors.compactMap({$0 as? ARPlaneAnchor})
        let gridAnchors = arView.scene.anchors.compactMap({$0 as? ARGrid})
        for anchor in filtered{
            let updateAnchor = gridAnchors.first(where: {$0.identifier == anchor.identifier})!
            updateAnchor.transform.matrix = anchor.transform
            updateAnchor.didUpdate(anchor: anchor)
        }
    }

I'm still very new to Metal, this being my first project. So I'd be more than happy about suggestions for improving this, if I messed up somewhere.
Hope this helps

萌吟 2025-01-18 10:46:55

我改进了 Andy 的代码,以解决一些崩溃问题(当尝试接近数组上的 [0] 时),或者创建名称无效的纹理,并更新它(范围不再使用):

网格:

class Grid: Entity, HasModel, HasAnchoring {
    var planeAnchor: ARPlaneAnchor
    var planeGeometry: MeshResource!
    
    init(planeAnchor: ARPlaneAnchor) {
        self.planeAnchor = planeAnchor
        super.init()
        self.didSetup()
    }
        
    fileprivate func didSetup() {
      self.planeGeometry = .generatePlane(width: planeAnchor.transform.columns.3.x,
                                            depth: planeAnchor.transform.columns.3.z)
        var material = UnlitMaterial()
        guard let texture = try? TextureResource.load(named: "grid", in: nil)
        else{
          print("<Grid> ERROR: cannot create texutre!")
          return
        }
      
        let textureMat = MaterialParameters.Texture(texture)
        material.color = .init(tint: .white.withAlphaComponent(0.75),
                            texture: textureMat)
        let model = ModelEntity(mesh: planeGeometry, materials: [material])
        model.position = [planeAnchor.center.x, planeAnchor.center.y, planeAnchor.center.z]
        self.addChild(model)
    }
    
    fileprivate func didUpdate(anchor: ARPlaneAnchor) {
        self.planeGeometry = .generatePlane(width: anchor.transform.columns.3.x,
                                            depth: anchor.transform.columns.3.z)
        let pose: SIMD3<Float> = [anchor.center.x, anchor.center.y, anchor.center.z]
        let model = self.children.first as? ModelEntity
        model?.position = pose
    }
    required init() { fatalError("Hasn't been implemented yet") }
}

ARSessionDelegate:

  func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
for anchor in anchors{
  if let plane = anchor as? ARPlaneAnchor{
        let grid = Grid(planeAnchor: plane)
        grid.transform.matrix = plane.transform
        self.arView?.scene.anchors.append(grid)
        self.grids.append(grid)
  }


   }
  }

  func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
    for anchor in anchors{
      if let plane = anchor as? ARPlaneAnchor{
        let grid: Grid? = grids.filter { grd in
        grd.planeAnchor.identifier == plane.identifier }.first
        guard let updatedGrid: Grid = grid else { return }
        updatedGrid.transform.matrix = plane.transform
        updatedGrid.didUpdate(anchor: plane)
      }
    }
  }

ViewController (我没有更改)

class ViewController: UIViewController {

@IBOutlet var arView: ARView!
var grids = [Grid]()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    arView.session.delegate = self
    
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = [.horizontal, .vertical]
        arView.session.run(config)
    }
}

I improved Andy's code, to solve some crashes ( when trying to approach [0] on array), or creating a texture with invalid name, and also update it (extent is no longer in use):

Grid:

class Grid: Entity, HasModel, HasAnchoring {
    var planeAnchor: ARPlaneAnchor
    var planeGeometry: MeshResource!
    
    init(planeAnchor: ARPlaneAnchor) {
        self.planeAnchor = planeAnchor
        super.init()
        self.didSetup()
    }
        
    fileprivate func didSetup() {
      self.planeGeometry = .generatePlane(width: planeAnchor.transform.columns.3.x,
                                            depth: planeAnchor.transform.columns.3.z)
        var material = UnlitMaterial()
        guard let texture = try? TextureResource.load(named: "grid", in: nil)
        else{
          print("<Grid> ERROR: cannot create texutre!")
          return
        }
      
        let textureMat = MaterialParameters.Texture(texture)
        material.color = .init(tint: .white.withAlphaComponent(0.75),
                            texture: textureMat)
        let model = ModelEntity(mesh: planeGeometry, materials: [material])
        model.position = [planeAnchor.center.x, planeAnchor.center.y, planeAnchor.center.z]
        self.addChild(model)
    }
    
    fileprivate func didUpdate(anchor: ARPlaneAnchor) {
        self.planeGeometry = .generatePlane(width: anchor.transform.columns.3.x,
                                            depth: anchor.transform.columns.3.z)
        let pose: SIMD3<Float> = [anchor.center.x, anchor.center.y, anchor.center.z]
        let model = self.children.first as? ModelEntity
        model?.position = pose
    }
    required init() { fatalError("Hasn't been implemented yet") }
}

ARSessionDelegate:

  func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
for anchor in anchors{
  if let plane = anchor as? ARPlaneAnchor{
        let grid = Grid(planeAnchor: plane)
        grid.transform.matrix = plane.transform
        self.arView?.scene.anchors.append(grid)
        self.grids.append(grid)
  }


   }
  }

  func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
    for anchor in anchors{
      if let plane = anchor as? ARPlaneAnchor{
        let grid: Grid? = grids.filter { grd in
        grd.planeAnchor.identifier == plane.identifier }.first
        guard let updatedGrid: Grid = grid else { return }
        updatedGrid.transform.matrix = plane.transform
        updatedGrid.didUpdate(anchor: plane)
      }
    }
  }

ViewController (which I didn't change)

class ViewController: UIViewController {

@IBOutlet var arView: ARView!
var grids = [Grid]()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    arView.session.delegate = self
    
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = [.horizontal, .vertical]
        arView.session.run(config)
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文