目次

imguiを使ってみる(1)
imguiを使ってみる(2)
imguiを使ってみる(3)


参考1:チュートリアル2:最初の三角形 - opengl-tutorial

参考2:チュートリアル14:テクスチャへのレンダー - opengl-tutorial


自分のシーンのレンダリング

とりあえず前回やったimgui-testをディレクトリごとコピーする。前回つくった実行ファイルは消してしまう。(残っていても問題はないが)

$ cp -r imgui-test imgui-triangle
$ cd imgui-triangle
$ rm imgui-test

ここからmain.cppをいじって自分のシーンをレンダリングするところまでやってみる。よくありがちだが三角形を表示してみたい。

以下、編集対象はmain.cppである。

main.cppの上のほうにある#include "imgui_demo.cpp"は今回は使わないのでコメントアウトするか消してしまう。

ここではコメントアウトすることにしよう。

// #include "imgui_demo.cpp"

imguiでのレンダリングは2通りあるようで、1つはフレームバッファに描画してそれをimguiにテクスチャ画像として描画してもらう。もう1つはimgui_impl_glfw_gl3.cppにある関数ImGui_ImplGlfwGL3_RenderDrawListsに相当するものを実装してimguiのRenderDrawListsFnに登録するという方法。

今回はフレームバッファに描画したものをテクスチャとしてimguiに渡す方法でやってみることにする。

シーンに必要な変数をまとめてクラスにしておく。一応クラスにしたが剥き出しのグローバル変数でも問題はない。単にまとまっていたほうが良いでしょうということです。Cでも構造体でまとめておくのが無難だと思う。

class MyScene
{
public:
    // width,height
    GLint width,height;
    
    // vao/vbo
    GLuint vao;
    GLuint vbo[2]; // vertex and color

    // shader
    GLuint program;
    GLuint mvp;
    
    // texture
    GLuint framebuffer;
    GLuint depthbuffer;
    GLuint texture;
    GLenum drawbuffer;

    MyScene() {};
    MyScene(GLint width, GLint height);
    void render();
};

MyScene myScene;

以下は順にコピペしていけば良いようになっている。

頂点と色の配列:

GLfloat triangleVertex[] = {
    -1.0f,-1.0f,
    1.0f,-1.0f,
    1.0f,1.0f
};

GLfloat triangleColor[] = {
    1.0f,0.0f,0.0f,
    0.0f,1.0f,0.0f,
    0.0f,0.0f,1.0f
};

シェーダー:

フラグメントシェーダーのほうにlocationを設定していることに注意。参考2を参照。

const char *mySceneVsh =
    "#version 330 core\n"
    "layout (location = 0) in vec2 inVertex;\n"
    "layout (location = 1) in vec3 inColor;\n"
    "uniform mat4 mvp;\n"
    "out vec3 color;\n"
    "void main()\n"
    "{\n"
    "    gl_Position = mvp * vec4(inVertex,0.0,1.0);\n"
    "    color = inColor;\n"
    "}";

const char *mySceneFsh =
    "#version 330 core\n"
    "in vec3 color;\n"
    "layout(location = 0) out vec3 outColor;\n"
    "void main()\n"
    "{\n"
    "    outColor = color;\n"
    "}";

次のシェーダーをコンパイルする関数は参考1のサイトにあるLoadShadersを少し修正したもの。

GLuint loadShaders(const char *vertexCode, const char *fragmentCode)
{
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
    
    GLint Result = GL_FALSE;
    GLint InfoLogLength;
    
    printf("Compiling vertex shader ... ");
    char const *VertexSourcePointer = vertexCode;
    glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
    glCompileShader(VertexShaderID);

    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);

    char VertexShaderErrorMessage[InfoLogLength];
    glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, VertexShaderErrorMessage);
    if (Result == GL_FALSE) {
        printf("%s\n", VertexShaderErrorMessage);
    } else {
        printf("ok.\n");
    }
    
    printf("Compiling fragment shader ... ");
    char const *FragmentSourcePointer = fragmentCode;
    glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
    glCompileShader(FragmentShaderID);

    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);

    char FragmentShaderErrorMessage[InfoLogLength];
    glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, FragmentShaderErrorMessage);
    if (Result == GL_FALSE) {
        printf("%s\n", FragmentShaderErrorMessage);
    } else {
        printf("ok.\n");
    }

    printf("Linking program ... ");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);

    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);

    char ProgramErrorMessage[(InfoLogLength>1)? InfoLogLength : 1];
    glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, ProgramErrorMessage);
    if (Result == GL_FALSE) {
        printf("%s\n", ProgramErrorMessage);
    } else
        printf("ok.\n");

    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);

    return ProgramID;
}

mySceneを初期化する。VAO/VBOに頂点や色を登録、シェーダをコンパイル、最後にフレームバッファとテクスチャを結びつける。こうすることでフレームバッファに描画したシーンをテクスチャとして扱えるようになる。参考2を参照。

ちょっと長いけれど一度書いてしまえば使いまわせるので我慢する。

