【openGLES3.0编程指南笔记-10】多重渲染目标MRTs

概述

1. 多重渲染目标

多重渲染目标允许应用程序一次渲染到多个颜色缓冲区。利用多重渲染目标,片段着色器输出多个颜色(可以用于保存RGBA颜色,法线,深度或者纹理坐标),每个颜色用于一个连接的颜色缓冲区。

片段着色器输出多个颜色 -> attachments指定为新创建的framebuffer -> 从新创建的framebuffer拷贝到绘图framebuffer中 -> 显示出来了

fragData0 -> GL_COLOR_ATTACHMENT0(glDrawBuffers函数指定渲染谁,就是指定数据流向) -> glBlitFramebuffer(复制到绘图framebuffer)

    char fShaderStr[] =
            "#version 300 es \n"
            "precision mediump float; \n"
        // 片段着色器输出了4个颜色,每个颜色用于一个连接的颜色缓冲区
            "layout(location = 0) out vec4 fragData0; \n"
            "layout(location = 1) out vec4 fragData1; \n"
            "layout(location = 2) out vec4 fragData2; \n"
            "layout(location = 3) out vec4 fragData3; \n"
            "void main() \n"
            "{ \n"
            "   fragData0 = vec4(1, 0, 0, 1); \n"
            "   fragData1 = vec4(0, 1, 0, 1); \n"
            "   fragData2 = vec4(0, 0, 1, 1); \n"
            "   fragData3 = vec4(0.5, 0.5, 0.5, 1); \n"
            "} \n";

2. 帧缓冲区对象

默认情况下,OpenGL ES使用窗口系统提供的帧缓冲区作为绘图表面。如果应用程序只在屏幕上的表面绘图,则窗口系统提供的帧缓冲区通常很高效。但是,许多应用程序需要渲染到纹理,如阴影贴图、动态反射和环境贴图、多道景深技术、运动模糊效果和处理后特效等。我们需要允许应用程序直接渲染到纹理的API。

帧缓冲区对象(FBO)是一组颜色、深度和模板纹理或者渲染目标。各种2D图像可以连接到帧缓冲区对象中的颜色附着点。

2.1 InitFBO-创建framebuffer,在framebuffer中创建纹理类型缓冲区

int InitFBO(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *)myesContext->userData;
    int i;
    GLint defaultFramebuffer = 0;
    const GLenum attachments[4] = {
            GL_COLOR_ATTACHMENT0,
            GL_COLOR_ATTACHMENT1,
            GL_COLOR_ATTACHMENT2,
            GL_COLOR_ATTACHMENT3,
    };
    // 获取当前绑定的framebuffer ID
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer);
    // 1. 创建帧缓冲区
    // void glGenFramebuffers (GLsizei n, GLuint *framebuffers);
    // 分配n个帧缓冲区对象名称,并在ids中返回它们
    glGenFramebuffers(1, &userData->fbo);
    // 2. 使用帧缓冲区对象
    // void glBindFramebuffer (GLenum target, GLuint framebuffer);
    // target必须设为GL_FRAMEBUFFER,GL_DRAW_FRAMEBUFFER,GL_READ_FRAMEBUFFER
    // framebuffer为帧缓冲区对象名称
    glBindFramebuffer(GL_FRAMEBUFFER, userData->fbo);
    userData->textureHight = userData->textureWidth = 400;
// 1. 创建一个纹理对象
// void glGenTextures (GLsizei n, GLuint *textures);
// n表示要生成的纹理对象数量;textures表示保存n个纹理对象ID的无符号整数数组
    glGenTextures(4, &userData->colorTexId[0]);
    for (i = 0; i < 4; ++i) {
        // 绑定纹理对象
        glBindTexture(GL_TEXTURE_2D, userData->colorTexId[i]);
        // 加载图像信息,这里为NULL
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, userData->textureWidth, userData->textureHight,
                0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
        // 设置采样器参数
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        // 连接一个2D纹理的某个mip级别连接到帧缓冲区附着点
// void  glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachments[i], GL_TEXTURE_2D, userData->colorTexId[i], 0);
    }
// 指定渲染的附加颜色,说明要渲染这些颜色附着点
    glDrawBuffers(4, attachments);
    if (GL_FRAMEBUFFER_COMPLETE != glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
        return GL_FALSE;
    }

    // 绑定回原来的framebuffer中
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
    return GL_TRUE;
}

2.2 BlitTextures-帧缓冲区位块传送(Blit)

帧缓冲区位块传送(Blit)可以高效地将一个矩形区域的像素值从一个帧缓冲区(读帧缓冲区)复制到另一个帧缓冲区(绘图帧缓冲区)。帧缓冲区位块传送的关键应用之一是将一个多重采样渲染缓冲区解析为一个纹理(用一个帧缓冲区对象,纹理绑定为它的颜色附着)

void BlitTextures(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *)myesContext->userData;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, userData->fbo);
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    // void glBlitFramebuffer (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
    // 这就表示从源 -> 目标,mask和filter
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, 0, 0, myesContext->width/2, myesContext->height/2,
            GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT1);
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, myesContext->width/2, 0, myesContext->width, myesContext->height/2,
                      GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT2);
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, 0, myesContext->height/2, myesContext->width/2, myesContext->height,
                      GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT3);
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, myesContext->width/2, myesContext->height/2, myesContext->width, myesContext->height,
                      GL_COLOR_BUFFER_BIT, GL_LINEAR);
}

