You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在放置虚拟物体前用ARKit、SceneKit测量水平面并生成房间模型?

Hey there! Let's walk through how to implement horizontal plane measurement and room 3D modeling using ARKit and SceneKit—this is a super common AR use case, so I’ll break it down into actionable, easy-to-follow steps.

一、Horizontal Plane Detection & Measurement

First, we need to get ARKit to detect horizontal surfaces, then layer on the measurement logic.

1. Configure the AR Session

Start by setting up your ARView and enabling horizontal plane detection. This tells ARKit to actively look for flat surfaces like floors or tables:

import ARKit
import SceneKit

class ARViewController: UIViewController, ARSessionDelegate {
    @IBOutlet weak var arView: ARView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        arView.session.delegate = self
        
        let configuration = ARWorldTrackingConfiguration()
        // Enable horizontal plane detection
        configuration.planeDetection = .horizontal
        // Use gravity + heading for a stable, real-world-aligned coordinate system
        configuration.worldAlignment = .gravityAndHeading
        
        arView.session.run(configuration)
    }
}

2. Visualize Detected Planes

Once ARKit finds a plane, we’ll render a semi-transparent surface so the user can see exactly where the detected plane is. Add this delegate method to handle new plane anchors:

func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
    guard let planeAnchor = anchors.first as? ARPlaneAnchor else { return }
    
    // Create a SceneKit plane matching the detected plane's dimensions
    let planeNode = SCNNode()
    let planeGeometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z))
    planeGeometry.firstMaterial?.diffuse.contents = UIColor.blue.withAlphaComponent(0.3)
    
    // Rotate the plane to lie flat (SCNPlane is vertical by default)
    planeNode.geometry = planeGeometry
    planeNode.position = SCNVector3(planeAnchor.center.x, 0, planeAnchor.center.z)
    planeNode.eulerAngles.x = -.pi / 2
    
    // Add the visualized plane to the AR scene
    arView.scene.rootNode.addChildNode(planeNode)
}

3. Implement Measurement Logic

To measure distances between two points on the plane, we’ll track user taps to record start and end points:

  • First, add variables to store the start point and a placeholder for measurement visualization nodes:
private var startPoint: SCNVector3?
private var measurementVisualizationNodes: [SCNNode] = []
  • Add a tap gesture handler to capture user input:
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
    let location = gesture.location(in: arView)
    // Perform a hit test to find the tapped point on a detected plane
    guard let hitResult = arView.hitTest(location, types: .existingPlaneUsingExtent).first else {
        print("No plane detected at tap location—try moving your device to scan more area")
        return
    }
    
    let worldPoint = hitResult.worldTransform.columns.3
    let tappedPoint = SCNVector3(worldPoint.x, worldPoint.y, worldPoint.z)
    
    if startPoint == nil {
        // First tap: set start point and place a red marker
        startPoint = tappedPoint
        let markerNode = SCNNode(geometry: SCNSphere(radius: 0.01))
        markerNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
        markerNode.position = tappedPoint
        arView.scene.rootNode.addChildNode(markerNode)
        measurementVisualizationNodes.append(markerNode)
    } else {
        // Second tap: calculate distance and display it
        guard let start = startPoint else { return }
        // Calculate 2D distance on the horizontal plane (ignore Y-axis)
        let distance = sqrt(pow(tappedPoint.x - start.x, 2) + pow(tappedPoint.z - start.z, 2))
        
        // Draw a line between start and end points
        let lineNode = SCNNode(geometry: createLine(from: start, to: tappedPoint))
        arView.scene.rootNode.addChildNode(lineNode)
        measurementVisualizationNodes.append(lineNode)
        
        // Display formatted distance text
        let textGeometry = SCNText(string: String(format: "%.2f m", distance), extrusionDepth: 0.01)
        textGeometry.firstMaterial?.diffuse.contents = UIColor.white
        let textNode = SCNNode(geometry: textGeometry)
        // Position text halfway between the two points, lifted slightly off the plane
        textNode.position = SCNVector3(
            (start.x + tappedPoint.x)/2,
            start.y + 0.1,
            (start.z + tappedPoint.z)/2
        )
        textNode.scale = SCNVector3(0.01, 0.01, 0.01) // Scale down the text to fit
        arView.scene.rootNode.addChildNode(textNode)
        measurementVisualizationNodes.append(textNode)
        
        // Reset start point for next measurement
        startPoint = nil
    }
}

