Skip to content

Commit

Permalink
update LUT
Browse files Browse the repository at this point in the history
  • Loading branch information
CharonChui committed May 6, 2024
1 parent 65a5f14 commit 2e70e73
Showing 1 changed file with 45 additions and 43 deletions.
88 changes: 45 additions & 43 deletions VideoDevelopment/OpenGL/13.LUT滤镜.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,104 +36,105 @@ LUT从查找方式上可以分为1D LUT和3D LUT:
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/3d_lut.jpg)


我们看到的这些方格子里面的蓝色是固定的,然后每个格子横坐标是红色,纵坐标是绿色,最左上角的格子因为蓝色全无,所以红色和绿色就很明显,而最右下角的那个格子,蓝色色值达到最大,因此整体看上去就非常的蓝(从左上角的格子到右下角的格子,一共是8行8列,正好是64个格子)。


### 为什么是64x64

对于RGB颜色,每种颜色可以有256种取值,因此一个3D LUT 如果全量表示的话,大小为`256*256*256`,如果我们有一个大小为`256*256*256`的3D LUT文件,那么颜色映射将非常简单,只要根据RGB的像素值按照用蓝色找到对应的格子,然后用红色和绿色找到对应格子的横列就可以找到映射的颜色值。但是通常情况下,我们不会这么干,因为一张`256*256*256`的图实在是太大了,至少需要48MB的存储空间。

如果要完全记录这种映射关系,设备需要耗费大量的内存,并且可能在计算时因为计算量大而产生性能问题, 为了简化计算量,降低内存占用,可以将相近的n种颜色采用一条映射记录并存储,(n通常为4)这样只需要64X64X64种就可以表示原来256X256X256的颜色数量,我们也将4称为采样步长。

我们看到的这些方格子里面的蓝色是固定的,然后每个格子横坐标是红色,纵坐标是绿色,最左上角的格子因为蓝色全无,所以红色和绿色就很明显,而最右下角的那个格子,蓝色色值达到最大,因此整体看上去就非常的蓝。
在专业领域一般认为17x17x17的3D LUT足够适用于预览和监看;65x65x65或者更大的3D LUT更适合渲染和调色。

对于RGB颜色,每种颜色可以有256种取值,因此一个3D LUT 如果全量表示的话,大小为256*256*256,如果我们有一个大小为256*256*256的3D LUT文件,那么颜色映射将非常简单,只要根据RGB的像素值按照用蓝色找到对应的格子,然后用红色和绿色找到对应格子的横列就可以找到映射的颜色值。但是通常情况下,我们不会这么干,因为一张256*256*256的图实在是太大了,至少需要48MB的存储空间,因此,通常会通过降低采样的方式来减少数据量。

### 3D LUT映射

要想熟练使用LUT滤镜,我们首先要了解它是怎么建立颜色映射关系的, 我们看下以下这张图,这张图展示了在LUT中RBG颜色是如何实现映射关系的:
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/lut.png)

首先这张图的大小是512X512,在横竖方向上这张图都被分成了8个小方格(64只是一种颜色粒度的划分,可以是其它数值,具体的看设计师给出查找表是怎么规划的,此处的粒度为64,也就是将RGB的分量划分为64份,颜色[0.0, 1.0] -> [0.0, 63.0]):

我们看到的这些方格子里面的蓝色是固定的,然后每个格子横坐标是红色,纵坐标是绿色,最左上角的格子因为蓝色全无,所以红色和绿色就很明显,而最右下角的那个格子,蓝色色值达到最大,因此整体看上去就非常的蓝。
对于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英镑)。
下面我们就以一个64*64*64的3D LUT为例,来说明一下3D LUT如何查找和使用的(用glsl来实现),一张3D LUT如下:
- 每个小方格的大小是64X64,也就是一张512X512的图被分割成64个小方格
- 每个小方格的大小是64X64,这64个小方格就代表了64种B通道的颜色映射
- 然后每个B通道的小方格上又是一个64X64像素大小的图像,这个小图像的横坐标代表R分量的64种映射情况,纵坐标代表了G分量的64种映射情况

这样就刚好这就和采样步长是4的映射表对应上了。

**在使用上面这张LUT表的时候首选需要找对B分量对应的小格子,然后在找到的小个子上再计算出R分量和G分量的映射结果即可得到完整的RGB映射结果。**



肉眼可见的有8*8个方格,假设整个图的范围我们定义为0.0~1.0 ,也就是说每个格子所表示的范围是1./8. = 0.125, 我们首先通过蓝色来查找我们在哪个方格,由于涉及到插值,因此通过蓝色进行查找的时候,可能不会那么幸运一定映射到整数的格子上面,因此我们需要通过向下取整和向上进位两种方式来获取蓝色所对应的方格,然后通过蓝色的颜色值来mix这两种的颜色值,从而获取最终的颜色值,glsl实现如下:
下面我们就以一个`64*64*64`的3D LUT为例,来说明一下3D LUT如何查找和使用的(用glsl来实现):

