0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

農工大Advent Calendar 2024

Day 21

C++でOpenGLを使ってゲーム用ライブラリを作った話 #2

Last updated at Posted at 2024-12-20

はじめに

農工大アドベントカレンダー21日目の記事です。

前回記事 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.hppinit.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*が指定されていますが、実際にはバッファ先頭からの距離を整数値で表したものをポインタにキャストしたものを渡します)

頂点属性のイメージ

OpenGL.png


もうすでにお腹一杯になるくらい説明した気もしますがもう少々お付き合いください。
(Buffer関連でやることが多いのもOpenGLの勉強が大変な要因だと思う)

次にglBindVertex()関数とglBindBuffer()関数について説明します。これらの関数はOpenGLのバッファスロットに特定のバッファをセットしたり、外したりするために呼び出します。

下の図のようにVBOIBOといったバッファはglBindBuffer()関数によってバインドされたときにバインドされているVAOに帰属します。ここで注意しなければいけないのが関数を呼び出す順番です。VAOバインドされている間にVBOをアンバインドしてしまうと紐付けが解除されてしまうので、アンバインドするときは VAOVBO&IBOの順で行う必要があります。これを適切に制御しないとデータの変更が反映されなかったり、要らん変更が反映されたりして面倒なことになります。

じゃあ、「バッファをアンバインドしないでバインドしっぱなしすれば良いじゃん!」と思うかもしれませんが、残念なことにこれができないんですね。OpenGLの仕様で同時にバインドできるバッファは各種類につき1つまでになっています。
(このあたりの仕様が意外と厄介で筆者はこれに散々苦しめられました...)

また、頂点属性も バインドされているVBO に帰属するのでVBOがバインドされている状態で頂点属性の設定関数を呼び出さないと、せっかくの設定が全く反映されないことになります。
(筆者はこの仕様にも散々はまり、4回位挫折しました...)

加えて、glBufferData()関数などのバッファに対して制御を行う関数もバインドされているVBO に対して変更を加えてくるので変更したいバッファを呼び出し前にバインドしておく必要があります。

バインドのイメージ

OpenGL2.png

今回はなかなか説明が長くなってしまいました...
では、最後に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;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?