それと今回は2次元のレンダリングなので以下のdepthbufferは実際には使っていない。3次元のレンダリングをする場合には必要になるので一応入れてある。

MyScene::MyScene(GLint width, GLint height)
{
    // set width, height
    this->width = width;
    this->height = height;
    
    // vao/vbo
    glGenVertexArrays(1, &this->vao);
    glBindVertexArray(this->vao);

    glGenBuffers(2, this->vbo);
    glBindBuffer(GL_ARRAY_BUFFER, this->vbo[0]);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertex), triangleVertex, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindBuffer(GL_ARRAY_BUFFER, this->vbo[1]);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glBufferData(GL_ARRAY_BUFFER, sizeof(triangleColor), triangleColor, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
   
    // shader
    this->program = loadShaders(mySceneVsh, mySceneFsh);
    this->mvp = glGetUniformLocation(this->program, "mvp");
    
    // texture 
    glGenFramebuffers(1, &this->framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, this->framebuffer);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glGenTextures(1, &this->texture);
    glBindTexture(GL_TEXTURE_2D, this->texture);

    glTexImage2D(GL_TEXTURE_2D,    // target
                 0,                // mipmap level 
                 GL_RGB,           // internal format
                 width,            // scene width
                 height,           // scene height
                 0,                // border
                 GL_RGB,           // format
                 GL_UNSIGNED_BYTE, // type 
                 (void*)0);        // image buffer

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    // depthbuffer
    glGenRenderbuffers(1, &this->depthbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, this->depthbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, this->depthbuffer);

    // 出力先のバッファを GL_COLOR_ATTACHMENT0 にする
    this->drawbuffer = GL_COLOR_ATTACHMENT0;

    // 初期化終わり
    glFramebufferTexture(GL_FRAMEBUFFER, this->drawbuffer, this->texture, 0);
    glBindFramebuffer(GL_FRAMEBUFFER,0);
}

mySceneをフレームバッファに描画する関数:

ここでは行列をハードコーディングしてしまったが実際にはglmなどの行列のライブラリを使うことになると思う。

レンダリングの関数は普通に書く場合とほとんど違いはない。

void MyScene::render()
{
    glBindFramebuffer(GL_FRAMEBUFFER, this->framebuffer);
    glDrawBuffers(1, &this->drawbuffer);

    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glViewport(0,0, this->width, this->height);

    // glOrtho(-1.0,1.0, -1.0,1.0, -1.0,1.0) と同じ行列
    const GLfloat mvp[] = {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};
    
    glUseProgram(this->program);
    glUniformMatrix4fv(this->mvp, 1, GL_FALSE, mvp);

    glBindVertexArray(this->vao);

    glBindBuffer(GL_ARRAY_BUFFER, this->vbo[0]);
    glBindBuffer(GL_ARRAY_BUFFER, this->vbo[1]);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glBindVertexArray(0);
    glUseProgram(0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

main関数の直前にはerror_callbackという関数が残っているはず。これは消さずに残しておく。

もとのmain関数は消して以下のものをコピペする。

int main(int, char**)
{
    // Setup window
    glfwSetErrorCallback(error_callback);
    if (!glfwInit())
        return 1;
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#if __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
    GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Triangle", NULL, NULL);
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1); // Enable vsync
    gl3wInit();

    // Setup ImGui binding
    ImGui_ImplGlfwGL3_Init(window, true);

    ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

    myScene = MyScene(500,500);
    
    // Main loop
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        ImGui_ImplGlfwGL3_NewFrame();

        // mySceneをフレームバッファに描画してimguiにテクスチャ画像として渡す
        { 
            const int W_MARGIN = 16, H_MARGIN = 36;
            ImGui::SetNextWindowSize(ImVec2(myScene.width+W_MARGIN, myScene.height+H_MARGIN));

            // 右下のリサイズ用のグリップが邪魔なのでリサイズ不可にした窓を作る
            ImGui::Begin("triangle", NULL, ImGuiWindowFlags_NoResize);
            {
                ImVec2 pos = ImGui::GetCursorScreenPos();
                myScene.render();
                ImGui::GetWindowDrawList()->AddImage((void*)myScene.texture,
                                                     ImVec2(pos.x,pos.y),
                                                     ImVec2(pos.x+myScene.width, pos.y+myScene.height),
                                                     ImVec2(0,1),
                                                     ImVec2(1,0));
            }
            ImGui::End();
        }

        // Rendering
        int display_w, display_h;
        glfwGetFramebufferSize(window, &display_w, &display_h);
        glViewport(0, 0, display_w, display_h);
        glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui::Render();
        glfwSwapBuffers(window);
    }

    // Cleanup
    ImGui_ImplGlfwGL3_Shutdown();
    glfwTerminate();

    return 0;
}

これをコンパイル:

$ g++ main.cpp -o imgui-triangle `pkg-config --static --libs glfw3`

実行:

$ ./imgui-triangle

これで三角形が表示されていれば成功。

imgui triangle

まとめ

コードをペタペタ貼ったので長くなってしまった。

次回はこれにスライダーを付けて三角形の色を変更できるようにする予定。