使用OpenGL纹理数组实现高精度实时Lut滤镜

本文介绍如何通过使用OpenGL的2D纹理数组来优化LUT滤镜,解决原始纹理采样精度问题,实现高精度颜色替换。作者分享了片元渲染器的编写、纹理载入方法及drawcall代码,展示了使用线性采样和纹理数组来节省存储并提供平滑映射的过程。
摘要由CSDN通过智能技术生成

之前写过的文章(使用OpenGL实现滤镜转换的一种思路_轮子初级玩家-CSDN博客),我把一整个Lut滤镜图作为单个纹理贴图,把图像原颜色采样后当作坐标,然后从lut纹理中查找出替换颜色实现滤镜功能,这是最简易的一种滤镜实现方式,但由于lut纹理的T轴占比过长,计算进度上容易导致命中错误的滤镜页的问题,那是否有办法实现更高精度的滤镜呢?其实是有的,而且我优化了一下。

本文默认读者已经具备基本的OpenGL代码阅读能力,所以只打算写一些必要的代码和解析。

一、片元渲染器的编写:

首先我们明确需要两个纹理,一个为原图纹理sTexture,用于给OpenGL灌入原画面;一个是滤镜Lut表纹理lutTexture,类型分别为sampler2D和sampler2DArray。

看到sampler2DArray相信读者已经猜到降低进度要求的办法是什么了,就是通过使用2d纹理数组,把一整个lut表,通过纹理数组,分割为一页页,即可解决行和页的命中精度问题。

由于这是纹理数组,所以对比二维纹理ST坐标多了一维R,R可以理解为下标,每+1可下翻一个二维纹理。而原颜色的R分量可以理解为页码,G可以理解为行,B为列,那么颜色替换的完整代码就是:

#version 300 es
            precision highp float;
            precision highp sampler2DArray;
            uniform sampler2D sTexture;//图像纹理输入
            uniform sampler2DArray lutTexture;//滤镜纹理输入
            uniform float pageSize;
            in vec4 fragObjectColor;//接收vertShader处理后的颜色值给片元程序
            in vec2 fragVTexCoord;//接收vertShader处理后的纹理内坐标给片元程序
            out vec4 fragColor;//输出到的片元颜色

            void main() {
                vec4 srcColor = texture(sTexture, fragVTexCoord);
                srcColor.r = clamp(srcColor.r, 0.01, 0.99);
                srcColor.g = clamp(srcColor.g, 0.01, 0.99);
                srcColor.b = clamp(srcColor.b, 0.01, 0.99);
                fragColor = texture(lutTexture, vec3(srcColor.b, srcColor.g, srcColor.r * (pageSize - 1.0)));
            }

为了防止采样越界,我使用了clamp把原颜色每一个分量都限制在(0.01, 0.99)范围中。

二、纹理载入代码:

很多时候,为了节约用户的存储空间消耗,而且LUT表的颜色单元之间存在一定的线性关系,LUT表并不是对RGB颜色的256种可能全部一一对应有替换种类的,例如我这个demo中使用的lut表,每种颜色分量只有64种可对应的颜色,所以最适合使用的纹理采样方式就是线性采样,顺带可以把颜色映射之间缺乏的中间颜色通过线性关系求出,如果使用NEAREST采样,虽然可以降低运算量,却无法做到平滑的线性映射,更像阶梯状的映射。所以还是使用线性映射更好,至于重复模式用最常见的截取式的就足够了。纹理数据的加载因为需要告诉OpenGL这一份数据的分页数——也就是数组数组的大小,也叫深度depth,所以需要使用glTexImage3D函数。

glGenTextures(1, mLutTexutresPointers);
glBindTexture(GL_TEXTURE_2D_ARRAY, mLutTexutresPointers[0]);
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
int longLen = mLutWidth > mLutHeight ? mLutWidth : mLutHeight;
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, mLutUnitLen, mLutUnitLen, longLen / mLutUnitLen, 0, GL_RGBA, GL_UNSIGNED_BYTE, mTestPixels);
free(mTestPixels);

三、drawcall代码编写:

这个和平常的纹理绑定并draw到顶点数组构成的面上没啥不同,只是纹理多了一个:

glActiveTexture(GL_TEXTURE0); //激活0号纹理
glBindTexture(GL_TEXTURE_2D, mInputTexturesArrayPointer); //0号纹理绑定内容
glUniform1i(glGetUniformLocation(mImageProgram.programHandle, "sTexture"), 0); //映射到渲染脚本,获取纹理属性的指针
glActiveTexture(GL_TEXTURE1); //激活1号纹理
glBindTexture(GL_TEXTURE_2D_ARRAY, mLutTexutresPointers[0]);
glUniform1i(glGetUniformLocation(mImageProgram.programHandle, "lutTexture"), 1); //映射到渲染脚本,获取纹理属性的指针
glUniform1f(glGetUniformLocation(mImageProgram.programHandle, "pageSize"), longLen / mLutUnitLen);

实际效果:

这次使用的是一款叫“朦胧”的LUT滤镜,为了对比原纹理和使用lut滤镜表替换颜色后生成的纹理画面,我把shader做了一点修改,使得画面左边为原画面,右边为滤镜效果画面(我的Demo使用了多个shaderProgram搭配FBO以流水线的方式处理,滤镜shader为最后一步):

                if (fragVTexCoord.x >= 0.5) {
                    fragColor = texture(lutTexture, vec3(srcColor.b, srcColor.g, srcColor.r * 63.0));
                } else {
                    fragColor = srcColor;
                }

我的滤镜、FBO多重渲染的DEMO

详细代码可以参考我的学习工程:

https://github.com/cjzjolly/learnopengl/blob/main/app/src/main/cpp/opengl_decoder/RenderProgramFilter.cpp

使用的滤镜Lut图像:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值