Моя цель - представить 2D анимированных персонажей в реальной среде, используя ARKit
. Анимированные персонажи являются частью видео, представленного в следующем снимке из видео:
Отображение самого видео было достигнуто без проблем при использовании кода:
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 item = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: item)
let videoNode = SKVideoNode(avPlayer: player)
videoNode.size = CGSize(width: 200.0, height: 150.0)
videoNode.anchorPoint = CGPoint(x: 0.5, y: 0.0)
return videoNode
}
Результат этого кода представлен на снимке экрана нижеприведенного приложения:
Но, как вы можете видеть, фон персонажей не очень приятный, поэтому мне нужно сделать так, чтобы он исчез, чтобы создать иллюзию персонажей, фактически стоящих на поверхности горизонтальной плоскости.
Я пытаюсь добиться этого, используя эффект хроматического ключа для видео.
- Для тех, кто не знаком с хроматическим ключом, это название «эффекта зеленого экрана», которое иногда можно увидеть на телевизоре, чтобы сделать цвет прозрачным.
Мой подход к эффекту хроматического ключа - создать пользовательский фильтр на основе "CIColorCube" CIFilter
, а затем применить фильтр к видео, используя AVVideoComposition
.
Во-первых, это код для создания фильтра:
func RGBtoHSV(r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
var h : CGFloat = 0
var s : CGFloat = 0
var v : CGFloat = 0
let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
return (Float(h), Float(s), Float(v))
}
func colorCubeFilterForChromaKey(hueAngle: Float) -> CIFilter {
let hueRange: Float = 20 // degrees size pie shape that we want to replace
let minHueAngle: Float = (hueAngle - hueRange/2.0) / 360
let maxHueAngle: Float = (hueAngle + hueRange/2.0) / 360
let size = 64
var cubeData = [Float](repeating: 0, count: size * size * size * 4)
var rgb: [Float] = [0, 0, 0]
var hsv: (h : Float, s : Float, v : Float)
var offset = 0
for z in 0 ..< size {
rgb[2] = Float(z) / Float(size) // blue value
for y in 0 ..< size {
rgb[1] = Float(y) / Float(size) // green value
for x in 0 ..< size {
rgb[0] = Float(x) / Float(size) // red value
hsv = RGBtoHSV(r: rgb[0], g: rgb[1], b: rgb[2])
// TODO: Check if hsv.s > 0.5 is really nesseccary
let alpha: Float = (hsv.h > minHueAngle && hsv.h < maxHueAngle && hsv.s > 0.5) ? 0 : 1.0
cubeData[offset] = rgb[0] * alpha
cubeData[offset + 1] = rgb[1] * alpha
cubeData[offset + 2] = rgb[2] * alpha
cubeData[offset + 3] = alpha
offset += 4
}
}
}
let b = cubeData.withUnsafeBufferPointer { Data(buffer: $0) }
let data = b as NSData
let colorCube = CIFilter(name: "CIColorCube", withInputParameters: [
"inputCubeDimension": size,
"inputCubeData": data
])
return colorCube!
}
А потом код для применения фильтра к видео путем изменения функции func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode?
, которую я написал ранее:
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 filter = colorCubeFilterForChromaKey(hueAngle: 38)
let composition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in
let source = request.sourceImage
filter.setValue(source, forKey: kCIInputImageKey)
let output = filter.outputImage
request.finish(with: output!, context: nil)
})
let item = AVPlayerItem(asset: asset)
item.videoComposition = composition
let player = AVPlayer(playerItem: item)
let videoNode = SKVideoNode(avPlayer: player)
videoNode.size = CGSize(width: 200.0, height: 150.0)
videoNode.anchorPoint = CGPoint(x: 0.5, y: 0.0)
return videoNode
}
Код должен заменить все пиксели каждого кадра видео на alpha = 0.0
, если цвет пикселя соответствует диапазону оттенка фона.
Но вместо того, чтобы получать прозрачные пиксели, я получаю эти пиксели черными, как видно на рисунке ниже:
Теперь, несмотря на то, что это не нужный эффект, меня это не удивляет, так как я знал, что именно так iOS отображает видео с альфа-каналом.
Но вот реальная проблема - при отображении обычного видео в AVPlayer
, есть возможность добавить AVPlayerLayer
к представлению и установить pixelBufferAttributes
, чтобы слой игрока знал, что мы используем прозрачный пиксельный буфер, вот так:
let playerLayer = AVPlayerLayer(player: player)
playerLayer.bounds = view.bounds
playerLayer.position = view.center
playerLayer.pixelBufferAttributes = [(kCVPixelBufferPixelFormatTypeKey as String): kCVPixelFormatType_32BGRA]
view.layer.addSublayer(playerLayer)
Этот код дает нам видео с прозрачным фоном ( ХОРОШО! ), но с фиксированным размером и положением ( НЕ ХОРОШО ... ), как вы можете видеть на этом скриншоте:
Я хочу добиться того же эффекта, но на SKVideoNode
, а не на AVPlayerLayer
. Тем не менее, я не могу найти способ установить pixelBufferAttributes
на SKVideoNode
, и установка слоя игрока не дает желаемого эффекта ARKit
, поскольку он зафиксирован в позиции.
Есть ли какое-нибудь решение моей проблемы, или, может быть, есть другой метод для достижения того же желаемого эффекта?