OpenglES之分屏滤镜

483 阅读3分钟

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

还记得我们在 Opengl ES之纹理贴图 一文中提到的分屏滤镜吗?当时我们的实现方式是通过GL_REPEAT的方式实现的, 今天我们再来系统地讲下在Opengl ES中实现分屏滤镜的一些细节点,而通过操作片元着色器实现分屏滤镜,这也是业界比较常用的做法。

分屏滤镜的几种实现方式

试想一下如果你拿到一个使用Opengl ES实现分屏滤镜的需求,你能想到几种实现方式呢?

  1. 首先最容易想到的也许就是多次绘制吧,比如四分屏就绘制四个相同的纹理,六分屏就绘制六个相同的纹理,以此类推。考虑到纹理相同,我又可以用到纹理数组了,《Opengl ES之纹理数组》

  2. 其次可以像上面提到的使用GL_REPEAT取巧的方式也可以实现。

  3. 还可以使用glViewport控制绘制区域的方式实现,可能这个比较复杂。

  4. 最后也是常用的一种方式就是在片元着色器中根据分屏情况动态改变函数texture的纹理坐标来实现分屏效果。

笔者在这之前也参考了网上的一些分屏滤镜的Opengl ES教程,发现有些是有缺陷的,虽然分屏效果是有了,但是图片发生了裁剪导致一整张图片显示不完整,如果遇到这样的问题,童鞋们知道该如何修改吗?

分屏滤镜具体实现

下面我们就以上面的第四中方式实现一下一个四分屏的滤镜效果,代码中也有GL_REPEAT方式实现的分屏滤镜,到时童鞋们可以自行打开相关注释运行查看效果。

首先看一下我们的实现效果:

四分屏,顾名思义就是将屏幕四等分,然后每个区域都完整地显示一张纹理图。

我们看下对应的纹理坐标映射图:

从这个图我们可以得出一个通用结论:

在四分屏中滤镜中假如当纹理片段坐标为(x,y),当x小于0.5的时候,则采样纹理图片对应的x值为0到1,
同理当y小于0.5时,采样的纹理图片的坐标值也应该时0到1,以此类推当x大于0.5时则采样纹理图片对应的x值也应该是0到1,
当y大于0.5时,采样的纹理图片的坐标y值也应该是0到1。

因此我们只要在片元着色器中做好相应的映射,当x小于0时将其坐标映射到0到1之间,同样大于0.5时也将其坐标映射到0到1之间即可。那么我们的片元着色器应该是这样的:

#version 300 es
precision mediump float;
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
    if(TexCoord.y <= 0.5){
        if(TexCoord.x <= 0.5){
            // x的值始终在0到1之间 y也是
            FragColor = texture(ourTexture,vec2(TexCoord.x * 2.0,(TexCoord.y - 0.5) * 2.0));
        }else{
            // x的值始终在0到1之间 y也是
            FragColor = texture(ourTexture,vec2((TexCoord.x - 0.5) * 2.0,(TexCoord.y - 0.5) * 2.0));
        }
    }else{
        if(TexCoord.x <= 0.5){
            FragColor = texture(ourTexture,vec2(TexCoord.x * 2.0,(TexCoord.y - 0.5) * 2.0));
        }else{
            FragColor = texture(ourTexture,vec2((TexCoord.x - 0.5) * 2.0,(TexCoord.y - 0.5) * 2.0));
        }
    }
}

上面着色器的核心思想就是始终将取样时的x和y值约束在0到1之间,有了着色器,再运用我们之前学过的纹理渲染的知识点,我们就可以完成四分屏滤镜的渲染啦。

下面是核心代码点:

SplitFilterOpengl.cpp

#include "SplitFilterOpengl.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"
//                              "out vec4 FragColor;\n"
//                              "in vec2 TexCoord;\n"
//                              "uniform sampler2D ourTexture;\n"
//                              "void main()\n"
//                              "{\n"
//                              "    FragColor = texture(ourTexture, TexCoord);\n"
//                              "}";


// 片元着色器 垂直二分屏
// 原理?搭配GL_REPEAT使用
// 同理y轴上的三分屏就是3.0 * TexCoord.y 以此类推
//static const char *fragment = "#version 300 es\n"
//                              "precision mediump float;\n"
//                              "out vec4 FragColor;\n"
//                              "in vec2 TexCoord;\n"
//                              "uniform sampler2D ourTexture;\n"
//                              "void main()\n"
//                              "{\n"
//                              "    FragColor = texture(ourTexture, vec2(TexCoord.x, 2.0 * TexCoord.y));\n"
//                              "}";


// 片元着色器 垂直二分屏
// 原理?将映射到0到1之间,不必搭配GL_REPEAT
//static const char *fragment = "#version 300 es\n"
//                              "precision mediump float;\n"
//                              "out vec4 FragColor;\n"
//                              "in vec2 TexCoord;\n"
//                              "uniform sampler2D ourTexture;\n"
//                              "void main()\n"
//                              "{\n"
//                              "\n"
//                              "    if(TexCoord.y <= 0.5){\n"
//                              "        FragColor = texture(ourTexture,vec2(TexCoord.x,TexCoord.y * 2.0));\n"
//                              "    }else{\n"
//                              "        FragColor = texture(ourTexture,vec2(TexCoord.x,(TexCoord.y - 0.5) * 2.0));\n"
//                              "    }\n"
//                              "}";

