Mugichoko's blog

読者です 読者をやめる 読者になる 読者になる

Mugichoko’s blog

プログラミングを中心としたメモ書き.

GLSL #9: Zバッファの線形化

目標

前回(下記参照)までの実装を少し変更して,Compute Shaderを使って,3DCGを描画した際に生成されるZバッファの線形化を行うプログラムを作成する.

mugichoko.hatenablog.com

Zバッファの線形化とは?

Zバッファは,実際の奥行値から非線形変換が行われて0~1の大きさに正規化されており,実際の空間内の距離とは異なる.そこで,以下の式に基づいてZバッファを線形化(本来の奥行値)に戻す. 尚, Z'は出力, ZはZバッファの値, nは3DCGを描画する際に用いたNear平面までの距離, fはFar平面まで距離. 詳細は下記を参照のこと.

 Z' = (2.0 * n * f) / (f + n - (Z * 2.0 - 1.0) * (f - n))

learnopengl.com

実装環境

レンダリングの流れ

  1. パス1:四角形ポリゴンに向いたカメラが奥行方向に0.5~5.5の範囲で移動する
  2. パス2:Compute Shaderに1のZバッファを渡し,上記の式を適用する
  3. パス3:パス1の結果はFBOに書き込まれているので,バックバッファにコピーする
  4. パス4:Compute Shaderで書き込んだテクスチャのデータをメインメモリに読み込んで,その中心の画素の値をコマンドプロンプトに表示する

※つまり,コマンドプロンプトには0.5~5.5の値が表示されるはずである!

サンプルプログラム

前回までのプログラムをそのまま利用し,以下の様に書き換えます.ただし,ここにあるバグを修正した.

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"
#include "OpenGLWrapper/fbo.h"

class imCompShader : public gl::compShader
{
public:
    imCompShader(
        int w,
        int h,
        const string &compShaderName,
        GLint internalFormat = GL_RGBA8,
        GLenum format = GL_RGBA,
        GLenum type = GL_UNSIGNED_BYTE
    ) : gl::compShader(w, h, 1, internalFormat, format, type, compShaderName)
    {
    }

    void imCompShader::execute(
        GLuint inTexID,
        float near,
        float far
    )
    {
        glUseProgram(prog);
        {
            // Uniforms
            glUniform1f(glGetUniformLocation(prog, "zNear"), near);
            glUniform1f(glGetUniformLocation(prog, "zFar"), far);

            // Input texture
            glBindImageTexture(0, inTexID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32F);
            // Output texture
            glBindImageTexture(1, texID, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);

            glDispatchCompute(width / 32, height / 32, depth);
        }
        glUseProgram(0);
    }
};

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    gl::fbo *myFBO;
    imCompShader *myZBuffComp;

    const float near;
    const float far;

    void path1()
    {
        glEnable(GL_DEPTH_TEST);

        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO->getFBOID());
        {
            // Clear color and depth buffers
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glm::vec3 eye(0.0f, 0.0f, 0.5f + 5.0f * abs(sin(glfwGetTime())));
            glm::vec3 center(0.0f);
            glm::vec3 up(0.0f, 1.0f, 0.0f);
            glm::mat4 V = glm::lookAt(eye, center, up);
            glm::mat4 P = glm::perspective(3.14f / 4.0f, (float)width / height, near, far);
            myTex->setViewMat(V);
            myTex->setProjMat(P);
            myTex->render();
        }
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
    void path2()
    {
        myZBuffComp->execute(myFBO->getDepthTexID(), near, far);
    }
    void path3()
    {
        // Framebuffer to back buffer copy
        glBindFramebuffer(GL_READ_FRAMEBUFFER, myFBO->getFBOID());
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glBlitFramebuffer(
            0, 0, width, height, 0, 0, width, height,
            GL_COLOR_BUFFER_BIT, GL_NEAREST
        );
        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    }
    void path4()
    {
        float *pixels = new float[width * height];

        glBindTexture(GL_TEXTURE_2D, myZBuffComp->getTexID());
        glGetTexImage(GL_TEXTURE_2D, 0, myZBuffComp->getFormat(), myZBuffComp->getType(), pixels);

        cout << pixels[width * (height + 1) / 2] << endl;
        delete[] pixels;
    }

public:
    MyWindow(
        int w,
        int h,
        float near,
        float far,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        const string &compShaderName
    ) : window(w, h, name), near(near), far(far)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
        myFBO = new gl::fbo(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
        myZBuffComp = new imCompShader(w, h, compShaderName, GL_R32F, GL_RED, GL_FLOAT);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myFBO) delete myFBO;
        if (myZBuffComp) delete myZBuffComp;
    }

    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            path1();
            path2();
            path3();
            path4();
            
            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        512, 512, 0.1f, 100.0f,
        "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag",
        "../shader/zbuff.comp"
    );
    wnd.render();

    return 0;
}

zbuff.comp

//
// zbuff.comp
//
#version 430

layout (binding = 0, r32f) readonly uniform image2D dataIn;
layout (binding = 1, r32f) writeonly uniform image2D dataOut;

layout (local_size_x = 32, local_size_y = 32) in;

uniform float zNear;
uniform float zFar;

void main(void)
{
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    vec4 val = imageLoad(dataIn, pos);

    float z = (2.0 * zNear * zFar) / (zFar + zNear - (val.r * 2.0 - 1.0) * (zFar - zNear));

    imageStore(
        dataOut,
        pos,
        vec4(z, 0.0, 0.0, 0.0)
        );
}

