目次

OpenGLの修行 #5 - 続・三角形を描く (1)

YouTubeの動画ページ

ソースコード

ダウンロード

プロジェクトレイアウト

HelloTriangle-simpleshader
├── .clang-format
├── CMakeLists.txt
├── glad
│   ├── CMakeLists.txt
│   ├── include
│   │   ├── KHR
│   │   │   └── khrplatform.h
│   │   └── glad
│   │       └── glad.h
│   └── src
│       └── glad.c
├── shaders
│   ├── hello_triangle.frag
│   └── hello_triangle.vert
└── src
    ├── main.cpp
    ├── simpleshader.cpp
    └── simpleshader.h

.clang-format

BasedOnStyle: Chromium
IndentWidth: 4
PointerAlignment: Right
AccessModifierOffset: -2
ConstructorInitializerIndentWidth: 8

CMakeLists.txt

cmake_minimum_required(VERSION 3.13 FATAL_ERROR)

project(HelloTriangle VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)

find_package(Boost REQUIRED CONFIG)

include_directories(${Boost_INCLUDE_DIRS})

find_package(glfw3 REQUIRED)

add_subdirectory(glad)

add_executable(
    HelloTriangle
        src/main.cpp
        src/simpleshader.cpp
    )

target_link_libraries(HelloTriangle PRIVATE glad glfw)

glad/CMakeLists.txt

enable_language(C)

add_library(glad OBJECT src/glad.c)

target_include_directories(
    glad
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
    )

glad/*

gladの入手

shaders/hello_triangle.frag

#version 410 core

in vec4 vert_color;
out vec4 color;

void main(void) {
    // color = vec4(1.0, 0.0, 0.0, 1.0);
    color = vert_color;
}

shaders/hello_triangle.vert

#version 410 core

out vec4 vert_color;

void main(void) {
    const float y = sqrt(3.0) / 2.0 / 2.0;

    const vec4 vertices[3] = vec4[3](vec4(-0.5, -y, 1.0, 1.0),
                                     vec4( 0.5, -y, 1.0, 1.0),
                                     vec4( 0.0,  y, 1.0, 1.0));
    gl_Position = vertices[gl_VertexID];

    const vec4 colors[3] = vec4[3](vec4(1.0, 0.0, 0.0, 1.0),
                                   vec4(0.0, 1.0, 0.0, 1.0),
                                   vec4(0.0, 0.0, 1.0, 1.0));
    vert_color = colors[gl_VertexID];
}

src/main.cpp

#include <glad/glad.h>
// glad.h must be included before glfw3.h
#include <GLFW/glfw3.h>

#include "simpleshader.h"

#include <iostream>
#include <string>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <optional>
#include <vector>

namespace fs = std::filesystem;
using namespace simpleshader;

std::optional<ShaderProgram> setup_shaders() {
    auto const vert_path = "shaders/hello_triangle.vert";
    auto const frag_path = "shaders/hello_triangle.frag";

    auto vert_src = read_to_string(vert_path);
    if (!vert_src) {
        std::cerr << "Error: failed to read file " << vert_path << '\n';
        return {};
    }

    auto frag_src = read_to_string(frag_path);
    if (!frag_src) {
        std::cerr << "Error: failed to read file " << frag_path << '\n';
        return {};
    }

    ShaderObject vert{GL_VERTEX_SHADER, *vert_src};
    if (!vert.ok()) {
        print_compilation_errors(vert, vert_path);
        return {};
    }

    ShaderObject frag{GL_FRAGMENT_SHADER, *frag_src};
    if (!frag.ok()) {
        print_compilation_errors(frag, frag_path);
        return {};
    }

    ShaderProgram program{vert, frag};
    if (!program.ok()) {
        print_link_errors(program);
        return {};
    }

    return program;
}

int main() {
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    
    GLFWwindow *window = glfwCreateWindow(800, 800, "Hello Triangle!", nullptr, nullptr);
    if (!window) {
        std::cerr << "Error: GLFW failed to create window\n" << std::endl;
        glfwTerminate();
        std::exit(1);
    }
    
    glfwMakeContextCurrent(window);
    
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cerr << "Error: glad failed to initialize OpenGL context\n";
        glfwTerminate();
        std::exit(1);
    }
    
    auto const version = glGetString(GL_VERSION);
    std::cout << "OpenGL version: " << version << '\n';
    
    auto shader_program = setup_shaders();
    if (!shader_program) {
        std::cerr << "Error: failed to setup shader program\n";
        glfwTerminate();
        std::exit(1);
    }
    
    GLuint VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    
    while (!glfwWindowShouldClose(window)) {
        glClearColor(0.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        
        glUseProgram(shader_program->name());
        glDrawArrays(GL_TRIANGLES, 0, 3);
        
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    shader_program->clear();

    glfwTerminate();

    std::clog << "Program finished successfully\n";
}

src/simpleshader.h

#ifndef SIMPLESHADER_H
#define SIMPLESHADER_H

#include <glad/glad.h>

#include <filesystem>
#include <optional>
#include <boost/noncopyable.hpp>

namespace simpleshader {

class ShaderObject : boost::noncopyable {
  public:
    ShaderObject(GLenum type, std::string const &src);
    ~ShaderObject();
    ShaderObject(ShaderObject &&other) noexcept;
    ShaderObject &operator=(ShaderObject &&other) noexcept;

    bool ok() const;
    GLuint name() const;

  private:
      GLenum type_;
      GLuint name_;
};

class ShaderProgram : boost::noncopyable {
  public:
    ShaderProgram(ShaderObject const &vert, ShaderObject const &frag);
    ~ShaderProgram();
    ShaderProgram(ShaderProgram &&other) noexcept;
    ShaderProgram &operator=(ShaderProgram &&other) noexcept;

    bool ok() const;
    GLuint name() const;
    void clear();

  private:
    GLuint name_;
};

std::optional<std::string> read_to_string(std::filesystem::path const &path);

void print_compilation_errors(ShaderObject const &shader, std::filesystem::path const &path);

void print_link_errors(ShaderProgram const &program);

}  // namespace simpleshader

#endif

src/simpleshader.cpp

#include "simpleshader.h"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <string>
#include <vector>

namespace fs = std::filesystem;

namespace /*anon*/ {

bool compile_shader(GLuint name, GLchar const *src) {
    GLchar const *src_arr[] = {src};
    glShaderSource(name, 1, src_arr, nullptr);
    glCompileShader(name);

    GLint success;
    glGetShaderiv(name, GL_COMPILE_STATUS, &success);
    return success;
}

bool link_shaders(GLuint program, GLuint vert, GLuint frag) {
    glAttachShader(program, vert);
    glAttachShader(program, frag);

    glLinkProgram(program);

    glDetachShader(program, frag);
    glDetachShader(program, vert);

    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    return success;
}

}  // namespace

