Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

GLSL #6: Frame Buffer Object (FBO)

目標

前回(下記参照)は,テクスチャマッピングを行うプログラムを作成しました. 今回は,そのプログラムをベースに,FBOに描画した結果を四角形ポリゴンにテクスチャとして利用する方法を実装します.

mugichoko.hatenablog.com

実装環境

レンダリングの流れと結果

今回は分かりやすさのために,レンダリングの流れと結果を先に示す.

  1. パス1:FBO(テクスチャ)にテクスチャ付き3Dポリゴンを描画
    • この際,カメラを3Dポリゴン (modelTex) の中心を注視して,円を描くように動かす
    • つまり,以下の動画を毎フレームFBOに書き出す youtu.be
  2. パス2:FBOが保持するテクスチャのIDをmodelTexクラスに渡し,今度は,バックバッファに描画
    • この際,カメラは3Dポリゴン (modelTex) から少し離れた位置に置く youtu.be

サンプルプログラム

前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • shaderUtil.h
  • shaderUtil.cpp
  • model.h
  • model.cpp
  • modelTex.vert
  • modelTex.frag

modelTex.h

前回から少し変更. 具体的には,画像ををファイルから読み込まずとも,別処理で作成されたテクスチャを受け取って描画できるようにした.

//
// modelTex.h
//
#pragma once
#include "model.h"
#include <IL/il.h>

namespace gl
{
    class modelTex : public model
    {
    private:
        static const GLfloat vertData[40];
        static const GLshort idxData[6];
        static const int numVert;
        static const int offsetTexUVData;

        GLuint texID;

        GLuint MVPMatLoc;
        GLuint vao; // Vertex array object
        GLuint vbo; // Vertex buffer object
        GLuint ibo; // Index buffer object

        void init(
            int w,
            int h,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat,
            GLenum format,
            GLenum type
        );

    public:
        modelTex(
            const string &texFileName,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        modelTex(
            int w,
            int h,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        ~modelTex();

        void render();
        void render(
            GLint texID
        );
    };
}

modelTex.cpp

ヘッダファイルに合わせて,前回から少し変更.

//
// modelTex.cpp
//
#include "modelTex.h"

namespace gl
{
    const GLfloat modelTex::vertData[40] =
    {
        // Vertex position
        +1.0f, +1.0f, 0.0f,      // #0: Upper right
        -1.0f, +1.0f, 0.0f,      // #1: Upper left
        -1.0f, -1.0f, 0.0f,      // #2: Lower left
        +1.0f, -1.0f, 0.0f,      // #3: Lower right
        // Texture uv position
        1.0f, 1.0f,               // #0: Upper right
        0.0f, 1.0f,               // #1: Upper left
        0.0f, 0.0f,               // #2: Lower left
        1.0f, 0.0f                // #3: Lower right
    };
    const GLshort modelTex::idxData[6] =
    {
        0, 1, 2,
        0, 2, 3
    };
    const int modelTex::numVert(4);
    const int modelTex::offsetTexUVData(sizeof(float) * 3 * numVert);

    void modelTex::init(
        int w,
        int h,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        // ----- Initialize shader programs
        initializeProgram(prog, vertShaderName, fragShaderName);

        // ----- Generate buffers
        // VBO
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertData), vertData, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // IBO
        glGenBuffers(1, &ibo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxData), idxData, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        // ----- Get a uiform location
        MVPMatLoc = glGetUniformLocation(prog, "MVPMat");

