OpenGLでUniform Buffer Objectを作る

初投稿になります。ゲームプログラマーを目指している学生として、勉強した内容や詰まった内容などを備忘録として書き残しておきます。

# UBOとは
OpenGLにはGPUにデータを送信する方法としてUBO(Uniform Buffer Object)があります。
単一のデータとして送信する`glUniform3f`や`glUniformMatrix4fv`がありますが、こちらは一つ一つ変数をプッシュしなければならないうえ、複数のオブジェクトに共有されることもないので、相当な労力や容量が食われます。

そこで、構造体としてデータを一斉に送信でき、複数のオブジェクトに共有できるUBOがとても便利です。

<br><br>
ということで実装していきたいのですが、複数のサイトを参考にしても全くできなかったです...

なので、ChatGPTに聞きました。
![スクリーンショット_20231227_194318.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663049/29e74a2a-38af-6b78-336c-7d4f3cd9ffb7.png)


手順通りに書くだけで簡単に実装できました...AIすごい。
<br>

# コード解説
### C++側のコード
```C++:main.h
		/// <summary>
		/// ライト用UB。
		/// </summary>
		struct LightUB
		{
			float ambient;
		};
```
まずはUBOに使う構造体を定義します。
<br>

```C++:UniformBuffer.cpp
    /// <summary>
    /// 初期化処理。
    /// </summary>
    /// <param name="data">構造体</param>
    /// <param name="size">構造体のサイズ</param>
    /// <param name="shaderID">シェーダープログラムID</param>
	void UniformBuffer::Init(void* data, const int size, GLuint shaderID, const char* blockName)
	{
		//UniformBufferを作成。
		glGenBuffers(1, &m_ubo);
		glBindBuffer(GL_UNIFORM_BUFFER, m_ubo);
		glBufferData(GL_UNIFORM_BUFFER, size, &data, GL_STATIC_DRAW);

		//UBOをシェーダーと紐付ける。
		m_blockIndex = glGetUniformBlockIndex(shaderID, blockName);
		glUniformBlockBinding(shaderID, m_blockIndex, 0);
		glBindBufferBase(GL_UNIFORM_BUFFER, m_blockIndex, m_ubo);
	}
```


```C++:UniformBuffer.cpp
    /// <summary>
    /// 更新処理。
    /// </summary>
    /// <param name="data">構造体</param>
    /// <param name="size">構造体のサイズ</param>
	void UniformBuffer::Update(void* data, const int size)
	{
		glBindBuffer(GL_UNIFORM_BUFFER, m_ubo);
		//データを送信。
		glBufferSubData(GL_UNIFORM_BUFFER, 0, size, data);
		glBindBuffer(GL_UNIFORM_BUFFER, 0);
	}
```
`glBufferData`でUBOにModelUBの構造体とサイズを入れ、
`glBufferSubData`でシェーダーにデータを送信します。

`glGetUniformBlockIndex`でブロックインデックスを取得し、
`glUniformBlockBinding`でプログラム側でブロックインデックスを何番に配置するかの割り当てを行います。
(作成に失敗すると`m_blockIndex`の値がunsigned int型の最大値になります。
そのときはシェーダーの作成に失敗していないか、uniform block nameが間違っていないかを確認する)
<br>

```C++:main.cpp
    int main()
    {
        m_lightUniformBuffer.Init(&m_lightUB, sizeof(LightUB), m_shaderProgram->ID, "LightUB");
    
        //メインのループ処理。
    	while (!glfwWindowShouldClose(window)) {
    
            glUseProgram(m_shaderProgram->ID);
        
            m_lightUniformBuffer.Update(&m_modelUB, sizeof(ModelUB));
    	}
    }
```
<br>

### シェーダー側のコード
```glsl:model.frag
    /// <summary>
    /// ライト用定数バッファ。
    /// </summary>
    layout (std140) uniform LightUB
    {
    	float ambient;
    } lightUniformBuffer;
```
作成したUBOは`lightUniformBuffer.ambient`などのように書くことでアクセスできます。
<br>

```glsl:model.frag
    void main()
    {
    	float ambient = lightUniformBuffer.ambient;
    
    	vec4 lig = CalcPointLight();
    
    	vec4 albedoColor = texture(g_normalMap, psIn.texCoord);
    
    	lig += albedoColor * ambient;
    
    	FragColor = lig;
    }
```
ambientの値をC++側でいじるとピラミッドの環境光が変わっているのがわかります。
<br>

![スクリーンショット_20231227_231443.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663049/64d73dc2-69e0-4dac-9816-29036d1c2e96.png)
![スクリーンショット_20231227_231412.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663049/a3272f6a-43e1-722e-56b5-b27658591b93.png)

<br>

間違いなどあればコメント欄にお願いします。