ARKit/SpriteKit:为SKVideoNode设置pixelBufferAttributes或实现视频抠像
在ARKit中实现SKVideoNode的视频像素透明(色度键抠像)
嘿,很高兴看到你已经搞定了用ARKit在真实场景里播放视频的基础功能!要给视频加上透明背景(也就是色度键抠像,把绿幕/蓝幕去掉),这里有两种实用的方案,我结合实践给你拆解下:
方法一:用CoreImage滤镜实时抠像(最通用)
如果你的视频是带绿幕/蓝幕的普通视频,这个方法最靠谱——通过CoreImage的CIColorKeyFilter把指定颜色的背景抠掉,再把处理后的帧渲染到SKNode上。
步骤&代码示例:
- 替换SKVideoNode为自定义SpriteNode:因为要手动处理每一帧,所以改用SKSpriteNode承载纹理。
- 设置AVPlayerItem的视频输出:捕获视频的原始像素缓冲区,方便后续处理。
- 实时处理每一帧:用CoreImage滤镜抠像,再更新SpriteNode的纹理。
// 先在视图控制器里定义变量 var videoOutput: AVPlayerItemVideoOutput! var player: AVPlayer! var videoSprite: SKSpriteNode! // 在nodeFor anchor方法里初始化视频和SpriteNode func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { guard let urlString = Bundle.main.path(forResource: "resourceName", ofType: "mp4") else { return nil } let url = URL(fileURLWithPath: urlString) let asset = AVAsset(url: url) let playerItem = AVPlayerItem(asset: asset) // 设置视频输出的像素格式(必须支持带alpha的BGRA格式) let pixelBufferAttrs = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, kCVPixelBufferAlphaChannelIsOpaqueKey as String: false ] videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: pixelBufferAttrs) playerItem.add(videoOutput) player = AVPlayer(playerItem: playerItem) player.play() // 创建用来显示处理后视频的SpriteNode videoSprite = SKSpriteNode() videoSprite.size = CGSize(width: 150, height: 300) // 按你的视频比例调整尺寸 // 启动CADisplayLink实时处理帧(比定时器更精准) let displayLink = CADisplayLink(target: self, selector: #selector(processVideoFrame)) displayLink.add(to: .main, forMode: .common) return videoSprite } // 处理每一帧的方法 @objc func processVideoFrame() { let currentTime = player.currentTime() // 获取当前帧的像素缓冲区 guard let pixelBuffer = videoOutput.copyPixelBuffer(forItemTime: currentTime, itemTimeForDisplay: nil) else { return } // 初始化CoreImage滤镜 let ciImage = CIImage(cvPixelBuffer: pixelBuffer) guard let colorKeyFilter = CIFilter(name: "CIColorKeyFilter") else { return } colorKeyFilter.setValue(ciImage, forKey: kCIInputImageKey) // 设置要抠掉的背景色(这里是纯绿色,你可以根据你的视频调整RGB值) colorKeyFilter.setValue(CIColor(red: 0.0, green: 1.0, blue: 0.0), forKey: kCIInputColorKey) // 调整容差(Threshold)和柔化(Softness),让抠像边缘更自然 colorKeyFilter.setValue(0.15, forKey: kCIInputThresholdKey) colorKeyFilter.setValue(0.1, forKey: kCIInputSoftnessKey) // 获取处理后的带透明通道的图像 guard let outputImage = colorKeyFilter.outputImage else { return } // 把CIImage转换成SKTexture(用GPU加速) let ciContext = CIContext(options: [.useSoftwareRenderer: false]) guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else { return } let texture = SKTexture(cgImage: cgImage) // 更新SpriteNode的纹理 videoSprite.texture = texture }
方法二:使用自带Alpha通道的视频(更高效)
如果你的视频本身已经导出了带透明通道的格式(比如ProRes 4444、PNG序列转的视频),那可以直接给SKVideoNode设置像素属性,让它支持透明:
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { guard let urlString = Bundle.main.path(forResource: "resourceName", ofType: "mov") else { return nil } let url = URL(fileURLWithPath: urlString) let asset = AVAsset(url: url) let playerItem = AVPlayerItem(asset: asset) let player = AVPlayer(playerItem: playerItem) let videoNode = SKVideoNode(avPlayer: player) // 设置像素属性,指定支持alpha的格式 videoNode.pixelBufferAttributes = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, kCVPixelBufferAlphaChannelIsOpaqueKey as String: false ] videoNode.size = CGSize(width: 150, height: 300) player.play() return videoNode }
实用提示:
- 调整
CIColorKeyFilter的Threshold和Softness参数是关键,多试几个值,避免把角色的边缘颜色也抠掉,或者留下背景色残留。 - 如果遇到性能问题,可以降低视频的分辨率,或者把CoreImage的处理移到Metal后台队列里。
- 在ARSKView中,记得给SKNode设置合适的
zPosition,确保角色显示在真实场景的前面。
内容的提问来源于stack exchange,提问作者2shy
相关产品推荐
相关产品推荐

