Opengl ES之水印贴图

简介: Opengl ES之连载系列

前言

水印贴图又称画中画,这种功能在Opengl中是如何实现的呢?我们可以简单地理解成两张纹理的叠加,一个纹理作为背景,另外一个纹理通过调整顶点坐标作为一个小的前景。

说到水印贴图的实现,很多朋友可能会想到通过mix混合函数实现,但是并不推荐这样做,抛开性能先不说,这种实现方式就不太方便后期拓展。今天我们就通过mix内建函数和
多纹理绘制两种方式实现一个水印贴图的功能。

mix混合贴图

先来看看我们用到的贴图资源,首先我们需要渲染的背景纹理是:

背景纹理图

需要贴图的水印是:

片元着色器:

#version 300 es
precision mediump float;
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
uniform sampler2D waterTexture;
void main()
{
   vec4 textColor = texture(ourTexture, TexCoord);
   vec4 waterColor = texture(waterTexture, TexCoord);
   FragColor = mix(textColor, waterColor, 0.7);
}

这个片元着色器很简单,就是传递两个纹理,一个是背景纹理,一个是水印纹理,然后通过mix内建混合函数将颜色混合渲染显示。

主要的渲染代码:

//    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
//    glClear(GL_COLOR_BUFFER_BIT);
//    glUseProgram(program);
//
//    // 激活纹理
//    glActiveTexture(GL_TEXTURE2);
//    // 绑定纹理
//    glBindTexture(GL_TEXTURE_2D, textureId);
//    glUniform1i(textureSampler, 2);
//
//
//    // 激活纹理
//    glActiveTexture(GL_TEXTURE3);
//    // 绑定纹理
//    glBindTexture(GL_TEXTURE_2D, waterTextureId);
//    glUniform1i(waterTextureSampler, 3);
//
//    /**
//     * 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);

运行起来看到效果是这样的:

貌似不太对?好像被蒙上一层黑色阴影的感觉,这是因为我们没有打开Opengl的混合功能,没有设置混合模式导致的,我们在绘制时打开对应的设置即可:

//    // 可以注释掉这两句看看效果是不一样的
////    glEnable(GL_BLEND); //打开混合功能
////    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); //指定混合模式

再次运行看看效果:

这次对了,看起来不好看是因为水印贴图的大小和背景纹理大小一致了,希望逼真一点的话可以适当修改着色器语言,在特定的顶点区域才采样使用mix函数混合,其他区域就显示背景纹理的采样即可,同时如果需要调整水印贴图的权重,直接调整着色器中mix函数的第三个参数即可。

背景纹理和水印贴图单独绘制

上面说到不推荐使用mix混合贴图的实现方式,那么更加合理的方式是什么呢?

我们编程讲究的是功能粒度拆分要合理,如果能坐到本经纹理和水印纹理单独绘制分离就能更加地方便后续功能的拓展,将绘制分离开来也能更好地搭配FBO离屏渲染。

这里为了贪方便实现单个功能而已,就不采用FBO的方式了,当然如果是实际应用中还是建议使用FBO的方式更加地合理。

废话少扯,直接上代码吧,注意看关键注释:

WaterMarkOpengl.cpp


// 顶点着色器
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"
                         "}";

// 片元着色器
// 多纹理重叠,通过mix混合实现水印
//static const char *fragment = "#version 300 es\n"
//                              "precision mediump float;\n"
//                              "out vec4 FragColor;\n"
//                              "in vec2 TexCoord;\n"
//                              "uniform sampler2D ourTexture;\n"
//                              "uniform sampler2D waterTexture;\n"
//                              "void main()\n"
//                              "{\n"
//                              "    vec4 textColor = texture(ourTexture, TexCoord);\n"
//                              "    vec4 waterColor = texture(waterTexture, TexCoord);\n"
//                              "    FragColor = mix(textColor, waterColor, 0.7);\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"
                              "    vec4 textColor = texture(ourTexture, TexCoord);\n"
                              "    FragColor = textColor;\n"
                              "}";


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

// 贴图纹理坐标(参考手机屏幕坐标系统,原点在左上角)
const static GLfloat TEXTURE_COORD[] = {
        1.0f,1.0f, // 右下
        1.0f,0.0f, // 右上
        0.0f,1.0f, // 左下
        0.0f,0.0f // 左上
};

