基于Metal Camera开发1:读取渲染结果生成UIImage及GPUImage的LookupFilter滤镜,本文档通过Metal compute shader对摄像头当前捕获的画面进行一左一右叠加两个滤镜,并根据手势修改滤镜的作用区域。
文档结构:
- 移植GPUImageLookupFilter Shader至Metal Computer Shader
- 手势改变滤镜作用区域
- 讨论:Metal Compute Shader纹理坐标及MTKTextureLoader默认生成sRGB纹理
1. 移植GPUImageLookupFilter Shader至Metal Computer Shader
值得注意的是,对于OpenGL (ES),纹理采样使用归一化的坐标值,而Metal Computer Shader使用原始坐标值。移植过程中我在这一问题上花了一些时间才定位到,也怪自己没看Metal相关文档。参考代码如下。
float4 lookup(texture2d<float, access::read> lookupTexture, float4 textureColor) {
const float blueColor = textureColor.b * 63.0;
float2 quad1;
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
float2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
float2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
float2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
float2 originCoord = float2(lookupTexture.get_width(), lookupTexture.get_height());
texPos1 = clamp(texPos1, 0.0, 1.0);
texPos2 = clamp(texPos2, 0.0, 1.0);
uint2 mapTexPos1 = uint2(texPos1 * originCoord);
uint2 mapTexPos2 = uint2(texPos2 * originCoord);
const float4 newColor1 = lookupTexture.read(mapTexPos1);
const float4 newColor2 = lookupTexture.read(mapTexPos2);
const float4 outColor = mix(newColor1, newColor2, fract(blueColor));
return outColor;
}
2. 手势改变滤镜作用区域
读取当前触摸点在视图的坐标并提供给Metal即可。读取坐标的过程中,因不熟悉Swift语法,花了些时间还是编译失败。在此感谢@江夏沐帮忙实现了CGFloat到Float的转换。
获取屏幕触摸点坐标的参考代码如下。
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let location = touches.first?.location(in: self)
filterPosition = Float(location?.x ?? 0) * Float(self.contentScaleFactor)
print(filterPosition ?? "-1.0")
}
Meta Compute Shader根据手势改变滤镜作用区域的参考代码如下。
float4 inColor = inTexture.read(gid);
float4 textureColor = clamp(inColor, 0.0, 1.0);
float4 outColor = float4(0.0);
unsigned int pos = (unsigned int)(*filterPosition);
if (gid.x < pos) {
outColor = lookup(leftLookupTexture, textureColor);
} else {
outColor = lookup(rightLookupTexture, textureColor);
}
outTexture.write(outColor, gid);
3. 讨论:Metal Compute Shader纹理坐标及MTKTextureLoader默认生成sRGB纹理
3.1. Metal Compute Shader纹理坐标
第1节移植GPUImageLookupFilter Shader至Metal Computer Shader已说明计算着色器默认情况下给的是整数坐标,说明它是原始像素坐标,区别于OpenGL ES。移植GLSL时应该格外注意。
3.2. MTKTextureLoader默认生成sRGB纹理
使用MTKTextureLoader加载颜色查找表(Lookup Table)图像时,默认情况下它生成sRGB颜色范围的纹理,即使图像元数据中并不声明sRGB。如果这影响了滤镜的表现,将MTKTextureLoaderOptionSRGB
设置为false让MTKTextureLoader按图像原始色彩空间加载即可,参考代码如下。
do {
try rightLookupTexture = textureLoader?.newTexture(withContentsOf: Bundle.main.url(forResource: "lookup.png", withExtension: nil)!, options: [
MTKTextureLoaderOptionSRGB : false as NSObject])
} catch {
print("load right lookup png failed.")
}