以前フレームバッファをやってみたときに、glFramebufferTexture2D関数を使ったが、第二引数に与えたGL_COLOR_ATTACHMENT0について言及しなかった(気がする)。当時は調べて納得はしていたが改めて読み返して混乱したのでここで明確にしておきたい。
glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); /* ... */ glGenTextures(1, texture0); glGenTextures(1, texture1); /* ... */ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture0, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, texture1, 0);
このように指定し、フラグメントシェーダ側で以下のように出力する。
#version 460 core layout (location = 0) out vec4 DefaultOut;// 0、つまりGL_COLOR_ATTACHMENT0 layout (location = 1) out vec4 RedOnly; // 1、つまりGL_COLOR_ATTACHMENT1 in vec4 vertexColor; void main() { DefaultOut = vertexColor; RedOnly = vec4(vertexColor[0],0,0,1); }
つまり、GL_COLOR_ATTACHMENT0はフラグメントシェーダのlocationを指定している。
#include <memory> #include <iostream> #include <vector> #include <algorithm> #include <array> #include<windows.h> #include <gl/glew.h> #include "CglfwBase.hpp" #pragma warning(disable:4996) #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"glew32.lib")
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] vmax 全てのRGBの中の最大値 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void pnmP3_Write(const TCHAR* const fname, const int vmax, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = _wfopen(fname, L"wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2]); k++; } fprintf(fp, "\n"); } fclose(fp); }
//! @brief フレームバッファに関する変数と機能をまとめる class FramebufferTest { public: GLuint ID_texture_0; // テクスチャ1 GLuint ID_texture_1; // テクスチャ2 GLuint ID_fbo;// フレームバッファ int width, height;//テクスチャのサイズ private: GLuint ID_depth;
//! @brief テクスチャを一枚作成し、フレームバッファに関連付ける void generate_texture(GLuint *texname, GLenum attachment) { glGenTextures(1, texname); glBindTexture(GL_TEXTURE_2D, *texname); //テクスチャを転送する。 //ただし最後がnullptrなので実質転送しない glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); //テクスチャのフィルタリングの設定 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //描画先の設定 // attachmentへの書き込みで、texnameに書き込まれるようにする glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, *texname, 0); }
public:
// フレームバッファオブジェクトの作成と設定を行う // 途中で描画先のテクスチャも作成する void prepare(int WIDTH,int HEIGHT) { width = WIDTH; height = HEIGHT; ////////////////////////////////////////////////////// //フレームバッファの作成 // 削除はglDeleteFrameBuffersを使用する glGenFramebuffers(1, &ID_fbo); glBindFramebuffer(GL_FRAMEBUFFER, ID_fbo);
////////////////////////////////////////////////////// //テクスチャバッファの作成 // 削除はglDeleteTextures generate_texture(&ID_texture_0, GL_COLOR_ATTACHMENT0); generate_texture(&ID_texture_1, GL_COLOR_ATTACHMENT1); // fragment shaderの // layout (location = 0) ・・・ GL_COLOR_ATTACHMENT0 // layout (location = 1) ・・・ GL_COLOR_ATTACHMENT1 // が書き込み先であることを教える(?) GLenum DrawBuffers[2] = { GL_COLOR_ATTACHMENT0 , GL_COLOR_ATTACHMENT1 }; glDrawBuffers(2, DrawBuffers); // "2"はDrawBuffersのサイズです。
////////////////////////////////////////////////////// // デプスバッファの作成 // レンダーバッファの一種なので、削除はglDeleteRenderbuffersを使用する glGenRenderbuffers(1, &ID_depth); glBindRenderbuffer(GL_RENDERBUFFER, ID_depth); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ID_depth); /////////////////////////////////////////////////// // フレームバッファのエラーチェック。 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) return;/*エラーデータ*/ }
}; class MyWindow :public MyWindowBase { int width; int height; FramebufferTest myfb; GLuint vertexbufferName; GLuint colorbufferName; public:
//! @brief glew,glfwの設定 フレームバッファの初期化、シェーダの作成、データの作成 virtual void setting()override { glewInit(); glfwGetFramebufferSize(_window, &width, &height); //フレームバッファの初期化 myfb.prepare(width, height); //シェーダに渡す描画データの作成 prepare_data(); //シェーダの作成 prepare_shaders(); }
//! @brief キーイベント。フレームバッファによって書き込んだテクスチャデータをファイルへ出力 virtual void key(int key, int scancode, int action, int mods)override { //テクスチャ(ID_texture_output)に書き込んだデータを取り出す領域を用意 std::vector<GLubyte> texdata; texdata.resize(myfb.width*myfb.height * 3); //////////////////////////////////////////////////// //テクスチャのデータを取り出す glBindTexture(GL_TEXTURE_2D, myfb.ID_texture_0); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, &texdata[0]); // 取り出したテクスチャをファイルに書き出す pnmP3_Write(LR"(C:\test\texture0.ppm)", 255, width, height, texdata.data()); //////////////////////////////////////////////////// //テクスチャのデータを取り出す glBindTexture(GL_TEXTURE_2D, myfb.ID_texture_1); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, &texdata[0]); // 取り出したテクスチャをファイルに書き出す pnmP3_Write(LR"(C:\test\texture1.ppm)", 255, width, height, texdata.data()); }
//! @brief 描画関数 virtual void draw()override { //以後の描画はフレームバッファに対して行われる glBindFramebuffer(GL_FRAMEBUFFER, myfb.ID_fbo); draw_core(); //以後の描画は画面に対して行われる glBindFramebuffer(GL_FRAMEBUFFER, 0); draw_core(); glfwSwapBuffers(_window); }
//! @brief 描画関数本体
void draw_core() { ///////////////////// // 描画 glViewport(0, 0, width, height); glClearColor(0.3, 0.3, 0.3, 1); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glUseProgram(ProgramID); //gl_PointSizeを有効にする glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); // 頂点バッファ glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vertexbufferName); glVertexAttribPointer( 0, // 属性0 3, // 1要素の要素数 x y z GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); // カラーバッファ glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, colorbufferName); glVertexAttribPointer( 1, // 属性1 4, // 1要素の要素数 r g b a GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); // 5頂点を描く glDrawArrays(GL_POINTS, 0, 5); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); // ///////////////////// glFlush(); } GLuint VertexShaderID; GLuint FragmentShaderID; GLuint ProgramID;
//! @brief シェーダの作成
//! シェーダコードを直接埋め込んで、compileshaderを呼び出している
void prepare_shaders() { const char* vshader = R"(
#version 460 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 incolor; out vec4 vertexColor; uniform mat4 gl_ModelViewMatrix; uniform mat4 gl_ProjectionMatrix; void main() { gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * vec4(aPos, 1.0); vertexColor = vec4(incolor, 1.0); gl_PointSize=30.0; // 頂点のサイズ(ピクセル) }
)"; VertexShaderID = compileshader(GL_VERTEX_SHADER, vshader); const char* fshader = R"(
#version 460 core layout (location = 0) out vec4 DefaultOut;// 0、つまりGL_COLOR_ATTACHMENT0 layout (location = 1) out vec4 RedOnly; // 1、つまりGL_COLOR_ATTACHMENT1 //out vec4 FragColor; in vec4 vertexColor; void main() { DefaultOut = vertexColor; RedOnly = vec4(vertexColor[0],0,0,1); }
)"; FragmentShaderID = compileshader(GL_FRAGMENT_SHADER, fshader); GLint Result = GL_FALSE; int InfoLogLength; //////////////////////////////////////// // プログラムをリンクします。 fprintf(stdout, "Linking program\n"); ProgramID = glCreateProgram(); glAttachShader(ProgramID, VertexShaderID); glAttachShader(ProgramID, FragmentShaderID); glLinkProgram(ProgramID); //////////////////////////////////////// // プログラムをチェックします。 glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); std::vector<char> ProgramErrorMessage((std::max)(InfoLogLength, int(1))); glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); fprintf(stdout, "%s\n", &ProgramErrorMessage[0]); }
//! @brief シェーダに渡す描画データの作成 void prepare_data() { using point_t = std::array<GLfloat, 3>; //モデルの座標用 x y z using color_t = std::array<GLfloat, 4>;//テクスチャ座標用 r g b a ///////////////////////////////// //バッファとデータ point_t vertex[5]; color_t color[5]; const int elemcount = 5; ////////////////////////////////////////// vertex[0] = { -0.7, 0.7, 0 }; vertex[1] = { -0.7,-0.7, 0 }; vertex[2] = { 0.7,-0.7, 0 }; vertex[3] = { 0.7, 0.7, 0 }; vertex[4] = { 0 , 0 , 0 }; color[0] = { 1.0,0.0,0.0,1.0 }; color[1] = { 0.0,1.0,0.0,1.0 }; color[2] = { 0.0,0.0,1.0,1.0 }; color[3] = { 1.0,0.0,1.0,1.0 }; color[4] = { 1.0,1.0,1.0,1.0 }; ////////////////////////////////////////// // バッファの転送 glGenBuffers(1, &vertexbufferName); glBindBuffer(GL_ARRAY_BUFFER, vertexbufferName); glBufferData(GL_ARRAY_BUFFER, 3 * elemcount * sizeof(GLfloat), vertex, GL_STATIC_DRAW); glGenBuffers(1, &colorbufferName); glBindBuffer(GL_ARRAY_BUFFER, colorbufferName); glBufferData(GL_ARRAY_BUFFER, 4 * elemcount * sizeof(GLfloat), color, GL_STATIC_DRAW); }
//! @brief シェーダをコンパイル・リンク GLuint compileshader(GLenum type,const char* VertexShaderCode) { GLuint shaderID; ////////////////////////////////////////////// // シェーダを作ります // (作るといっても宣言みたいなもの) shaderID = glCreateShader(type); //////////////////////////////////////////// // 頂点シェーダをコンパイルします。 printf("Compiling shader... \n"); glShaderSource(shaderID, 1, &VertexShaderCode, NULL); glCompileShader(shaderID); //////////////////////////////////////////// // エラーチェック GLint Result = GL_FALSE; int InfoLogLength; // 頂点シェーダをチェックします。 glGetShaderiv(shaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if (Result == FALSE) { std::vector<char> VertexShaderErrorMessage(InfoLogLength); glGetShaderInfoLog(shaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]); } return shaderID; }
virtual void framebuffer_size(int _width, int _height)override { width = _width; height = _height; } }; int main() { MyWindow win; win.init(600,600,"glfw window"); win.setting(); win.loop(); }
画面出力ができたら、キーを何か押すとc:\test\texture0.ppmとtexture1.ppmを出力する。
画像が逆転しているのはOpenGLは左下原点なので仕方がない。