Metal片段着色器转Metal CoreImage内核:内置滤镜实现及采样器格式咨询
将Metal片段着色器转换为CoreImage内核的方案
一、用CoreImage内置滤镜实现需求
你的需求是基于BT.709权重(0.2126、0.7152、0.0722为BT.709亮度系数)计算亮度,再按阈值替换颜色,完全可以通过内置滤镜组合实现,无需自定义内核:
步骤拆解
- 生成亮度图
使用CIColorMatrix滤镜,将RGB通道按BT.709权重转换为灰度值:
CIFilter *colorMatrix = [CIFilter filterWithName:@"CIColorMatrix"]; [colorMatrix setValue:inputImage forKey:kCIInputImageKey]; // 矩阵参数:[R通道系数, G通道系数, B通道系数, 0, 0, // R通道系数, G通道系数, B通道系数, 0, 0, // R通道系数, G通道系数, B通道系数, 0, 0, // 0, 0, 0, 1, 0] [colorMatrix setValue:[CIVector vectorWithValues:(CGFloat[]){0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0, 0, 0, 1, 0} count:20] forKey:@"inputMatrix"]; CIImage *luminanceImage = colorMatrix.outputImage;
- 创建阈值遮罩
用CIColorThreshold分别生成高、低亮度区域的遮罩:
- 高亮度遮罩(亮度≥0.95):
CIFilter *highThreshold = [CIFilter filterWithName:@"CIColorThreshold"]; [highThreshold setValue:luminanceImage forKey:kCIInputImageKey]; [highThreshold setValue:@0.95 forKey:@"inputThreshold"]; CIImage *highMask = highThreshold.outputImage;
- 低亮度遮罩(亮度≤0.05):
CIFilter *lowThreshold = [CIFilter filterWithName:@"CIColorThreshold"]; [lowThreshold setValue:luminanceImage forKey:kCIInputImageKey]; [lowThreshold setValue:@0.05 forKey:@"inputThreshold"]; // 反转遮罩,保留阈值以下的区域 CIFilter *invertLow = [CIFilter filterWithName:@"CIColorInvert"]; [invertLow setValue:lowThreshold.outputImage forKey:kCIInputImageKey]; CIImage *lowMask = invertLow.outputImage;
- 合成颜色替换效果
用CIBlendWithMask将红、蓝色分别合成到原图对应区域:
- 生成纯色图并裁剪到原图尺寸:
CIImage *redImage = [CIImage imageWithColor:[CIColor colorWithRed:1 green:0 blue:0 alpha:1]]; redImage = [redImage imageByCroppingToRect:inputImage.extent]; CIImage *blueImage = [CIImage imageWithColor:[CIColor colorWithRed:0 green:0 blue:1 alpha:1]]; blueImage = [blueImage imageByCroppingToRect:inputImage.extent];
- 先合成红色区域:
CIFilter *blendRed = [CIFilter filterWithName:@"CIBlendWithMask"]; [blendRed setValue:inputImage forKey:kCIInputImageKey]; [blendRed setValue:redImage forKey:@"inputBackgroundImage"]; [blendRed setValue:highMask forKey:@"inputMaskImage"]; CIImage *tempImage = blendRed.outputImage;
- 再合成蓝色区域:
CIFilter *blendBlue = [CIFilter filterWithName:@"CIBlendWithMask"]; [blendBlue setValue:tempImage forKey:kCIInputImageKey]; [blendBlue setValue:blueImage forKey:@"inputBackgroundImage"]; [blendBlue setValue:lowMask forKey:@"inputMaskImage"]; CIImage *finalImage = blendBlue.outputImage;
如果要支持BT.2020色彩空间,只需把CIColorMatrix的权重换成BT.2020系数(0.2627, 0.6780, 0.0593)即可,其余步骤完全一致。
二、自定义CoreImage内核实现(若不用内置滤镜)
如果需要自定义内核,对应的Metal代码如下:
#include <CoreImage/CoreImage.h> kernel vec4 thresholdKernel(sampler inputImage) { vec2 coord = samplerCoord(inputImage); vec3 rgb = sample(inputImage, coord).rgb; vec3 weights = vec3(0.2126, 0.7152, 0.0722); float intensity = dot(rgb, weights); if (intensity >= 0.95) { rgb = vec3(1.0, 0.0, 0.0); } else if (intensity <= 0.05) { rgb = vec3(0.0, 0.0, 1.0); } return vec4(rgb, 1.0); }
对比原片段着色器,CoreImage内核语法更简洁:无需顶点输入,直接用sampler类型参数,通过samplerCoord获取纹理坐标、sample函数采样。
三、关于CoreImage采样器的像素格式问题
CoreImage采样器并非仅支持RGB类格式,它支持包括单通道灰度格式(如kCIFormatR8、kCIFormatR16Half等)在内的多种像素格式。处理灰度图时,可直接将灰度CIImage传入自定义内核,采样后读取r通道即为亮度值,示例代码:
kernel vec4 grayThresholdKernel(sampler grayImage) { vec2 coord = samplerCoord(grayImage); float intensity = sample(grayImage, coord).r; vec3 rgb; if (intensity >= 0.95) { rgb = vec3(1.0, 0.0, 0.0); } else if (intensity <= 0.05) { rgb = vec3(0.0, 0.0, 1.0); } else { rgb = vec3(intensity); // 保留原灰度 } return vec4(rgb, 1.0); }
注意:用内置滤镜生成的灰度图默认是RGB格式(三通道值相同),若要得到单通道灰度图,需在创建CIContext时指定输出格式,或用CIColorMatrix合并通道后转换格式。
内容的提问来源于stack exchange,提问作者Deepak Sharma
相关产品推荐
相关产品推荐

