Modern OpenGL - 頂點(diǎn)數(shù)組、屬性與綁定點(diǎn)(OpenGL 4.5+)

本文介紹如何在OpenGL 4.5+環(huán)境下用最現(xiàn)代的方式渲染頂點(diǎn)。本文持續(xù)更新中。

前言

上一節(jié)我們講了如何在Modern OpenGL下渲染矩形體,但其中用到的主要是OpenGL 3.x中內(nèi)容。OpenGL 4.x增添了很多新內(nèi)容,并且一部分3.x的內(nèi)容得到改進(jìn)。本節(jié)中會(huì)詳細(xì)講解新版和與舊版的區(qū)別與聯(lián)系,并給出應(yīng)用最新技術(shù)的例子。

1. 直接狀態(tài)訪問 DSA (Direct State Access)

OpenGL 4.5中給了我們DSA,可以在調(diào)用方法時(shí)直接傳入要操作的OpenGL對(duì)象,而不再需要綁定操作,更加高效。與之對(duì)應(yīng)的,所有glGen*方法均不再使用,因?yàn)檫@些方法只生成了一個(gè)對(duì)象(實(shí)質(zhì)上是分配了一個(gè)ID),并沒有定義這個(gè)對(duì)象的類型。在4.5以前,一個(gè)對(duì)象的定義發(fā)生在首次綁定時(shí)。例如:

  • glGenTextures(1, &texture)生成一個(gè)紋理
  • 第一次調(diào)用glBindTexture(GL_TEXTURE_2D, texture)時(shí),定義了該紋理是2D紋理。

從OpenGL 4.5起,由于不再需要綁定操作,所有創(chuàng)建OpenGL對(duì)象的操作都被glCreate*所替代,例如:

  • glCreateTextures(GL_TEXTURE_2D, 1, &texture),創(chuàng)建一個(gè)2D紋理。

如果依然使用glGen*而不進(jìn)行首次綁定,則該OpenGL對(duì)象是無效對(duì)象,操作時(shí)會(huì)報(bào)出GL_INVALID_OPERATION錯(cuò)誤。

2. 頂點(diǎn)數(shù)組對(duì)象 Vertex Array Object

Modern OpenGL渲染中必須使用VAO,它無處不在。VAO本身不存儲(chǔ)任何頂點(diǎn)數(shù)據(jù),它會(huì)保存我們要渲染時(shí)所需要的頂點(diǎn)的定義、規(guī)格與配置。一旦我們配置好了一個(gè)VAO,只要綁定它,就可以直接調(diào)用渲染函數(shù),而不需要調(diào)用任何定義/配置類的函數(shù)。

創(chuàng)建一個(gè)VAO

GLuint vao_id;
glCreateVertexArrays(/* number */ 1, &vao_id);

3. 通用頂點(diǎn)屬性 Generic Vertex Attribute

頂點(diǎn)屬性是頂點(diǎn)著色器(Vertex Shader)的輸入。在Modern OpenGL中,我們必須自己定義我們需要哪些頂點(diǎn)屬性。每定義的一個(gè)屬性,都可以叫做通用頂點(diǎn)屬性。與之對(duì)應(yīng)的,在傳統(tǒng)固定管線中,頂點(diǎn)著色器存在內(nèi)置頂點(diǎn)屬性,但它們都已經(jīng)廢棄。所以我們說的頂點(diǎn)屬性都是指通用頂點(diǎn)屬性,它們的名字、類型都是完全自定義的。例如,我們的頂點(diǎn)著色器:

layout(location = 0) in vec2 a_Pos;
layout(location = 1) in vec4 a_Color;

smooth out ...
void main() {
...

頂點(diǎn)屬性用in表示,location = 0顯式指定了屬性索引(attribute index)是0,location不一定連續(xù)。屬性名在這種情況下僅在shader內(nèi)使用。

4. 頂點(diǎn)緩沖綁定點(diǎn) Vertex Buffer Binding Point

頂點(diǎn)緩沖綁定點(diǎn)是在一個(gè)VAO內(nèi)共享的,我們可以將一種配置綁定到一個(gè)綁定點(diǎn)上,再將一個(gè)或多個(gè)頂點(diǎn)屬性與之綁定,這樣就可以隨時(shí)切換頂點(diǎn)屬性而不需要重新配置緩沖。也就是說,我們定義一個(gè)綁定點(diǎn),和它如何從VBO中讀取數(shù)據(jù),這個(gè)定義信息會(huì)被存入VAO,關(guān)系圖如下:

20210609183833.png

設(shè)置綁定點(diǎn)與VBO關(guān)系的函數(shù)為glVertexArrayVertexBuffer

glVertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride)
  • vaobj - 要操作的VAO
  • bindingindex - 綁定點(diǎn)的索引,0起始
  • buffer - 存儲(chǔ)頂點(diǎn)數(shù)據(jù)的那個(gè)VBO
  • offset - 第一個(gè)頂點(diǎn)屬性的起始偏移量
  • stride - 到下個(gè)頂點(diǎn)數(shù)據(jù)的跨度

