本文正在参加「金石计划」

什么是LUT滤镜

从今日开端咱们开端进入Opengl ES的滤镜专题,提到滤镜就不得不提用得最多的LUT滤镜了。

LUT全称LookUpTable,也称为色彩查找表,它代表的是一种映射联系,通过LUT能够将输入的像素数组通过映射联系转换输出成别的的像素数组。 比方一个像素的色彩值分别是 R1 G1 B1,通过一次LUT操作后变为R2 G2 B2:

R2 = LUT(R1)
G2 = LUT(G1)
B2 = LUT(B1)

通过这个映射联系就能够将一个像素的色彩转换为别的一种色彩。

为什么运用LUT滤镜

在正常状况下,8位的RGB色彩形式能够表明的色彩数量为256X256X256种,假如要彻底记录这种映射联系,设备需求耗费大量的内存,并且可能在核算时由于核算量大而产生性能问题, 为了简化核算量,下降内存占用,能够将附近的n种色彩采用一条映射记录并存储,(n一般为4)这样只需求64X64X64种就能够表明原来256X256X256的色彩数量,咱们也将4称为采样步长。

要想熟练运用LUT滤镜,咱们首要要了解它是怎样建立色彩映射联系的, 咱们看下以下这张图,这张图展示了在LUT中RBG色彩是如何完成映射联系的:

OpenglES之LUT滤镜

首要这张图的巨细是512X512,在反正方向上这张图都被分成了8个小方格,每个小方格的巨细是64X64,也便是一张512X512的图被分割成64个小方格,每个小方格的巨细是64X64,这64个小方格就代表了64种B通道的色彩映射, 然后每个B通道的小方格上又是一个64X64像素巨细的图画,这个小图画的横坐标代表R重量的64种映射状况,纵坐标代表了G重量的64种映射状况,这样就刚好这就和采样步长是4的映射表对应上了。

在运用上面这张LUT表的时分首选需求找对B重量对应的小格子,然后在找到的小个子上再核算出R重量和G重量的映射成果即可得到完整的RGB映射成果。

如何在Opengl中运用LUT滤镜

假如咱们想要对图画进行LUT滤镜处理比较常用的有两种方法,一种是运用OpenCV而别的一种便是运用Opengl了,今日咱们就用opengl来完成一个LUT滤镜的小demo。

理论总是枯燥的,下面咱们结合一个512X512的LUT滤镜片元着色器讲解以下映射进程:

#version 300 es
precision mediump float;
in vec2 TexCoord;
uniform sampler2D ourTexture;
uniform sampler2D textureLUT;
out vec4 FragColor;
vec4 lookupTable(vec4 color){
    float blueColor = color.b * 63.0;
    //取与 B 重量值最接近的 2 个小方格的坐标
    vec2 quad1;
    quad1.y = floor(floor(blueColor) / 8.0);
    quad1.x = floor(blueColor) - (quad1.y * 8.0);
    vec2 quad2;
    quad2.y = floor(ceil(blueColor) / 8.0);
    quad2.x = ceil(blueColor) - (quad2.y * 8.0);
    vec2 texPos1;
    texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);
    texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);
    vec2 texPos2;
    texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);
    texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);
    //取方针映射对应的像素值
    vec4 newColor1 = texture(textureLUT, texPos1);
    vec4 newColor2 = texture(textureLUT, texPos2);
    // 色彩混合
    vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
    return vec4(newColor.rgb, color.w);
}
void main()
{
    // 原始图画的RGBA值
    vec4 tmpColor = texture(ourTexture, TexCoord);
    // 通过原始图画LUT映射
    FragColor = lookupTable(tmpColor);
}

读懂上面的着色器程序需求了解一些Opengl的内置函数的意思,例如floor(x)是表明向下取整,回来小于等于x的整数,而ceil(x)表明向上取整,回来大于等于x的整数。 这部分的内容我们自行查找学习即可。

留意上面这个着色器的算法仅仅合适与512X512的LUT图,没有适配到各种尺寸的LUT图。

  • 获取B重量

首要咱们留意看第8行float blueColor = color.b * 63.0; 通过main行数中的texture采样出来的RGB值都在0到1之间,因而乘以63即可得到B重量所在的小方格, 但是由于会呈现浮点差错,为了削减差错,所以取两个B重量,也便是与下一步的B重量值最接近的2个小方格的坐标,最终依据小数点进行插值运算。