結果

カメラが0.5の位置に近づいた時. f:id:Mugichoko:20170524172755j:plain

カメラが5.5の位置に近づいた時. f:id:Mugichoko:20170524172750j:plain

尚,0.5,5.5といった固定値を設定した場合にも,それぞれ0.5,5.5とコマンドプロンプトに表示された. ちなみに,線形化をしない場合,つまり「zbuff.comp」内で「float z = val.r;」とする場合,0.9辺りを前後した.

GLSLのCompute ShaderでZバッファを読み込む際のトラブルと解決方法

問題

GLSLのCompute ShaderでZバッファを読み込みたかったのだが,下記のリンクと同じ問題に遭遇した.

stackoverflow.com

簡単には,Zバッファを可視化したところ,ワイヤーフレームが強調された用な結果が出力されてしまう問題だ. 結果的に,私の場合は単なる記述ミス(バグ)だった.

問題に遭遇した手順

  1. カラーバッファとZバッファをアタッチしたFBOに3DCGを描画
  2. 1のZバッファ (GL_DEPTH_COMPONENT) をCompute Shaderに読み込み何らかの処理を実行し,テクスチャとして出力
    • 今回は,非線形に保存されているZバッファ値を線形に直す処理を行った(正に,上記のリンクにある内容と同じ!)
  3. 2のテクスチャをバックバッファに描画
  4. 3DCGのポリゴンの面にワイヤーフレームが加わったようなZバッファが描画される
    • 本来はワイヤフレームがない結果がほしい f:id:Mugichoko:20170517171951j:plain

実装環境

解決方法

FBOでZバッファを用意する時,internalFormatを「GL_DEPTH_COMPONENT」ではなく「GL_DEPTH_COMPONENT32F」とする.

glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

↓ 修正後

glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

修正結果が以下の通り.ワイヤーフレームのような画素が消えた.

f:id:Mugichoko:20170517172221j:plain

所感

よく分からないのは,最初に挙げたリンクでは既に今回の修正に相当する記述になっているのに同じ問題が起きることだ.

GLSL #8: Compute Shaderを使った画像処理

目標

前回(下記参照)までの実装を組み合わせて用いることで,3DCGを描画した結果に対してCompute Shaderを使って画像処理(Sobelフィルタによるエッジ検出)を行うプログラムを作成する.

mugichoko.hatenablog.com

実装環境

レンダリングの流れ

  1. パス1:第6回のパス1と同じ
  2. パス2:Compute Shaderに1の描画結果を渡し,Sobelフィルタをかける

サンプルプログラム

シェーダプログラムに関しては,ほとんどがこちらの丸コピです. 前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • model.h
  • model.cpp
  • modelTex.h
  • modelTex.cpp
  • shaderUtil.h
    • execute関数を仮想関数としていたが,処理によって入出力が変わるため,削除した
  • shaderUtil.cpp

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"
#include "OpenGLWrapper/fbo.h"

class imCompShader : public gl::compShader
{
public:
    imCompShader(
        int w,
        int h,
        const string &compShaderName,
        GLint internalFormat = GL_RGBA8,
        GLenum format = GL_RGBA,
        GLenum type = GL_UNSIGNED_BYTE
    ) : gl::compShader(w, h, 1, internalFormat, format, type, compShaderName)
    {
    }

    void imCompShader::execute(
        GLuint inTexID
    )
    {
        glUseProgram(prog);
        {
            // Input texture
            glBindImageTexture(0, inTexID, 0, GL_FALSE, 0, GL_READ_ONLY, internalFormat);
            // Output texture
            glBindImageTexture(1, texID, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);

            glDispatchCompute(width / 32, height / 32, depth);
        }
        glUseProgram(0);
    }
};

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    gl::fbo *myFBO;
    imCompShader *mySobelComp;


    void path1()
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO->getFBOID());
        {
            // Clear color and depth buffers
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glm::vec3 eye(cos(glfwGetTime()), sin(glfwGetTime()), 1.5f);
            glm::vec3 center(0.0f);
            glm::vec3 up(0.0f, 1.0f, 0.0f);
            glm::mat4 V = glm::lookAt(eye, center, up);
            glm::mat4 P = glm::perspective(3.14f / 4.0f, (float)width / height, 0.1f, 100.0f);
            myTex->setViewMat(V);
            myTex->setProjMat(P);
            myTex->render();
        }
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
    void path2()
    {
        mySobelComp->execute(myFBO->getColorTexID());
        myTex->setViewMat(glm::mat4());
        myTex->setProjMat(glm::mat4());
        myTex->render(mySobelComp->getTexID());
    }

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        const string &compShaderName
    ) : window(w, h, name)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
        myFBO = new gl::fbo(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
        mySobelComp = new imCompShader(w, h, compShaderName);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myFBO) delete myFBO;
        if (mySobelComp) delete mySobelComp;
    }

    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            path1();
            path2();
            
            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        512, 512, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag",
        "../shader/sobel.comp"
    );
    wnd.render();

    return 0;
}

sample.comp

//
// sobel.comp
//
#version 430

layout (binding = 0, rgba8) readonly uniform image2D dataIn;
layout (binding = 1) writeonly uniform image2D dataOut;

layout (local_size_x = 32, local_size_y = 32) in;

