如何计算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) }
核心问题:
- 如何计算
createScnPlane方法中SCNPlane的正确尺寸,使其铺满整个ARSCNView画面? - 作为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
相关产品推荐
相关产品推荐

