# OpenGL 4.5 [toc] ## OpenGL 4.5 跟 3.3 差在哪裡? 現在網路上的主流教學都是 3.3,所以多半應該都比較熟悉這個版本,但如果你還在學 2.x 甚至 1.x,那我建議你至少跳到 3.3,最好的話就直接學 4.5,因為你現在可能心想:幹嘛一定都要用最新版的 API?老實跟你說 OpenGL 4.5 也不新了,這個版本的 API 規範是在 2014/08/11 釋出的,最新的 4.6 則是 2017/07/31;而且 Khronos Group 有 Vulkan 了,硬要說比較的話,大概就是 OpenGL 3.x 對應的是 DirectX 3D 10、GL 4.x 對應的是 DX 11、Vulkan 對應 DX12。 你現在應該沒有聽到還有新遊戲是用 DX10 做了吧,艾爾登法環用 DX12,多數遊戲都是在 DX11;所以對我來說 OpenGL 4.5 是能唯一抗衡的 3D API 了。 ## DSA 方法 ## 程式碼 ### 建立 VAO 與 VBO #### OpenGL 3.3 ```cpp= unsigned int vao, vbo, ebo; glGenVertexArrays(1, &vao); glBindVertexArray(vao); glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), reinterpret_cast<void*>(0)); glGenBuffers(1, &ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW); ``` #### OpenGL 4.5 ```cpp= unsigned int vao, vbo, ebo; int binding_index = 2; int attribute_index = 0; int size = 3; int offset = 0; int stride = size * sizeof(float); glCreateVertexArrays(1, &vao); glCreateBuffers(1, &vbo); glNamedBufferStorage(vbo, m_Vertices.size() * sizeof(float), m_Vertices.data(), GL_MAP_WRITE_BIT); glVertexArrayVertexBuffer(vao, binding_index, vbo, offset, stride); glVertexArrayAttribFormat(vao, attribute_index, size, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribBinding(vao, attribute_index, binding_index); glEnableVertexArrayAttrib(vao, attribute_index); glCreateBuffers(1, &ebo); glNamedBufferStorage(ebo, m_Indices.size() * sizeof(unsigned int), m_Indices.data(), GL_MAP_WRITE_BIT); glVertexArrayElementBuffer(vao, ebo); ``` 3.3 版本的 OpenGL 寫法會有點類似狀態機,你要綁定 vao、vbo 或者 ebo 到當前的狀態下,你才可以對該 Buffer 進行操作。但現在 OpenGL 提出一種新的做法,就是你可以不用綁了,你需要傳參數進去才知道對哪一個 Buffer 修改。 例如,`glVertexArray` 開頭的就是代表需要 `vao` 傳入, `glBindVertexBuffer()` 和 `glVertexArrayVertexBuffer()` 兩個版本的函數但基本上是做同一件事情,前者是依據當前綁定的 `vao` 做操作,後者需要參數傳入。至於 `vbo` 跟 `ebo` 以前都是用 `glBufferData()`,現在也可以用 `glNamedBufferData()`。 另外 4.4 版本才有的 `glBufferStorage()`,在 4.5 也有 `glNamedBufferStorage()` 可以使用。有 `glNamed` 前綴就是代表需要傳入 buffer id,型態是 `GLuint`。 然後 Storage 跟 Data 的差別,其實兩個基本上都是做一樣的事情,把資料送入 buffer,差別在於 Storage 產生出來的 buffer 是屬於 **immutable**,其產生出來的記憶體大小是不可變的,但你可以透過 `glBufferSubData()` (2.x 就有了函式) 來修改與更新 buffer 內部的資料。 其實 `glBufferSubData()` 它都有一個 sub 了,顧名思義就是可以局部修改、更新 buffer 內的資料,也可以全部修改與更新。 以前我們圖學不是有作業嗎,可能要繪製 billboard 或者 view frustum,每一個 frame 都要更新頂點資料,如果都是用 `glBufferData()` 其實是最耗效能的,即使你有下 `GL_DYNAMIC_DRAW` 或許會好一點,但之所以效能會比 `glBufferStorage()` 差就是因為 data 每次跑,它就會重新 alloc 一個 buffer 出來,原本的 buffer 就會被孤立(應該是會回收吧不然就 memory leak了 哈哈) 如果你是用 `glBufferStorage()`,那產生出來的 buffer 就不能被改變,據說這樣會有性能優勢 (persistent mapped memory),然後你不能再次對相同的 buffer call `glBufferStorage()`,否則就會報錯了。 > 如果用 `glBufferStorage()` 建立 buffer,後續需要透過 `glBufferSubData()` 來修改或者更新 buffer 內部資料的話,要記得下 GL_DYNAMIC_STORAGE_BIT 然後有一個坑就是,如果想要使用 `glNamed` 函數,那些 buffer,用 `glCreate` 是最省事的。如果堅持要用 `glGen` 的話,就必須還是要 `glBindBuffer()`,那就失去用 `glNamed` 的含義了 XD 會有這樣的差別是因為,`glGenBuffer()` 還不會真正建立 buffer,此時不具備任何意義,必須要 `glBindBuffer()`,並且看你 bind 的 target,OpenGL 才會賦予這個 buffer 意義,也就是變成 vertex buffer object。 glVertexAttribPointer 被拆解成 glVertexArrayVertexBuffer、glVertexArrayAttribFormat 和 glVertexArrayAttribBinding ![](https://i.imgur.com/0uY5ZeA.png) ### 建立 Texture #### OpenGL 3.3 ```cpp= glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); ``` #### OpenGL 4.5 ```cpp= unsigned int textureID; unsigned int width = desc.width; unsigned int height = desc.height; // upload image glCreateTextures(GL_TEXTURE_2D, 1, &textureID); glTextureStorage2D(textureID, 1, GL_RGB8, width, height); glTextureSubImage2D(textureID, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, data); // 設定 MAG MIN Filter glTextureParameteri(textureID, GL_TEXTURE_WRAP_S, GL_REPEAT); glTextureParameteri(textureID, GL_TEXTURE_WRAP_T, GL_REPEAT); glTextureParameteri(textureID, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTextureParameteri(textureID, GL_TEXTURE_MAG_FILTER, GL_REPEAT); // Mipmap glGenerateMipmap(textureID); // 建立 Sampler glCreateSamplers(1, &m_Sampler); glSamplerParameteri(m_Sampler, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glSamplerParameteri(m_Sampler, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); glSamplerParameteri(m_Sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glSamplerParameteri(m_Sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindSampler(0, m_Sampler); ``` 據說 glBindTextureUnit 就等價於 glActiveTexture 和 glBindTexture 只是我想不通 Sampler 如果可以獨立創建,它有什麼好處 XD 因為在 DirectX 中,Texture 和 Sampler 基本上是兩個獨立傳入 shader 的東西 所以在 hlsl 你可以用 A Sampler 去讀取 B、C Texture,然後用 B Sampler 讀取 A Texture 但 OpenGL,似乎只能一個 Sampler 就對應到一個 Texture 正確的講法來說是 Texture Slot,你必須要把 Sampler 和 Texture 都綁定在同一個 binding index 上,並且從 shader 的 sampler2D,指定對應的 slot,才可以使用你選擇的 Sampler 去讀取你想要的 Texture。 ## Reference https://stackoverflow.com/questions/37972229/glvertexattribpointer-and-glvertexattribformat-whats-the-difference https://stackoverflow.com/questions/21153729/glbindvertexbuffer-vs-glbindbuffer