void main(void)
{
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    vec3 px00 = imageLoad(dataIn, pos + ivec2(-1, 1)).rgb;
    vec3 px01 = imageLoad(dataIn, pos + ivec2( 0, 1)).rgb;
    vec3 px02 = imageLoad(dataIn, pos + ivec2( 1, 1)).rgb;
    vec3 px10 = imageLoad(dataIn, pos + ivec2(-1, 0)).rgb;
    vec3 px12 = imageLoad(dataIn, pos + ivec2( 1, 0)).rgb;
    vec3 px20 = imageLoad(dataIn, pos + ivec2(-1,-1)).rgb;
    vec3 px21 = imageLoad(dataIn, pos + ivec2( 0,-1)).rgb;
    vec3 px22 = imageLoad(dataIn, pos + ivec2( 1,-1)).rgb;

    // Sobel
    vec3 hori = -px00 - 2.0 * px10 -px20 + px02 + 2.0 * px12 + px22;
    vec3 vert = -px00 - 2.0 * px01 -px02 + px20 + 2.0 * px21 + px22;

    imageStore(
        dataOut,
        pos,
        vec4(sqrt(hori * hori + vert * vert), 0.0)
        );
}

結果

第6回で得た出力結果に対してエッジ検出をかけた結果が出力されました!目標達成です.

youtu.be

GLSL #7: Compute Shader

目標

こちらの内容を参考に,とりあえずCompute Shaderが動作することを確認する. 尚,今回実装する内容は,前回(下記参照)までの実装に基づいております.

mugichoko.hatenablog.com

実装環境

レンダリングの流れ

  1. Compute Shaderを実行し,テクスチャに結果を書き込む
  2. 1の結果をmodelTexクラスを用いて画面に描画

サンプルプログラム

シェーダプログラムに関しては,ほとんどがこちらの丸コピです. 前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • model.h
  • model.cpp
  • modelTex.h
  • modelTex.cpp

shaderUtil.h

前回から,以下を追加. 追加内容は,Compute Shaderをコンパイルするためのインタフェース「initializeProgram」とCompute Shader実装時のインタフェースとなる「compShader」クラス.

//
// shaderUtil.h
//

//
// 前回までの記述
//

namespace gl
{
    // Interface for compute shader
    void initializeProgram(
        GLuint &theProgram,
        const string &compShaderFileName
    );
    
    // ----- Compute shader handlar
    class compShader
    {
    protected:
        GLuint prog;
        GLuint texID;

        const GLint internalFormat;
        const GLsizei width;
        const GLsizei height;
        const GLuint depth;
        const GLenum format;
        const GLenum type;
    public:
        compShader(
            GLsizei width,
            GLsizei height,
            GLuint dpeth,
            GLint internalFormat,
            GLenum format,
            GLenum type,
            const string compShaderName
        );
        ~compShader();

        GLuint getTexID();
        GLint getInternalFormat();
        GLsizei getWidth();
        GLsizei getHeight();
        GLenum getFormat();
        GLenum getType();
        GLuint getDepth();

        virtual void execute() = 0;
    };
}

shaderUtil.cpp

ヘッダファイルに合わせて,前回から,以下を追加.

//
// shaderUtil.cpp
//
#include "shaderUtil.h"

//
// 前回までの記述
//

namespace gl
{
    void initializeProgram(
        GLuint &theProgram,
        const string &compShaderFileName
    )
    {
        try
        {
            cout << "[Shader Utility] Making a shader program..." << endl;

            vector<GLuint> shaderList;

            // ----- Load shaders
            stringstream ssComputeShader;
            readFile(compShaderFileName, ssComputeShader);

            // ----- Compile shaders
            shaderList.push_back(createShader(GL_COMPUTE_SHADER, ssComputeShader.str()));

            // ----- Link the shaders to the program
            theProgram = createProgram(shaderList);

            // ----- Delete
            std::for_each(shaderList.begin(), shaderList.end(), glDeleteShader);

            cout << "[Shader Utility] ... done!" << endl;
        }
        catch (string e)
        {
            cerr << "Error: " << e << endl;
            exit(EXIT_FAILURE);
        }
    }
    
    // ----- Compute shader handlar
    compShader::compShader(
        GLsizei width,
        GLsizei height,
        GLuint dpeth,
        GLint internalFormat,
        GLenum format,
        GLenum type,
        const string compShaderName
    ) : internalFormat(internalFormat), width(width), height(height), depth(dpeth), format(format), type(type)
    {
        // ----- Generate data texture
        glGenTextures(1, &texID);
        glBindTexture(GL_TEXTURE_2D, texID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, NULL);

        // ----- Generate a compute shader program
        initializeProgram(prog, compShaderName);
    }
    compShader::~compShader()
    {
        glDeleteProgram(prog);
    }

    GLuint compShader::getTexID()
    {
        return texID;
    }
    GLint compShader::getInternalFormat()
    {
        return internalFormat;
    }
    GLsizei compShader::getWidth()
    {
        return width;
    }
    GLsizei compShader::getHeight()
    {
        return height;
    }
    GLenum compShader::getFormat()
    {
        return format;
    }
    GLenum compShader::getType()
    {
        return type;
    }
    GLuint compShader::getDepth()
    {
        return depth;
    }
}

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"

class MyCompShader : public gl::compShader
{
public:
    MyCompShader(
        int w,
        int h,
        const string &compShaderName
    ) : gl::compShader(w, h, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, compShaderName)
    {
    }

