OpenGLで三角形を表示してみる
はじめに
この記事はシリーズ記事です。目次はこちら。
この記事ではOpenGLで三角形を描画するところまでを行います。
プログラムの変更点
前回の記事からの変更点を次に示します。
includeするファイルの追加
まずはincludeするファイルの追加です。
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <fstream>#include <glm/glm.hpp>#include <iostream>
#include <string>#include <vector>
...
後に使うヘッダファイルをインクルードしました。
main関数の変更点
main関数の内部に次のように追記します。
...
// OpenGL エラーのコールバック
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(
[](auto source, auto type, auto id, auto severity, auto length,
const auto* message, const void* userParam) {
auto t = type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "";
std::cerr << "GL CALLBACK: " << t << " type = " << type
<< ", severity = " << severity << ", message = " << message
<< std::endl;
},
0);
// Shaderプログラムの作成 const GLuint program = createProgram("shader.vert", "shader.frag"); // 表示する三角形の作成 const std::vector<glm::vec3> vertices = { glm::vec3(0.0f, 0.5f, 0.0f), glm::vec3(-0.5f, -0.5f, 0.0f), glm::vec3(0.5f, -0.5f, 0.0f), }; const std::vector<glm::vec3> colors = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), }; // VAOの作成とバインド GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); // VBOを作成 GLuint verticesVbo; glGenBuffers(1, &verticesVbo); glBindBuffer(GL_ARRAY_BUFFER, verticesVbo); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, static_cast<void*>(0)); GLuint colorsVbo; glGenBuffers(1, &colorsVbo); glBindBuffer(GL_ARRAY_BUFFER, colorsVbo); glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec3), &colors[0], GL_STATIC_DRAW); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, static_cast<void*>(0));
// メインループ
while (glfwWindowShouldClose(window) == GL_FALSE) {
glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, vertices.size());
glfwSwapBuffers(window);
glfwPollEvents();
}
...
次の部分でシェーダをコンパイルし、programを取得しています。
createProgram
は後ほど説明しますが、引数はシェーダファイルのパスです。
シェーダファイルについても後ほど説明します。
// Shaderプログラムの作成
const GLuint program = createProgram("shader.vert", "shader.frag");
次に三角形のメッシュを作成しています。
次の部分でメッシュを構築する頂点属性をvectorに格納しています。
// 表示する三角形の作成
const std::vector<glm::vec3> vertices = {
glm::vec3(0.0f, 0.5f, 0.0f),
glm::vec3(-0.5f, -0.5f, 0.0f),
glm::vec3(0.5f, -0.5f, 0.0f),
};
const std::vector<glm::vec3> colors = {
glm::vec3(1.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f),
glm::vec3(0.0f, 0.0f, 1.0f),
};
次の部分でVAOを作成しバインドしています。 VAOを利用するとVBOを個別にバインドする手間が省けます。
// VAOの作成とバインド
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
次の部分で頂点位置のVBOを作成しています。 先のVAOをバインドした状態でVBOを作成しているので、このVBOは先に作成したVAOに属します。
// VBOを作成
GLuint verticesVbo;
glGenBuffers(1, &verticesVbo);
glBindBuffer(GL_ARRAY_BUFFER, verticesVbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3),
&vertices[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, static_cast<void*>(0));
glGenBuffers
でVBOを作成し、glBindBuffer
でバインドした後にglBufferData
でデータを流し込んでいます。
この頂点属性はシェーダで0
番目として受け取りたいのでglEnableVertexAttribArray
およびglVertexAttribPointer
の第一引数には0
を指定しています。
次の部分で頂点カラーを転送しています。
GLuint colorsVbo;
glGenBuffers(1, &colorsVbo);
glBindBuffer(GL_ARRAY_BUFFER, colorsVbo);
glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec3), &colors[0],
GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, static_cast<void*>(0));
頂点カラー用のVBOを作成し、1
番のattributeとして渡しています。
メインループではglClear
でカラーバッファをクリアした後、glUseProgram
でシェーダプログラムを有効にして、glBindVertexArray
でVAOをバインドし、glDrawArrays
で三角形を描画しています。
// メインループ
while (glfwWindowShouldClose(window) == GL_FALSE) {
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, vertices.size());
glfwSwapBuffers(window);
glfwPollEvents();
}
createProgram関数
main関数の他にcreateProgram
関数を作成しました。
createProgram
は次のとおりです。
GLuint createProgram(std::string vertexShaderFile,
std::string fragmentShaderFile) {
// 頂点シェーダの読み込み
std::ifstream vertexIfs(vertexShaderFile, std::ios::binary);
if (vertexIfs.fail()) {
std::cerr << "Error: Can't open source file: " << vertexShaderFile
<< std::endl;
return 0;
}
auto vertexShaderSource =
std::string(std::istreambuf_iterator<char>(vertexIfs),
std::istreambuf_iterator<char>());
if (vertexIfs.fail()) {
std::cerr << "Error: Can't read source file: " << vertexShaderFile
<< std::endl;
return 0;
}
const GLchar* vertexShaderSourcePointer = vertexShaderSource.c_str();
// フラグメントシェーダの読み込み
std::ifstream fragmentIfs(fragmentShaderFile, std::ios::binary);
if (fragmentIfs.fail()) {
std::cerr << "Error: Can't open source file: " << fragmentShaderFile
<< std::endl;
return 0;
}
auto fragmentShaderSource =
std::string(std::istreambuf_iterator<char>(fragmentIfs),
std::istreambuf_iterator<char>());
if (fragmentIfs.fail()) {
std::cerr << "Error: Can't read source file: " << fragmentShaderFile
<< std::endl;
return 0;
}
const GLchar* fragmentShaderSourcePointer = fragmentShaderSource.c_str();
// プログラムオブジェクトを作成
const GLuint program = glCreateProgram();
GLint status = GL_FALSE;
GLsizei infoLogLength;
// 頂点シェーダのコンパイル
const GLuint vertexShaderObj = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderObj, 1, &vertexShaderSourcePointer, nullptr);
glCompileShader(vertexShaderObj);
glAttachShader(program, vertexShaderObj);
// 頂点シェーダのチェック
glGetShaderiv(vertexShaderObj, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
std::cerr << "Compile Error in Vertex Shader." << std::endl;
glGetShaderiv(vertexShaderObj, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 1) {
std::vector<GLchar> vertexShaderErrorMessage(infoLogLength);
glGetShaderInfoLog(vertexShaderObj, infoLogLength, nullptr,
vertexShaderErrorMessage.data());
std::cerr << vertexShaderErrorMessage.data() << std::endl;
}
glDeleteShader(vertexShaderObj);
// フラグメントシェーダのコンパイル
const GLuint fragmentShaderObj = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderObj, 1, &fragmentShaderSourcePointer, nullptr);
glCompileShader(fragmentShaderObj);
glAttachShader(program, fragmentShaderObj);
// フラグメントシェーダのチェック
glGetShaderiv(fragmentShaderObj, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
std::cerr << "Compile Error in Fragment Shader." << std::endl;
glGetShaderiv(fragmentShaderObj, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 1) {
std::vector<GLchar> fragmentShaderErrorMessage(infoLogLength);
glGetShaderInfoLog(fragmentShaderObj, infoLogLength, nullptr,
fragmentShaderErrorMessage.data());
std::cerr << fragmentShaderErrorMessage.data() << std::endl;
}
glDeleteShader(fragmentShaderObj);
// プログラムのリンク
glLinkProgram(program);
// リンクのチェック
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) std::cerr << "Link Error." << std::endl;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 1) {
std::vector<GLchar> programLinkErrorMessage(infoLogLength);
glGetProgramInfoLog(program, infoLogLength, nullptr,
programLinkErrorMessage.data());
std::cerr << programLinkErrorMessage.data() << std::endl;
}
return program;
}
最初に頂点シェーダのファイルの中身をテキストとして読み込んでいます。
C形式の文字列が必要なため、c_str()
で取得しています。
// 頂点シェーダの読み込み
std::ifstream vertexIfs(vertexShaderFile, std::ios::binary);
if (vertexIfs.fail()) {
std::cerr << "Error: Can't open source file: " << vertexShaderFile
<< std::endl;
return 0;
}
auto vertexShaderSource =
std::string(std::istreambuf_iterator<char>(vertexIfs),
std::istreambuf_iterator<char>());
if (vertexIfs.fail()) {
std::cerr << "Error: Can't read source file: " << vertexShaderFile
<< std::endl;
return 0;
}
const GLchar* vertexShaderSourcePointer = vertexShaderSource.c_str();
同様にしてフラグメントシェーダのファイルの中身をテキストとして読み込んでいます。
// フラグメントシェーダの読み込み
std::ifstream fragmentIfs(fragmentShaderFile, std::ios::binary);
if (fragmentIfs.fail()) {
std::cerr << "Error: Can't open source file: " << fragmentShaderFile
<< std::endl;
return 0;
}
auto fragmentShaderSource =
std::string(std::istreambuf_iterator<char>(fragmentIfs),
std::istreambuf_iterator<char>());
if (fragmentIfs.fail()) {
std::cerr << "Error: Can't read source file: " << fragmentShaderFile
<< std::endl;
return 0;
}
const GLchar* fragmentShaderSourcePointer = fragmentShaderSource.c_str();
次にprogramオブジェクトを作成しています。
// プログラムオブジェクトを作成
const GLuint program = glCreateProgram();
次のようにして頂点シェーダをコンパイルし、プログラムオブジェクトにアタッチします。
// 頂点シェーダのコンパイル
const GLuint vertexShaderObj = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderObj, 1, &vertexShaderSourcePointer, nullptr);
glCompileShader(vertexShaderObj);
glAttachShader(program, vertexShaderObj);
次のようにして頂点シェーダのコンパイルなどでエラーが出ていないかをチェックします。
// 頂点シェーダのチェック
glGetShaderiv(vertexShaderObj, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
std::cerr << "Compile Error in Vertex Shader." << std::endl;
glGetShaderiv(vertexShaderObj, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 1) {
std::vector<GLchar> vertexShaderErrorMessage(infoLogLength);
glGetShaderInfoLog(vertexShaderObj, infoLogLength, nullptr,
vertexShaderErrorMessage.data());
std::cerr << vertexShaderErrorMessage.data() << std::endl;
}
すでにシェーダオブジェクトはプログラムオブジェクトにアタッチして必要なくなったので次のようにして削除します。
glDeleteShader(vertexShaderObj);
次のようにしてフラグメントシェーダについても同様にコンパイルしエラーチェックをします。
// フラグメントシェーダのコンパイル
const GLuint fragmentShaderObj = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderObj, 1, &fragmentShaderSourcePointer, nullptr);
glCompileShader(fragmentShaderObj);
glAttachShader(program, fragmentShaderObj);
// フラグメントシェーダのチェック
glGetShaderiv(fragmentShaderObj, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
std::cerr << "Compile Error in Fragment Shader." << std::endl;
glGetShaderiv(fragmentShaderObj, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 1) {
std::vector<GLchar> fragmentShaderErrorMessage(infoLogLength);
glGetShaderInfoLog(fragmentShaderObj, infoLogLength, nullptr,
fragmentShaderErrorMessage.data());
std::cerr << fragmentShaderErrorMessage.data() << std::endl;
}
glDeleteShader(fragmentShaderObj);
次にアタッチしたシェーダをプログラムにリンクします。
// プログラムのリンク
glLinkProgram(program);
リンクでエラーが出ていないかを次のようにしてチェックします。
// リンクのチェック
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) std::cerr << "Link Error." << std::endl;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 1) {
std::vector<GLchar> programLinkErrorMessage(infoLogLength);
glGetProgramInfoLog(program, infoLogLength, nullptr,
programLinkErrorMessage.data());
std::cerr << programLinkErrorMessage.data() << std::endl;
}
最後に作成したprogramのIDを返しています。
return program;
頂点シェーダ
プロジェクトにshader.vert
というファイルを作成します。
作成したファイルに次のように記述します。
#version 460
layout (location = 0) in vec4 position;
layout (location = 1) in vec3 vertexColor;
out vec3 color;
void main() {
color = vertexColor;
gl_Position = position;
}
最初の#version 460
はGLSLのバージョン情報です。
次の部分では頂点属性(attribute)を記述しています。
layout (location = 0) in vec4 position;
layout (location = 1) in vec3 vertexColor;
layout (location = 0)
で0番目の頂点属性を受け取り、layout (location = 1)
で1番目の頂点属性を受け取ります。
main.cppのmain関数内でglVertexAttribPointer
を利用して0番目に頂点位置を、1番目に頂点カラーを割り当てました。
その頂点属性を受け取っています。
out vec3 color;
でフラグメントシェーダに受け渡す変数を定義しています。
フラグメントシェーダには色情報を受け渡します。
shader.vertのmain関数では先に定義したフラグメントシェーダに受け渡すcolor
に頂点カラーを、組み込みのgl_Position
に頂点位置を代入しています。
void main() {
color = vertexColor;
gl_Position = position;
}
フラグメントシェーダ
プロジェクトにshader.frag
というファイルを作成します。
作成したファイルに次のように記述します。
#version 460
in vec3 color;
layout (location = 0) out vec4 fragment;
void main() {
fragment = vec4(color, 1.0);
}
最初の#version 460
はGLSLのバージョン情報です。
次の部分で頂点シェーダから渡される色情報を受け取る変数を定義しています。
in vec3 color;
次の部分でフレームバッファに書き込む色情報の変数を定義しています。
layout (location = 0) out vec4 fragment;
locationを指定しています。 今回はデフォルトフレームバッファに書き込んでいるのでそれを指定していることになります。
main関数では出力のfragment
に頂点シェーダから渡されてきた色を渡しています。
void main() {
fragment = vec4(color, 1.0);
}
実行結果
プログラムを実行すると次のようになります。
頂点カラーがフラグメントシェーダに渡される過程で補間されるので、グラデーションになります。
プログラム全文
プログラム全文はGitHubにアップロードしてあります。
GitHub: MatchaChoco010/OpenGL-PBR-Map at v1
参考資料
今回は細かい解説はだいぶ省いてしまいました。 細かい部分について知りたい場合は次の資料がとても役に立ちます。