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

如何计算SCNPlane尺寸以覆盖整个ARSCNView?ARKit绿屏开发疑问

ARKit绿屏应用:SCNPlane尺寸计算及替代方案

我正在用ARKit开发一款iOS绿屏应用,基于ARFaceTrackingConfiguration会话,要把图片作为背景显示(类似Zoom的绿屏功能),要求图片始终铺满屏幕,不受设备移动影响。目前用ARKit+SceneKit实现,步骤如下:

  • 向视图控制器添加ARSCNView
  • 创建与ARSCNView尺寸(点为单位)一致的SKScene
  • 创建SKSpriteNode并添加为SKScene的子节点
  • 创建特定尺寸(米为单位)的SCNPlane
  • 创建SCNNode并将其几何属性设为上述SCNPlane
  • 将SCNNode添加到ARSCNView

完整实现代码:

@IBOutlet var arSceneView: ARSCNView!
var virtualBackgroundNode = SCNNode()
...

func addGreenScreenScene() {
    let skScene = createSKScene()
    let plane = createScnPlane()
    
    virtualBackgroundNode.geometry = plane
    
    let material = SCNMaterial()
    material.diffuse.contents = skScene
    virtualBackgroundNode.geometry?.materials = [material]
    
    if let cameraPosition = arSceneView.pointOfView?.scale {
        let position = SCNVector3(0, 0, cameraPosition.z - 13)
        os_log("camera position=\(cameraPosition.x) \(cameraPosition.y) \(cameraPosition.z)")
        os_log("arSceneView frame=\(self.arSceneView.frame.width) \(self.arSceneView.frame.height)")
        virtualBackgroundNode.position = position
    }

    arSceneView.pointOfView?.addChildNode(virtualBackgroundNode)
}

func createSKScene() -> SKScene? {
    guard let image = getImage() else {
        return nil
    }
    
    let skScene = SKScene(size: CGSize(width: arSceneView.frame.width,
                                       height: arSceneView.frame.height))
    let texture = SKTexture(image: image)
    let spriteNode = SKSpriteNode(texture:texture)
    spriteNode.position = CGPoint(x: skScene.size.width / 2.0,
                                  y: skScene.size.height / 2.0)
    spriteNode.size = skScene.size
    spriteNode.yScale = -1.0
    skScene.scaleMode = .aspectFit
    skScene.addChild(spriteNode)
    
    return skScene
}

func createScnPlane() -> SCNPlane {
    // TODO: Determine the correct size for this
    // such that it covers the entire frame of
    // arSceneView.
    return SCNPlane(width: 12, height: 18)
}

func getImage() -> UIImage? {
    return UIImage(named: photoInfo.imageName)
}

核心问题:

  1. 如何计算createScnPlane方法中SCNPlane的正确尺寸,使其铺满整个ARSCNView画面?
  2. 作为AR开发新手,有没有其他开发绿屏功能的可行方式?

一、计算SCNPlane的正确尺寸

要让平面铺满屏幕,关键是利用ARSCNView相机的视场角(FOV)和平面到相机的固定距离来计算尺寸,而非硬编码数值。

原理

相机的视场角决定了不同距离下,平面需要多大才能填满屏幕。对于ARFaceTrackingConfiguration,可以直接从当前帧的相机对象中获取视场角,再通过三角函数计算平面的宽高。

实现代码

修改你的createScnPlane和addGreenScreenScene方法:

func addGreenScreenScene() {
    let skScene = createSKScene()
    // 固定平面到相机的距离(比如10米,可根据需求调整)
    let planeDistance: Float = 10.0
    let plane = createScnPlane(atDistance: planeDistance)
    
    virtualBackgroundNode.geometry = plane
    
    let material = SCNMaterial()
    material.diffuse.contents = skScene
    // 关闭光照影响,避免AR环境光干扰背景显示
    material.lightingModel = .constant
    virtualBackgroundNode.geometry?.materials = [material]
    
    // 将平面固定在相机前方指定距离处
    virtualBackgroundNode.position = SCNVector3(0, 0, -planeDistance)
    // 把平面设为相机的子节点,设备移动时背景会同步跟随
    arSceneView.pointOfView?.addChildNode(virtualBackgroundNode)
}