    void MyCompShader::execute()
    {
        static float frame(0.0f);

        glUseProgram(prog);
        {
            glBindImageTexture(0, texID, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);

            glUniform1f(glGetUniformLocation(prog, "roll"), (float)frame * 0.01f);
            glDispatchCompute(width / 32, height / 32, depth);
        }
        glUseProgram(0);

        frame += 0.5f;
        if (frame >= FLT_MAX - 1.0f)
        {
            frame = 0.0f;
        }
    }
};

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    MyCompShader *myComp;

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        const string &compShaderName
    ): window(w, h, name)
    {
        myTex = new gl::modelTex(w, h, vertShaderName, fragShaderName);
        myComp = new MyCompShader(w, h, compShaderName);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myComp) delete myComp;
    }

    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            myComp->execute();
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            myTex->render(myComp->getTexID());

            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        512, 512, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag",
        "../shader/sample.comp"
    );
    wnd.render();

    return 0;
}

sample.comp

//
// sample.comp
//
#version 430

uniform float roll;
layout (binding = 0) writeonly uniform image2D data;

layout (local_size_x = 32, local_size_y = 32) in;

void main(void)
{
    ivec2 storePos = ivec2(gl_GlobalInvocationID.xy);
    float localCoef = length(vec2(256, 256) - vec2(ivec2(gl_GlobalInvocationID.xy)));
    float val = 0.5 + 0.5 * sin(localCoef * 0.05 + roll);
    imageStore(
        data,
        storePos,
        vec4(val, val, val, 0)
        );
}

結果

動作しました!バッチリです.

youtu.be

GLSL #6: Frame Buffer Object (FBO)

目標

前回(下記参照)は,テクスチャマッピングを行うプログラムを作成しました. 今回は,そのプログラムをベースに,FBOに描画した結果を四角形ポリゴンにテクスチャとして利用する方法を実装します.

mugichoko.hatenablog.com

実装環境

レンダリングの流れと結果

今回は分かりやすさのために,レンダリングの流れと結果を先に示す.

  1. パス1:FBO(テクスチャ)にテクスチャ付き3Dポリゴンを描画
    • この際,カメラを3Dポリゴン (modelTex) の中心を注視して,円を描くように動かす
    • つまり,以下の動画を毎フレームFBOに書き出す youtu.be
  2. パス2:FBOが保持するテクスチャのIDをmodelTexクラスに渡し,今度は,バックバッファに描画
    • この際,カメラは3Dポリゴン (modelTex) から少し離れた位置に置く youtu.be

サンプルプログラム

前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • shaderUtil.h
  • shaderUtil.cpp
  • model.h
  • model.cpp
  • modelTex.vert
  • modelTex.frag

modelTex.h

前回から少し変更. 具体的には,画像ををファイルから読み込まずとも,別処理で作成されたテクスチャを受け取って描画できるようにした.

//
// modelTex.h
//
#pragma once
#include "model.h"
#include <IL/il.h>

namespace gl
{
    class modelTex : public model
    {
    private:
        static const GLfloat vertData[40];
        static const GLshort idxData[6];
        static const int numVert;
        static const int offsetTexUVData;

        GLuint texID;

        GLuint MVPMatLoc;
        GLuint vao; // Vertex array object
        GLuint vbo; // Vertex buffer object
        GLuint ibo; // Index buffer object

        void init(
            int w,
            int h,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat,
            GLenum format,
            GLenum type
        );

    public:
        modelTex(
            const string &texFileName,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        modelTex(
            int w,
            int h,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        ~modelTex();

        void render();
        void render(
            GLint texID
        );
    };
}

modelTex.cpp

ヘッダファイルに合わせて,前回から少し変更.

//
// modelTex.cpp
//
#include "modelTex.h"

namespace gl
{
    const GLfloat modelTex::vertData[40] =
    {
        // Vertex position
        +1.0f, +1.0f, 0.0f,      // #0: Upper right
        -1.0f, +1.0f, 0.0f,      // #1: Upper left
        -1.0f, -1.0f, 0.0f,      // #2: Lower left
        +1.0f, -1.0f, 0.0f,      // #3: Lower right
        // Texture uv position
        1.0f, 1.0f,               // #0: Upper right
        0.0f, 1.0f,               // #1: Upper left
        0.0f, 0.0f,               // #2: Lower left
        1.0f, 0.0f                // #3: Lower right
    };
    const GLshort modelTex::idxData[6] =
    {
        0, 1, 2,
        0, 2, 3
    };
    const int modelTex::numVert(4);
    const int modelTex::offsetTexUVData(sizeof(float) * 3 * numVert);

    void modelTex::init(
        int w,
        int h,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        // ----- Initialize shader programs
        initializeProgram(prog, vertShaderName, fragShaderName);

        // ----- Generate buffers
        // VBO
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertData), vertData, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // IBO
        glGenBuffers(1, &ibo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxData), idxData, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        // ----- Get a uiform location
        MVPMatLoc = glGetUniformLocation(prog, "MVPMat");

