实现原理
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];
}