- 整张图从左到右和从上到下为蓝色的渐变,每个小格子从左到右为红色渐变,从上到下为绿色渐变
- 肉眼可见的有8*8个方格,假设整个图的范围我们定义为0.0~1.0 ,也就是说每个格子所表示的范围是1./8. = 0.125
- 我们首先通过蓝色来查找我们在哪个方格,由于涉及到插值,因此通过蓝色进行查找的时候,可能不会那么幸运一定映射到整数的格子上面,因此我们需要通过向下取整和向上进位两种方式来获取蓝色所对应的方格,然后通过蓝色的颜色值来mix这两种的颜色值,从而获取最终的颜色值。

## 为什么使用LUT滤镜

### 映射示例

以一个具体的颜色Color(r=30.0, g=30.0, b=25.4)为例子来说明是如何通过LUT来做映射的。

在正常情况下,8位的RGB颜色模式可以表示的颜色数量为256X256X256种,如果要完全记录这种映射关系,设备需要耗费大量的内存,并且可能在计算时因为计算量大而产生性能问题, 为了简化计算量,降低内存占用,可以将相近的n种颜色采用一条映射记录并存储,(n通常为4)这样只需要64X64X64种就可以表示原来256X256X256的颜色数量,我们也将4称为采样步长。
要想熟练使用LUT滤镜,我们首先要了解它是怎么建立颜色映射关系的, 我们看下以下这张图,这张图展示了在LUT中RBG颜色是如何实现映射关系的:
lut映射关系图
首先这张图的大小是512X512,在横竖方向上这张图都被分成了8个小方格,每个小方格的大小是64X64,也就是一张512X512的图被分割成64个小方格,每个小方格的大小是64X64,这64个小方格就代表了64种B通道的颜色映射, 然后每个B通道的小方格上又是一个64X64像素大小的图像,这个小图像的横坐标代表R分量的64种映射情况,纵坐标代表了G分量的64种映射情况,这样就刚好这就和采样步长是4的映射表对应上了。
在使用上面这张LUT表的时候首选需要找对B分量对应的小格子,然后在找到的小个子上再计算出R分量和G分量的映射结果即可得到完整的RGB映射结果。
#### 1.1 寻找B值的两个格子

B值对应的是0 ~ 63,共64个格子,在计算时,并不一定会是一个整数,对应到某一个格子,大概率会是一个小数,为了能够更精确的进行映射,需要使用两个格子,然后根据小数(因子)进行插值计算,从而能够更精确。

##### 蓝色值用来定位两个相邻的小格子

对输入图颜色转换时,以颜色(rgb)的b值作为索引,找到所属于的小格。
最后根据r和g的值在小格中定位到映射的目标值上(两个格子上对应的像素进行插值)。

最后根据r和g的值在小格中定位到映射的目标值上(两个格子上对应的像素进行插值)。


每个小方格的横向有512 / 8 = 64个像素,表示间距为4的R通道,即R色值序列为0, 4, 8, .....251, 255;纵向同样有64个像素,表示间距为4的G通道;B通道被巧妙的放在大格子中(从左到右,从上到下共64个小方格,表示B通道的64个数值)




整张图从左到右和从上到下为蓝色的渐变,每个小格子从左到右为红色渐变,从上到下为绿色渐变

64只是一种颜色粒度的划分,可以是其它数值,具体的看设计师给出查找表是怎么规划的,此处的粒度为64,也就是将RGB的分量划分为64份,颜色[0.0, 1.0] -> [0.0, 63.0]


以一个具体的颜色Color(r = 30.0, g = 30.0, b = 25.4)为例子来说明是如何通过LUT来做映射的。



蓝色值用来定位两个相邻的小格子

第一个小格子:
- 第一个小格子:

```python
float blueColor = textureColor.b * 63.0;
vec2 quad1;
// blueColor = 25.4, 第3行的第1个小格子
// floor:向下取整
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
```

第二个小格子:

- 第二个小格子:

```python
vec2 quad2;
// blueColor = 25.4,第3行的第2个小格子
// ceil:向上取整
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
```

##### 确定每个格子对应的R G值

红色值和绿色值用来确定相对于整个LUT的纹理坐标:

```python
vec2 texPos1;
texPos1.x = (quad1.x * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.r);
texPos1.y = (quad1.y * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.g);

vec2 texPos2;
texPos2.x = (quad2.x * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.r);
texPos2.y = (quad2.y * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.g);
```

通过纹理坐标获取两个新的颜色:
##### 通过纹理坐标获取两个新的颜色

```python
vec4 newColor1 = texture2D(u_LookupTable, texPos1);
vec4 newColor2 = texture2D(u_LookupTable, texPos2);
```

##### 然后根据蓝色值小数部分作为权重做线性混合,获取最终的颜色输出

然后根据蓝色值小数部分作为权重做线性混合,获取最终的颜色输出:

```python
// mix(x, y, a) -> x * (1 - a) + y * a
vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
```

完整的顶点着色器:


```python
attribute vec4 a_Position;
attribute vec4 a_TextureCoordinate;

Expand All @@ -143,10 +144,11 @@ void main() {
gl_Position = a_Position;
vTextureUnitCoordinate = a_TextureCoordinate.xy;
}
```

片元着色器代码:

片元着色器代码

```python
precision mediump float;

uniform sampler2D u_TextureSampler;
Expand Down Expand Up @@ -180,7 +182,7 @@ void main() {
vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), u_Intensity);
}

```



Expand Down

0 comments on commit 2e70e73

Please sign in to comment.