        // ----- VAO
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);

        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);  // 3 elements per vertex
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)offsetTexUVData); // 2 elements per color
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        glBindVertexArray(0);

        // ----- Currently the texture is empty
        glGenTextures(1, &texID);
        glBindTexture(GL_TEXTURE_2D, texID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, NULL);
    }

    modelTex::modelTex(
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        // ----- Load an image and create a texture of the image
        ilInit();
        
        ILuint imgID;
        ilGenImages(1, &imgID);

        ilBindImage(imgID);
        ilEnable(IL_ORIGIN_SET);
        ilOriginFunc(IL_ORIGIN_LOWER_LEFT);

        try {
            ILboolean isLoaded = ilLoadImage((ILstring)texFileName.c_str());
            if (isLoaded)
            {
                cout << "Loaded " << texFileName << endl;

                init(
                    ilGetInteger(IL_IMAGE_WIDTH), ilGetInteger(IL_IMAGE_HEIGHT),
                    vertShaderName, fragShaderName,
                    internalFormat, format, type
                );

                ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, ilGetInteger(IL_IMAGE_WIDTH),
                    ilGetInteger(IL_IMAGE_HEIGHT), 0, format, type,
                    ilGetData());
            }
            else
            {
                throw "Could not load image: " + texFileName;
            }
        }
        catch (string e)
        {
            cerr << "Error: " << e.c_str() << endl;
            exit(EXIT_FAILURE);
        }
    }
    modelTex::modelTex(
        int w,
        int h,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        init(w, h, vertShaderName, fragShaderName, internalFormat, format, type);
    }
    modelTex::~modelTex()
    {
        glDeleteVertexArrays(1, &vao);
        glDeleteTextures(1, &texID);
        glDeleteBuffers(1, &vbo);
        glDeleteBuffers(1, &ibo);
        glDeleteBuffers(1, &MVPMatLoc);
    }

    void modelTex::render()
    {
        render(-1);
    }
    void modelTex::render(
        GLint texID
    )
    {
        glUseProgram(prog);
        {
            // Send matrix data to GPU
            glUniformMatrix4fv(MVPMatLoc, 1, GL_FALSE, glm::value_ptr(projMat * viewMat * modelMat));
            
            // Texture
            if (texID >= 0)
            {
                glBindTexture(GL_TEXTURE_2D, texID);
            }
            else
            {
                glBindTexture(GL_TEXTURE_2D, this->texID);
            }

            // VAO
            glBindVertexArray(vao);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
            glBindVertexArray(0);
        }
        glUseProgram(0);
    }
}

fbo.h

FBOを扱うクラスを記述したヘッダファイル.

//
// fbo.h
//
#pragma once

#include <iostream>
using namespace std;
#include <GL/glew.h>
#include <GLFW/glfw3.h>

namespace gl
{
    class fbo
    {
    private:
        const int width;
        const int height;
        GLuint texColorID;
        GLuint texDepthID;
        GLuint fboID;

    public:
        fbo(
            int w,
            int h,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        ~fbo();

        int getWidth();
        int getHeight();
        GLuint getColorTexID();
        GLuint getDepthTexID();
        GLuint getFBOID();
    };
}

fbo.cpp

FBOを扱うクラスを記述したソースファイル.

//
// fbo.cpp
//
#include "fbo.h"

namespace gl
{
    fbo::fbo(
        int w,
        int h,
        GLint internalFormat,
        GLenum format,
        GLenum type
    ) : width(w), height(h)
    {
        // ----- Create a texture
        // Color buffer
        glGenTextures(1, &texColorID);
        glBindTexture(GL_TEXTURE_2D, texColorID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
        // Z buffer
        glGenTextures(1, &texDepthID);
        glBindTexture(GL_TEXTURE_2D, texDepthID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
        glBindTexture(GL_TEXTURE_2D, 0);

        // ----- Create a framebuffer object (FBO)
        glGenFramebuffers(1, &fboID);
        glBindFramebuffer(GL_FRAMEBUFFER, fboID);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorID, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texDepthID, 0);

        // ----- Get back to backbuffer
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    fbo::~fbo()
    {
        glDeleteTextures(1, &texColorID);
        glDeleteTextures(1, &texDepthID);
        glDeleteFramebuffers(1, &fboID);
    }

    int fbo::getWidth()
    {
        return width;
    }
    int fbo::getHeight()
    {
        return height;
    }
    GLuint fbo::getColorTexID()
    {
        return texColorID;
    }
    GLuint fbo::getDepthTexID()
    {
        return texDepthID;
    }
    GLuint fbo::getFBOID()
    {
        return fboID;
    }
}

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"
#include "OpenGLWrapper/fbo.h"

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    gl::fbo *myFBO;

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName
    ): window(w, h, name)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
        myFBO = new gl::fbo(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myFBO) delete myFBO;
    }

    void path1()
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO->getFBOID());
        {
            // Clear color and depth buffers
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glm::vec3 eye(cos(glfwGetTime()), sin(glfwGetTime()), 1.5f);
            glm::vec3 center(0.0f);
            glm::vec3 up(0.0f, 1.0f, 0.0f);
            glm::mat4 V = glm::lookAt(eye, center, up);
            glm::mat4 P = glm::perspective(3.14f / 4.0f, (float)width / height, 0.1f, 100.0f);
            myTex->setViewMat(V);
            myTex->setProjMat(P);
            myTex->render();
        }
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
    void path2()
    {
        // Clear color and depth buffers
        glClearColor(0.8f, 0.5f, 0.3f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glm::vec3 eye(-1.0f, 1.0f, 2.5f);
        glm::vec3 center(0.0f);
        glm::vec3 up(0.0f, 1.0f, 0.0f);
        glm::mat4 V = glm::lookAt(eye, center, up);
        myTex->setViewMat(V);
        myTex->render(myFBO->getColorTexID());
    }
    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            path1();
            path2();

            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        640, 480, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag"
    );
    wnd.render();

    return 0;
}

