OpenGL ES 多目标渲染(MRT)

简介: Opengl ES连载系列

大家好,我又来啦!今天给大家介绍一个OpenGL ES 3.0中的新特性,多渲染目标(Multiple Render Target)。

所谓的多渲染目标就是指将片元着色器中的输出对应到多个纹理上了,在之前的例子上我们的片元着色器都是只有一个输出,只能对应输出到一个目标上(FBO纹理或者屏幕),
今天我们使用MRT特性,在片元着色器中定义多个输出,并且附着到多个纹理之上。

首先我们来回顾一下之前在 Opengl ES之FBO 一文中提到的挂在点这张图,上面所说的多渲染目标所用到的技术就是
FBO中的多个颜色附着点(GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1...)

FBO提供的挂接点如图所示

在FBO中我们通过glFramebufferTexture2D()将texture绑定到color attachment上,这时绑定这个frame buffer进行渲染,就会渲染到绑定的texture。

在OpenGL ES 2.0中,FBO只能绑定到0号的color attachment即GL_COLOR_ATTACHMENT0上,而在Opengl ES3.0中则没有了这个限制,这或许就是为什么在OpenGL ES 2.0中片元着色器给我们
内置了一个默认输出变量gl_FragColor,而在Opengl ES3.0则需要开发者自定义输出变量的一个原因吧。

下面我们使用MRT特性实现一个小demo,首先使用FBO输出到四个纹理上,分别是原图、R颜色通道分离图、G颜色通道分离图和B颜色通道分离图,然后再将这四个纹理各取四分之一部分组成一张新图渲染到屏幕上。

MRT例子demo

首先我们需要生产一个FBO和四个附着的frame buffer上的纹理,准备代码如下:

    // TEXTURE_NUM就是4
    glGenTextures(TEXTURE_NUM, fboTextureIds);
    glGenFramebuffers(1, &fboId);

    glBindFramebuffer(GL_FRAMEBUFFER, fboId);

    for (int i = 0; i < TEXTURE_NUM; ++i) {
        attachments[i] = GL_COLOR_ATTACHMENT0 + i;
        glBindTexture(GL_TEXTURE_2D, fboTextureIds[i]);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        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, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachments[i], GL_TEXTURE_2D, fboTextureIds[i], 0);
        // 解绑
        glBindTexture(GL_TEXTURE_2D, GL_NONE);
    }

    //告诉 OpenGL ,我要渲染到 4 个颜色附着上
    glDrawBuffers(TEXTURE_NUM, attachments);

    // 检查FBO状态
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        LOGE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE");
    }
    // 解绑
    glBindTexture(GL_TEXTURE_2D, GL_NONE);
    glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);

然后在片元着色器中分别定义四个颜色输出,分别对应FBO上的四个颜色附着点:

// 片元着色器
// 四个输出,一个是完整的图片数据、另外三个是RGB分离的数据
#version 300 es
precision mediump float;
in vec2 TexCoord;
uniform sampler2D ourTexture;
// 定义多个输出的话layout(location = n)是必须的
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
layout(location = 3) out vec4 outColor3;
void main()
{
    vec4 outColor = texture(ourTexture, TexCoord);
    // 原图
    outColor0 = outColor;
    // R分离通道
    outColor1 = vec4(outColor.r, 0.0, 0.0, 1.0);
    // G分离通道
    outColor2 = vec4(0.0, outColor.g, 0.0, 1.0);
    // B分离通道
    outColor3 = vec4(0.0, 0.0, outColor.b, 1.0);
}

都加上注释了,这里不多说,最后的步骤就是进行FBO的渲染,再将FBO渲染后的四个纹理图组合渲染到屏幕上,也就是绘制两次,一次绘制到FBO,一次是将FBO的
内容绘制到屏幕,关于FBO的使用绘制请参考之前的博文 Opengl ES之FBO

这里不同的是两次绘制所使用的片元着色器是不一样的,绘制到屏幕上时所使用到的片元着色器如下:

#version 300 es
precision mediump float;
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D s_Texture0;
uniform sampler2D s_Texture1;
uniform sampler2D s_Texture2;
uniform sampler2D s_Texture3;
void main()
{
if(TexCoord.x < 0.5 && TexCoord.y < 0.5)
    {   
        FragColor = texture(s_Texture0, TexCoord);
    }
    else if(TexCoord.x > 0.5 && TexCoord.y < 0.5)
    {
        FragColor = texture(s_Texture1, TexCoord);
    }
    else if(TexCoord.x < 0.5 && TexCoord.y > 0.5)
    {
        FragColor = texture(s_Texture2, TexCoord);
    }
    else
    {
        FragColor = texture(s_Texture3, TexCoord);
    }
}