比如:我們定義0號(hào)綁定點(diǎn)對(duì)應(yīng)12個(gè)字節(jié)大小,然后我們的VBO創(chuàng)建為vbo_first,它的數(shù)據(jù)是每12個(gè)字節(jié)一組頂點(diǎn)屬性,那么offset是0,stride為12。如果它第8到20字節(jié),28到40字節(jié)以此類推,是我們需要的數(shù)據(jù)的話,那么offset是8(第一組數(shù)據(jù)是8開始),stride是20(8到28是20, 20到40是20)。剩下的一個(gè)個(gè)8字節(jié)(0到8,20到28...)也許是其他地方需要的數(shù)據(jù)。因?yàn)橐粋€(gè)VBO可以存任何東西,VBO用一個(gè)還是多個(gè),數(shù)據(jù)怎么存放,可自由設(shè)置。

5. 頂點(diǎn)屬性格式 Vertex Attribute Format

重點(diǎn)來了,雖然我們?cè)趕hader中定義了頂點(diǎn)屬性,但OpenGL不知道我們是如何定義的,也就不知道怎么把數(shù)據(jù)傳給shader的attribute,所以我們必須指定它們。

glVertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, 
                GLboolean normalized, GLuint relativeoffset);

第一個(gè)參數(shù)傳入我們的VAO。attribindex傳入屬性索引,也就是頂點(diǎn)著色器中(location = 幾)的幾。

  • size - 指定了該頂點(diǎn)屬性的大小,不是字節(jié)數(shù),是分量數(shù)!該值只能是1,2,3,4。例如 vec2,分量數(shù)是 2。
  • type - 指定了VBO數(shù)據(jù)中對(duì)應(yīng)此屬性的類型??蛇x的值有 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REVGL_UNSIGNED_INT_10F_11F_11F_REV
  • normalized - 指定了數(shù)據(jù)是否要被標(biāo)準(zhǔn)化。如果數(shù)據(jù)類型的符號(hào)型數(shù)值,則轉(zhuǎn)換為[-1, 1]的浮點(diǎn)值,如果是無符號(hào)型數(shù)值,則轉(zhuǎn)換為[0, 1]的浮點(diǎn)值。如果不標(biāo)準(zhǔn)化,則直接轉(zhuǎn)換為浮點(diǎn)值。坐標(biāo)不需要標(biāo)準(zhǔn)化,所以這里輸入 GL_FALSE。

注意:上面的函數(shù)最終會(huì)把數(shù)據(jù)轉(zhuǎn)成浮點(diǎn)型。如果使用 glVertexArrayAttribIFormat(多了一個(gè)I, Integer),則會(huì)轉(zhuǎn)成整數(shù)類型,并且 type 只能 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INTGL_UNSIGNED_INT。這個(gè)方法不含 normalized 參數(shù)。

此外還有一個(gè)relativeoffset參數(shù),相對(duì)偏移量,也就是在綁定點(diǎn)中的偏移量,而不是VBO中的偏移量。我們剛剛定義了綁定點(diǎn)0占據(jù)12個(gè)字節(jié),這樣的話我們可以把之前的vec2 a_Posvec4 a_Color都綁定到這個(gè)綁定點(diǎn)上,如果我們的VBO是前8個(gè)字節(jié)是坐標(biāo),后4個(gè)字節(jié)是顏色,那么就分別調(diào)用:

glVertexArrayAttribFormat(vao_id, 0, 2, GL_FLOAT, false, 0);
// location = 1, vec4 (4個(gè)分量), 相對(duì)偏移量是8
glVertexArrayAttribFormat(vao_id, 1, 4, GL_UNSIGNED_BYTE, true, 8);

在VBO中存儲(chǔ)后4個(gè)字節(jié)為RGBA顏色值,標(biāo)準(zhǔn)化到浮點(diǎn)型,因?yàn)閡nsigned int是0~255,所以轉(zhuǎn)換時(shí)會(huì)除255得到shader中使用的顏色值。8是因?yàn)樽鴺?biāo)2兩個(gè)浮點(diǎn)值8字節(jié),一個(gè)綁定點(diǎn)是12字節(jié)。

別忘了,glVertexArrayAttribFormat只定義了屬性格式,要綁定到綁定點(diǎn)上,還需要調(diào)用glVertexArrayAttribBinding

glVertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);

第一個(gè)參數(shù)是VAO,第二個(gè)是屬性索引,第三個(gè)是綁定點(diǎn)的索引。這里我們就需要把0和1號(hào)屬性全都綁定到0號(hào)綁定點(diǎn):

glVertexArrayAttribBinding(vao_id, 0, 0);
glVertexArrayAttribBinding(vao_id, 1, 0);

假設(shè)我們頂點(diǎn)著色器中還有2號(hào)和3號(hào)屬性,也是vec2和vec4。那么只需最開始調(diào)用glVertexArrayAttribFormat,然后在需要切換的時(shí)候調(diào)用glVertexArrayAttribBinding切換綁定即可。你會(huì)發(fā)現(xiàn),我們并沒有重新配置綁定點(diǎn),也就是怎么從VBO中讀取。而且,我們從來沒綁定過VBO,它只是作為參數(shù)傳入。

6. 元素緩沖對(duì)象 Element Buffer Object

如果需要索引繪制(indexed drawing),我們還要將所使用的EBO配置進(jìn)VAO:

glVertexArrayElementBuffer(vao_id, ebo_id);

這樣一來,所有的配置都存進(jìn)了VAO,由多個(gè)VBO存放頂點(diǎn)數(shù)據(jù),只要綁定VAO即可渲染,渲染循環(huán)如下:

glBindVertexArray(vao_id);
glUseProgram(program);
glDrawElementsInstanced(...);

要更新頂點(diǎn)數(shù)據(jù),只需在下一幀渲染開始前使用glNamedBufferSubData更新VBO即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容