GLSL #5: テクスチャマッピング

目標

前回のプログラムをベースに,今度はテクスチャマッピングを実装する.より具体的には,画面いっぱいに四角形を描き,そこに任意の画像をテクスチャマッピングする.

mugichoko.hatenablog.com

実装環境

DevILのダウンロード

  1. ここから「DevIL-Windows-SDK-1.8.0.zip」をダウンロード
  2. 「C:\Libraries\」に「DevIL Windows SDK」フォルダを解凍
    • フォルダ名が分かりにくいので「DevIL-1.8.0」とする
    • 以降,この「C:\Libraries\DevIL-1.8.0」を「Path」と呼ぶ

Visual Studioのプロジェクト作成

  1. Visual Studioを開いて「新しいプロジェクト」を作成
  2. 「インクルードディレクトリ」に以下を追加
    • Path\include
  3. 「ライブラリディレクトリ」に以下を追加
    • Debug: Path\lib\x64\Release
    • Release: 同上
  4. 「追加の依存ファイル」に必要なライブラリを追加
    • Debug: DevIL.lib
    • Release: 同上
  5. 3のフォルダに入っている「.dll」ファイルを「C:\Windows\System32」に追加

サンプルプログラム

前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • shaderUtil.h
  • shaderUtil.cpp

model.h

モデルデータを扱うためのインタフェースを記述したヘッダファイル.

//
// model.h
//
#pragma once
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "shaderUtil.h"

namespace gl
{
    class model
    {
    protected:
        GLuint prog;
        glm::mat4 projMat;
        glm::mat4 viewMat;
        glm::mat4 modelMat;

    public:
        virtual void render() = 0;
        void setProjMat(
            glm::mat4 &mat
        );
        void setViewMat(
            glm::mat4 &mat
        );
        void setModelMat(
            glm::mat4 &mat
        );
    };
}

model.cpp

モデルデータを扱うためのインタフェースを記述したソースファイル.

//
// model.cpp
//
#include "model.h"

namespace gl
{
    void model::setProjMat(
        glm::mat4 &mat
    )
    {
        projMat = mat;
    }

    void model::setViewMat(
        glm::mat4 &mat
    )
    {
        viewMat = mat;
    }

    void model::setModelMat(
        glm::mat4 &mat
    )
    {
        modelMat = mat;
    }
}

modelTex.h

画像データをDevILを用いて読み込んで,テクスチャとして描画するクラスを記述したヘッダファイル.

//
// modelTex.h
//
#pragma once
#include "model.h"
#include <IL/il.h>

namespace gl
{
    class modelTex : public model
    {
    private:
        static const GLfloat vertData[40];
        static const GLshort idxData[6];
        static const int numVert;
        static const int offsetTexUVData;

        GLuint texID;

        GLuint MVPMatLoc;
        GLuint vao; // Vertex array object
        GLuint vbo; // Vertex buffer object
        GLuint ibo; // Index buffer object

    public:
        modelTex(
            const string &texFileName,
            const string &vertShaderName,
            const string &fragShaderName
        );
        ~modelTex();

        void render();
    };
}

modelTex.cpp

画像データをDevILを用いて読み込んで,テクスチャとして描画するクラスを記述したソースファイル.

//
// modelTex.cpp
//
#include "modelTex.h"

namespace gl
{
    const GLfloat modelTex::vertData[40] =
    {
        // Vertex position
        +1.0f, +1.0f, 0.0f,      // #0: Upper right
        -1.0f, +1.0f, 0.0f,      // #1: Upper left
        -1.0f, -1.0f, 0.0f,      // #2: Lower left
        +1.0f, -1.0f, 0.0f,      // #3: Lower right
        // Texture uv position
        1.0f, 1.0f,               // #0: Upper right
        0.0f, 1.0f,               // #1: Upper left
        0.0f, 0.0f,               // #2: Lower left
        1.0f, 0.0f                // #3: Lower right
    };
    const GLshort modelTex::idxData[6] =
    {
        0, 1, 2,
        0, 2, 3
    };
    const int modelTex::numVert(4);
    const int modelTex::offsetTexUVData(sizeof(float) * 3 * numVert);

    modelTex::modelTex(
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName
    )
    {
        // ----- Initialize shader programs
        initializeProgram(prog, vertShaderName, fragShaderName);

        // ----- Generate buffers
        // VBO
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertData), vertData, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // IBO
        glGenBuffers(1, &ibo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxData), idxData, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        // ----- Load an image and create a texture of the image
        ilInit();
        
        ILuint imgID;
        ilGenImages(1, &imgID);
        glGenTextures(1, &texID);

        ilBindImage(imgID);
        ilEnable(IL_ORIGIN_SET);
        ilOriginFunc(IL_ORIGIN_LOWER_LEFT);

