はじめに
前回記事 C++でOpenGLを使ってゲーム用ライブラリを作った話 #1
この連載のリポジトリ
今回はArticle2
フォルダの内容です。
宣伝
今回の連載の元ネタにあたる、筆者が実際に作成したライブラリです。
気が向いたら覗いてみてください。(スターしてくれると筆者がとっても喜びます✨)
TL;DR
VAO、VBO、IBOを自作クラスでラッピングしていい感じにしたよ。以上
(今回のコード全文)
本記事の内容
今回の目標
なんだかんだOpenGLの記事、第3回ですね。
今回は前回、前々回と放置してきたバッファに手をつけようと思います。今回の記事では説明が多くなってしまい少々苦しいですが、頑張っていきましょう〜
クラスの設計
前回と同じ感じで適当に書いていきましょう〜
Buffer
は頂点データの格納と描画ができればいいので今回の記事では下みたいな設計にしました。
コンストラクタ
バッファのタイプを設定して描画に必要なバッファを生成する。バッファのタイプはGL_XXX_YYY
で表される。(詳しい説明はKhronos-glBufferData-を参照)
- データタイプ(
XXX
の部分)
名前 | 説明 |
---|---|
STREAM | 内容の変更が1回で使われる(今回の場合は描画される)回数が少ない |
STATIC | 内容の変更が1回で使われる(今回の場合は描画される)回数が多い |
DYNAMIC | 内容の変更が高頻度で使われる(今回の場合は描画される)回数が多い |
- アクセスタイプ(
YYY
の部分)
名前 | 説明 |
---|---|
DRAW | アプリケーション側からデータが変更され、OpenGL側から読み出される(描画される・コピーされる) |
READ | OpenGL側からデータが変更され、アプリケーション側から読み出される |
COPY | OpenGL内だけでデータのコピーが行われる(つまりアプリケーション側からはノータッチ) |
デストラクタ
生成したバッファを破棄してリソースを解放する。
GetSize()関数
現在のバッファサイズを取得する。(特に利用用途はない)
AddAttribute()関数
頂点属性を追加する。前々回の記事に色々書きましたがもう一回載せときます。
-
VAO
複数のVBO
をまとめて管理するためのオブジェクトで複数のVBO
を利用する場合に使うと便利。(今回の場合はVBO
を1つしか利用しない実装なのであんまり意味がないです。) -
VBO
図形の頂点情報(位置や色など)を格納して置くためのバッファを管理するオブジェクトでこれが無いと図形を描画することができません。 -
IBO(これは今回が初出)
頂点情報をどんなふうに組み合わせて図形を組み立てるかを指定するためのもの。これを使うと頂点情報の重複を避けてバッファ効率を上げることができます。(今は特に気にしなくて大丈夫です)
SetBuffer()関数
VBO
を初期化してデータを送信する。送信時にVBO
が保持するバッファのサイズを送信するデータのサイズにリサイズしてから送信する。
構成済みの頂点データが渡されることを想定。(構成済みとは関数内で特に順番を変える等の処理を行わなくても良い状態を指します)
SetIndices()関数
IBO
にインデックスデータを送信する。送信時にIBO
が保持するインデックスバッファのサイズをリサイズしてから送信する。インデックスの要素数が描画時に必要なのでこの関数の呼び出し時に保存しておく。
ReloadBuffer()関数
VBO
のデータを更新する。更新時にはVBO
のバッファのサイズは変更されない。送信するデータのサイズがVBO
のバッファのサイズと異なる場合はエラーを返す。
Draw()関数
指定された描画モードでバッファを描画する。
この関数が呼び出される前にSetBuffer()
関数とSetIndices()
関数が少なくとも1回は呼び出されている必要がある。(呼び出していない場合は何も描画されない)
ここからは実際にコードを書いていきましょう〜
まずはヘッダファイル(includes/Article2/buffer.hpp
)の中身
#ifndef __BUFFER_HPP__
#define __BUFFER_HPP__
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <vector>
class Buffer {
private:
GLuint mVAO;
GLuint mVBO;
GLuint mIBO;
GLuint mIndexCount; // インデックスの要素数
unsigned mSize;
int mBufferType;
public:
Buffer(int bufferType);
~Buffer();
unsigned const &GetSize() const { return mSize; }
void SetBuffer(std::vector<float> data);
void SetIndices(std::vector<unsigned> indices);
void AddAttribute(int loc, int comps, int offset);
void ReloadBuffer(std::vector<float> data);
void Draw(int drawmode);
};
#endif // __BUFFER_HPP__
次に、ソースファイル(impls/Article2/buffer.cpp
)の中身
#include <stdexcept>
#include "../../includes/Article2/buffer.hpp"
Buffer::Buffer(int bufferType) {
// VAO, VBO, IBOの生成
glGenVertexArrays(1, &mVAO);
glGenBuffers(1, &mVBO);
glGenBuffers(1, &mIBO);
glBindVertexArray(mVAO); // VAOをバインド
// VBOをバインド(バインドされているVAOにVBOを紐付け)
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// IBOをバインド(バインドされているVAOにIBOを紐付け)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);
mIndexCount = 0;
mSize = 0;
mBufferType = bufferType;
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
Buffer::~Buffer() {
// バッファの削除
glDeleteVertexArrays(1, &mVAO);
glDeleteBuffers(1, &mVBO);
glDeleteBuffers(1, &mIBO);
}
void Buffer::SetBuffer(std::vector<float> data) {
mSize = data.size();
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// データをVBOに転送
glBufferData(GL_ARRAY_BUFFER, mSize * sizeof(float), data.data(),
mBufferType);
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void Buffer::SetIndices(std::vector<unsigned> indices) {
mIndexCount = indices.size();
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);
// データをIBOに転送
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndexCount * sizeof(unsigned),
indices.data(), mBufferType);
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void Buffer::AddAttribute(int loc, int comps, int offset) {
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// 頂点属性の設定
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, comps, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<void *>(offset));
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void Buffer::ReloadBuffer(std::vector<float> data) {
if (data.size() !=
mSize) { // データサイズがバッファサイズと一致しない場合はエラー
throw std::runtime_error("Data size does not match buffer size");
}
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// データをVBOに転送
glBufferSubData(GL_ARRAY_BUFFER, 0, mSize * sizeof(float), data.data());
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void Buffer::Draw(int drawmode) {
if (mIndexCount == 0) {
throw std::runtime_error("No indices to draw");
}
glBindVertexArray(mVAO); // VAOをバインド
// バッファを描画(VAOにIBOが紐付いているのでIBOの情報を使って描画)
glDrawElements(drawmode, mIndexCount, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0); // VAOのバインドを解除
}
前回の記事と同じ感じで、main()
関数にベタ書きされていた内容をBuffer
クラスの各メソッドに切り出しています。今回も各メソッドの処理内容はコメントに書いてありますが、少し複雑な部分があるので次の章で説明します。
(特に今後の処理には関係無いので読み飛ばしても大丈夫です〜)
また、今回のプログラムはクラスのデストラクタを利用する関係上、glfwTerminate()
関数の呼び出しを工夫しないと実行後にSegmentation fault
を吐きます。これを解決するためにinit.hpp
とinit.cpp
を書き換えています。
init.hppの変更
まず、`init.hpp`の中身#ifndef __INIT_HPP__
#define __INIT_HPP__
#include <glad/gl.h>
#include <GLFW/glfw3.h>
class OpenGLContext {
private:
static bool sGLFWInitialized;
static bool sGLADInitialized;
public:
OpenGLContext(unsigned major, unsigned minor);
~OpenGLContext();
public:
static void InitGLAD();
};
#endif // __INIT_HPP__
次に、init.cpp
の中身
#include "../../includes/Article2/init.hpp"
#include <stdexcept>
bool OpenGLContext::sGLFWInitialized = false;
bool OpenGLContext::sGLADInitialized = false;
OpenGLContext::OpenGLContext(unsigned major, unsigned minor) {
if (!glfwInit()) {
throw std::runtime_error("Failed to initialize GLFW");
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}
OpenGLContext::~OpenGLContext() {
if (OpenGLContext::sGLADInitialized) {
glfwTerminate();
}
}
void OpenGLContext::InitGLAD() {
if (!OpenGLContext::sGLADInitialized) {
if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) {
throw std::runtime_error("Failed to initialize GLAD");
}
OpenGLContext::sGLADInitialized = true;
}
}
OpenGLContext
クラスのインスタンスをmain()
関数の最初の部分で作成すると自動的にglfw
の初期化と終了を適切なタイミングで呼び出されるようにしました。ついでにInitGLAD()
関数も静的関数として取り込んで、window.hpp
内のInitGLAD()
関数の呼び出しも置き換えました。
バッファの説明
まず#0
で少しだけ説明した頂点属性について説明します。
OpenGLの仕様では1つの頂点属性には次の表にある6つの情報を指定する必要があります。
引数名 | 型 | 説明 |
---|---|---|
index | int | 頂点属性のインデックス |
size | int | 頂点属性に含まれる要素数 |
type | int | 頂点属性に含まれる要素の型 |
normalized | int | 頂点属性に含まれる要素が正規化されているか |
stride | int | 頂点1つ分のデータ長 |
pointer | void* | 頂点属性のオフセット |
-
index
はその頂点属性が何番目の頂点属性であるかを表しています。この値を利用して頂点属性を制御します(glEnableVertexAttribArray()
関数が代表例)。下の図でそれぞれの頂点属性に注目すると位置
は0
、法線
は1
、テクスチャ座標
は2
になります -
size
はその頂点属性を構成する要素の数を表しています。下の図でそれぞれの頂点属性に注目すると位置
と法線
はx
,y
,z
の3つの要素で構成されているので3
、テクスチャ座標
はu
,v
の2つの要素で構成されているので2
になります -
type
はその頂点属性を構成する要素の型を表しています。下の図では全てfloat
型で格納されるべき値なのでGL_FLOAT
を指定します。この他にもGL_UNSIGNED_BYTE
等が使用できます。詳細はKhronos Group -OpenGL Type-を参照してください -
normalized
はその頂点属性を構成する要素が正規化されているかを指定します。今回は特に正規化が行われていないことを想定するのでGL_FALSE
を指定します。(基本的にはGL_FALSE
で問題ないと思います) -
stride
は頂点1つ分のデータ長を表しています。下の図では位置
が3*sizeof(float)
、法線
が3*sizeof(float)
、テクスチャ座標
が2*sizeof(float)
で合計32バイト
となり、1つの頂点は32バイト
のデータで構成されていることがわかるので32
になります -
pointer
はその頂点属性がバッファの先頭からどれだけ離れているかを表しています。下の図でそれぞれの頂点属性に注目すると位置
は先頭にあるので0
、法線
は位置
の分だけ離れているので3*sizeof(float) = 12
、テクスチャ座標
は位置
と法線
の分だけ離れているので3*sizeof(float)+3*sizeof(float)=24
になります
(型はvoid*
が指定されていますが、実際にはバッファ先頭からの距離を整数値で表したものをポインタにキャストしたものを渡します)
頂点属性のイメージ
もうすでにお腹一杯になるくらい説明した気もしますがもう少々お付き合いください。
(Buffer
関連でやることが多いのもOpenGL
の勉強が大変な要因だと思う)
次にglBindVertex()
関数とglBindBuffer()
関数について説明します。これらの関数はOpenGL
のバッファスロットに特定のバッファをセットしたり、外したりするために呼び出します。
下の図のようにVBO
やIBO
といったバッファはglBindBuffer()
関数によってバインドされたときにバインドされているVAO
に帰属します。ここで注意しなければいけないのが関数を呼び出す順番です。VAO
バインドされている間にVBO
をアンバインドしてしまうと紐付けが解除されてしまうので、アンバインドするときは VAO
→VBO
&IBO
の順で行う必要があります。これを適切に制御しないとデータの変更が反映されなかったり、要らん変更が反映されたりして面倒なことになります。
じゃあ、「バッファをアンバインドしないでバインドしっぱなしすれば良いじゃん!」と思うかもしれませんが、残念なことにこれができないんですね。OpenGL
の仕様で同時にバインドできるバッファは各種類につき1つまでになっています。
(このあたりの仕様が意外と厄介で筆者はこれに散々苦しめられました...)
また、頂点属性も バインドされているVBO
に帰属するのでVBO
がバインドされている状態で頂点属性の設定関数を呼び出さないと、せっかくの設定が全く反映されないことになります。
(筆者はこの仕様にも散々はまり、4回位挫折しました...)
加えて、glBufferData()
関数などのバッファに対して制御を行う関数もバインドされているVBO
に対して変更を加えてくるので変更したいバッファを呼び出し前にバインドしておく必要があります。
バインドのイメージ
今回はなかなか説明が長くなってしまいました...
では、最後にmain()
関数を書き換えて変更を反映して締めくくりましょう!
変更の反映
まず今回作成したヘッダファイルをインクルードしましょう。
...
+ #include "../../includes/Article2/buffer.hpp"
#include "../../includes/Article2/init.hpp"
#include "../../includes/Article2/window.hpp"
...
続けて、初期化部分やらバッファの生成部分やらを書き換えましょう〜
...
int main() {
// GLFWの初期化
- if (!glfwInit()) {
- std::cerr << "Failed to initialize GLFW" << std::endl;
- return -1;
- }
- // OpenGLのバージョン設定 (3.3 Core Profile)
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
- glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+ OpenGLContext context(3, 3);
// 三角形の頂点データ
- float vertices[] = {
- 0.0f, 0.5f, 0.0f, // 上
- -0.5f, -0.5f, 0.0f, // 左下
- 0.5f, -0.5f, 0.0f // 右下
- };
+ std::vector<float> vertices = {
+ 0.0f, 0.5f, 0.0f, // 上
+ -0.5f, -0.5f, 0.0f, // 左下
+ 0.5f, -0.5f, 0.0f // 右下
+ };
- // VAOとVBOの作成
- unsigned int VAO, VBO;
- glGenVertexArrays(1, &VAO);
- glGenBuffers(1, &VBO);
- // VAOの設定
- glBindVertexArray(VAO);
- // VBOの設定
- glBindBuffer(GL_ARRAY_BUFFER, VBO);
- glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
- // 頂点属性の設定
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
- glEnableVertexAttribArray(0);
+ Buffer buffer(GL_STATIC_DRAW);
+ buffer.SetBuffer(vertices); // データをバッファにセット
+ buffer.AddAttribute(0, 3, 0); // 頂点属性を追加
+ buffer.SetIndices({0, 1, 2}); // インデックスをセット
...
最後に描画ループと終了処理を書き換えておしまいです。
...
// メインループ
while (!window.IsClosed()) {
...
// 三角形の描画
- glBindVertexArray(VAO);
- glDrawArrays(GL_TRIANGLES, 0, 3);
+ buffer.Draw(GL_TRIANGLES);
// バッファのスワップとイベントのポーリング
window.Update();
}
- // リソースの解放
- /* glDeleteVertexArrays(1, &VAO); */
- /* glDeleteBuffers(1, &VBO); */
- /* glDeleteProgram(shaderProgram); */
- // GLFWの終了処理
- /* glfwTerminate(); */
return 0;
}
...
バッファの生成と初期化の部分がだいぶスッキリとして、頂点属性の設定部分も見やすくなりましたね!描画部分はあまり変化がありませんが、何をやっているかが少しわかりやすくなりましたね。これで今回の記事でやることはおわりです!お疲れ様でした!
おわりに
今回はOpenGL
のバッファについて色々とやって来ましたが、今までの記事とは違いつまらん説明が大量に生成されてしまいました...
次回はもう少し軽い感じの記事にしたいなぁとは思っているのですが、いい加減シェーダーに手をつけないと行けない気がしているので今回と同じくらい説明だらけになる予感...(つらい)
それではまた次の記事でお会いしましょう。(^^)ノシ
コード全文
buffer.hpp
#ifndef __BUFFER_HPP__
#define __BUFFER_HPP__
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <vector>
class Buffer {
private:
GLuint mVAO;
GLuint mVBO;
GLuint mIBO;
GLuint mIndexCount; // インデックスの要素数
unsigned mSize;
int mBufferType;
public:
Buffer(int bufferType);
~Buffer();
unsigned const &GetSize() const { return mSize; }
void SetBuffer(std::vector<float> data);
void SetIndices(std::vector<unsigned> indices);
void AddAttribute(int loc, int comps, int offset);
void ReloadBuffer(std::vector<float> data);
void Draw(int drawmode);
};
#endif // __BUFFER_HPP__
buffer.cpp
#include <stdexcept>
#include "../../includes/Article2/buffer.hpp"
Buffer::Buffer(int bufferType) {
// VAO, VBO, IBOの生成
glGenVertexArrays(1, &mVAO);
glGenBuffers(1, &mVBO);
glGenBuffers(1, &mIBO);
glBindVertexArray(mVAO); // VAOをバインド
// VBOをバインド(バインドされているVAOにVBOを紐付け)
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// IBOをバインド(バインドされているVAOにIBOを紐付け)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);
mIndexCount = 0;
mSize = 0;
mBufferType = bufferType;
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
Buffer::~Buffer() {
// バッファの削除
glDeleteVertexArrays(1, &mVAO);
glDeleteBuffers(1, &mVBO);
glDeleteBuffers(1, &mIBO);
}
void Buffer::SetBuffer(std::vector<float> data) {
mSize = data.size();
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// データをVBOに転送
glBufferData(GL_ARRAY_BUFFER, mSize * sizeof(float), data.data(),
mBufferType);
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void Buffer::SetIndices(std::vector<unsigned> indices) {
mIndexCount = indices.size();
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);
// データをIBOに転送
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndexCount * sizeof(unsigned),
indices.data(), mBufferType);
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void Buffer::AddAttribute(int loc, int comps, int offset) {
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// 頂点属性の設定
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, comps, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<void *>(offset));
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void Buffer::ReloadBuffer(std::vector<float> data) {
if (data.size() !=
mSize) { // データサイズがバッファサイズと一致しない場合はエラー
throw std::runtime_error("Data size does not match buffer size");
}
// バッファをバインド
glBindVertexArray(mVAO);
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
// データをVBOに転送
glBufferSubData(GL_ARRAY_BUFFER, 0, mSize * sizeof(float), data.data());
// バインドを解除
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void Buffer::Draw(int drawmode) {
if (mIndexCount == 0) {
throw std::runtime_error("No indices to draw");
}
glBindVertexArray(mVAO); // VAOをバインド
// バッファを描画(VAOにIBOが紐付いているのでIBOの情報を使って描画)
glDrawElements(drawmode, mIndexCount, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0); // VAOのバインドを解除
}
init.hpp
#ifndef __INIT_HPP__
#define __INIT_HPP__
#include <glad/gl.h>
#include <GLFW/glfw3.h>
class OpenGLContext {
private:
static bool sGLFWInitialized;
static bool sGLADInitialized;
public:
OpenGLContext(unsigned major, unsigned minor);
~OpenGLContext();
public:
static void InitGLAD();
};
#endif // __INIT_HPP__
init.cpp
#include "../../includes/Article2/init.hpp"
#include <stdexcept>
bool OpenGLContext::sGLFWInitialized = false;
bool OpenGLContext::sGLADInitialized = false;
OpenGLContext::OpenGLContext(unsigned major, unsigned minor) {
if (!glfwInit()) {
throw std::runtime_error("Failed to initialize GLFW");
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}
OpenGLContext::~OpenGLContext() {
if (OpenGLContext::sGLADInitialized) {
glfwTerminate();
}
}
void OpenGLContext::InitGLAD() {
if (!OpenGLContext::sGLADInitialized) {
if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) {
throw std::runtime_error("Failed to initialize GLAD");
}
OpenGLContext::sGLADInitialized = true;
}
}
window.hpp
#ifndef __WINDOW_HPP__
#define __WINDOW_HPP__
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <functional>
#include <string>
class Window; // クラスの前方宣言
// コールバック関数の型
typedef std::function<void(Window *, int, int)> FramebufferSizeCallback;
typedef std::function<void(Window *, int, int)> WindowPosCallback;
typedef std::function<void(Window *, int, int)> WindowSizeCallback;
typedef std::function<void(Window *, int)> CursorEnterCallback;
typedef std::function<void(Window *, double, double)> CursorPosCallback;
typedef std::function<void(Window *, double, double)> ScrollCallback;
namespace callback {
// コールバック関数のラッパー
void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height);
void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y);
void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height);
void CursorEnterCallbackWrapper(GLFWwindow *window, int entered);
void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y);
void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset);
} // namespace callback
// ウィンドウクラス
class Window {
private:
GLFWwindow *mWindow;
std::pair<double, double> mCursorPos;
std::pair<double, double> mScroll;
FramebufferSizeCallback mFramebufferSizeCallback;
WindowPosCallback mWindowPosCallback;
WindowSizeCallback mWindowSizeCallback;
CursorEnterCallback mCursorEnterCallback;
CursorPosCallback mCursorPosCallback;
ScrollCallback mScrollCallback;
// コールバック関数のフレンド宣言
friend void(callback::FramebufferSizeCallbackWrapper)(GLFWwindow *, int width,
int height);
friend void(callback::WindowPosCallbackWrapper)(GLFWwindow *window, int x,
int y);
friend void(callback::WindowSizeCallbackWrapper)(GLFWwindow *window,
int width, int height);
friend void(callback::CursorEnterCallbackWrapper)(GLFWwindow *window,
int entered);
friend void(callback::CursorPosCallbackWrapper)(GLFWwindow *window, double x,
double y);
friend void(callback::ScrollCallbackWrapper)(GLFWwindow *window,
double xoffset, double yoffset);
public:
Window(int width, int height, std::string title);
std::pair<double, double> GetCursorPos() const { return mCursorPos; }
std::pair<double, double> GetScroll() const { return mScroll; }
void SetFramebufferSizeCallback(FramebufferSizeCallback callback) {
mFramebufferSizeCallback = callback;
}
void SetWindowPosCallback(WindowPosCallback callback) {
mWindowPosCallback = callback;
}
void SetWindowSizeCallback(WindowSizeCallback callback) {
mWindowSizeCallback = callback;
}
void SetCursorEnterCallback(CursorEnterCallback callback) {
mCursorEnterCallback = callback;
}
void SetCursorPosCallback(CursorPosCallback callback) {
mCursorPosCallback = callback;
}
void SetScrollCallback(ScrollCallback callback) {
mScrollCallback = callback;
}
bool IsPressed(int key) const {
return glfwGetKey(mWindow, key) == GLFW_PRESS;
}
bool IsClicked(int mousebutton) const {
return glfwGetMouseButton(mWindow, mousebutton) == GLFW_PRESS;
}
bool IsClosed() const { return glfwWindowShouldClose(mWindow); }
void Close() { glfwSetWindowShouldClose(mWindow, true); }
void Update();
void Fill(float r, float g, float b, float a);
};
#endif // __WINDOW_HPP__
window.cpp
#include "../../includes/Article2/window.hpp"
#include "../../includes/Article2/init.hpp"
namespace callback {
void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
Window *ptr = (Window *)glfwGetWindowUserPointer(window); // 紐付けたポインタ
if (ptr->mFramebufferSizeCallback) { // コールバック関数が登録されている
ptr->mFramebufferSizeCallback(ptr, width, height);
}
}
void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y) {
Window *ptr = (Window *)glfwGetWindowUserPointer(window);
if (ptr->mWindowPosCallback) {
ptr->mWindowPosCallback(ptr, x, y);
}
}
void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
Window *ptr = (Window *)glfwGetWindowUserPointer(window);
if (ptr->mWindowSizeCallback) {
ptr->mWindowSizeCallback(ptr, width, height);
}
}
void CursorEnterCallbackWrapper(GLFWwindow *window, int entered) {
Window *ptr = (Window *)glfwGetWindowUserPointer(window);
if (ptr->mCursorEnterCallback) {
ptr->mCursorEnterCallback(ptr, entered);
}
}
void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y) {
Window *ptr = (Window *)glfwGetWindowUserPointer(window);
ptr->mCursorPos = {x, y};
if (ptr->mCursorPosCallback) {
ptr->mCursorPosCallback(ptr, x, y);
}
}
void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset) {
Window *ptr = (Window *)glfwGetWindowUserPointer(window);
ptr->mScroll = {xoffset, yoffset};
if (ptr->mScrollCallback) {
ptr->mScrollCallback(ptr, xoffset, yoffset);
}
}
} // namespace callback
Window::Window(int width, int height, std::string title) {
// ウィンドウの作成
mWindow = glfwCreateWindow(width, height, title.c_str(), NULL, NULL);
glfwMakeContextCurrent(mWindow);
OpenGLContext::InitGLAD();
glfwSetWindowUserPointer(mWindow, this); // 自分のポインタをGLFWwindowと紐付け
// コールバックを登録
glfwSetFramebufferSizeCallback(mWindow,
callback::FramebufferSizeCallbackWrapper);
glfwSetWindowPosCallback(mWindow, callback::WindowPosCallbackWrapper);
glfwSetWindowSizeCallback(mWindow, callback::WindowSizeCallbackWrapper);
glfwSetCursorEnterCallback(mWindow, callback::CursorEnterCallbackWrapper);
glfwSetCursorPosCallback(mWindow, callback::CursorPosCallbackWrapper);
glfwSetScrollCallback(mWindow, callback::ScrollCallbackWrapper);
}
void Window::Update() {
// バッファのスワップとイベントのポーリング
glfwSwapBuffers(mWindow);
glfwPollEvents();
}
void Window::Fill(float r, float g, float b, float a) {
// 背景色の設定とクリア
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT);
}
main.cpp
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "../../includes/Article2/buffer.hpp"
#include "../../includes/Article2/init.hpp"
#include "../../includes/Article2/window.hpp"
// 関数宣言
void framebuffer_size_callback(Window *window, int width, int height);
unsigned int createShaderProgram(const char *vertexSource,
const char *fragmentSource);
// 頂点シェーダーのソースコード
const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos, 1.0);
}
)";
// フラグメントシェーダーのソースコード
const char *fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}
)";
int main() {
// GLFWの初期化
OpenGLContext context(3, 3);
// ウィンドウの作成
Window window(800, 600, "OpenGL");
// ビューポートの設定
glViewport(0, 0, 800, 600);
window.SetFramebufferSizeCallback(framebuffer_size_callback);
// シェーダープログラムの作成
unsigned int shaderProgram =
createShaderProgram(vertexShaderSource, fragmentShaderSource);
// 三角形の頂点データ
std::vector<float> vertices = {
0.0f, 0.5f, 0.0f, // 上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, -0.5f, 0.0f // 右下
};
Buffer buffer(GL_STATIC_DRAW);
buffer.SetBuffer(vertices); // データをバッファにセット
buffer.AddAttribute(0, 3, 0); // 頂点属性を追加
buffer.SetIndices({0, 1, 2}); // インデックスをセット
// メインループ
while (!window.IsClosed()) {
// 入力処理
if (window.IsPressed(GLFW_KEY_ESCAPE)) {
window.Close();
}
// 背景色の設定とクリア
window.Fill(0.2f, 0.2f, 0.2f, 1.0f);
// シェーダープログラムの使用
glUseProgram(shaderProgram);
// 三角形の描画
buffer.Draw(GL_TRIANGLES);
// バッファのスワップとイベントのポーリング
window.Update();
}
return 0;
}
// ウィンドウサイズが変更されたときのコールバック関数
void framebuffer_size_callback(Window *window, int width, int height) {
glViewport(0, 0, width, height);
}
// シェーダープログラムの作成関数
unsigned int createShaderProgram(const char *vertexSource,
const char *fragmentSource) {
// 頂点シェーダーのコンパイル
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexSource, nullptr);
glCompileShader(vertexShader);
// コンパイルエラーチェック
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
std::cerr << "ERROR::VERTEX_SHADER::COMPILATION_FAILED\n"
<< infoLog << std::endl;
}
// フラグメントシェーダーのコンパイル
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
glCompileShader(fragmentShader);
// コンパイルエラーチェック
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
std::cerr << "ERROR::FRAGMENT_SHADER::COMPILATION_FAILED\n"
<< infoLog << std::endl;
}
// シェーダープログラムのリンク
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// リンクエラーチェック
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER_PROGRAM::LINKING_FAILED\n"
<< infoLog << std::endl;
}
// シェーダーオブジェクトの削除
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}