裏でこっそりフレームバッファ ― 2013/11/06
今までのサンプルではテクスチャなどを画面に直接描画していましたが、これを画面ではなくフレームバッファと呼ばれる自分で作成したバッファに描画することができます(画面に描画するのも、デフォルトのフレームバッファに描画するということらしいですが)。
そんなことをして何が嬉しいのかと言いますと…
昨日のサンプルでは描画しているテクスチャは一枚だけでした。これがテクスチャなどの枚数が増えてくると、描画にいくらかの時間がかかるようになります。そうすると描画途中の絵が画面に出ることになり、絵がちらついたりします。
フレームバッファを使用する場合、絵は一旦フレームバッファに描画して、全て描画した後に画面に表示します。描画途中の絵が画面に出ることはないのでちらつきはありません。オフスクリーンレンダリングと呼ばれる方法です。
フレームバッファクラス sys::FrameBufferを見てみます。
FrameBuffer.h
FrameBuffer.cpp
テクスチャとしてカラーバッファを持っていますので、sys::Textureの派生クラスになっています。普通はデプスバッファやステンシルバッファといったレンダーバッファを持っているものなんですが、ここではカラーバッファしか使いません。
作成(make)時には幅と高さを指定しています。今回のように画面全体のフレームバッファとして使う場合はサイズは仮想画面と同じになりますが、描画(されること)のできるテクスチャとして他にも使いようがありますのでサイズは指定できるようになっています。
描画時の座標をサイズに合わせるために、座標変換の行列はフレームバッファで持っておきます。
描画クラス sys::Rendererの変更部分です。
Renderer.cpp
まず初期化部分では、仮想画面サイズでフレームバッファを作成しています。
ビューポートの設定はフレームバッファの切り替えごとに行うので、ここでは値の計算だけしておきます。
今までは Rendererの処理はメインの処理前にだけ行っていましたが、最後にフレームバッファを画面に描画しなければならないので後にも処理を入れています。
プロジェクト一式は、こちらです。
そんなことをして何が嬉しいのかと言いますと…
昨日のサンプルでは描画しているテクスチャは一枚だけでした。これがテクスチャなどの枚数が増えてくると、描画にいくらかの時間がかかるようになります。そうすると描画途中の絵が画面に出ることになり、絵がちらついたりします。
フレームバッファを使用する場合、絵は一旦フレームバッファに描画して、全て描画した後に画面に表示します。描画途中の絵が画面に出ることはないのでちらつきはありません。オフスクリーンレンダリングと呼ばれる方法です。
フレームバッファクラス sys::FrameBufferを見てみます。
FrameBuffer.h
#ifndef ___FRAME_BUFFER_H___ #define ___FRAME_BUFFER_H___ #include "common.h" #include "Texture.h" namespace sys { /******************************** フレームバッファテクスチャ ********************************/ class FrameBuffer : public Texture { GLuint frame_buffer; // フレームバッファオブジェクト GLfloat mat_projection[4*4]; // 透視変換行列 public : FrameBuffer(void) // コンストラクタ { frame_buffer = 0; } FrameBuffer(int _w, int _h) { make(_w, _h); } ~FrameBuffer() // デストラクタ { clear(); } void make(int, int); // 作成 void clear(void); // 削除 void bind(void); // 使用 }; } #endif /********************* End of File ********************************/
FrameBuffer.cpp
/********************************************* フレームバッファテクスチャ *********************************************/ #include "FrameBuffer.h" #include "Renderer.h" namespace sys { /******************************** 作成 引数 _width = 幅 _height = 高さ ********************************/ void FrameBuffer::make(int _width, int _height) { glGenFramebuffers(1, &frame_buffer); // フレームバッファオブジェクト作成 glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); format = FORMAT_RGBA; // テクスチャフォーマット width = _width; // 幅 height = _height; // 高さ Texture::make(NULL); // テクスチャ作成 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); // フレームバッファにアタッチ glBindFramebuffer(GL_FRAMEBUFFER, 0); for (int i = 0; i < 4*4; i++) { // 透視変換行列初期化 mat_projection[i] = 0.0f; } mat_projection[0] = 2.0f/width; mat_projection[5] = -2.0f/height; mat_projection[10] = 1.0f; mat_projection[15] = 1.0f; } /********** 削除 **********/ void FrameBuffer::clear(void) { if ( frame_buffer ) { glDeleteFramebuffers(1, &frame_buffer); frame_buffer = 0; Texture::clear(); } } /********** 使用 **********/ void FrameBuffer::bind(void) { glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); glViewport(0, 0, width, height); // ビューポート設定 Renderer::mat_projection = mat_projection; // 透視変換行列設定 if ( Renderer::current_shader ) { glUniformMatrix4fv(Renderer::current_shader->projection, 1, GL_FALSE, mat_projection); } } } /**************** End of File ******************************************/
テクスチャとしてカラーバッファを持っていますので、sys::Textureの派生クラスになっています。普通はデプスバッファやステンシルバッファといったレンダーバッファを持っているものなんですが、ここではカラーバッファしか使いません。
作成(make)時には幅と高さを指定しています。今回のように画面全体のフレームバッファとして使う場合はサイズは仮想画面と同じになりますが、描画(されること)のできるテクスチャとして他にも使いようがありますのでサイズは指定できるようになっています。
描画時の座標をサイズに合わせるために、座標変換の行列はフレームバッファで持っておきます。
描画クラス sys::Rendererの変更部分です。
Renderer.cpp
/************************************************ 初期化 引数 width, height = 端末画面サイズ ************************************************/ void Renderer::init(int width, int height) { initShader(); // シェーダ初期化 frame_buffer = new FrameBuffer(SCREEN_WIDTH, SCREEN_HEIGHT); // フレームバッファ作成 if ( width*SCREEN_HEIGHT < height*SCREEN_WIDTH ) { // 横長(上下カット) screen_rect.w = width; screen_rect.h = width*SCREEN_HEIGHT/SCREEN_WIDTH; } else { // 縦長(左右カット) screen_rect.w = height*SCREEN_WIDTH/SCREEN_HEIGHT; screen_rect.h = height; } screen_rect.x = (width - screen_rect.w)/2; screen_rect.y = (height - screen_rect.h)/2; }
まず初期化部分では、仮想画面サイズでフレームバッファを作成しています。
ビューポートの設定はフレームバッファの切り替えごとに行うので、ここでは値の計算だけしておきます。
/******************** 稼働(前処理) ********************/ void Renderer::update(void) { glEnable(GL_BLEND); // αブレンド初期化 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); frame_buffer->bind(); // フレームバッファ使用 } /******************** 描画(後処理) ********************/ void Renderer::draw(void) { static const GLfloat _projection[4*4] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static const GLfloat _texcoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }; static const GLfloat _vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; static const GLubyte _colors[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; ShaderProgram* _shader = use_shader(SHADER_TEXTURE); // フレームバッファテクスチャ描画 glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(screen_rect.x, screen_rect.y, screen_rect.w, screen_rect.h); glUniformMatrix4fv(_shader->projection, 1, GL_FALSE, _projection); glBindTexture(GL_TEXTURE_2D, frame_buffer->texture); glVertexAttribPointer(_shader->texcoord, 2, GL_FLOAT, GL_FALSE, 0, _texcoords); glVertexAttribPointer(_shader->position, 2, GL_FLOAT, GL_FALSE, 0, _vertices); glVertexAttribPointer(_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, _colors); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }
今までは Rendererの処理はメインの処理前にだけ行っていましたが、最後にフレームバッファを画面に描画しなければならないので後にも処理を入れています。
プロジェクト一式は、こちらです。
最近のコメント