关于iOS的图像处理目前主流的有OpenGL ES和Metal,这里介绍一下Metal的第三方库,这个库简化了非常多的原生初始化方法,可以非常简便的实现各种滤镜效果,MetalPetal的自带滤镜使用很简单这里不展开讨论,作为一个初学者,简单记录一下学习MetalPetal库时遇到的的问题,重点是自定义Metal文件下如何使用MetalPetal
设置三个属性
//计算管线
@property(nonatomic,strong) MTIComputePipelineKernel *customComputeKernel;
//渲染管线
@property(nonatomic,strong) MTIRenderPipelineKernel *customRenderKernel;
//图像
@property (nonatomic, strong) MTIImage *inputImage;
首先是初始化一个MTIImage
UIImage *img = [UIImage imageNamed:@"gakiki"];
self.inputImage = [[MTIImage alloc] initWithCGImage:img.CGImage options:nil isOpaque:YES];
初始化计算管线和渲染管线
-(void)setupPipeline
{
//计算管线
NSURL *defaultUrl = [[NSBundle mainBundle] URLForResource:@"default" withExtension:@"metallib"];
MTIFunctionDescriptor *customKernelDes = [[MTIFunctionDescriptor alloc] initWithName:@"normalTestKernel" libraryURL:defaultUrl];
//初始化计算管线
self.customComputeKernel = [[MTIComputePipelineKernel alloc] initWithComputeFunctionDescriptor:customKernelDes];
//渲染管线
//顶点描述
MTIFunctionDescriptor *VertexFunctionDescriptor = [[MTIFunctionDescriptor alloc] initWithName:@"normalvertexShader" libraryURL:defaultUrl];
//片元描述
MTIFunctionDescriptor *fragmentFunctionDescriptor = [[MTIFunctionDescriptor alloc] initWithName:@"normalfragmentShader" libraryURL:defaultUrl];
//初始化渲染管线
self.customRenderKernel = [[MTIRenderPipelineKernel alloc] initWithVertexFunctionDescriptor:VertexFunctionDescriptor fragmentFunctionDescriptor:fragmentFunctionDescriptor];
}
这里有第一个要注意的点,如果是自定义的.metal文件MTIFunctionDescriptor的描述必须要用libraryURL的方式去初始化,因为MetalPetal默认的library是自带的那些文件方法,如果不用libraryURL这个方式是读取不到你自己的方法的,剩下的就是根据方法名去初始化MTIFunctionDescriptor,记得MTIRenderPipelineKernel要顶点和片元两个描述,别漏了就行
渲染管线和计算管线已经初始化好了,接下来就是应用了,首先看一下计算管线的使用
- (void)drawViewWithCompute {
MTIImage *outImage = [_customComputeKernel applyToInputImages:@[_inputImage] parameters:@{} outputTextureDimensions:MTITextureDimensionsMake2DFromCGSize(_inputImage.size) outputPixelFormat:MTLPixelFormatRGBA32Float];
_renderView.image = outImage;
}
这里我们对比一下原生的metal
id<MTLComputeCommandEncoder> commandEncoder = [commandBuffer computeCommandEncoder];
//计算管线的配置
[commandEncoder setComputePipelineState:self.pipeline];
//传参给kernel函数
[commandEncoder setTexture:inputTexture atIndex:0];
[commandEncoder setTexture:self.internalTexture atIndex:1];
原生的我们在index0和index1分别输入inputTexture和internalTexture,inputTexture的access::read作为处理的数据源纹理,internalTexture 的access::write作为输出纹理,然后self.internalTexture作为处理过的纹理作为参数传到渲染管线,输出到屏幕上,我们知道kernel函数的返回值必须是void,那为什么MetalPetal的计算方法会 有返回值呢?
这里是第二个要注意的点,就是MetalPetal的applyToInputImages的参数问题,在MetalPetal里 计算管线的输出直接用一个MTIImageComputeRecipe接收然后[[MTIImage alloc] initWithPromise:receipt]作为一个MTIImage的返回值输出,看下metal里的计算方法
kernel void
normalKernel(texture2d<float, access::read> sourceTexture [[texture(0)]],
texture2d<float, access::write> destTexture [[texture(1)]],
uint2 grid [[thread_position_in_grid]])
{
float4 color = sourceTexture.read(grid);
color.r += 0.5;
destTexture.write(color, grid);
}
这里sourceTexture对应_inputImage,而destTexture是空的,是不需要外界传递的,所以在使用MetalPetal时直接输入一个纹理然后外界接收到的MTIImage就相当于internalTexture了,这个outImage就可以像internalTexture一样用于下面的渲染管线的渲染了
MTIVertices *geometry = [[MTIVertices alloc] initWithVertices:(MTIVertex []){ { .position = {1, -1, 0.0, 1.0} , .textureCoordinate = { 1.f, 1.f } }, { .position = {-1, -1, 0.0, 1.0 } , .textureCoordinate = { 0.f, 1.f } }, { .position = {-1, 1, 0.0, 1.0} , .textureCoordinate = { 0.f, 0.f } }, { .position = {1, -1, 0.0, 1.0} , .textureCoordinate = { 1.f, 1.f } },{ .position = {-1, 1, 0.0, 1.0} , .textureCoordinate = { 0.f, 0.f } },{ .position = {1, 1, 0.0, 1.0} , .textureCoordinate = { 1.f, 0.f } } } count:6 primitiveType:MTLPrimitiveTypeTriangleStrip];
//
// //定点数据和纹理数据都传递给GPU
MTIRenderCommand *command = [[MTIRenderCommand alloc] initWithKernel:_customRenderKernel geometry:geometry images:@[outImage] parameters:@{}];
MTIRenderPassOutputDescriptor *outputDescriptor = [[MTIRenderPassOutputDescriptor alloc] initWithDimensions:MTITextureDimensionsMake2DFromCGSize(_outputImage.size) pixelFormat:_renderView.colorPixelFormat];
self.renderView.image = [MTIRenderCommand imagesByPerformingRenderCommands:@[command]
outputDescriptors:@[outputDescriptor]].firstObject;
这里geometry是顶点数据跟Metal原生的格式一样的
这里只是最基本的自定义metal的实现,如果想做更多的自定义功能,比如多重纹理什么的,还是要对metal原生框架有过深入了解,作为初学者,我会不断记录遇到的问题,以便日后随时查看
网友评论