WindowsでOpenGLES --- 三角形描画

下記記事にてGLFWで、OpenGLESが初期化されたウィンドウを作成しました。
WindowsでOpenGLES --- GLFW導入 - 何でもプログラミング

今回はとりあえず三角形を描画してみたいと思います。

shared_ptrでリソースの管理

OpenGLではCreateとDeleteをきちんと管理する必要があります。(glCreateProgram() → glDeleteProgram(GLuint)など)

手動で管理するのは面倒なので、shared_ptrに解放を任せてみます。

shared_ptrにはカスタムデリータが設定できるので、そこで解放を行います。

OpenGLにはglCreateProgramのような単体リソース作成と、glGenTexturesのような複数リソース作成の2パターンがあるので、2つ定義してみました。

今回は暗黙キャストが利用したかったのでクラスを定義しましたが、不必要であればshared_ptrそのもので良いと思います。

class GLResource
{
    std::shared_ptr<GLuint> _resource;
public:
    GLResource() {}
    // glCreateProgram, glDeleteProgramなど用
    GLResource(GLuint resource, std::function<void(GLuint)> glDelete)
    {
        if (resource != 0)
            _resource.reset(new GLuint(resource), [=](GLuint* x) { glDelete(*x); delete x; });
    }
    // glGenTextures、glDeleteTextursなど用
    GLResource(std::function<void(GLsizei, GLuint*)> glGen, std::function<void(GLsizei, GLuint*)> glDelete)
    {
        GLuint resource = 0;
        glGen(1, &resource);
        if (resource != 0)
            _resource.reset(new GLuint(resource), [=](GLuint* x) { glDelete(1, x); delete x; });
    }
    operator bool()   { return (bool)_resource; }
    operator GLuint() { return *_resource; }
};


Program(Shader)作成

シェーダコードをコンパイルしてプログラムを作成する関数を定義します。

CompileShader、PrintProgramLogは後述いたします。

GLResource CreateProgram(std::string vertexCode, std::string fragmentCode)
{
    // Vertexシェーダコンパイル
    GLResource vertexShader(glCreateShader(GL_VERTEX_SHADER), glDeleteShader);
    assert(vertexShader);
    CompileShader(vertexShader, vertexCode.c_str());

    // Fragmentシェーダコンパイル
    GLResource fragmentShader(glCreateShader(GL_FRAGMENT_SHADER), glDeleteShader);
    assert(fragmentShader);
    CompileShader(fragmentShader, fragmentCode.c_str());

    GLResource program(glCreateProgram(), glDeleteProgram);
    assert(program);

    // リンクプログラム
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    PrintProgramLog(program);
    glDetachShader(program, fragmentShader);
    glDetachShader(program, vertexShader);

    // リンクエラー確認
    GLint status;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    assert(status != GL_FALSE);

    return program;
}

CompileShader

void CompileShader(GLuint shader, const GLchar* code)
{
    glShaderSource(shader, 1, &code, NULL);
    glCompileShader(shader);

    PrintShaderLog(shader);

    // コンパイルエラー確認
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    assert(status != GL_FALSE);
}
void PrintShaderLog(GLuint shader)
{
    GLint length;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
    if (0 < length)
    {
        std::vector<GLchar> log(length);
        glGetShaderInfoLog(shader, log.size(), &length, log.data());
        printf("%s\n", log.data());
    }
}

PrintProgramLog

void PrintProgramLog(GLuint program)
{
    GLint length;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
    if (0 < length)
    {
        std::vector<GLchar> log(length);
        glGetProgramInfoLog(program, log.size(), &length, log.data());
        printf("%s\n", log.data());
    }
}


main関数

入力ポリゴンを赤色に塗りつぶすだけのGLSLで、三角形を描画しています。

ウィンドウのサイズ変更に追従するよう、コールバックを登録しています。

GLFWのコールバック登録は関数ポインタであり、ローカル変数キャプチャのためにdrawグローバル変数を用意しました。

std::function<void()> draw;

int main()
{
    assert(glfwInit());

    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
    glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

    GLFWwindow* window = glfwCreateWindow(200, 200, "Triangle", NULL, NULL);
    assert(window);
    glfwMakeContextCurrent(window);

    // Program準備
    std::string vertexCode =
        "#version 300 es\n"
        "layout (location = 0) in vec4 position;\n"
        "void main() { gl_Position = position; }";
    std::string fragmentCode =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 fragColor;\n"
        "void main() { fragColor = vec4(1.0, 0.0, 0.0, 1.0); }";
    GLResource program = CreateProgram(vertexCode, fragmentCode);
    glUseProgram(program);

    // 頂点データ設定    
    glEnableVertexAttribArray(0);
    const GLfloat positions[] =
    {
         0.0,  1.0,
         1.0, -1.0,
        -1.0, -1.0
    };
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, positions);

    // Draw関数定義
    draw = [&]
    {
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
    };

    // コールバック登録
    glfwSetWindowSizeCallback(window, [](GLFWwindow*, int w, int h) 
    {  
        glViewport(0, 0, w, h);
    });        
    glfwSetWindowRefreshCallback(window, [](GLFWwindow*)
    {
        draw();
    });

    // メインループ
    while (glfwWindowShouldClose(window) == GL_FALSE)
    {
        glfwWaitEvents();
    }

    glfwTerminate();

    return 0;
}

f:id:any-programming:20170425133858p:plain