// Helper method to create a line between two 3D points
private func createLine(from start: SCNVector3, to end: SCNVector3) -> SCNGeometry {
    let positions = [start, end]
    let source = SCNGeometrySource(vertices: positions)
    let indices: [Int32] = [0, 1]
    let element = SCNGeometryElement(indices: indices, primitiveType: .line)
    let line = SCNGeometry(sources: [source], elements: [element])
    line.firstMaterial?.diffuse.contents = UIColor.white
    return line
}
  • Don’t forget to add the tap gesture recognizer in viewDidLoad():
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
arView.addGestureRecognizer(tapGesture)
二、Room 3D Modeling

For generating a full room model, we’ll use ARKit’s mesh reconstruction feature (available in ARKit 3.0+) to capture the environment’s 3D structure, then refine it into a clean, structured room model.

1. Enable Mesh Reconstruction

Switch to ARMeshConfiguration to let ARKit generate a detailed 3D mesh of the room:

let meshConfig = ARMeshConfiguration()
meshConfig.isLightEstimationEnabled = true
// Reset previous tracking to start fresh with mesh scanning
arView.session.run(meshConfig, options: [.resetTracking, .removeExistingAnchors])

2. Visualize the Scanned Mesh

Render the generated mesh so the user can see the scanned environment in real time:

func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
    guard let meshAnchor = anchors.first as? ARMeshAnchor else { return }
    
    // Create SceneKit geometry from the mesh anchor's data
    let meshGeometry = ARSCNGeometry(meshAnchor: meshAnchor)
    let meshNode = SCNNode(geometry: meshGeometry)
    
    // Set a semi-transparent green material for the mesh
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.green.withAlphaComponent(0.5)
    meshGeometry.materials = [material]
    
    arView.scene.rootNode.addChildNode(meshNode)
}

3. Generate a Structured Room Model

To turn the raw mesh into a clean, usable room model (walls, floor, ceiling), you’ll need to:

  • Merge adjacent planes: If you enable vertical plane detection (set configuration.planeDetection = [.horizontal, .vertical]), use ARPlaneAnchor data to identify large, flat surfaces. Check the alignment property to distinguish horizontal (floor/ceiling) vs vertical (walls) planes.
  • Extract boundaries: For each detected plane, use the extent and center properties to define edges, then create SCNPlane or SCNBox nodes to represent the room’s structure.
  • Clean up noise: Filter out small, irrelevant mesh fragments to keep the model tidy (you can use threshold checks on plane size to exclude tiny surfaces).

4. Export the Room Model

Once you’ve built the structured room model, export it as a USDZ file (Apple’s preferred AR format) for sharing or further editing:

func exportRoomModel() {
    guard let scene = arView.scene else { return }
    let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let exportURL = documentsDir.appendingPathComponent("RoomModel.usdz")
    
    do {
        try scene.write(to: exportURL, options: nil, delegate: nil, progressHandler: nil)
        print("Model successfully exported to \(exportURL)")
    } catch {
        print("Export failed: \(error.localizedDescription)")
    }
}
三、Key Tips for Better Performance & UX
  • Improve detection accuracy: Move the device slowly around the room to let ARKit capture more detail. Pause briefly over surfaces to refine plane detection.
  • Reduce measurement error: Perform multiple hit tests at the tap location and average the results to minimize inaccuracies from camera noise.
  • Optimize mesh rendering: On older devices, lower the mesh detail level or use occlusion to hide parts of the mesh that aren’t visible to the user.
  • Guide the user: Add on-screen prompts like "Slowly scan your room to detect walls and floors" to help users get better, more complete scans.

Hope these steps help you build your AR measurement and room modeling feature smoothly! If you run into specific code bugs or edge cases (like handling curved walls), feel free to share more details.

内容的提问来源于stack exchange,提问作者Ancuta Cozma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:47:26