美文网首页从八开始——图形渲染/Metal专题
Metal与图形渲染六:颜色查找表LUT

Metal与图形渲染六:颜色查找表LUT

作者: 肠粉白粥_Hoben | 来源:发表于2021-07-15 16:31 被阅读0次

    零. 前言

    票圈里总能看到一些很唯美的滤镜,让我惊叹之余又好奇,这个东西是怎么实现的呢,后面经过一番搜索之后,发现了有LUT这个好东西,自己鼓捣了一番,借鉴了别的APP的滤镜,然后得到了下面的效果:

    虽然抄过来的滤镜和原滤镜相比还是有很大区别,原滤镜应该还有自己的算法,但这个效果也还算可以啦,来看看怎么弄的吧~

    一. 实现原理

    LUT的实现原理其实是基于RGB的映射,由原始的RGB映射到结果的RGB中去,而LUT的作用就是这次映射过程中的查找表。

    举个例子,比如你参加一场考试,现在给你一张表和三个数字,第一个数字代表哪栋楼,第二个数字代表哪一层,第三个数字代表哪个房间

    现在给个数字345,那你在这张表里面就能找到,你要去科研楼12层的15号房间参加考试,这个原理和LUT的原理非常接近,可以这样初步理解一下。

    我们知道,RGB是相互独立的三种颜色通道,其取值范围均为[0, 255],如果我们需要建立一个映射表,就可以用一个三维的数组来存储,总共有256 * 256 * 256种情况。

    但有个问题,256的三次方,实在太大了,相当于我们需要这么多个像素情况进行一一映射,显然,对于一个APP来说,弄这么大的映射表实在浪费性能。

    幸好,LUT通过巧妙的设计,用一张图包含了所有的信息,他是怎么做到的呢,可以看下面这张图:

    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了

    二. 开始实战

    我们来举个例子,我们来在LUT表里面找到(R, G, B) = (255, 255, 255)对应的像素。

    1. 归一化

    首先,将RGB归一化除以255,得到(1,1,1)。

    2. 根据B值定位小方格

    由于一共有[0, 63]共64个小方格,我们可以根据B * 63得到对应的小方格的位置,所以我们的小方格是第64个小方格,下标n=63。

    我们根据n,定位小方格相对于LUT图的quadX、quadY,可以得到

    quadY = floor(n / 8) = 7
    quadX = n - quadY * 8 = 7
    

    也就是该小方格处于第7行第7列。(下标从0开始算)

    3. 根据RG值定位像素位置

    首先定位该像素在小方格里面的相对位置,R决定了x坐标,G决定了y坐标:

    stepSize = 0.5 // 由于要取中点,所以得到像素的左上角之后再+0.5
    
    squareX = R * 63 + stepSize = 63.5
    squareY = G * 63 + stepSize = 63.5
    

    每个小方格大小为64 * 64,再定位到该像素点的位置:

    x = quadX * 64 + squareX = 511.5
    y = quadY * 64 + squareY = 511.5
    

    最后归一化,得到该像素点对应的坐标

    x = 511.5 / 512
    y = 511.5 / 512
    

    三. 更复杂的情况

    当然,一个像素点很大概率不是整数,而有可能是小数,那就说一下小数的情况,如(0.6, 0.8, 0.2):

    1. 根据B值定位小方格

    由于B = 0.2,我们根据n = 63 * 0.2 = 12.6,可以得到两个小方格,n=12、n=13,最终结果由这两个小方格的像素点混合而成

    其中:

    quad1.y = floor(12.6) / 8 = 1
    quad1.x = floor(12.6) - quad1.y * 8 = 4
    
    quad2.y = ceil(12.6) / 8 = 1
    quad2.x = ceil(12.6) - quad2.y * 8 = 5
    

    可以看到两个小方格分别分布在第二行(下标为1)的第5个(下标为4)和第6个(下标为5)。

    2. 根据RG值定位像素位置

    首先定位该像素在小方格里面的相对位置,R决定了x坐标,G决定了y坐标:

    stepSize = 0.5 // 由于要取中点,所以得到像素的左上角之后再+0.5
    
    squareX = R * 63 + stepSize = 38.3
    squareY = G * 63 + stepSize = 50.9
    

    找到上面两个小方格像素点相对于LUT图的位置:

    texPos1.x = (quad1.x * 64 + squareX) / 512 = 294.3 / 512
    texPos1.y = (quad1.y * 64 + squareY) / 512 = 306.9 / 512
    
    texPos2.x = (quad2.x * 64 + squareX) / 512 = 294.3 / 512
    texPos2.y = (quad2.y * 64 + squareY) / 512 = 370.9 / 512
    

    最后采样、混合即可,值得注意的是,需要根据B值的小数部分决定mix的percent值,小数部分越大,越靠近正方形2

    float4 newColor1 = lookupTableTexture.sample(textureSampler, texPos1); // 正方形1的颜色值
    float4 newColor2 = lookupTableTexture.sample(textureSampler, texPos2); // 正方形2的颜色值
    
    float4 newColor = mix(newColor1, newColor2, fract(blueColor)); // 根据小数点的部分进行mix
    

    我们还可以根据slideBar值决定最后的颜色更接近哪一边:

    return mix(textureColor, float4(newColor.rgb, textureColor.a), intensity);
    

    Shader代码如下:

    #include <metal_stdlib>
    #include "CCAVPShaderTypes.h"
    
    using namespace metal;
    
    constant float stepSize = 0.5;
    
    fragment float4 lutFragment(SingleInputVertexIO input [[ stage_in ]],
                                texture2d<float> normalTexture [[ texture(0) ]],
                                texture2d<float> lookupTableTexture [[ texture(1) ]],
                                constant float &intensity [[ buffer(0) ]])
    {
        constexpr sampler textureSampler (mag_filter::linear,
                                          min_filter::linear);
        float4 textureColor = normalTexture.sample(textureSampler, input.textureCoordinate); //正常的纹理颜色
        
        float blueColor = textureColor.b * 63.0; // 蓝色部分[0, 63] 共64种
        
        float2 quad1; // 第一个正方形的位置, 假如blueColor=22.5,则y=22/8=2,x=22-8*2=6,即是第2行,第6个正方形;(因为y是纵坐标)
        quad1.y = floor(floor(blueColor) * 0.125);
        quad1.x = floor(blueColor) - (quad1.y * 8.0);
        
        float2 quad2; // 第二个正方形的位置,同上。注意x、y坐标的计算,还有这里用int值也可以,但是为了效率使用float
        quad2.y = floor(ceil(blueColor) * 0.125);
        quad2.x = ceil(blueColor) - (quad2.y * 8.0);
        
        // 该像素点相对于小方格的位置(取中间点,所以乘以63再加0.5)
        float squareX = textureColor.r * 63 + stepSize;
        float squareY = textureColor.g * 63 + stepSize;
        
        float2 texPos1; // 正方形1对应像素点相对于LUT图的位置
        texPos1.x = quad1.x * 64 + squareX;
        texPos1.y = quad1.y * 64 + squareY;
        
        float2 texPos2; // 正方形2对应像素点相对于LUT图的位置
        texPos2.x = quad2.x * 64 + squareX;
        texPos2.y = quad2.y * 64 + squareY;
        
        float4 newColor1 = lookupTableTexture.sample(textureSampler, texPos1 / 512); // 正方形1的颜色值
        float4 newColor2 = lookupTableTexture.sample(textureSampler, texPos2 / 512); // 正方形2的颜色值
        
        float4 newColor = mix(newColor1, newColor2, fract(blueColor)); // 根据小数点的部分进行mix
        return mix(textureColor, float4(newColor.rgb, textureColor.a), intensity);
    }
    

    四. 怎么用其他软件的滤镜

    有个很简单的方法,下图是原始的LUT图,我们把这个图放进其他APP后,可以得到另一个LUT图

    比如我找了个油画滤镜,得到了下面的效果:

    然后把这个LUT图导入,和原图一起输入,就能得到开头的效果啦~不过和原滤镜的效果还是有比较大的区别,可能他们加了点算法吧= =

    五. 参考文章

    图像处理之LUT

    Metal图像处理——颜色查找表(Color Lookup Table)

    一图彻底弄懂LUT

    相关文章

      网友评论

        本文标题:Metal与图形渲染六:颜色查找表LUT

        本文链接:https://www.haomeiwen.com/subject/jrgspltx.html