namespace simpleshader {

std::optional<std::string> read_to_string(fs::path const &path) {
    if (std::ifstream ifs{path}) {
        return std::string(std::istreambuf_iterator<char>{ifs}, {});
    } else {
        return std::nullopt;
    }
}

void print_compilation_errors(ShaderObject const &shader,
                              fs::path const &path) {
    GLint log_length;
    glGetShaderiv(shader.name(), GL_INFO_LOG_LENGTH, &log_length);
    if (log_length > 0) {
        std::vector<GLchar> log(log_length);
        glGetShaderInfoLog(shader.name(), log.size(), nullptr, log.data());
        std::cerr << path.string() << ':' << log.data() << '\n';
    }
}

void print_link_errors(ShaderProgram const &program) {
    GLint log_length;
    glGetProgramiv(program.name(), GL_INFO_LOG_LENGTH, &log_length);
    if (log_length > 0) {
        std::vector<GLchar> log(log_length);
        glGetProgramInfoLog(program.name(), log.size(), nullptr, log.data());
        std::cerr << log.data() << '\n';
    }
}

ShaderObject::ShaderObject(GLenum type, std::string const &src)
        : type_{type}, name_{} {
    name_ = glCreateShader(type_);
    compile_shader(name_, src.c_str());
}

ShaderObject::~ShaderObject() {
    glDeleteShader(name_);
}

ShaderObject::ShaderObject(ShaderObject &&other) noexcept
        : type_{other.type_}, name_{other.name_} {
    type_ = 0;  // nothing?
    name_ = 0;
}

ShaderObject &ShaderObject::operator=(ShaderObject &&other) noexcept {
    using std::swap;
    swap(type_, other.type_);
    swap(name_, other.name_);
    return *this;
}

bool ShaderObject::ok() const {
    GLint success;
    glGetShaderiv(name_, GL_COMPILE_STATUS, &success);
    return success;
}

GLuint ShaderObject::name() const {
    return name_;
}

ShaderProgram::ShaderProgram(ShaderObject const &vert, ShaderObject const &frag)
        : name_{} {
    name_ = glCreateProgram();
    link_shaders(name_, vert.name(), frag.name());
}

ShaderProgram::~ShaderProgram() {
    glDeleteProgram(name_);
}

ShaderProgram::ShaderProgram(ShaderProgram &&other) noexcept
        : name_{other.name_} {
    other.name_ = 0;
}

ShaderProgram &ShaderProgram::operator=(ShaderProgram &&other) noexcept {
    using std::swap;
    swap(name_, other.name_);
    return *this;
}

bool ShaderProgram::ok() const {
    GLint success;
    glGetProgramiv(name_, GL_LINK_STATUS, &success);
    return success;
}

GLuint ShaderProgram::name() const {
    return name_;
}

void ShaderProgram::clear() {
    glDeleteProgram(name_);
    name_ = 0;
}

}  // namespace simpleshader