通常使用 CIFilter 可以對(duì)圖像做一些處理巡莹,如果有些效果我們不滿意励稳,我們需要自己去實(shí)現(xiàn)的話刨秆,是可以通過寫 metal 來自定義 CIFilter,和 fragment shader 類似份蝴,處理對(duì)象都是一個(gè)像素點(diǎn)犁功。
如何使用 metal shader 自定義 CIFilter 網(wǎng)上的教程有很多,我這里還是贅述一下婚夫。
第一步:創(chuàng)建一個(gè).metal文件浸卦,定義 filter。記住自己的方法名案糙,后面需要用到限嫌。
第二步:繼承 CIFilter 定義出一個(gè)子類靴庆,通過加載 default.metallib 找到對(duì)應(yīng)的方法即可。
第三步:在 Build Settings 里面加入兩個(gè)flag怒医。
下面是第一步的代碼炉抒,隨便定義一個(gè)文件比如叫 kernel.metal,里面放上這些代碼稚叹。注意到這里的方法名是 myColor焰薄。
myColor 方法就是簡(jiǎn)單的返回當(dāng)前點(diǎn)的顏色。
#include <metal_stdlib>
#include <CoreImage/CoreImage.h>
using namespace metal;
extern "C" {
namespace coreimage {
float4 myColor(sample_t s, float value) {
return s.rgba;
}
}
}
第二步扒袖,建立一個(gè)自己的 CIFilter 子類塞茅,然后做下面這些事情。
class CustomFilter : CIFilter {
var value: Double = 0
private var kernel: CIKernel!
override init() {
super.init()
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// 找到默認(rèn)的 default.metallib
guard let url = Bundle.main.url(forResource: "default", withExtension: "metallib"),
let data = try? Data(contentsOf: url) else {
fatalError("Unable to get metallib")
}
// 從lib中加載到 myColor 方法
guard let myKernel = try? CIKernel.init(functionName: "myColor", fromMetalLibraryData: data) else {
fatalError("Unable to create CIKernel from myKernel")
}
kernel = myKernel
}
var inputImage:CIImage?
override var outputImage: CIImage? {
let src = CISampler(image: self.inputImage!)
// apply 這個(gè) filter
return kernel.apply(extent: inputImage!.extent, roiCallback: { _, rect in return rect}, arguments: [src, value])
}
}
第三步,在 Build Setting 里面加入 flag
需要加兩個(gè)參數(shù)一個(gè) -fcikernel
逆巍,另一個(gè)是 -cikernel
。這篇文檔第10頁有講 https://developer.apple.com/metal/MetalCIKLReference6.pdf 照著加一下即可。
好耶堪滨,自定義的 CIFilter 可以使用了,BUT鳖粟,此時(shí)此刻你之前寫的 pipeline 卻無法正常工作了禾唁,會(huì)提示找不到vertex function。
為什么會(huì)這樣呢迟蜜?因?yàn)榧恿?flag 之后刹孔,編譯的時(shí)候會(huì)將所有的 .metal 文件都通過某種形式進(jìn)行特殊的編譯使之成為適合 CoreImage 框架使用的代碼,正常的 pipeline 里面需要的 vertex shader 和 fragment shader 都失效了娜睛。
為了解決這個(gè)問題髓霞,需要將區(qū)分哪些是需要經(jīng)過 -cikernel, -fcikernel flag 打包的,哪些是不需要的畦戒。
我踩了不少坑才完成這項(xiàng)功能方库。
其實(shí)在 https://developer.apple.com/metal/MetalCIKLReference6.pdf 文檔中已經(jīng)寫了如何編譯 metal 使之成為kernel適用的代碼,見下圖
原理就是如此障斋,所以我們第一步需要區(qū)分 正常的metal 和 cikernel的metal纵潦,為了區(qū)分這一點(diǎn),直接將剛剛寫的 kernel.metal
的后綴改為 .kernel
垃环。這樣普通的metal文件仍然是 *.metal
邀层,而為 CIFilter 用的 metal 文件后綴則是 .kernel
。
第二步遂庄,編譯前需要改回 .metal
后綴寥院,經(jīng)過實(shí)踐發(fā)現(xiàn),metal編譯器直接忽略了后綴名不正確的文件以至于無法編譯成功涛目,所以我們需要先執(zhí)行一個(gè) cp 步驟秸谢。
第三步经磅,執(zhí)行如上圖所示的編譯,變成一個(gè)我們自定義的 xxxx.metallib
第四步钮追,將 xxxx.metallib 復(fù)制到打包路徑中预厌,以便打包的時(shí)候可以打進(jìn)ipa文件。
第五步元媚,在自定義的 CIFilter 中加載metallib時(shí)轧叽,使用剛剛自定義的 xxxx.metallib,而不是 default刊棕。
現(xiàn)在來講講具體操作炭晒。
在 Build Rules 中添加一個(gè)步驟
在里面的輸入框中放入
# 復(fù)制一下,不然metal編譯器不認(rèn)識(shí)
cp "${SRCROOT}/Varlens/Shader/kernel.kernel" "${DERIVED_FILES_DIR}/kernel.metal"
# 后面這兩句就是編譯了甥角,注意輸入輸出路徑即可
xcrun metal -fcikernel "${DERIVED_FILES_DIR}/kernel.metal" -c -o "${DERIVED_FILES_DIR}/MyKernels.air"
xcrun metallib -cikernel "${DERIVED_FILES_DIR}/MyKernels.air" -o ${DERIVED_FILE_DIR}/kernel.metallib
然后在 Build Phases 中添加一個(gè)步驟网严,做復(fù)制 metallib 的操作。
完事嗤无。
krosshj @ 2021-04-22 14:57