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

张开发
2026/4/4 4:54:14 15 分钟阅读
使用OpenGL纹理数组实现高精度实时Lut滤镜
之前写过的文章(使用OpenGL实现滤镜转换的一种思路_轮子初级玩家-CSDN博客)我把一整个Lut滤镜图作为单个纹理贴图把图像原颜色采样后当作坐标然后从lut纹理中查找出替换颜色实现滤镜功能这是最简易的一种滤镜实现方式但由于lut纹理的T轴占比过长计算进度上容易导致命中错误的滤镜页的问题那是否有办法实现更高精度的滤镜呢其实是有的而且我优化了一下。本文默认读者已经具备基本的OpenGL代码阅读能力所以只打算写一些必要的代码和解析。一、片元渲染器的编写首先我们明确需要两个纹理一个为原图纹理sTexture用于给OpenGL灌入原画面;一个是滤镜Lut表纹理lutTexture类型分别为sampler2D和sampler2DArray。看到sampler2DArray相信读者已经猜到降低进度要求的办法是什么了就是通过使用2d纹理数组把一整个lut表通过纹理数组分割为一页页即可解决行和页的命中精度问题。由于这是纹理数组所以对比二维纹理ST坐标多了一维RR可以理解为下标每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图像

更多文章