其间第11到第12行的核心意思便是核算B重量的小格子在LUT的8X8各种的行列序号,quad1的x和y的值应该在0到7之间。

  • 获取RG重量

这部分的核心代码是16到21行,这个核算成果是归一化后的纹路坐标,所以x和y的值应该是在0到1之间,首要解析下(quad1.x * 0.125),在512的LUT图中,反正都被分成了8个小方格,因而在归一化坐标中, 每个小方格所占的比例便是1/8=0.125,因而(quad1.x * 0.125)的意思便是当时格子的左上角在纹路坐标系中的横坐标的详细坐标点。

由于上面(quad1.x * 0.125)得出的是当时格子的左上角在纹路坐标系中的横坐标的详细坐标点,那么再加上0.5/512.0便是当时格子的中心点在纹路坐标系中的横坐标的详细坐标点。

最终看下((0.125 - 1.0/512.0) * color.r),其实这是一个通过优化后的核算,原始的核算应该是((0.125 - 1.0/512.0) * textureColor.r) = ((64-1)* textureColor.r)/512

(64-1)* textureColor.r 意思是首要将当时实践像素的r值映射到0-63的范围内,再除以512是转化为纹路坐标中实践的点,由于咱们的LUT纹路图的分辨率为512*512。

同理G通道的核算也是相同的。

下面附上完整核心的demo烘托代码:

Lut2DOpengl.cpp


#include "Lut2DOpengl.h"
#include "../utils/Log.h"
// 极点着色器
static const char *ver = "#version 300 es\n"
                         "in vec4 aPosition;\n"
                         "in vec2 aTexCoord;\n"
                         "out vec2 TexCoord;\n"
                         "void main() {\n"
                         "  TexCoord = aTexCoord;\n"
                         "  gl_Position = aPosition;\n"
                         "}";
// 片元着色器
static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "in vec2 TexCoord;\n"
                              "uniform sampler2D ourTexture;\n"
                              "uniform sampler2D textureLUT;\n"
                              "out vec4 FragColor;\n"
                              "vec4 lookupTable(vec4 color){\n"
                              "    float blueColor = color.b * 63.0;\n"
                              "    vec2 quad1;\n"
                              "    quad1.y = floor(floor(blueColor) / 8.0);\n"
                              "    quad1.x = floor(blueColor) - (quad1.y * 8.0);\n"
                              "    vec2 quad2;\n"
                              "    quad2.y = floor(ceil(blueColor) / 8.0);\n"
                              "    quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n"
                              "    vec2 texPos1;\n"
                              "    texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);\n"
                              "    texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);\n"
                              "    vec2 texPos2;\n"
                              "    texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);\n"
                              "    texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);\n"
                              "    vec4 newColor1 = texture(textureLUT, texPos1);\n"
                              "    vec4 newColor2 = texture(textureLUT, texPos2);\n"
                              "    vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n"
                              "    return vec4(newColor.rgb, color.w);\n"
                              "}\n"
                              "\n"
                              "void main()\n"
                              "{\n"
                              "    vec4 tmpColor = texture(ourTexture, TexCoord);\n"
                              "    FragColor = lookupTable(tmpColor);\n"
                              "}";
