GLSL #6: Frame Buffer Object (FBO)
目標
前回(下記参照)は,テクスチャマッピングを行うプログラムを作成しました. 今回は,そのプログラムをベースに,FBOに描画した結果を四角形ポリゴンにテクスチャとして利用する方法を実装します.
実装環境
- Windows 10 64bit
- Visual Studio 2015
- GLEW 3.2.1
- 設定方法はこちらを参照
- GLEW 1.13.0
- 設定方法はこちらを参照
- GLM
- 設定方法はこちらを参照
- DevIL
- 設定方法はこちらを参照
レンダリングの流れと結果
今回は分かりやすさのために,レンダリングの流れと結果を先に示す.
- パス1:FBO(テクスチャ)にテクスチャ付き3Dポリゴンを描画
- この際,カメラを3Dポリゴン (modelTex) の中心を注視して,円を描くように動かす
- つまり,以下の動画を毎フレームFBOに書き出す youtu.be
- パス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[20]; 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[20] = { // 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; }