func createScnPlane(atDistance distance: Float) -> SCNPlane {
    guard let camera = arSceneView.session.currentFrame?.camera else {
        //  fallback值,防止崩溃
        return SCNPlane(width: 12, height: 18)
    }
    
    // 获取相机垂直视场角(弧度转角度)
    let verticalFOV = camera.fieldOfViewRadians * 180 / .pi
    // 计算平面高度:2 * 距离 * tan(半视场角)
    let planeHeight = 2 * distance * tan((verticalFOV / 2) * .pi / 180)
    // 根据屏幕宽高比计算平面宽度
    let screenAspectRatio = arSceneView.frame.width / arSceneView.frame.height
    let planeWidth = planeHeight * Float(screenAspectRatio)
    
    return SCNPlane(width: planeWidth, height: planeHeight)
}

补充说明

  • 将平面设为相机子节点,能保证设备移动时背景始终在视野内,不会偏移。
  • 关闭材质光照模型为.constant,可以让背景图片保持原本的色彩,不受AR场景光照影响。

二、其他绿屏功能的实现方式

作为新手,有两种更简单的方案可以选择:

1. 用ARKit内置虚拟背景功能

ARKit从iOS 13开始支持ARConfiguration的virtualBackgroundProvider属性,原生支持绿屏效果,无需手动创建平面。

实现步骤:

let configuration = ARFaceTrackingConfiguration()
// 加载背景图片并创建材质
if let image = UIImage(named: photoInfo.imageName) {
    let backgroundMaterial = SCNMaterial()
    backgroundMaterial.diffuse.contents = image
    backgroundMaterial.lightingModel = .constant
    // 设置虚拟背景为该材质
    configuration.virtualBackgroundProvider = .material(backgroundMaterial)
}
// 运行会话
arSceneView.session.run(configuration)

优势:

  • ARKit原生适配,自动处理尺寸、位置和渲染层级,完全不用手动计算。
  • 性能更优,官方优化过的渲染逻辑比自定义平面更稳定。

2. 用AVFoundation实现纯绿屏替换

如果你的应用不需要AR人脸追踪的其他功能,只是单纯替换绿屏背景,用AVFoundation的滤镜方案更轻量。

核心思路:

  • 捕获摄像头画面,用CIGreenScreen滤镜替换绿色区域为目标图片,再渲染到视图。

示例代码片段:

import AVFoundation
import CoreImage

class GreenScreenVC: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
    let previewView = UIImageView()
    let captureSession = AVCaptureSession()
    var backgroundCIImage: CIImage!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCaptureSession()
        // 加载背景图片
        guard let backgroundImage = UIImage(named: photoInfo.imageName) else { return }
        backgroundCIImage = CIImage(image: backgroundImage)!
    }
    
    func setupCaptureSession() {
        // 配置摄像头输入
        guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else { return }
        guard let input = try? AVCaptureDeviceInput(device: camera) else { return }
        captureSession.addInput(input)
        
        // 配置视频输出
        let videoOutput = AVCaptureVideoDataOutput()
        videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
        captureSession.addOutput(videoOutput)
        
        // 设置预览视图
        previewView.frame = view.bounds
        previewView.contentMode = .scaleAspectFill
        view.addSubview(previewView)
        
        // 启动会话
        captureSession.startRunning()
    }
    
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
        let inputImage = CIImage(cvImageBuffer: pixelBuffer)
        
        // 绿屏滤镜处理
        let filter = CIFilter(name: "CIGreenScreen")!
        filter.setValue(inputImage, forKey: kCIInputImageKey)
        filter.setValue(backgroundCIImage, forKey: kCIInputBackgroundImageKey)
        // 调整阈值适配你的绿色背景(可根据实际情况微调)
        filter.setValue(CIVector(x: 0.2, y: 0.8, z: 0.2), forKey: "inputColorThreshold")
        
        // 渲染到预览视图
        let context = CIContext()
        if let outputImage = filter.outputImage,
           let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
            DispatchQueue.main.async {
                self.previewView.image = UIImage(cgImage: cgImage)
            }
        }
    }
}

优势:

  • 无需依赖ARKit,适合仅需绿屏替换的场景。
  • 资源消耗更低,适配更多旧设备。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:35:20