# OpenGL
## 教學目的
* 以撐過圖學課的過來人的經驗讓大家知道圖學大概在幹嘛
* OpenGL基礎知識(Buffer OBJ、Shader、Coordinate Systems...)
* 激盪圖學的火花
## 教學目標
參考 [learnopengl](https://learnopengl.com/) 網站,讓大家一起
* 畫出三角形(2D)
## 什麼是 OpenGL
* 跨平台的開放圖形庫,用於開發2D和3D的圖像。
* 可輕鬆創建各種圖像效果。
* 它還提供了一個強大的矩陣庫,可以方便地進行3D數學運算。
## 環境建置
### 環境建置(簡易版)
1. 前往初始專案的 Github Repository
[OpenGL_Startup](https://github.com/ken5170696/OpenGL_Startup)
2. 選擇 learnopengl-draw-triangle 分支

3. 下載至自己的電腦,可以使用Bash、github desktop、download zip等方式。
4. 打開專案檔(.sln),並執行
5. 看到黑色的視窗就代表成功執行瞜~~

::: danger
### Trouble Shooting:
:::spoiler

### Solution:
1. 開啟專案屬性

2. 一般 > 平台工具組

3. 將v143改為v142

:::
## 畫出三角形
### 介紹專案架構
1. GameManager:
管理整個遊戲程式,初始化OpenGL相關的設定、管理場景(Scene)及維護Game Loop。
Game Loop:
遊戲程式設計架構中的核心部分,控制遊戲的流程。它是一個持續的過程,在遊戲的整個運行期間一直運行,主要包含以下幾個內容:
* 處理用戶輸入(Process Input):Game Loop 等待玩家的輸入並處理它,例如按鈕按下或滑鼠移動等。
* 更新遊戲狀態(Update):Game Loop 根據從玩家獲得的輸入和遊戲中發生的任何其他外部事件來更新遊戲狀態。例如,它可能會更新遊戲對象的位置和速度、應用物理效果、檢查碰撞、更新分數等。
* 渲染圖形(Render):Game Loop 基於更新後的遊戲狀態將圖形渲染到屏幕上。這包括繪製遊戲對象、應用照明、陰影和其他視覺效果,並根據需要更新用戶界面。

2. SimpleScene:
實作的入口點,負責管理遊戲的初始設置和處理玩家的輸入事件。
* SimpleScene 是一種場景的類別,繼承自 Scene 並實作其純虛擬函式。
* 負責場景的渲染、更新和處理輸入事件。
* 建構子初始化該類別的成員變數,包括場景堆疊和場景上下文。
* 包含處理滑鼠和鍵盤事件的函式。
* SimpleScene 包含私有變數,如視窗高度和寬度、GLFW 視窗的指標和 SceneContext 物件,這些變數用於管理 SimpleScene 的狀態和與遊戲引擎的互動。
### Shader
* Shader 在 GPU 上運行,可以利用 GPU 的強大計算能力提高渲染速度和效率。
* OpenGL Shader 使用 OpenGL Shading Language (GLSL) 進行編寫,通常需要編譯後才能在 OpenGL 中使用。
* Shader 可以分為 Vertex Shader、Fragment Shader、Geometry Shader和Compute Shader 等。
### OpenGL Graphics Pipeline
OpenGL中所有物體都在3D空間中,但螢幕或視窗卻是一個2D像素陣列,因此OpenGL的其中一個重要工作是將3D坐標轉換為在螢幕上顯示的2D像素。將3D坐標轉換為2D的過程由OpenGL Graphics Pipeline負責。

1. Vertex Input:將3D物件的頂點座標輸入到OpenGL Graphics Pipeline。
1. Vertex Shader:將每個頂點轉換成經過運算後的新座標。
1. Primitive Assembly:使用頂點座標生成基本圖形,如線、三角形等。
1. Geometry Shader:對基本圖形(點、線、三角形)進行增減、變形等。
1. 光柵化(Rasterization):將基本圖形轉換成螢幕上的像素點。
1. Fragment Shader:對每個像素進行顏色的計算和處理,並決定每個像素的顏色值。
1. Test和Blending:最後對像素進行Alpha Test和Blending,經過這個步驟後,圖像就完成了最終的渲染。
### 開始畫三角形!!
1. 輸入頂點
* 頂點座標皆為 3D
* OPENGL不會自動將物體座標轉到螢幕的座標,必須要在Vertex Shader處理後轉換為螢幕的座標(Normalized device coordinates)。
* Normalized device coordinates: YXZ的範圍皆為 -1.0 ~ 1.0
* 超出範圍會被CLIP:
* 
```cpp=
// step 1: Define triangle vertices in NDC
float vertices[9] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
```
2. 創建 VBO(vertex buffer objects)
* 將頂點資訊儲存在GPU內
* 空間允許的話,一次將大量資訊儲存在GPU內,可大幅提升速度
* 所有OpenGL Object都有一個ID
a. 用 glGenBuffers 產生一個 Buffer ID
```cpp=
// SimpleScene.h
// step 2-1: Generate a VBO
unsigned int VBO;
// SimpleScene.cpp
// step 2-1: Generate a VBO
glGenBuffers(1, &VBO);
```
b. 將 Buffer Bind 至 GL_ARRAY_BUFFER
```cpp=
// step 2-2: Bind buffer to the GL_ARRAY_BUFFER target
glBindBuffer(GL_ARRAY_BUFFER, VBO);
```
:::info
補充: OpenGL Buffer Objects
:::spoiler
* Vertex Buffer Objects(VBOs):用於存儲頂點數據,包括頂點位置,法線,顏色等等。
* Index Buffer Objects(IBOs):用於存儲索引數據,這些索引指向頂點緩衝對象中的頂點,用於描述如何組合頂點以生成形狀。
* Pixel Buffer Objects(PBOs):用於存儲像素數據,這些數據可以在CPU和GPU之間進行快速傳輸。
* Uniform Buffer Objects(UBOs):用於存儲統一數據,這些數據是在vertex shader 和 fragment shader之間共享的數據,如矩陣和光線方向等。
* Texture Buffer Objects(TBOs):用於存儲紋理數據,這些數據可以被用於在vertex shader 和 fragment shader中進行紋理取樣。
:::
c. 將頂點資料複製至VBO
```cpp=
// step 2-3: Copies the previously defined vertex data into the buffer's memory
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
```
3. 創建 VAO(vertex array object)
Linking Vertex Attributes: 告訴OpenGL如何解讀頂點資訊
以下舉三角形的頂點資訊為例:

* 每個頂點座標資訊由三個數值組成(xyz)
* 數值以float的形式儲存。
* 一組頂點座標資訊佔據 3 * sizeof(float) 的大小。
* 第一個頂點座標資訊的位置在頂點資訊的開頭
```cpp=
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
```
a. 用 glGenVertexArrays 產生一個 vertex array object ID
```cpp=
// SimpleScene.h
// step 3-1: Generate a VAO
unsigned int VAO;
// SimpleScene.cpp
// step 3-1: Generate a VAO
glGenVertexArrays(1, &VAO);
```
b. 設定 Vertex Attrib Pointer
```cpp=
// step 3-2: Bind Vertex Array Object
glBindVertexArray(VAO);
// step 3-3: Set Vertex Attributes Pointers and enable them
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
```
4. Vertex Shader
Vertex Shader 主要用途為將3D空間中的坐標轉換為2D空間中的坐標
以下是一個簡單的 GLSL Vertex Shader範例:
```cpp=
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
```
* "#version 330 core" : 指定使用GLSL 3.30核心版本。
* "layout (location = 0) in vec3 aPos;" : 設置一個頂點屬性,位置是0,型別是一個3元素向量vec3。
* "gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);" : 將頂點資訊賦值給內建的變數 gl_Position。
a. 將vertex shader的source code儲存在一個常數變數
```cpp=
// step 4-1: Vertex Shader content
const char* vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
```
b. 用 glCreateShader 創建一個shader
```cpp=
// SimpleScene.h
// step 4-2: Create Vertex Shader
unsigned int vertexShader;
// SimpleScene.cpp
// step 4-2: Create Vertex Shader
vertexShader = glCreateShader(GL_VERTEX_SHADER);
```
c. 將source code attach到剛剛創建的shader裡,並編譯shader
```cpp=
// step 4-3: Attach source code to Vertex Shader and compile Vertex Shader.
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
```
5. Fragment Shader
Fragment Shader 將像素的顏色計算好並輸出
以下是一個簡單的 GLSL Fragment Shader範例:
```cpp=
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
// R G B A
}
```
a. 將Fragment Shader的source code儲存在一個常數變數
```cpp=
// step 5-1: Fragment Shader content
const char* fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
```
b. 用 glCreateShader 創建一個shader
```cpp=
// SimpleScene.h
// step 5-2: Create Fragment Shader
unsigned int fragmentShader;
// SimpleScene.cpp
// step 5-2: Create Fragment Shader
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
```
c. 將source code attach到剛剛創建的shader裡,並編譯shader
```cpp=
// step 5-3: Attach source code to Fragment Shader and compile Fragment Shader.
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
```
6. Shader program
a. 用glCreateProgram創建一個Shader Program
```cpp=
// SimpleScene.h
// step 6-1: Create Shader program
unsigned int shaderProgram;
// SimpleScene.cpp
// step 6-1: Create Shader program
shaderProgram = glCreateProgram();
```
b. 將Vertex shader和Fragment Shader與Shader Program連結
```cpp=
// step 6-2: Attach Shader to Shader program and link them.
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
```
c. 將Shader Objects刪掉
```cpp=
// step 6-3: Delete the shader objects.
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
```
7. 將三角形畫出來
```cpp=
// step 7: Draw the triangle on the screen!
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
```

### Final Code
SimpleScene.h
:::spoiler
```cpp=
/*
This class represents a SimpleScene, which is a type of scene in the game engine.
It inherits from the Scene class and provides implementations for its pure virtual functions.
The SimpleScene class is responsible for rendering the scene, updating the scene, and handling input events.
It has a constructor that initializes the class with a scene stack and a scene context.
This class also includes functions for handling mouse movement, mouse scroll, and keyboard events.
The SimpleScene class contains private variables for the viewport height and width, a pointer to a GLFW window, and a SceneContext object.
These variables are used to manage the SimpleScene's state and interactions with the game engine.
Overall, the SimpleScene class serves as the entry point for a game and is responsible for managing the game's initial setup and processing input events from the player.
*/
#pragma once
#include "Scene.h"
class SimpleScene : public Scene
{
public:
// SimpleScene constructor:
// Initializes the SimpleScene class with the given scene stack and scene context.
SimpleScene( SceneStack _scene, SceneContext _context);
// Draw the object on the screen.
virtual void draw() override;
// Inherited from the Scene class, responsible for updating the scene.
// Takes deltaTime as input, which is the time elapsed since the last update.
virtual bool update(float deltaTime) override;
// Handling input events
virtual bool processInput(GLFWwindow* window) override;
// Overrides the mouse callback function from the Scene class. Responsible for handling mouse movement events.
virtual void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) override;
// Overrides the scroll callback function from the Scene class. Responsible for handling mouse scroll events.
virtual void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) override;
// Overrides the key callback function from the Scene class. Responsible for handling keyboard events.
virtual void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) override;
private:
// step 4-1: Vertex Shader content
const char* vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
// step 5-1: Fragment Shader content
const char* fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
// step 1: Define triangle vertices in NDC
float vertices[9] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// step 2-1: Generate a VBO
unsigned int VBO;
// step 3-1: Generate a VAO
unsigned int VAO;
// step 4-2: Create Vertex Shader
unsigned int vertexShader;
// step 5-2: Create Fragment Shader
unsigned int fragmentShader;
// step 6-1: Create Shader program
unsigned int shaderProgram;
int m_vh;
int m_vw;
GLFWwindow* m_window;
SceneContext m_context;
};
```
:::
SimpleScene.cpp
:::spoiler
```cpp=
#include "SimpleScene.h"
SimpleScene::SimpleScene(SceneStack _stack, SceneContext _context)
: Scene(_stack, _context), m_context(_context)
{
m_window = _context.window;
glfwGetWindowSize(m_window, &m_vw, &m_vh);
// step 2-1: Generate a VBO
glGenBuffers(1, &VBO);
// step 2-2: Bind buffer to the GL_ARRAY_BUFFER target
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// step 2-3: Copies the previously defined vertex data into the buffer's memory
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// step 3-1: Generate a VAO
glGenVertexArrays(1, &VAO);
// step 3-2: Bind Vertex Array Object
glBindVertexArray(VAO);
// step 3-3: Set Vertex Attributes Pointers and enable them
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// step 4-2: Create Vertex Shader
vertexShader = glCreateShader(GL_VERTEX_SHADER);
// step 4-3: Attach source code to Vertex Shader and compile Vertex Shader.
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// step 5-2: Create Fragment Shader
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// step 5-3: Attach source code to Fragment Shader and compile Fragment Shader.
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// step 6-1: Create Shader program
shaderProgram = glCreateProgram();
// step 6-2: Attach Shader to Shader program and link them.
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// step 6-3: Delete the shader objects.
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
void SimpleScene::draw()
{
// Draw the object on the screen.
// step 7: Draw the triangle on the screen!
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
bool SimpleScene::update(float deltaTime)
{
// Update game logic.
return false;
}
bool SimpleScene::processInput(GLFWwindow* window)
{
// Handle user input here.
return false;
}
void SimpleScene::mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
}
void SimpleScene::scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
}
void SimpleScene::key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE)
glfwSetWindowShouldClose(window, true);
}
```
:::