Android OpenGL ES 3.0 LUT 滤镜

1.什么是 LUT

LUT 是 Look Up Table 的简称,称作颜色查找表,是一种针对色彩空间的管理和转换技术。它可以分为一维 LUT(1D LUT) 和 三维 LUT(3D LUT),其中三维 LUT 比较常用。简单来讲,LUT 就是一个 RGB 组合到另一个 RGB 组合的映射关系表。

LUT 滤镜是一种比较经典的滤镜,本质上属于独立像素点替换,即根据 OpenGL 采样器对纹理进行采样得到的像素点,再基于像素点的(R,G,B)分量查表,获得 LUT 映射的(R1,G1,B1),替换原来的输出。

2.LUT图

一般 RGB 像素占用 3 个字节,包含 3 个分量,每个分量有 256 种取值,那么三维 LUT 模板就可以包含 256 X 256 X 256 种情况,占用 48MB 内存空间。这样一个 LUT 模板内存占用过大同时也降低了查找的效率,通常会采取下采样方式来降低数据量。

可以对三维 LUT 模板每个分量分别进行 64 次采样,这样就获得一个 64 X 64 X 64 大小的映射关系表,对于不在表内的颜色值可以进行插值获得其相似结果。

三维 LUT 模板,即 64 X 64 X 64 大小的映射关系表,通常是用一张分辨率为 512 X 512 的二维图片表示,称为 LUT 图。

LUT 图在横竖方向上被分成了 8 X 8 一共 64 个小方格,每一个小方格内的 B(Blue)分量为一个定值,64 个小方格一共表示了 B 分量的 64 种取值。

对于每一个小方格,横竖方向又各自分为 64 个小格,以左下角为原点,横向小格的 R(Red)分量依次增加,纵向小格的 G(Green)分量依次增加。

至此可以根据原始采样像素 RGB 中的 B 分量值,确定要选用 LUT 图中的第几个小格,然后再根据(R,G)分量值为纵横坐标,确定映射的 RGB 组合。

3.LUTl滤镜实现

3.1顶点着色器
#version 300 es
layout(location = 0) in vec3 attr_position;
layout(location = 1) in vec2 attr_uv;

uniform mat4   uni_mat;
out vec2   v_texcoord;

void main(void)
{
    v_texcoord = attr_uv;
    gl_Position = uni_mat* vec4(attr_position,1.0);
}

mvp矩阵和材质顶点

3.2 片元着色器
#version 300 es
precision mediump float;
//precision highp float;

//Lut 采样器
uniform sampler2D s_LutTexture;

uniform sampler2D uni_textureY;
uniform sampler2D uni_textureU;
uniform sampler2D uni_textureV;

in vec2 v_texcoord;
out vec4 fragColor;

uniform float u_offset;//偏移量
uniform vec2 texSize;//纹理尺寸

vec4 YuvToRgb(vec2 uv){
    vec3 yuv;
    vec3 rgb;
    yuv.x = texture(uni_textureY, uv).r;
    yuv.y = texture(uni_textureU, uv).r - 0.5;
    yuv.z = texture(uni_textureV, uv).r - 0.5;
    rgb = mat3( 1,1,1, 0,-0.39465,2.03211,1.13983,-0.58060,0) * yuv;
    return vec4(rgb, 1);
}

vec4 LutFilter(vec2 texCoord)
{
    //原始采样像素的 RGBA 值
    vec4 textureColor = YuvToRgb(texCoord);

    //获取 B 分量值,确定 LUT 小方格的 index, 取值范围转为 0~63
    float blueColor = textureColor.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) / 7.9999);
    quad2.x = ceil(blueColor) - (quad2.y * 8.0);

    //通过 R 和 G 分量的值确定小方格内目标映射的 RGB 组合的坐标,然后归一化,转化为纹理坐标。
    vec2 texPos1;
    texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
    texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);

    vec2 texPos2;
    texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
    texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);

    //取目标映射对应的像素值
    vec4 newColor1 = texture(s_LutTexture, texPos1);
    vec4 newColor2 = texture(s_LutTexture, texPos2);

    //使用 Mix 方法对 2 个边界像素值进行混合
    vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
    return mix(textureColor, vec4(newColor.rgb, textureColor.w), 1.0);
}


void main(void)
{

	//为了方便查看效果,做了分屏处理
    if(v_texcoord.x > 0.5)
    {
        fragColor = LutFilter(v_texcoord);
    }
    else
    {
        fragColor = YuvToRgb(v_texcoord);
    }

}

拿到原始的RGB颜色值,然后获取B分量,对B分量的RG分量进行处理,重新采样得到新的颜色,然后生成新的RGB颜色进行返回。

为了查看效果,做了左右分屏处理。

3.3 渲染部分
void MSDynamicGridLine::Render(MSGLCamera *pCamera) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if(m_bUpdateData == false){
        return;
    }



    static MSVertex triangleVert[] = {
            {-1, 1,  1,     0,0},
            {-1, -1,  1,    0,1},
            {1,  1,  1,     1,0},
            {1,  -1,  1,    1,1},
    };


    glm::mat4x4  objectMat = glm::mat4x4(1.0);
    glm::mat4x4  objectTransMat = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -5));
    objectMat = objectMat * objectTransMat;

    objectMat = pCamera->projectionMatrix * pCamera->viewMatrix * objectMat ;


    m_pOpenGLShader->Bind();

    m_pOpenGLShader->SetUniformValue("uni_mat",objectMat);


    m_pOpenGLShader->EnableAttributeArray("attr_position");
    m_pOpenGLShader->SetAttributeBuffer("attr_position",GL_FLOAT,triangleVert,3,sizeof(MSVertex));

    m_pOpenGLShader->EnableAttributeArray("attr_uv");
    m_pOpenGLShader->SetAttributeBuffer("attr_uv",GL_FLOAT,&triangleVert[0].u,2,sizeof(MSVertex));

    m_PeriodicFrameIndex++;
    float progress = GetFrameProgress();
    m_pOpenGLShader->SetUniformValue("u_offset",0.2f * progress);

//    LOGD("m_nVideoW is %d,progress is %f",m_nVideoW,progress);

    if (m_nVideoW>0){
        m_pOpenGLShader->SetUniformValue("texSize",glm::vec2(m_nVideoW,m_nVideoH));
    }else{
        m_pOpenGLShader->SetUniformValue("texSize",glm::vec2(720,1280));
    }


    m_pOpenGLShader->SetUniformValue("uni_textureY",0);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_textures[0]);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_nVideoW, m_nVideoH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pBufYuv420p);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    m_pOpenGLShader->SetUniformValue("uni_textureU",1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, m_textures[1]);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,m_nVideoW/2, m_nVideoH/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char*)(m_pBufYuv420p+m_yFrameLength));
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    m_pOpenGLShader->SetUniformValue("uni_textureV",2);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, m_textures[2]);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_nVideoW/2, m_nVideoH/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char*)(m_pBufYuv420p+m_yFrameLength+m_uFrameLength));
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	
    //增加 Lut滤镜采样器
    m_pOpenGLShader->SetUniformValue("s_LutTexture",3);
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D,m_texID);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glBindTexture(GL_TEXTURE_2D, 0);
    m_pOpenGLShader->DisableAttributeArray("attr_position");
    m_pOpenGLShader->DisableAttributeArray("attr_uv");

    m_pOpenGLShader->Release();

    return;
}

渲染部分,除了增加了Lut采样器之外其他的都是常规操作。

猜你喜欢

转载自blog.csdn.net/u014078003/article/details/128001009