Я новичок в OpenGL/Metal и пытаюсь понять некоторые фундаментальные понятия.
В нашем приложении мы используем CIFilter для фильтрации видео. Я видел WWDC видео от 2017 года, объясняющее, что вы можете обернуть CIFilter с Metal и использовать его как обычный фильтр.
Я пытаюсь понять, как преобразовать этот OpenGL видеоэффект в Metal, чтобы я мог использовать его в качестве ориентира для будущих эффектов.
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
float amount = sin(iTime) * 0.1;
// uv coords
vec2 uv = fragCoord / iResolution.xy;
amount *= 0.3;
float split = 1. - fract(iTime / 2.);
float scanOffset = 0.01;
vec2 uv1 = vec2(uv.x + amount, uv.y);
vec2 uv2 = vec2(uv.x, uv.y + amount);
if (uv.y > split) {
uv.x += scanOffset;
uv1.x += scanOffset;
uv2.x += scanOffset;
}
float r = texture(iChannel0, uv1).r;
float g = texture(iChannel0, uv).g;
float b = texture(iChannel0, uv2).b;
fragColor = vec4(r, g, b, 1.);
}
Что производит:
После преобразования кода OpenGL в Metal я использую оболочку CIFilter, чтобы использовать его с AVPlayerItem:
class MetalFilter: CIFilter {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let kernel: CIKernel
var inputImage: CIImage?
override init() {
let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
let data = try! Data(contentsOf: url)
kernel = try! CIKernel(functionName: "vhs", fromMetalLibraryData: data)
super.init()
}
func outputImage() -> CIImage? {
guard let inputImage = inputImage else {return nil}
let sourceSize = inputImage.extent.size
let outputImage = kernel.apply(extent: CGRect(x: 0, y: 0, width: sourceSize.width, height: sourceSize.height), roiCallback: { index, destRect in
return destRect
}, arguments: [inputImage, NSNumber(value: Float(1.0 / sourceSize.width)), NSNumber(value: Float(1.0 / sourceSize.height)), NSNumber(value: 60.0)])
return outputImage
}
}
Любая помощь будет высоко оценена!
@FrankSchlegel Эй, Фрэнк, спасибо за ответ. Я пытаюсь понять, как преобразовать код фильтра OpenGL в Metal (файл filename.metal). Есть ли руководство о том, как это сделать?





Я попробовал. Вот код ядра:
#include <metal_stdlib>
using namespace metal;
#include <CoreImage/CoreImage.h>
extern "C" { namespace coreimage {
float4 vhs(sampler_h src, float time, float amount) {
const float magnitude = sin(time) * 0.1 * amount;
float2 greenCoord = src.coord(); // this is alreay in relative coords; no need to devide by image size
const float split = 1.0 - fract(time / 2.0);
const float scanOffset = 0.01;
float2 redCoord = float2(greenCoord.x + magnitude, greenCoord.y);
float2 blueCoord = float2(greenCoord.x, greenCoord.y + magnitude);
if (greenCoord.y > split) {
greenCoord.x += scanOffset;
redCoord.x += scanOffset;
blueCoord.x += scanOffset;
}
float r = src.sample(redCoord).r;
float g = src.sample(greenCoord).g;
float b = src.sample(blueCoord).b;
return float4(r, g, b, 1.0);
}
}}
А вот небольшая корректировка outputImage в вашем фильтре:
override var outputImage: CIImage? {
guard let inputImage = self.inputImage else { return nil }
// could be filter parameters
let inputTime: NSNumber = 60
let inputAmount: NSNumber = 0.3
// You need to tell the kernel the region of interest of the input image,
// i.e. what region of input pixels you need to read for a given output region.
// Since you sample pixels to the right and below the center pixel, you need
// to extend the ROI accordingly.
let magnitude = CGFloat(sin(inputTime.floatValue) * 0.1 * inputAmount.floatValue)
let inputExtent = inputImage.extent
let roiCallback: CIKernelROICallback = { _, rect -> CGRect in
return CGRect(x: rect.minX, y: rect.minY,
width: rect.width + (magnitude + 0.01) * inputExtent.width, // scanOffset
height: rect.height + magnitude * inputExtent.height)
}
return self.kernel.apply(extent: inputExtent,
roiCallback: roiCallback,
arguments: [inputImage, inputTime, inputAmount])
}
Привет, Фрэнк! Большое спасибо! Это работает :) Теперь я могу использовать его в качестве ориентира :) Супер!
Хороший! Рад, что смог помочь. Фильтры Core Image Metal и API, к сожалению, очень плохо документированы... Дайте мне знать, если у вас возникнут дополнительные вопросы.
У вас есть рекомендации, что/где почитать на эту тему, чтобы я мог лучше понять, как преобразовать фильтр OpenGL в металл самостоятельно? Я пытаюсь понять, какие «ключевые фразы» мне следует искать. Например, я даже не был уверен, подходит ли сюда слово «Шейдер».
Я думаю, что «ядро фильтра» было бы правильным словом. Честно говоря, лучший (и почти единственный) источник документации от Apple — это сессия WWDC, которую вы уже смотрели. Возможно, также ознакомьтесь с теми, что были в 2018 году, в которых отмечены некоторые недавние улучшения. И вот один очень полезный PDF-файл: developer.apple.com/metal/MetalCIKLReference6.pdf. И попробуйте нажать Cmd+Shift+O в Xcode и открыть CIKernelMetalLib.h. Это заголовок, содержащий все дополнения к Metal, связанные с CI, и некоторую документацию.
Спасибо, Фрэнк. Я посмотрю, собираюсь провести выходные, обучая себя этому :) Также, могу ли я спросить: сколько фактического прироста производительности мы получаем, используя Metal с CIFilter, по сравнению с использованием кода OpenGL в нашем приложении? Если мы разместим их рядом, производительность металлической обертки заметно улучшится?
И последний небольшой вопрос, касающийся этого конкретного фильтра. Я видел, что inputTime было статическим в свойстве outputImage. Вместо этого я сделал его свойством класса MetalFilter и каждому кадру присваиваю его значение по времени композиции filter.inputTime = NSNumber(value: CMTimeGetSeconds(request.compositionTime)). Теперь я действительно вижу, как линии фильтра поднимаются и опускаются (:D). Это правильный способ сделать это?
Я думаю, это зависит от сложности вашего конвейера фильтров. Для этого простого фильтра вы, вероятно, не заметите разницы. Но если вы соедините множество фильтров вместе, среда выполнения CI может выполнить большую внутреннюю оптимизацию. Кроме того, Apple явно осуждает OpenGL везде, так что Metal в любом случае — это путь.
Это! Если вы хотите максимально имитировать CI API, вы должны объявить свойство следующим образом @objc dynamic var inputTime: NSNumber, а затем установить значение с помощью filter.setValue(CMTimeGetSeconds(request.compositionTime), forKey: "inputTime")
Потрясающий. Еще раз спасибо! Желаем вам отличных и спокойных выходных :)
Ребята, это потрясающе и очень помогает мне, новичку в шейдерах, очень!
Ваша реализация
MetalFilterпока выглядит нормально. С чем вы боретесь?