        // ----- VAO
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);

        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);  // 3 elements per vertex
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)offsetTexUVData); // 2 elements per color
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        glBindVertexArray(0);

        // ----- Currently the texture is empty
        glGenTextures(1, &texID);
        glBindTexture(GL_TEXTURE_2D, texID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, NULL);
    }

    modelTex::modelTex(
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        // ----- Load an image and create a texture of the image
        ilInit();
        
        ILuint imgID;
        ilGenImages(1, &imgID);

        ilBindImage(imgID);
        ilEnable(IL_ORIGIN_SET);
        ilOriginFunc(IL_ORIGIN_LOWER_LEFT);

        try {
            ILboolean isLoaded = ilLoadImage((ILstring)texFileName.c_str());
            if (isLoaded)
            {
                cout << "Loaded " << texFileName << endl;

                init(
                    ilGetInteger(IL_IMAGE_WIDTH), ilGetInteger(IL_IMAGE_HEIGHT),
                    vertShaderName, fragShaderName,
                    internalFormat, format, type
                );

                ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, ilGetInteger(IL_IMAGE_WIDTH),
                    ilGetInteger(IL_IMAGE_HEIGHT), 0, format, type,
                    ilGetData());
            }
            else
            {
                throw "Could not load image: " + texFileName;
            }
        }
        catch (string e)
        {
            cerr << "Error: " << e.c_str() << endl;
            exit(EXIT_FAILURE);
        }
    }
    modelTex::modelTex(
        int w,
        int h,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        init(w, h, vertShaderName, fragShaderName, internalFormat, format, type);
    }
    modelTex::~modelTex()
    {
        glDeleteVertexArrays(1, &vao);
        glDeleteTextures(1, &texID);
        glDeleteBuffers(1, &vbo);
        glDeleteBuffers(1, &ibo);
        glDeleteBuffers(1, &MVPMatLoc);
    }

    void modelTex::render()
    {
        render(-1);
    }
    void modelTex::render(
        GLint texID
    )
    {
        glUseProgram(prog);
        {
            // Send matrix data to GPU
            glUniformMatrix4fv(MVPMatLoc, 1, GL_FALSE, glm::value_ptr(projMat * viewMat * modelMat));
            
            // Texture
            if (texID >= 0)
            {
                glBindTexture(GL_TEXTURE_2D, texID);
            }
            else
            {
                glBindTexture(GL_TEXTURE_2D, this->texID);
            }

            // VAO
            glBindVertexArray(vao);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
            glBindVertexArray(0);
        }
        glUseProgram(0);
    }
}

fbo.h

FBOを扱うクラスを記述したヘッダファイル.

//
// fbo.h
//
#pragma once

#include <iostream>
using namespace std;
#include <GL/glew.h>
#include <GLFW/glfw3.h>

namespace gl
{
    class fbo
    {
    private:
        const int width;
        const int height;
        GLuint texColorID;
        GLuint texDepthID;
        GLuint fboID;

    public:
        fbo(
            int w,
            int h,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        ~fbo();

        int getWidth();
        int getHeight();
        GLuint getColorTexID();
        GLuint getDepthTexID();
        GLuint getFBOID();
    };
}

fbo.cpp

FBOを扱うクラスを記述したソースファイル.

//
// fbo.cpp
//
#include "fbo.h"

namespace gl
{
    fbo::fbo(
        int w,
        int h,
        GLint internalFormat,
        GLenum format,
        GLenum type
    ) : width(w), height(h)
    {
        // ----- Create a texture
        // Color buffer
        glGenTextures(1, &texColorID);
        glBindTexture(GL_TEXTURE_2D, texColorID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
        // Z buffer
        glGenTextures(1, &texDepthID);
        glBindTexture(GL_TEXTURE_2D, texDepthID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
        glBindTexture(GL_TEXTURE_2D, 0);

        // ----- Create a framebuffer object (FBO)
        glGenFramebuffers(1, &fboID);
        glBindFramebuffer(GL_FRAMEBUFFER, fboID);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorID, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texDepthID, 0);

        // ----- Get back to backbuffer
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    fbo::~fbo()
    {
        glDeleteTextures(1, &texColorID);
        glDeleteTextures(1, &texDepthID);
        glDeleteFramebuffers(1, &fboID);
    }

    int fbo::getWidth()
    {
        return width;
    }
    int fbo::getHeight()
    {
        return height;
    }
    GLuint fbo::getColorTexID()
    {
        return texColorID;
    }
    GLuint fbo::getDepthTexID()
    {
        return texDepthID;
    }
    GLuint fbo::getFBOID()
    {
        return fboID;
    }
}

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"
#include "OpenGLWrapper/fbo.h"

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    gl::fbo *myFBO;

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName
    ): window(w, h, name)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
        myFBO = new gl::fbo(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myFBO) delete myFBO;
    }

    void path1()
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO->getFBOID());
        {
            // Clear color and depth buffers
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glm::vec3 eye(cos(glfwGetTime()), sin(glfwGetTime()), 1.5f);
            glm::vec3 center(0.0f);
            glm::vec3 up(0.0f, 1.0f, 0.0f);
            glm::mat4 V = glm::lookAt(eye, center, up);
            glm::mat4 P = glm::perspective(3.14f / 4.0f, (float)width / height, 0.1f, 100.0f);
            myTex->setViewMat(V);
            myTex->setProjMat(P);
            myTex->render();
        }
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
    void path2()
    {
        // Clear color and depth buffers
        glClearColor(0.8f, 0.5f, 0.3f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glm::vec3 eye(-1.0f, 1.0f, 2.5f);
        glm::vec3 center(0.0f);
        glm::vec3 up(0.0f, 1.0f, 0.0f);
        glm::mat4 V = glm::lookAt(eye, center, up);
        myTex->setViewMat(V);
        myTex->render(myFBO->getColorTexID());
    }
    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            path1();
            path2();

            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        640, 480, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag"
    );
    wnd.render();

    return 0;
}