Metal-LUT-滤镜

实现原理

LUT的实现原理其实是基于RGB的映射,由原始的RGB映射到结果的RGB中去,而LUT的作用就是这次映射过程中的查找表。
举个例子,比如你参加一场考试,现在给你一张表和三个数字,第一个数字代表哪栋楼,第二个数字代表哪一层,第三个数字代表哪个房间


image

现在给个数字345,那你在这张表里面就能找到,你要去科研楼12层的15号房间参加考试,这个原理和LUT的原理非常接近,可以这样初步理解一下。
我们知道,RGB是相互独立的三种颜色通道,其取值范围均为[0, 255],如果我们需要建立一个映射表,就可以用一个三维的数组来存储,总共有256 * 256 * 256种情况。
但有个问题,256的三次方,实在太大了,相当于我们需要这么多个像素情况进行一一映射,显然,对于一个APP来说,弄这么大的映射表实在浪费性能。
幸好,LUT通过巧妙的设计,用一张图包含了所有的信息,他是怎么做到的呢,可以看下面这张图:


image

LUT分割成了8 * 8 = 64个方格,每个方格等分成了64 * 64个像素,如果够细心的话,我们可以看到这里可以用64 * 64 * 64来对应RGB值了,和256 * 256 * 256相比,我们按1:4的值进行了压缩,也就是说,LUT里面的每一个像素跨度了4个值的信息。
如上图所示,LUT里,对于不同的小方格,其R、G的分布规律是相同的,而在同一个小方格中,从左到右包含了64个R值,从上到下包含了64个G值,呈现递增关系。
而对于同一个小方格,其B值是相同的,对于不同方格来说,B值随着方格所在的位置变化而变化,从左到右、从上到下递增。

一句话总结就是:根据B值定位到小方格、根据RG值定位到小方格里面的像素点。
知道了上面的原理之后,我们就可以根据原有的RGB映射到新的RGB了

纹理上传

- (void)p_setupTexture {

        UIImage *image = [UIImage imageWithContentsOfFile:_tablePath];
        // 纹理描述符
        MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
        textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; // 图片的格式要和数据一致
        textureDescriptor.width = image.size.width;
        textureDescriptor.height = image.size.height;
        textureDescriptor.usage = MTLTextureUsageShaderRead; // 原图片只需要读取
        self.lookupTableTexture = [self.device newTextureWithDescriptor:textureDescriptor]; // 创建纹理
        
        MTLRegion region = {{ 0, 0, 0 }, {image.size.width, image.size.height, 1}}; // 纹理上传的范围
        Byte *imageBytes = [self loadImage:image];
        if (imageBytes) { // UIImage的数据需要转成二进制才能上传,且不用jpg、png的NSData
            [self.lookupTableTexture replaceRegion:region
                                  mipmapLevel:0
                                    withBytes:imageBytes
                                  bytesPerRow:4 * image.size.width];
            free(imageBytes); // 需要释放资源
            imageBytes = NULL;
        }
}

- (Byte *)loadImage:(UIImage *)image {
    // 1获取图片的CGImageRef
    CGImageRef spriteImage = image.CGImage;
    
    // 2 读取图片的大小
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    
    Byte * spriteData = (Byte *) calloc(width * height * 4, sizeof(Byte)); //rgba共4个byte
    
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,
                                                       CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    
    // 3在CGContextRef上绘图
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
    
    CGContextRelease(spriteContext);
    
    return spriteData;
}

渲染

- (void)draw {
    id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
    // MTLRenderPassDescriptor描述一系列attachments的值,类似GL的FrameBuffer;同时也用来创建MTLRenderCommandEncoder
    if(self.renderPassDescriptor) {
        self.destTexture = [self.textureCache getTextureWithSize:[self outputSize] format:self.sourceTexture.pixelFormat];
        self.renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.5, 0.5, 1.0f); // 设置默认颜色
        self.renderPassDescriptor.colorAttachments[0].texture = self.destTexture.texture;
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; //编码绘制指令的Encoder
        [renderEncoder setViewport:(MTLViewport){0.0, 0.0, [self outputSize].width, [self outputSize].height, -1.0, 1.0 }]; // 设置显示区域
        [renderEncoder setRenderPipelineState:self.renderPipelineState]; // 设置渲染管道,以保证顶点和片元两个shader会被调用
        [renderEncoder setVertexBuffer:self.vertices
                                offset:0
                               atIndex:ZZHVertexInputIndexVertices]; // 设置顶点缓存
        float a = 1.f;
        id<MTLBuffer> buffer = [self.device newBufferWithBytes:&a length:sizeof(a) options:MTLResourceStorageModeShared];
        [renderEncoder setFragmentBuffer:buffer offset:0 atIndex:0];
        [renderEncoder setFragmentTexture:self.sourceTexture.texture
                            atIndex:ZZHFragmentTextureIndexNormal]; // 设置纹理
        [renderEncoder setFragmentTexture:self.lookupTableTexture
                            atIndex:ZZHFragmentTextureIndexLookupTable]; // 设置纹理
        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                          vertexStart:0
                          vertexCount:self.numVertices]; // 绘制
        [renderEncoder endEncoding]; // 结束
    }
    
    [commandBuffer commit]; // 提交;
    [self sendToTarget];
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容