const static GLfloat VERTICES_AND_TEXTURE[] = {
        0.5f, -0.5f, // 右下
        // 纹路坐标
        1.0f,1.0f,
        0.5f, 0.5f, // 右上
        // 纹路坐标
        1.0f,0.0f,
        -0.5f, -0.5f, // 左下
        // 纹路坐标
        0.0f,1.0f,
        -0.5f, 0.5f, // 左上
        // 纹路坐标
        0.0f,0.0f
};
// 真实的纹路坐标在图片的左下角
const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {
        1.0f, -1.0f, // 右下
        // 纹路坐标
        1.0f,0.0f,
        1.0f, 1.0f, // 右上
        // 纹路坐标
        1.0f,1.0f,
        -1.0f, -1.0f, // 左下
        // 纹路坐标
        0.0f,0.0f,
        -1.0f, 1.0f, // 左上
        // 纹路坐标
        0.0f,1.0f
};
// 运用byte类型比运用short或许int类型节约内存
const static uint8_t indices[] = {
        // 留意索引从0开端!
        // 此例的索引(0,1,2,3)便是极点数组vertices的下标,
        // 这样能够由下标代表极点组合成矩形
        0, 1, 2, // 第一个三角形
        1, 2, 3  // 第二个三角形
};
Lut2DOpengl::Lut2DOpengl() {
    initGlProgram(ver,fragment);
    positionHandle = glGetAttribLocation(program,"aPosition");
    textureHandle = glGetAttribLocation(program,"aTexCoord");
    textureSampler = glGetUniformLocation(program,"ourTexture");
    lut_textureSampler = glGetUniformLocation(program,"textureLUT");
    LOGD("program:%d",program);
    LOGD("positionHandle:%d",positionHandle);
    LOGD("textureHandle:%d",textureHandle);
    LOGD("textureSample:%d",textureSampler);
    // VAO
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    // vbo
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_AND_TEXTURE), VERTICES_AND_TEXTURE, GL_STATIC_DRAW);
    // stride 步长 每个极点坐标之间相隔4个数据点,数据类型是float
    glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);
    // 启用极点数据
    glEnableVertexAttribArray(positionHandle);
    // stride 步长 每个色彩坐标之间相隔4个数据点,数据类型是float,色彩坐标索引从2开端
    glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
                          (void *) (2 * sizeof(float)));
    // 启用纹路坐标数组
    glEnableVertexAttribArray(textureHandle);
    // EBO
    glGenBuffers(1,&ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
    // 这个次序不能乱啊,先免除vao,再免除其他的,不然在制作的时分可能会不起作用,需求从头glBindBuffer才生效
    // vao免除
    glBindVertexArray(0);
    // 免除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // 免除绑定
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
}
Lut2DOpengl::~Lut2DOpengl() noexcept {
    glDeleteBuffers(1,&ebo);
    glDeleteBuffers(1,&vbo);
    glDeleteVertexArrays(1,&vao);
    // ... 删除其他,例如fbo等
}
void Lut2DOpengl::setPixel(void *data, int width, int height, int length) {
    imageWidth = width;
    imageHeight = height;
    glGenTextures(1, &imageTextureId);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, imageTextureId);
    // 为当时绑定的纹路方针设置盘绕、过滤方法
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    // 生成mip贴图
    glGenerateMipmap(GL_TEXTURE_2D);
    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}
void Lut2DOpengl::setLutPixel(void *data, int width, int height, int length) {
    glGenTextures(1, &lut_imageTextureId);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, lut_imageTextureId);
    // 为当时绑定的纹路方针设置盘绕、过滤方法
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    // 生成mip贴图
    glGenerateMipmap(GL_TEXTURE_2D);
    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}
void Lut2DOpengl::onDraw() {
    // 恢复制作屏幕宽高
    glViewport(0,0,eglHelper->viewWidth,eglHelper->viewHeight);
    // 制作到屏幕
    // 清屏
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);
    // 激活纹路
    glActiveTexture(GL_TEXTURE1);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, imageTextureId);
    glUniform1i(textureSampler, 1);
    // 激活纹路 lut
    glActiveTexture(GL_TEXTURE2);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, lut_imageTextureId);
    glUniform1i(lut_textureSampler, 2);
    checkError(program);
    // VBO与VAO配合制作
    // 运用vao
    glBindVertexArray(vao);
    // 运用EBO
// 运用byte类型节省内存
    glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_BYTE,(void *)0);
    glUseProgram(0);
    // vao免除绑定
    glBindVertexArray(0);
    // 禁用极点
    glDisableVertexAttribArray(positionHandle);
    if (nullptr != eglHelper) {
        eglHelper->swapBuffers();
    }
    glBindTexture(GL_TEXTURE_2D, 0);
}

demo运转成果图

以下这张是需求添加滤镜的原图:

OpenglES之LUT滤镜

以下这张是所运用的LUT纹路图:

OpenglES之LUT滤镜

以下这张是运转成果图:

OpenglES之LUT滤镜

思考

假如要在Opengl中给LUT加上滤镜强度调整参数该怎样处理呢?

系列教程源码

github.com/feiflyer/ND…

Opengl ES系列入门介绍

Opengl ES之EGL环境搭建
Opengl ES之着色器
Opengl ES之三角形制作
Opengl ES之四边形制作
Opengl ES之纹路贴图
Opengl ES之VBO和VAO
Opengl ES之EBO
Opengl ES之FBO
Opengl ES之PBO
Opengl ES之YUV数据烘托
YUV转RGB的一些理论知识
Opengl ES之RGB转NV21
Opengl ES之踩坑记
Opengl ES之矩阵改换(上)
Opengl ES之矩阵改换(下)
Opengl ES之水印贴图
Opengl ES之纹路数组
OpenGL ES之多方针烘托(MRT

重视我,一同进步,人生不止coding!!!