源码解析

#include <stdlib.h>
#include "esUtil.h"

typedef struct
{
    GLuint programObject;
    GLuint fbo;
    GLuint colorTexId[4];
    GLsizei textureWidth;
    GLsizei textureHight;
}myUserData;

int InitFBO(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *)myesContext->userData;
    int i;
    GLint defaultFramebuffer = 0;
    const GLenum attachments[4] = {
            GL_COLOR_ATTACHMENT0,
            GL_COLOR_ATTACHMENT1,
            GL_COLOR_ATTACHMENT2,
            GL_COLOR_ATTACHMENT3,
    };
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer);

    glGenFramebuffers(1, &userData->fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, userData->fbo);
    userData->textureHight = userData->textureWidth = 400;
    glGenTextures(4, &userData->colorTexId[0]);
    for (i = 0; i < 4; ++i) {
        glBindTexture(GL_TEXTURE_2D, userData->colorTexId[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, userData->textureWidth, userData->textureHight,
                0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachments[i], GL_TEXTURE_2D, userData->colorTexId[i], 0);
    }

    glDrawBuffers(4, attachments);
    if (GL_FRAMEBUFFER_COMPLETE != glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
        return GL_FALSE;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
    return GL_TRUE;
}

int Init(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *)myesContext->userData;
    char vShaderStr[] =
            "#version 300 es \n"
            "layout(location = 0) in vec4 a_position; \n"
            "void main() \n"
            "{ \n"
            "   gl_Position = a_position; \n"
            "} \n";
    char fShaderStr[] =
            "#version 300 es \n"
            "precision mediump float; \n"
            "layout(location = 0) out vec4 fragData0; \n"
            "layout(location = 1) out vec4 fragData1; \n"
            "layout(location = 2) out vec4 fragData2; \n"
            "layout(location = 3) out vec4 fragData3; \n"
            "void main() \n"
            "{ \n"
            "   fragData0 = vec4(1, 0, 0, 1); \n"
            "   fragData1 = vec4(0, 1, 0, 1); \n"
            "   fragData2 = vec4(0, 0, 1, 1); \n"
            "   fragData3 = vec4(0.5, 0.5, 0.5, 1); \n"
            "} \n";

    userData->programObject = myesLoadProgram(vShaderStr, fShaderStr);

    InitFBO(myesContext);

    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    return GL_TRUE;
}

void DrawGeometry(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *)myesContext->userData;
    GLfloat vVertices[] {
        -1.0f, 1.0f, 0.0f,
        -1.0f, -1.0f, 0.0f,
        1.0f, -1.0f, 0.0f,
        1.0f, 1.0f, 0.0f,
    };
    GLushort indices[] = {0, 1, 2, 0, 2, 3};
    glViewport(0, 0, myesContext->width, myesContext->height);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(userData->programObject);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), vVertices);
    glEnableVertexAttribArray(0);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
}

void BlitTextures(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *)myesContext->userData;
    // 绑定新创建的framebuffer
    glBindFramebuffer(GL_READ_FRAMEBUFFER, userData->fbo);
    // 从附着点读数据
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    // 从新创建的framebuffer往绘图缓冲区拷贝数据
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, 0, 0, myesContext->width/2, myesContext->height/2,
            GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT1);
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, myesContext->width/2, 0, myesContext->width, myesContext->height/2,
                      GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT2);
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, 0, myesContext->height/2, myesContext->width/2, myesContext->height,
                      GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT3);
    glBlitFramebuffer(0, 0, userData->textureWidth, userData->textureHight, myesContext->width/2, myesContext->height/2, myesContext->width, myesContext->height,
                      GL_COLOR_BUFFER_BIT, GL_LINEAR);
}

void Draw(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *) myesContext->userData;
    GLint defaultFramebuffer = 0;
    const GLenum attachments[4] = {
            GL_COLOR_ATTACHMENT0,
            GL_COLOR_ATTACHMENT1,
            GL_COLOR_ATTACHMENT2,
            GL_COLOR_ATTACHMENT3,
    };

    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer);
    // 先清除framebuffer中的内容
    glBindFramebuffer(GL_FRAMEBUFFER, userData->fbo);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 指定渲染的附着点
    glDrawBuffers(4, attachments);
    // 画正方形
    DrawGeometry(myesContext);
	// 绑定默认的framebuffer
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFramebuffer);
    BlitTextures(myesContext);
}

void ShutDown(MYESContext *myesContext)
{
    myUserData *userData = (myUserData *)myesContext->userData;
    glDeleteTextures(4, userData->colorTexId);
    glDeleteFramebuffers(1, &userData->fbo);
    glDeleteProgram(userData->programObject);
}

int myesMain(MYESContext *myesContext)
{
    myesContext->userData = (myUserData *) malloc(sizeof(myUserData));
    myesCreateWindow(myesContext, "Multiple Render Targets", 400, 400, MY_ES_WINDOW_RGB | MY_ES_WINDOW_DEPTH);

    if (!Init(myesContext)) {
        return GL_FALSE;
    }

    esRegisterDrawFunc(myesContext, Draw);
    esRegisterShutdownFunc(myesContext, ShutDown);

    return GL_TRUE;
}

效果图

image

posted @ 2021-06-06 20:34  pyjetson  阅读(804)  评论(0)    收藏  举报