WaterMarkOpengl::WaterMarkOpengl() {
    initGlProgram(ver,fragment);
    positionHandle = glGetAttribLocation(program,"aPosition");
    textureHandle = glGetAttribLocation(program,"aTexCoord");
    textureSampler = glGetUniformLocation(program,"ourTexture");
    waterTextureSampler = glGetUniformLocation(program,"waterTexture");
}

WaterMarkOpengl::~WaterMarkOpengl() noexcept {

}

void WaterMarkOpengl::onDraw() {

//    // 可以注释掉这两句看看效果是不一样的
////    glEnable(GL_BLEND); //打开混合功能
////    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); //指定混合模式
//
//    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
//    glClear(GL_COLOR_BUFFER_BIT);
//    glUseProgram(program);
//
//    // 激活纹理
//    glActiveTexture(GL_TEXTURE2);
//    // 绑定纹理
//    glBindTexture(GL_TEXTURE_2D, textureId);
//    glUniform1i(textureSampler, 2);
//
//
//    // 激活纹理
//    glActiveTexture(GL_TEXTURE3);
//    // 绑定纹理
//    glBindTexture(GL_TEXTURE_2D, waterTextureId);
//    glUniform1i(waterTextureSampler, 3);
//
//    /**
//     * 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);

    // 可以注释掉这两句看看效果是不一样的
    glEnable(GL_BLEND); //打开混合功能
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); //指定混合模式

    // 先绘制背景,正常纹理
   // 让纹理图片居中
    glViewport((eglHelper->viewWidth - imageWidth)/2,(eglHelper->viewHeight - imageHeight)/2,imageWidth,imageHeight);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);

    // 激活纹理
    glActiveTexture(GL_TEXTURE2);
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    glUniform1i(textureSampler, 2);

    /**
     * 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);

    // 然后使用统一的着色器,改变纹理数据再绘制一次水印 顶点坐标和纹理坐标和上面使用的一样,因此不用重新赋值了
    // 调整窗口位置,调整水印的绘制位置,让水印绘制的背景的右下角
    int waterWidth = 200;
    int waterHeight = 300;
    // 以手机屏幕左下角为坐标原点
    glViewport((eglHelper->viewWidth - imageWidth)/2 + imageWidth - waterWidth,(eglHelper->viewHeight - imageHeight)/2,waterWidth,waterHeight);
    // 激活纹理
    glActiveTexture(GL_TEXTURE3);
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, waterTextureId);
    glUniform1i(textureSampler, 3);
    // 4个顶点绘制两个三角形组成矩形
    glDrawArrays(GL_TRIANGLE_STRIP,0,4);

    glUseProgram(0);

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

    glBindTexture(GL_TEXTURE_2D, 0);
}

void WaterMarkOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel");
    imageWidth = width;
    imageHeight = height;
    glGenTextures(1, &textureId);
    // 绑定纹理
    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, 0);
}

void WaterMarkOpengl::setWaterPixel(void *data, int width, int height, int length) {
    LOGD("texture setWaterPixel");
    glGenTextures(1, &waterTextureId);

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

主要思想就是先绘制背景纹理,然后通过glViewport改变视窗的大小再绘制水印纹理,实例中我们将心形水印绘制在背景纹理的右下角。

运行效果:

系列教程源码

https://github.com/feiflyer/NDK_OpenglES_Tutorial

后续demo如果有完善可能会更新。

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之水印贴图

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

目录
相关文章
|
4月前
|
XML 小程序 Java
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
52 0
|
存储 编解码 算法
Opengl ES之LUT滤镜(上)
Opengl ES之连载系列
341 0
|
数据安全/隐私保护 开发者
OpenGL ES 多目标渲染(MRT)
Opengl ES连载系列
221 0
|
数据安全/隐私保护 索引
Opengl ES之纹理数组
Opengl ES连载系列
172 0
|
Java 数据安全/隐私保护 Android开发
Opengl ES之矩阵变换(下)
Opengl ES连载系列
84 0
|
Java API 数据安全/隐私保护
Opengl ES之矩阵变换(上)
Opengl ES连载系列
83 0
|
存储
Opengl ES之踩坑记
Opengl ES之连载系列
90 0
|
存储 编解码 算法
Opengl ES之RGB转NV21
Opengl ES连载系列
116 0
|
并行计算 C++
Opengl ES之YUV数据渲染
Opengl ES连载系列
131 0
|
Android开发 数据安全/隐私保护 C++
Opengl ES之PBO
Opengl ES连载系列
141 0