おもちゃラボ

Unityで遊びを作ってます

【OpenGL】VBOを使って描画を高速化しよう

前回はOpenGLからシェーダに頂点データを渡して三角形を表示しました。

nn-hokuson.hatenablog.com

今回の記事は、この頂点データの受け渡しを効率的に行うためのVBOという仕組みの説明をします。

VBOとは

前回はポリゴンを画面上に表示するために、OpenGLのプログラムで定義した頂点座標のデータをバーテックスシェーダに配列として渡していました。シェーダに頂点データを渡す場合、配列よりもVBOと呼ばれるものを使ったほうが効率が良くなります。VBOはVertex Berffer Objectの頭文字をとったもので、OpenGLからシェーダに渡す値を詰める専用オブジェクトのようなものです。

VBOを利用する流れは次のようになります。

  1. VBOの生成
  2. VBOに頂点データを格納する
  3. VBOと頂点シェーダのattribute変数を紐付ける

これだけでは、どのようにしてVBOを使えばよいのかイメージしにくいと思うので、早速プログラムを見ていきましょう。

VBOを使うプログラムを書く

次のプログラムは画面上に3角形を描画するプログラムです。必要な部分の解説はコメントに書いているので、あわせて読んでみてください。

#include <GLFW/glfw3.h>
#include <iostream>
#include <fstream>

using namespace std;

const int g_width  = 640;
const int g_height = 480;

GLuint crateShader()
{
    //バーテックスシェーダのコンパイル
    GLuint vShaderId = glCreateShader(GL_VERTEX_SHADER);
    string vertexShader = R"#(
                            attribute vec3 position;
                            void main(void){
                                gl_Position = vec4(position, 1.0);
                            }
                            )#";
    const char* vs = vertexShader.c_str();
    glShaderSource(vShaderId, 1, &vs, NULL);
    glCompileShader(vShaderId);
    
    //フラグメントシェーダのコンパイル
    GLuint fShaderId = glCreateShader(GL_FRAGMENT_SHADER);
    string fragmentShader = R"#(
                            void main(void){
                                gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
                            }
                            )#";
    const char* fs = fragmentShader.c_str();
    glShaderSource(fShaderId, 1, &fs, NULL);
    glCompileShader(fShaderId);
    
    //プログラムオブジェクトの作成
    GLuint programId = glCreateProgram();
    glAttachShader(programId,vShaderId);
    glAttachShader(programId,fShaderId);
    
    // リンク
    glLinkProgram(programId);
   
    glUseProgram(programId);
    
    return programId;
}

int main()
{
    if( !glfwInit() ){
        return -1;
    }
    
    GLFWwindow* window = glfwCreateWindow(g_width, g_height, "Simple", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    
    // モニタとの同期
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);
    
    GLuint programId = crateShader();
    
    // ゲームループ
    while (!glfwWindowShouldClose(window)) {
        
        // 画面の初期化
        glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearDepth(1.0);
       
        // 頂点データ
        float vertex_position[] = {
           0.0, 0.5,
           0.4, -0.25,
           -0.4,-0.25, 
        };
        
        // VBOの生成
        GLuint vbo;
        glGenBuffers(1, &vbo);
        
        // バッファをバインドする
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        
        // バッファにデータをセット
        glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), vertex_position, GL_STATIC_DRAW);
        
        // 何番目のattribute変数か
        int attLocation = glGetAttribLocation(programId, "position");
        
        // attribute属性を有効にする
        glEnableVertexAttribArray(attLocation);
        
        // attribute属性を登録
        glVertexAttribPointer(attLocation, 2, GL_FLOAT, false, 0, NULL);
        
        // モデルの描画
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // バッファの入れ替え
        glfwSwapBuffers(window);
        
        // Poll for and process events
        glfwPollEvents();
    }
    
    glfwTerminate();
    
    return 0;
}


上のプログラムを実行すると前回と同様、画面上に三角形が描画されます。

f:id:nn_hokuson:20170203234041p:plain

VBOの作成

頂点座標の定義に続けてVBOを作成します。VBOの生成はクラスをnewするイメージに近いですね。

// VBOの生成
GLuint vbo;
glGenBuffers(1, &vbo);

glGenBuffersはVBOを作成するための関数です。第一引数には作成するvboの個数、第二引数にはvboのインデックスを渡します。ここでは作成するVBOは1つだけなので、第二引数にはポインタを渡しています。

VBOに頂点データを格納する

続いて、VBOに頂点データを格納します。VBOに頂点データを格納する前に、まずは今作成したVBOをバインドする必要があります。バインドとは、頂点データを格納するVBOを指定する作業です。

// バッファをバインドする
glBindBuffer(GL_ARRAY_BUFFER, vbo);

// バッファにデータをセット
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), vertex_position, GL_STATIC_DRAW);

glBindBufferを使って、作成したVBOをOpenGLにバインドしてから、glBufferDataで作成したVBOに頂点情報を書き込んでいます。第二引数に全頂点情報のサイズ、第三引数には頂点情報へのポインタを渡しています。

VBOを使って三角形を描画する

これで、ようやくVBO経由で受け渡し可能な状態になったので、glVertexAttribPointer関数を使って、頂点情報をシェーダに渡しています。今回はVBOを使っているため、 最後の引数にはNULLを渡しています。

// attribute属性を登録
glVertexAttribPointer(attLocation, 2, GL_FLOAT, false, 0, NULL);

まとめ

今回はVBOを使ってポリゴンの表示を行いました。前回のプログラムと比べると、少々複雑になってしまいましたね。でも、やっていることは「OpenGLで定義した頂点データをVBO経由でシェーダに渡して描画している」だけです。