|
1 | 1 | # 13.LUT滤镜 |
2 | 2 |
|
3 | 3 |
|
| 4 | +LUT全称LookUpTable,也称为颜色查找表,它代表的是一种映射关系,通过LUT可以将输入的像素数组通过映射关系转换输出成另外的像素数组。 |
| 5 | + |
| 6 | +比如一个像素的颜色值分别是R1 G1 B1,经过一次LUT操作后变为R2 G2 B2。 |
| 7 | + |
| 8 | +通过这个映射关系就可以将一个像素的颜色转换为另外一种颜色。 |
| 9 | + |
| 10 | + |
| 11 | +LUT从查找方式上可以分为1D LUT和3D LUT: |
| 12 | + |
| 13 | +- 1D LUT |
| 14 | + |
| 15 | +对于一张RGB图像,每个通道都可以作为一个输入,用公式可以描述如下: |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | +由于颜色值的范围是(0~255),我们可以用`256*3` 的表来表示一个1D LUT,在实际操作中,我们通常以一张`256*3*1`的图片来存储这个映射表,如下,这是一张1D LUT在Mac访达中文件信息图: |
| 20 | + |
| 21 | + |
| 22 | + |
| 23 | +由于1D LUT各个通道都是相互独立的,无法对其他通道产生影响,因此1D LUT 只能用来调节亮度/伽马/饱和度/色彩平衡等,如果我们希望对其他通道产生影响,就需要了解另一种滤镜查找方式--3D LUT。 |
| 24 | + |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +- 3D LUT |
| 29 | + |
| 30 | + 3D LUT在滤镜中的影响比1D LUT更为深刻,下面我们用一张图(图是网上找的,参考“参考资料”)来说面3D LUT,假设下图的红色平面发生移动,其中对应的绿色和蓝色分量也会发生改变,也就是说,一个颜色通道改变可以影响其他的颜色通道,理论上 3D LUT 可以在立体色彩空间中描述所有颜色调整行为,所以它可以处理任何显示的非线性属性,从简单的 gamma 值、颜色范围和追踪错误,到修正高级的非线性属性、颜色串扰(去耦)、色相、饱和度、亮度等,3D LUT都可以胜任。 |
| 31 | + |
| 32 | + |
| 33 | + |
| 34 | + |
| 35 | +常见的3D LUT滤镜文件与.cube或者.3dl都是把3D坐标二维化后的数据表现,如下图 |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | + 我们看到的这些方格子里面的蓝色是固定的,然后每个格子横坐标是红色,纵坐标是绿色,最左上角的格子因为蓝色全无,所以红色和绿色就很明显,而最右下角的那个格子,蓝色色值达到最大,因此整体看上去就非常的蓝。 |
| 46 | + |
| 47 | + 对于RGB颜色,每种颜色可以有256种取值,因此一个3D LUT 如果全量表示的话,大小为256*256*256,如果我们有一个大小为256*256*256的3D LUT文件,那么颜色映射将非常简单,只要根据RGB的像素值按照用蓝色找到对应的格子,然后用红色和绿色找到对应格子的横列就可以找到映射的颜色值。但是通常情况下,我们不会这么干,因为一张256*256*256的图实在是太大了,至少需要48MB的存储空间,因此,通常会通过降低采样的方式来减少数据量。 |
| 48 | + |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | +我们看到的这些方格子里面的蓝色是固定的,然后每个格子横坐标是红色,纵坐标是绿色,最左上角的格子因为蓝色全无,所以红色和绿色就很明显,而最右下角的那个格子,蓝色色值达到最大,因此整体看上去就非常的蓝。 |
| 53 | + 对于RGB颜色,每种颜色可以有256种取值,因此一个3D LUT 如果全量表示的话,大小为256*256*256,如果我们有一个大小为256*256*256的3D LUT文件,那么颜色映射将非常简单,只要根据RGB的像素值按照用蓝色找到对应的格子,然后用红色和绿色找到对应格子的横列就可以找到映射的颜色值。但是通常情况下,我们不会这么干,因为一张256*256*256的图实在是太大了,至少需要48MB的存储空间,因此,通常会通过降低采样的方式来减少数据量。在专业领域一般认为17x17x17的3D LUT足够适用于预览和监看;65x65x65或者更大的3D LUT更适合渲染和调色。3D LUT的实现在计算机环境会相对容易,在嵌入式环境就意味着成本的提高,越大的LUT需要越昂贵的硬件支持。所以行内比较常用的LUT box一般都不是很大,比如说BMD的HDLink Pro采用了17x17x17的3D LUT(495美元);Fujifilm的IS-Mini则采用了26x26x26的3D LUT(865英镑)。 |
| 54 | + 下面我们就以一个64*64*64的3D LUT为例,来说明一下3D LUT如何查找和使用的(用glsl来实现),一张3D LUT如下: |
| 55 | + |
| 56 | + |
| 57 | + |
| 58 | + |
| 59 | + |
| 60 | +肉眼可见的有8*8个方格,假设整个图的范围我们定义为0.0~1.0 ,也就是说每个格子所表示的范围是1./8. = 0.125, 我们首先通过蓝色来查找我们在哪个方格,由于涉及到插值,因此通过蓝色进行查找的时候,可能不会那么幸运一定映射到整数的格子上面,因此我们需要通过向下取整和向上进位两种方式来获取蓝色所对应的方格,然后通过蓝色的颜色值来mix这两种的颜色值,从而获取最终的颜色值,glsl实现如下: |
| 61 | + |
| 62 | + |
| 63 | +## 为什么使用LUT滤镜 |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +在正常情况下,8位的RGB颜色模式可以表示的颜色数量为256X256X256种,如果要完全记录这种映射关系,设备需要耗费大量的内存,并且可能在计算时因为计算量大而产生性能问题, 为了简化计算量,降低内存占用,可以将相近的n种颜色采用一条映射记录并存储,(n通常为4)这样只需要64X64X64种就可以表示原来256X256X256的颜色数量,我们也将4称为采样步长。 |
| 68 | +要想熟练使用LUT滤镜,我们首先要了解它是怎么建立颜色映射关系的, 我们看下以下这张图,这张图展示了在LUT中RBG颜色是如何实现映射关系的: |
| 69 | +lut映射关系图 |
| 70 | +首先这张图的大小是512X512,在横竖方向上这张图都被分成了8个小方格,每个小方格的大小是64X64,也就是一张512X512的图被分割成64个小方格,每个小方格的大小是64X64,这64个小方格就代表了64种B通道的颜色映射, 然后每个B通道的小方格上又是一个64X64像素大小的图像,这个小图像的横坐标代表R分量的64种映射情况,纵坐标代表了G分量的64种映射情况,这样就刚好这就和采样步长是4的映射表对应上了。 |
| 71 | +在使用上面这张LUT表的时候首选需要找对B分量对应的小格子,然后在找到的小个子上再计算出R分量和G分量的映射结果即可得到完整的RGB映射结果。 |
| 72 | + |
| 73 | + |
| 74 | + |
| 75 | +对输入图颜色转换时,以颜色(rgb)的b值作为索引,找到所属于的小格。 |
| 76 | +最后根据r和g的值在小格中定位到映射的目标值上(两个格子上对应的像素进行插值)。 |
| 77 | + |
| 78 | +最后根据r和g的值在小格中定位到映射的目标值上(两个格子上对应的像素进行插值)。 |
| 79 | + |
| 80 | + |
| 81 | +每个小方格的横向有512 / 8 = 64个像素,表示间距为4的R通道,即R色值序列为0, 4, 8, .....251, 255;纵向同样有64个像素,表示间距为4的G通道;B通道被巧妙的放在大格子中(从左到右,从上到下共64个小方格,表示B通道的64个数值) |
| 82 | + |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +整张图从左到右和从上到下为蓝色的渐变,每个小格子从左到右为红色渐变,从上到下为绿色渐变 |
| 87 | + |
| 88 | +64只是一种颜色粒度的划分,可以是其它数值,具体的看设计师给出查找表是怎么规划的,此处的粒度为64,也就是将RGB的分量划分为64份,颜色[0.0, 1.0] -> [0.0, 63.0] |
| 89 | + |
| 90 | + |
| 91 | +以一个具体的颜色Color(r = 30.0, g = 30.0, b = 25.4)为例子来说明是如何通过LUT来做映射的。 |
| 92 | + |
| 93 | + |
| 94 | + |
| 95 | +蓝色值用来定位两个相邻的小格子 |
| 96 | + |
| 97 | +第一个小格子: |
| 98 | + |
| 99 | +float blueColor = textureColor.b * 63.0; |
| 100 | +vec2 quad1; |
| 101 | +// blueColor = 25.4, 第3行的第1个小格子 |
| 102 | +// floor:向下取整 |
| 103 | +quad1.y = floor(floor(blueColor) / 8.0); |
| 104 | +quad1.x = floor(blueColor) - (quad1.y * 8.0); |
| 105 | + |
| 106 | +第二个小格子: |
| 107 | + |
| 108 | + |
| 109 | +vec2 quad2; |
| 110 | +// blueColor = 25.4,第3行的第2个小格子 |
| 111 | +// ceil:向上取整 |
| 112 | +quad2.y = floor(ceil(blueColor) / 8.0); |
| 113 | +quad2.x = ceil(blueColor) - (quad2.y * 8.0); |
| 114 | + |
| 115 | +红色值和绿色值用来确定相对于整个LUT的纹理坐标: |
| 116 | + |
| 117 | +vec2 texPos1; |
| 118 | +texPos1.x = (quad1.x * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.r); |
| 119 | +texPos1.y = (quad1.y * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.g); |
| 120 | + |
| 121 | +vec2 texPos2; |
| 122 | +texPos2.x = (quad2.x * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.r); |
| 123 | +texPos2.y = (quad2.y * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.g); |
| 124 | + |
| 125 | +通过纹理坐标获取两个新的颜色: |
| 126 | + |
| 127 | +vec4 newColor1 = texture2D(u_LookupTable, texPos1); |
| 128 | +vec4 newColor2 = texture2D(u_LookupTable, texPos2); |
| 129 | + |
| 130 | +然后根据蓝色值小数部分作为权重做线性混合,获取最终的颜色输出: |
| 131 | + |
| 132 | +// mix(x, y, a) -> x * (1 - a) + y * a |
| 133 | +vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); |
| 134 | + |
| 135 | +完整的顶点着色器: |
| 136 | + |
| 137 | + |
| 138 | +attribute vec4 a_Position; |
| 139 | +attribute vec4 a_TextureCoordinate; |
| 140 | + |
| 141 | +varying vec2 vTextureUnitCoordinate; |
| 142 | + |
| 143 | +void main() { |
| 144 | + gl_Position = a_Position; |
| 145 | + vTextureUnitCoordinate = a_TextureCoordinate.xy; |
| 146 | +} |
| 147 | + |
| 148 | + |
| 149 | +片元着色器代码 |
| 150 | + |
| 151 | +precision mediump float; |
| 152 | + |
| 153 | +uniform sampler2D u_TextureSampler; |
| 154 | +uniform sampler2D u_LookupTable; |
| 155 | +uniform float u_Intensity; |
| 156 | + |
| 157 | +varying vec2 vTextureUnitCoordinate; |
| 158 | + |
| 159 | +void main() { |
| 160 | + vec4 textureColor = texture2D(u_TextureSampler, vTextureUnitCoordinate); |
| 161 | + float blueColor = textureColor.b * 63.0; |
| 162 | + vec2 quad1; |
| 163 | + quad1.y = floor(floor(blueColor) / 8.0); |
| 164 | + quad1.x = floor(blueColor) - (quad1.y * 8.0); |
| 165 | + |
| 166 | + vec2 quad2; |
| 167 | + quad2.y = floor(ceil(blueColor) / 8.0); |
| 168 | + quad2.x = ceil(blueColor) - (quad2.y * 8.0); |
| 169 | + |
| 170 | + vec2 texPos1; |
| 171 | + texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); |
| 172 | + texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); |
| 173 | + |
| 174 | + vec2 texPos2; |
| 175 | + texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); |
| 176 | + texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); |
| 177 | + |
| 178 | + vec4 newColor1 = texture2D(u_LookupTable, texPos1); |
| 179 | + vec4 newColor2 = texture2D(u_LookupTable, texPos2); |
| 180 | + |
| 181 | + vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); |
| 182 | + gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), u_Intensity); |
| 183 | +} |
| 184 | + |
| 185 | + |
4 | 186 |
|
5 | 187 |
|
6 | 188 |
|
|
0 commit comments