也就是将FBO中的四张纹理图各取四分之一组合绘制成一张新图。

完整代码如下:


MRTRenderOpengl.cpp

/**
 * 本教程版权属于公众号"思想觉悟"
 /
#include "MRTRenderOpengl.h"


#include "FBOOpengl.h"
#include "../utils/Log.h"
#include "../utils/ShaderUtils.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"
                         "}";

// 片元着色器
// 四个输出,一个是完整的图片数据、另外三个是RGB分离的数据
// 定义多个输出的话layout(location = n)是必须的
static const char *outs_fragment = "#version 300 es\n"
                                   "precision mediump float;\n"
                                   "in vec2 TexCoord;\n"
                                   "uniform sampler2D ourTexture;\n"
                                   "layout(location = 0) out vec4 outColor0;\n"
                                   "layout(location = 1) out vec4 outColor1;\n"
                                   "layout(location = 2) out vec4 outColor2;\n"
                                   "layout(location = 3) out vec4 outColor3;\n"
                                   "void main()\n"
                                   "{\n"
                                   "    vec4 outColor = texture(ourTexture, TexCoord);\n"
                                   "    outColor0 = outColor;\n"
                                   "    outColor1 = vec4(outColor.r, 0.0, 0.0, 1.0);\n"
                                   "    outColor2 = vec4(0.0, outColor.g, 0.0, 1.0);\n"
                                   "    outColor3 = vec4(0.0, 0.0, outColor.b, 1.0);\n"
                                   "}";

// 片元着色器
static const char *render_fragment = "#version 300 es\n"
                                     "precision mediump float;\n"
                                     "out vec4 FragColor;\n"
                                     "in vec2 TexCoord;\n"
                                     "uniform sampler2D s_Texture0;"
                                     "uniform sampler2D s_Texture1;"
                                     "uniform sampler2D s_Texture2;"
                                     "uniform sampler2D s_Texture3;"
                                     "void main()\n"
                                     "{\n"
                                     "if(TexCoord.x < 0.5 && TexCoord.y < 0.5)\n"
                                     "    {   \n"
                                     "        FragColor = texture(s_Texture0, TexCoord);\n"
                                     "    }\n"
                                     "    else if(TexCoord.x > 0.5 && TexCoord.y < 0.5)\n"
                                     "    {\n"
                                     "        FragColor = texture(s_Texture1, TexCoord);\n"
                                     "    }\n"
                                     "    else if(TexCoord.x < 0.5 && TexCoord.y > 0.5)\n"
                                     "    {\n"
                                     "        FragColor = texture(s_Texture2, TexCoord);\n"
                                     "    }\n"
                                     "    else\n"
                                     "    {\n"
                                     "        FragColor = texture(s_Texture3, TexCoord);\n"
                                     "    }"
                                     "}";

const static GLfloat VERTICES_AND_TEXTURE[] = {
        0.5f, -0.5f, // 右下
        // 纹理坐标
        1.0f, 1.0f,
        0.5f, 0.5f, // 右上
        // 纹理坐标
        1.0f, 0.0f,
        -0.5f, -0.5f, // 左下
        // 纹理坐标
        0.0f, 1.0f,
        -0.5f, 0.5f, // 左上
        // 纹理坐标
        0.0f, 0.0f
};

// 真正的纹理坐标在图片的左下角
const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {
        1.0f, -1.0f, // 右下
        // 纹理坐标
        1.0f, 0.0f,
        1.0f, 1.0f, // 右上
        // 纹理坐标
        1.0f, 1.0f,
        -1.0f, -1.0f, // 左下
        // 纹理坐标
        0.0f, 0.0f,
        -1.0f, 1.0f, // 左上
        // 纹理坐标
        0.0f, 1.0f
};

// 使用byte类型比使用short或者int类型节约内存
const static uint8_t indices[] = {
        // 注意索引从0开始!
        // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
        // 这样可以由下标代表顶点组合成矩形
        0, 1, 2, // 第一个三角形
        1, 2, 3  // 第二个三角形
};


MRTRenderOpengl::MRTRenderOpengl() {

    mrtProgram = createProgram(ver,outs_fragment);
    mrtPositionHandle = glGetAttribLocation(mrtProgram, "aPosition");
    mrtTextureHandle = glGetAttribLocation(mrtProgram, "aTexCoord");
    mrtTextureSampler = glGetUniformLocation(mrtProgram, "ourTexture");

    initGlProgram(ver, render_fragment);
    positionHandle = glGetAttribLocation(program, "aPosition");
    textureHandle = glGetAttribLocation(program, "aTexCoord");
    textureSampler1 = glGetUniformLocation(program, "s_Texture0");
    textureSampler2 = glGetUniformLocation(program, "s_Texture1");
    textureSampler3 = glGetUniformLocation(program, "s_Texture2");
    textureSampler4 = glGetUniformLocation(program, "s_Texture3");
    LOGD("program:%d", program);
    LOGD("positionHandle:%d", positionHandle);
    LOGD("textureHandle:%d", textureHandle);
    LOGD("textureSampler1:%d", textureSampler1);
    LOGD("textureSampler2:%d", textureSampler2);
    LOGD("textureSampler3:%d", textureSampler3);
    LOGD("textureSampler4:%d", textureSampler4);
    // VAO
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // vbo
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_AND_TEXTURE), VERTICES_AND_TEXTURE,
                 GL_STATIC_DRAW);

    // stride 步长 每个顶点坐标之间相隔4个数据点,数据类型是float
    glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);
    // 启用顶点数据
    glEnableVertexAttribArray(positionHandle);
    // stride 步长 每个颜色坐标之间相隔4个数据点,数据类型是float,颜色坐标索引从2开始
    glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
                          (void *) (2 * sizeof(float)));
    // 启用纹理坐标数组
    glEnableVertexAttribArray(textureHandle);

    // EBO
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 这个顺序不能乱啊,先解除vao,再解除其他的,不然在绘制的时候可能会不起作用,需要重新glBindBuffer才生效
    // vao解除
    glBindVertexArray(0);
    // 解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // 解除绑定
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    LOGD("program:%d", program);
    LOGD("positionHandle:%d", positionHandle);
    LOGD("colorHandle:%d", textureHandle);
}

MRTRenderOpengl::~MRTRenderOpengl() {
    glDeleteBuffers(1, &ebo);
    glDeleteBuffers(1, &vbo);
    glDeleteVertexArrays(1, &vao);
    // ... 删除其他,例如fbo等
}

void MRTRenderOpengl::onDraw() {

    // 先在FBO离屏渲染
    onFboDraw();

    //首先获取当前默认帧缓冲区的 id
    GLint defaultFrameBuffer = GL_NONE;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFrameBuffer);

    //绑定默认帧缓冲区对象,绘制到屏幕上
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFrameBuffer);

    // 恢复绘制屏幕宽高
    glViewport(0, 0, eglHelper->viewWidth, eglHelper->viewHeight);

    // 绘制到屏幕
    // 清屏
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);

    //指定 4 个纹理作为输入
    for (int i = 0; i < TEXTURE_NUM; ++i)
    {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, fboTextureIds[i]);
        LOGD("-----onDraw:%d",fboTextureIds[i]);
        switch (i) {
            case 0:
                glUniform1i(textureSampler1,  i);
                break;
            case 1:
                glUniform1i(textureSampler2,  i);
                break;
            case 2:
                glUniform1i(textureSampler3,  i);
                break;
            case 3:
                glUniform1i(textureSampler4, i);
                break;
        }
    }

    // VBO与VAO配合绘制
    // 使用vao
    glBindVertexArray(vao);
    // 使用EBO
// 使用byte类型节省内存
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, (void *) 0);
    glUseProgram(0);
    // vao解除绑定
    glBindVertexArray(0);

    if (nullptr != eglHelper) {
        eglHelper->swapBuffers();
    }

    glBindTexture(GL_TEXTURE_2D, 0);
    //绑定默认帧缓冲区对象,绘制到屏幕上
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GL_NONE);
}


void MRTRenderOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel");
    imageWidth = width;
    imageHeight = height;
    glGenTextures(1, &imageTextureId);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, imageTextureId);
    // 为当前绑定的纹理对象设置环绕、过滤方式
    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 MRTRenderOpengl::fboPrepare() {
    // VAO
    glGenVertexArrays(1, &fboVao);
    glBindVertexArray(fboVao);

    // vbo
    glGenBuffers(1, &fboVbo);
    glBindBuffer(GL_ARRAY_BUFFER, fboVbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(FBO_VERTICES_AND_TEXTURE), FBO_VERTICES_AND_TEXTURE,
                 GL_STATIC_DRAW);

    // stride 步长 每个顶点坐标之间相隔4个数据点,数据类型是float
    glVertexAttribPointer(mrtPositionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);
    // 启用顶点数据
    glEnableVertexAttribArray(mrtPositionHandle);
    // stride 步长 每个颜色坐标之间相隔4个数据点,数据类型是float,颜色坐标索引从2开始
    glVertexAttribPointer(mrtTextureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
                          (void *) (2 * sizeof(float)));
    // 启用纹理坐标数组
    glEnableVertexAttribArray(mrtTextureHandle);

    // EBO
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 这个顺序不能乱啊,先解除vao,再解除其他的,不然在绘制的时候可能会不起作用,需要重新glBindBuffer才生效
    // vao解除
    glBindVertexArray(0);
    // 解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // 解除绑定
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    glGenTextures(TEXTURE_NUM, fboTextureIds);
    glGenFramebuffers(1, &fboId);

    glBindFramebuffer(GL_FRAMEBUFFER, fboId);

    for (int i = 0; i < TEXTURE_NUM; ++i) {
        attachments[i] = GL_COLOR_ATTACHMENT0 + i;
        glBindTexture(GL_TEXTURE_2D, fboTextureIds[i]);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        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, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachments[i], GL_TEXTURE_2D, fboTextureIds[i], 0);
        // 解绑
        glBindTexture(GL_TEXTURE_2D, GL_NONE);
    }

    //告诉 OpenGL ,我要渲染到 4 个颜色附着上
    glDrawBuffers(TEXTURE_NUM, attachments);

    // 检查FBO状态
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        LOGE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE");
    }
    // 解绑
    glBindTexture(GL_TEXTURE_2D, GL_NONE);
    glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
}

void MRTRenderOpengl::onFboDraw() {
    fboPrepare();

    glBindFramebuffer(GL_FRAMEBUFFER, fboId);

    glDrawBuffers(TEXTURE_NUM, attachments);

    // 主要这个的大小要与FBO绑定时的纹理的glTexImage2D 设置的大小一致呀
    glViewport(0, 0, imageWidth, imageHeight);

    // FBO绘制
    // 清屏
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mrtProgram);

    // 激活纹理
    glActiveTexture(GL_TEXTURE1);
    glUniform1i(mrtTextureSampler, 1);

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

    // VBO与VAO配合绘制
    // 使用vao
    glBindVertexArray(fboVao);
    // 使用EBO
// 使用byte类型节省内存
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, (void *) 0);
    glUseProgram(0);
    // vao解除绑定
    glBindVertexArray(0);
    if (nullptr != eglHelper) {
        eglHelper->swapBuffers();
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

运行结果图:

思考

在一篇博文中我们介绍了纹理数组的使用,那么针对以上这个demo,当绘制到屏幕时,如何使用纹理数组的方式实现呢?

系列教程源码

https://github.com/feiflyer/NDK_OpenglES_Tutorial

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之纹理数组

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

目录
相关文章
|
3月前
|
Linux API iOS开发
【Qt 渲染引擎】一文带你了解qt的三种 渲染引擎,包括栅格引擎(Raster)、OpenGL 和本地绘图系统
【Qt 渲染引擎】一文带你了解qt的三种 渲染引擎,包括栅格引擎(Raster)、OpenGL 和本地绘图系统
113 0
|
3月前
|
JavaScript C++
从OpenGL渲染的角度排查 creator native 局部换肤的问题
从OpenGL渲染的角度排查 creator native 局部换肤的问题
53 0
|
3月前
|
XML 小程序 Java
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
92 0
|
存储 编解码 算法
Opengl ES之LUT滤镜(上)
Opengl ES之连载系列
396 0
|
数据安全/隐私保护 索引
Opengl ES之纹理数组
Opengl ES连载系列
230 0
|
数据安全/隐私保护
Opengl ES之水印贴图
Opengl ES之连载系列
122 0
|
Java 数据安全/隐私保护 Android开发
Opengl ES之矩阵变换(下)
Opengl ES连载系列
103 0
|
缓存 C++
Opengl ES之FBO
Opengl ES连载系列
112 0
|
Java API 数据安全/隐私保护
Opengl ES之矩阵变换(上)
Opengl ES连载系列
111 0
|
存储
Opengl ES之踩坑记
Opengl ES之连载系列
120 0