// 三分屏 效果:上下二分,下部分在左右二分
//static const char *fragment = "#version 300 es\n"
//                              "precision mediump float;\n"
//                              "out vec4 FragColor;\n"
//                              "in vec2 TexCoord;\n"
//                              "uniform sampler2D ourTexture;\n"
//                              "void main()\n"
//                              "{\n"
//                              "\n"
//                              "    if(TexCoord.y <= 0.5){\n"
//                              "        FragColor = texture(ourTexture,vec2(TexCoord.x,TexCoord.y * 2.0));\n"
//                              "    }else{\n"
//                              "        if(TexCoord.x <= 0.5){\n"
//                              "            FragColor = texture(ourTexture,vec2(TexCoord.x * 2.0,(TexCoord.y - 0.5) * 2.0));\n"
//                              "        }else{\n"
//                              "            FragColor = texture(ourTexture,vec2((TexCoord.x - 0.5) * 2.0,(TexCoord.y - 0.5) * 2.0));\n"
//                              "        }\n"
//                              "    }\n"
//                              "}";

// 四分屏
// 不想写这么复杂的着色器,使用GL_REPEAT如何实现四分屏呢
// 可参考 https://mp.weixin.qq.com/s/jHcR4zzUa4uDw4DV7JlwRQ

static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "out vec4 FragColor;\n"
                              "in vec2 TexCoord;\n"
                              "uniform sampler2D ourTexture;\n"
                              "void main()\n"
                              "{\n"
                              "    if(TexCoord.y <= 0.5){\n"
                              "        if(TexCoord.x <= 0.5){\n"
                              "            FragColor = texture(ourTexture,vec2(TexCoord.x * 2.0,(TexCoord.y - 0.5) * 2.0));\n"
                              "        }else{\n"
                              "            FragColor = texture(ourTexture,vec2((TexCoord.x - 0.5) * 2.0,(TexCoord.y - 0.5) * 2.0));\n"
                              "        }\n"
                              "    }else{\n"
                              "        if(TexCoord.x <= 0.5){\n"
                              "            FragColor = texture(ourTexture,vec2(TexCoord.x * 2.0,(TexCoord.y - 0.5) * 2.0));\n"
                              "        }else{\n"
                              "            FragColor = texture(ourTexture,vec2((TexCoord.x - 0.5) * 2.0,(TexCoord.y - 0.5) * 2.0));\n"
                              "        }\n"
                              "    }\n"
                              "}";

// 使用绘制两个三角形组成一个矩形的形式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
        1.0f,-1.0f, // 右下
        1.0f,1.0f, // 右上
        -1.0f,-1.0f, // 左下
        -1.0f,1.0f // 左上
};

// 贴图纹理坐标(参考手机屏幕坐标系统,原点在左上角)
//由于对一个OpenGL纹理来说,它没有内在的方向性,因此我们可以使用不同的坐标把它定向到任何我们喜欢的方向上,然而大多数计算机图像都有一个默认的方向,它们通常被规定为y轴向下,X轴向右
const static GLfloat TEXTURE_COORD[] = {
        1.0f,1.0f, // 右下
        1.0f,0.0f, // 右上
        0.0f,1.0f, // 左下
        0.0f,0.0f // 左上
};

SplitFilterOpengl::SplitFilterOpengl() {
    initGlProgram(ver,fragment);
    positionHandle = glGetAttribLocation(program,"aPosition");
    textureHandle = glGetAttribLocation(program,"aTexCoord");
    textureSampler = glGetUniformLocation(program,"ourTexture");
    LOGD("program:%d",program);
    LOGD("positionHandle:%d",positionHandle);
    LOGD("textureHandle:%d",textureHandle);
    LOGD("textureSample:%d",textureSampler);
}

SplitFilterOpengl::~SplitFilterOpengl() noexcept {
    LOGD("SplitFilterOpengl析构函数");
}

void SplitFilterOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel");
    glGenTextures(1, &textureId);

    // 激活纹理,注意以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个
    // 默认是0,如果不是0的话,需要在onDraw的时候重新激活一下?
//    glActiveTexture(GL_TEXTURE0);
//    glUniform1i(textureSampler, 0);

// 例如,一样的
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    // 为当前绑定的纹理对象设置环绕、过滤方式
    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, textureId);

    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}

void SplitFilterOpengl::onDraw() {
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);

    // 激活纹理
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);

    /**
     * size 几个数字表示一个点,显示是两个数字表示一个点
     * normalized 是否需要归一化,不用,这里已经归一化了
     * stride 步长,连续顶点之间的间隔,如果顶点直接是连续的,也可填0
     */
    // 启用顶点数据
    glEnableVertexAttribArray(positionHandle);
    glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);

    // 纹理坐标
    glEnableVertexAttribArray(textureHandle);
    glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD);

    // 4个顶点绘制两个三角形组成矩形
    glDrawArrays(GL_TRIANGLE_STRIP,0,4);

    glUseProgram(0);

    // 禁用顶点
    glDisableVertexAttribArray(positionHandle);
    if(nullptr != eglHelper){
        eglHelper->swapBuffers();
    }

    glBindTexture(GL_TEXTURE_2D, 0);
}

需要注意的问题

在着色器中需要明确区分整型和浮点型数据,比如如果数据是浮点型的就该写成0.0,而不要写成0,否则着色器编译报错Cannot compare 'float' with 'int'

系列教程源码

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
Opengl ES之LUT滤镜(上)
Opengl ES之LUT滤镜(下)-3DLUT

关注我,一起进步,人生不止coding!!!