OpenGLの修行 #5 - 続・三角形を描く (1)
ソースコード
プロジェクトレイアウト
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
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/*
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