        try {
            ILboolean isLoaded = ilLoadImage((ILstring)texFileName.c_str());
            if (isLoaded)
            {
                ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ilGetInteger(IL_IMAGE_WIDTH),
                    ilGetInteger(IL_IMAGE_HEIGHT), 0, GL_RGBA, GL_UNSIGNED_BYTE,
                    ilGetData());
            }
            else
            {
                throw "Could not load image: " + texFileName;
            }
        }
        catch (string e)
        {
            cerr << "Error: " << e.c_str() << endl;
            exit(EXIT_FAILURE);
        }

        ilDeleteImages(1, &imgID);

        // ----- Get a uiform location
        MVPMatLoc = glGetUniformLocation(prog, "MVPMat");

        // ----- VAO
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
        
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);  // 3 elements per vertex
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)offsetTexUVData); // 2 elements per color
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        glBindVertexArray(0);
    }
    modelTex::~modelTex()
    {
        glDeleteVertexArrays(1, &vao);
        glDeleteTextures(1, &texID);
        glDeleteBuffers(1, &vbo);
        glDeleteBuffers(1, &ibo);
        glDeleteBuffers(1, &MVPMatLoc);
    }

    void modelTex::render()
    {
        glUseProgram(prog);
        {
            // Send matrix data to GPU
            glUniformMatrix4fv(MVPMatLoc, 1, GL_FALSE, glm::value_ptr(projMat * viewMat * modelMat));
            
            // Texture
            glBindTexture(GL_TEXTURE_2D, texID);

            // VAO
            glBindVertexArray(vao);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
            glBindVertexArray(0);
        }
        glUseProgram(0);
    }
}

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName
    ): window(w, h, name)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
    }

    void render()
    {
        // Rendering loop
        glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
        while (!glfwWindowShouldClose(wnd))
        {
            // Clear color and depth buffers
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            myTex->render();

            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        512, 512, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag"
    );
    wnd.render();

    return 0;
}

modelTex.vert

#version 330

layout(location = 0) in vec3 position;
layout(location = 1) in vec2 uv;

uniform mat4 MVPMat;

out vec2 texCoord;

void main()
{
    gl_Position = MVPMat * vec4(position, 1.0);
    texCoord = uv;
}

modelTex.vert

#version 420

layout(binding = 0) uniform sampler2D texLoc;

in vec2 texCoord;

void main()
{
    gl_FragColor = texture(texLoc, texCoord);
}

結果

動いた!目標達成です.

f:id:Mugichoko:20170516190925j:plain

GLSL #4: シェーダを使った三角形の描画

目標

前回作成したプログラム(下記参照)の固定パイプラインによる描画部分をシェーダに置き換える.つまり,前回と同じ動作をするプログラムをシェーダで記述する.

mugichoko.hatenablog.com

実装環境

GLMのダウンロード

  1. ここから「zip」ボタンを押して「glm-0.9.8.4.zip」をダウンロード
  2. 「C:\Libraries\」に「glm-0.9.8.4」フォルダを解凍
    • 以降,この「C:\Libraries\glm-0.9.8.4」を「Path」と呼ぶ

Visual Studioのプロジェクト作成

  1. Visual Studioを開いて「新しいプロジェクト」を作成
  2. 「インクルードディレクトリ」に以下を追加
    • Path

サンプルプログラム

今回から,「main.cpp」以外のファイルを「OpenGLWrapper」フォルダに入れておくことにしました.

window.h

こちらは前回から変更なし.

//
// window.h
//

// 前回から変更なし

window.cpp

こちらも前回から変更なし.

//
// window.cpp
//

// 前回から変更なし

shaderUtil.h

シェーダを読み込んでコンパイルする関数を記述したヘッダファイル. 基本的には「initializeProgram」関数を使います.

//
// shaderUtil.h
//

#pragma once
#include <iostream>
using namespace std;
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <GL/glew.h>

namespace gl
{
    void initializeProgram(
        GLuint &theProgram,
        const string &vertShaderFileName,
        const string &fragShaderFileName,
        const string &geomShaderFileName = ""
    );

    void readFile(
        const string &fileName,
        stringstream &contents
    );

    GLuint createShader(
        GLenum eShaderType,
        const string &strShaderFile
    );

    GLuint createProgram(
        const vector<GLuint> &shaderList
    );
}

shaderUtil.cpp

シェーダを読み込んでコンパイルする関数を記述したソースファイル.

//
// shaderUtil.cpp
//
#include "shaderUtil.h"

namespace gl
{
    void initializeProgram(
        GLuint &theProgram,
        const string &vertShaderFileName,
        const string &fragShaderFileName,
        const string &geomShaderFileName
    )
    {
        try
        {
            cout << "[Shader Utility] Making a shader program..." << endl;

            vector<GLuint> shaderList;

            // ----- Load shaders
            stringstream ssVertexShader, ssFragmentShader, ssGeometryShader;
            readFile(vertShaderFileName, ssVertexShader);
            readFile(fragShaderFileName, ssFragmentShader);
            if (!geomShaderFileName.empty()) readFile(geomShaderFileName, ssGeometryShader);

            // ----- Compile shaders
            shaderList.push_back(createShader(GL_VERTEX_SHADER, ssVertexShader.str()));
            shaderList.push_back(createShader(GL_FRAGMENT_SHADER, ssFragmentShader.str()));
            if (!geomShaderFileName.empty()) shaderList.push_back(createShader(GL_GEOMETRY_SHADER, ssGeometryShader.str()));

            // ----- Link the shaders to the program
            theProgram = createProgram(shaderList);

            // ----- Delete
            std::for_each(shaderList.begin(), shaderList.end(), glDeleteShader);

            cout << "[Shader Utility] ... done!" << endl;
        }
        catch (string e)
        {
            cerr << "Error: " << e << endl;
            exit(EXIT_FAILURE);
        }

    }

