## OpenGL 記憶體控管 OpenGL擁有 VAO,VBO,EBO 等等的VRAM物件,並與Shader(for GPU rendering program)搭配進行渲染。 ### Vertex Buffer Object (VBO) VBO(Vertex Buffer Object) 是 OpenGL 中的一種緩衝區物件,用於在顯示卡記憶體(VRAM)中儲存頂點資料(如位置、顏色、法線、紋理座標等)。它使得數據從系統記憶體傳輸到VRAM中,並允許 GPU 有效率地存取這些數據,從而提高渲染效能。 ```c++= //Step1 GLuint vbo; glGenBuffers(1, &vbo); // create a VBO glBindBuffer(GL_ARRAY_BUFFER, vbo); // binding VBO into GL_ARRAY_BUFFER //Step2 : uploading cpu data into VRAM of gpu, 一次性上傳 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //Step3 : unbind to avoid any modification glBindBuffer(GL_ARRAY_BUFFER, 0); //Step4 : drawing while (draw) { //Once use vbo, we need to bind. glBindBuffer(GL_ARRAY_BUFFER, vbo); //And then call glDrawElements() to draw glDrawElements(GL_TRIANGLES, indexSize, GL_UNSIGNED_INT, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); } ``` #### 解綁 VBO 與釋放 VRAM 資料的差異: - 解綁(```glBindBuffer(GL_ARRAY_BUFFER, 0);```): 解綁操作只是取消目前緩衝區物件與目標綁定的關係。它不會刪除或釋放顯存中的資料。 解綁定後,目前綁定的 VBO 物件會被解除綁定,之後的 OpenGL 呼叫就不再操作該對象,直到它被重新綁定到某個目標。 - 釋放資料(```glDeleteBuffers(1, &vbo);```): 若要釋放VRAM中的數據,可以使用 ```glDeleteBuffers``` 來刪除緩衝區對象,這將釋放與該物件相關的記憶體。 ### Element Buffer Object (EBO) EBO 是 OpenGL 中用於儲存索引資料的一種緩衝區物件。它的作用是透過索引來組織和存取頂點數據,而**不需要重複儲存vertex資訊**。 EBO 在某些渲染場景中是非常必要的,尤其是在需要處理大量頂點資料時,能顯著提高效能並減少記憶體使用。 ### Vertex Array Object (VAO) VAO 是一個狀態容器,用於記錄與 vertex 資料相關的資料格式。VAO 不能單獨使用,需要和 VBO 配合。再進階的使用會和 EBO 一起使用。本身存在是為了高效性:透過儲存指向 VBO 的方式,VAO 可以在不同的渲染呼叫時重複使用相同的 VBO 配置(i.g. 3D 位置),而不需要每次都重新綁定 VBO 和設定屬性。 #### VAO 儲存的內容 - vertex 屬性配置資料**格式** VAO 記錄每個vertex屬性(如位置、顏色、法線等)在緩衝區中的**格式**(如資料類型、步長、偏移量等)。這些資訊由 ```glVertexAttribPointer``` 配置。 - 指向 VBO 的指標(綁定狀態) VAO 記錄的是 綁定的 VBO(通常是 ```GL_ARRAY_BUFFER``` 類型的緩衝區)的位置和狀態。例如,```glVertexAttribPointer``` 指定的 VBO 被綁定為 ```GL_ARRAY_BUFFER``` 時,VAO 會記錄該 VBO 的綁定資訊。簡而言之,VAO 會儲存“目前綁定的 VBO 的標識符”,而不是直接儲存緩衝區的資料。 - EBO綁定狀態 如果使用了索引緩衝(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)),VAO 也會記錄目前綁定的 EBO。 <center> <img width=60% src=https://hackmd.io/_uploads/SyyWxYVfyg.png> </center> #### VAO 和 VBO 的關係 - VBO 儲存資料:VBO 本身用於儲存實際的頂點資料(如位置、顏色、法線等)。 - VAO 儲存狀態:VAO 不會儲存 VBO 的數據,而是儲存 VBO 的綁定狀態以及如何從 VBO 中讀取資料的方式(例如,頂點屬性的格式和偏移量)。 #### 不靠VAO僅VBO的繪圖方式 這種方式需要在每次繪製之前重新配置頂點屬性指標 (```glVertexAttribPointer```),因為沒有 VAO 來儲存這些設定。 :::spoiler 不靠VAO僅VBO的繪圖方式 ```c++= // 1. 建立並綁定 VBO GLuint VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 2. 配置頂點屬性指標 // 頂點位置屬性 (vec3) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 紋理座標屬性 (vec2) glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // 頂點顏色屬性 (vec3) glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float))); glEnableVertexAttribArray(2); // 3. 解除 VBO 綁定(資料配置已儲存在 GPU 中) glBindBuffer(GL_ARRAY_BUFFER, 0); // 繪製時每次都重新設定 while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); // 重新綁定 VBO glBindBuffer(GL_ARRAY_BUFFER, VBO); // 再次設定頂點屬性指標 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float))); glEnableVertexAttribArray(2); // 繪製呼叫 glDrawArrays(GL_TRIANGLES, 0, 6); glfwSwapBuffers(window); glfwPollEvents(); } ``` ::: #### VAO+VBO+EBO 程式碼 請與上圖搭配使用: ```c++= int main() { //... initVertex(); while(isDraw) { draw(frame, projection, view, model); } //... } void initVertex() { glGenVertexArrays(1, &mVAO); glGenBuffers(1, &mVBO); glGenBuffers(1, &mEBO); // Bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). // [1] bind Vertex Array Object [VAO] glBindVertexArray(mVAO); // [2] copy our vertices array in a vertex buffer object [VBO] for OpenGL to use glBindBuffer(GL_ARRAY_BUFFER, mVBO); glBufferData(GL_ARRAY_BUFFER, positionSize * 6 * sizeof(float), p_vertexes, GL_STATIC_DRAW); // [3] copy our index array in a element buffer object [EBO] for OpenGL to use glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize * sizeof(unsigned int), p_indexes, GL_STATIC_DRAW); // [4] set the vertex attributes pointers for shader, [VAO] will store glVertexAttribPointer and [VBO]. // ours shader program of vertex: // layout (location = 0) in vec3 position; // layout (location = 1) in vec2 texCoord; // layout (location = 2) in float alpha; // format : // glVertexAttribPointer(location, dimention, dataType, IsNormalized, stride of a vertex, startPositionOfPointer) // glEnableVertexAttribArray(location) // layout (location = 0) in vec3 position; glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)0); //according to vertex shader glEnableVertexAttribArray(0); //according to vertex shader // layout (location = 1) in vec2 texCoord; glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)(sizeof(float) * 3)); glEnableVertexAttribArray(1); // layout (location = 2) in float alpha; glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)(sizeof(float) * 5)); glEnableVertexAttribArray(2); // [5] finish configuration, then unbind buffer to avoid any modification and occuping GPU resource. // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind glBindBuffer(GL_ARRAY_BUFFER, 0); // [Remember]: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound. // i.g. Do not write : glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindVertexArray(0); } void draw(const cv::Mat& frame, const glm::mat4& projection, const glm::mat4& view, const glm::mat4& model) { //[Texture] glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame.data); //[Vertex] shader.use(); shader.setMat4("projection", projection); shader.setMat4("view", view); shader.setMat4("model", model); //[Start Drawing] //Once use VAO, we need to bind. glBindVertexArray(mVAO); //And then call glDrawElements() to draw glDrawElements(GL_TRIANGLES, indexSize, GL_UNSIGNED_INT, 0); glBindVertexArray(0); } ``` ## Reference 1. [OpengGL 記憶體控管, csdn](https://blog.csdn.net/csxiaoshui/article/details/53197527) 2.