将OpenGL着色器转换为可用于CIFilter的Metal(Swift)格式

6

我对 OpenGL/Metal 还比较陌生,正在尝试理解一些基本概念。
在我们的应用程序中,我们使用 CIFilter 来过滤视频。我看了一个来自 2017 年的 WWDC 视频,解释说可以将 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.);

}

最终效果如下图所示:

enter image description here

在将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
   }
}

非常感谢您的帮助!


1
你的 MetalFilter 实现目前看起来还不错。你遇到了什么问题? - Frank Rupprecht
1
@FrankSchlegel 嘿,弗兰克,谢谢你的回复。我正在尝试理解如何将OpenGL滤镜代码转换为Metal(filename.metal文件)。是否有关于如何进行此操作的指南? - Roi Mulia
1个回答

10

我试着去做了一下,这是核心代码:

#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])
}

嘿,弗兰克!非常感谢!它有效 :) 现在我可以将其用作参考点 :) 太棒了! - Roi Mulia
谢谢,Frank。我会看一下的,这个周末打算自学一下 :) 另外,如果可以问一下:使用Metal和CIFilter相比在我们的应用程序中使用OpenGL代码,我们能获得多少实际性能提升?如果我们将它们并排放置,Metal包装器的性能是否明显更好? - Roi Mulia
1
我猜这取决于你的过滤管道的复杂性。对于这个简单的过滤器,你可能不会注意到差异。但是如果你将许多过滤器链接在一起,CI运行时可以在幕后进行一些优化。此外,苹果显然正在全面废弃OpenGL,因此Metal无论如何都是正确的选择。 - Frank Rupprecht
1
没错!如果您想尽可能地模拟CI API,您可以像这样声明属性:@objc dynamic var inputTime: NSNumber,然后使用filter.setValue(CMTimeGetSeconds(request.compositionTime), forKey:"inputTime")设置值。 - Frank Rupprecht
大家好,这太棒了,对于我这个完全不懂着色器的新手来说,帮助很大! - meaning-matters
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接