    void readFile(
        const string &fileName,
        stringstream &contents
    )
    {
        ifstream ifs(fileName);
        if (!ifs.is_open()) {
            throw "There is no such file: " + fileName;
        }
        else {
            cout << "Loaded \"" << fileName << "\"." << endl;
        }

        string buf;
        contents = stringstream("");
        while (ifs && getline(ifs, buf)) {
            contents << buf << endl;
        }
    }

    GLuint createShader(
        GLenum eShaderType,
        const string &strShaderFile
    )
    {
        // ----- Create a shader object
        // Shader type (vertex or fragment) is specified by "eShaderType"
        GLuint shader = glCreateShader(eShaderType);
        const char *strFileData = strShaderFile.c_str();
        // Fed a shader in text format into the shader object
        glShaderSource(shader, 1, &strFileData, NULL);

        // ----- Compile shader
        glCompileShader(shader);

        // ----- Check compile status
        GLint status;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
        if (status == GL_FALSE)
        {
            GLint infoLogLength;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);

            GLchar *strInfoLog = new GLchar[infoLogLength + 1];
            glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);

            string strShaderType;
            switch (eShaderType)
            {
            case GL_VERTEX_SHADER: strShaderType = "vertex"; break;
            case GL_GEOMETRY_SHADER: strShaderType = "geometry"; break;
            case GL_FRAGMENT_SHADER: strShaderType = "fragment"; break;
            }

            string errorMsg("Failed to compile \"" + strShaderType + "\" shader: \n" + strInfoLog);
            delete[] strInfoLog;

            throw errorMsg;
        }
        else
        {
            string strShaderType;
            switch (eShaderType)
            {
            case GL_VERTEX_SHADER: strShaderType = "vertex"; break;
            case GL_GEOMETRY_SHADER: strShaderType = "geometry"; break;
            case GL_FRAGMENT_SHADER: strShaderType = "fragment"; break;
            }

            cout << "Successfully compiled \"" << strShaderType << "\" shader." << endl;
        }

        return shader;
    }

    GLuint createProgram(const vector<GLuint> &shaderList)
    {
        GLuint program = glCreateProgram();

        for (size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
            glAttachShader(program, shaderList[iLoop]);

        glLinkProgram(program);

        GLint status;
        glGetProgramiv(program, GL_LINK_STATUS, &status);
        if (status == GL_FALSE)
        {
            GLint infoLogLength;
            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);

            GLchar *strInfoLog = new GLchar[infoLogLength + 1];
            glGetProgramInfoLog(program, infoLogLength, NULL, strInfoLog);
            string errorMsg("Linker failure: " + string(strInfoLog));
            delete[] strInfoLog;

            throw errorMsg;
        }
        else
        {
            cout << "Linked shader program to the program successfully." << endl;
        }

        for (size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
            glDetachShader(program, shaderList[iLoop]);

        return program;
    }
}

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"

class MyWindow : gl::window
{
private:
    GLuint prog;    // Shader program
    GLuint vbo;     // Vertex buffer object (VBO)

    glm::vec3 vertData[6] =
    {
        glm::vec3(-0.6f, -0.4f, 0.0f),   // Position #0
        glm::vec3(0.6f, -0.4f, 0.0f),    // Position #1
        glm::vec3(0.0f, 0.6f, 0.0f), // Position #2
        glm::vec3(1.0f, 0.0f, 0.0f), // Color #0
        glm::vec3(0.0f, 1.0f, 0.0f), // Color #1
        glm::vec3(0.0f, 0.0f, 1.0f)      // Color #2
    };

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &vertShader,
        const string &fragShader,
        const string &geomShader = ""
    ): window(w, h, name)
    {
        // ----- Load and compile shaders
        gl::initializeProgram(prog, vertShader, fragShader, geomShader);

        // ----- Cereate vertex buffer object
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertData), &vertData, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    void render()
    {
        // Rendering loop
        glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
        while (!glfwWindowShouldClose(wnd))
        {
            // Clear color and depth buffers
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glUseProgram(prog);
            {
                static glm::mat4 rotMat;
                rotMat = glm::rotate(glm::mat4(), (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
                GLuint uniRotMat = glGetUniformLocation(prog, "rotMat");
                glUniformMatrix4fv(uniRotMat, 1, GL_FALSE, glm::value_ptr(rotMat));

                glBindBuffer(GL_ARRAY_BUFFER, vbo);
                glEnableVertexAttribArray(0);  // Vertices
                glEnableVertexAttribArray(1);  // Colors
                glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
                glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(vertData) / 2));

                glDrawArrays(GL_TRIANGLES, 0, 3);

                glDisableVertexAttribArray(0);
                glDisableVertexAttribArray(1);
            }
            glUseProgram(0);

            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        640, 480, "GLFW 3 Window",
        "../shader/simple.vert",
        "../shader/simple.frag"
    );
    wnd.render();

    return 0;
}

simple.vert

今回からシェーダを使います. こちらは頂点に関する記述をするVertex Shaderです.

//
// simple.vert
//
#version 330

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;

uniform mat4 rotMat;

out vec3 Color;

void main()
{
    gl_Position = rotMat * vec4(position, 1.0);
    Color = color;
}

simple.frag

今回からシェーダを使います. こちらは画素に関する記述をするFragment Shaderです.

//
// simple.frag
//
#version 330

in vec3 Color;

void main()
{
    gl_FragColor = vec4(Color, 1.0);
}

結果

動いた!目標達成です.前回同様,カラフルな三角形がゆっくりと回転します.

f:id:Mugichoko:20170516092810j:plain