Mugichoko's blog

Mugichoko’s blog

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

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