在VBO、VAO和EBO那一節,介紹瞭如何向Vertex Shader傳遞vertex attribute的基本方法。現在我準備把這個話題再次擴充套件開。
傳遞整型資料
之前我們的頂點屬性資料都是float型別的,現在我使用int(unsigned int)型別或者double型別的資料怎麼辦?
比如我現在用GLubyte
來定義三角形的顏色:
GLfloat trianglePosition[] =
{
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
GLubyte triangleColor[] =
{
255, 0, 0,
0, 255, 0,
0, 0, 255
};
GLuint vbo[2] = { 0 };
glCreateBuffers(2, vbo);
glNamedBufferStorage(vbo[0], sizeof(trianglePosition), trianglePosition, 0);
glNamedBufferStorage(vbo[1], sizeof(triangleColor), triangleColor, 0);
GLuint vao = 0;
glCreateVertexArrays(1, &vao);
glEnableVertexArrayAttrib(vao, 0);
glEnableVertexArrayAttrib(vao, 1);
glVertexArrayVertexBuffer(vao, 3, vbo[0], 0, sizeof(GLfloat) * 3);
glVertexArrayVertexBuffer(vao, 5, vbo[1], 0, sizeof(GLubyte) * 3); //設定vao與binding point關聯的buffer的stride是sizeof(GLubyte)*3
glVertexArrayAttribBinding(vao, 0, 3);
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vao, 1, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0); //設定data type為GL_UNSIGNED_BYTE,並且normalized設定為GL_TRUE
glVertexArrayAttribBinding(vao, 1, 5);
以上程式碼你應該很熟悉,有兩處我加了註釋,標記出與傳遞GLfloat
型別資料的不同之處。第一處是設定binding point對應的buffer的stride,這個很容易理解,沒什麼值得討論的東西。關鍵看第二處:
glVertexArrayAttribFormat(vao, 1, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0); //設定data type為GL_UNSIGNED_BYTE,並且normalized設定為GL_TRUE
之前我就一直納悶這個命令的GLboolean normalized
到底是幹啥用的,現在終於搞清楚了:這個引數只對整型的頂點屬性資料起作用,來決定是否對整數型別的資料進行歸一化,怎麼個歸一化法呢?我演示給你看:
//vertex shader
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color; //是vec3而不是uvec3
...
void main(void)
{
...
}
注意雖然資料型別是GLubyte型別的,但是傳遞到vertex shader卻是以float為基礎的vec3。這時候vertex shader接受到的color資料其實是:(1.0, 0.0, 0.0)
,(0.0, 1.0, 0.0)
和(0.0, 0.0, 1.0)
。對於無符號的整數型別譬如GLuint,GLushort和GLubyte,會從[0, MAX]線性對映到[0.0, 1.0];而對於有符號整型譬如GLuint,GLushort和GLubyte,則會從[Min, Max]線性對映到[-1.0, 1.0]。公式分別如下:
- 無符號型別的歸一化:\(f = \frac{c}{2^{b}-1}\)
- 有符號型別的歸一化:\(f = \frac{2c-1}{2^{b}-1}\)
c表示整數數值的大小,b表示這個整數有多少位,比如GLubyte和GLbyte是8位,GLuint和GLint是32位。
如果我們設定nomalized屬性為GL_FALSE,那麼整數會被直接強制轉換為浮點數型別,也就是說vertex shader接受的color資料就會變成:(255.0, 0.0, 0.0)
,
(0.0, 255.0, 0.0)
和(0.0, 0.0, 255.0)
。
如果數值非常大的整數歸一化到浮點數,是會丟失精度的,因此範圍比較大的整數不適合歸一化成浮點數,這時候我們需要直接引用整數型別的頂點屬性(或者更多的時候是你需要的就是整數型別的頂點屬性):
glVertexAttribIFormat(vao, 1, 3, GL_UNSIGNED_BYTE, 0);
此命令中的I
字元表示的是整數型別的意思,因為是直接引用的整數型別的資料,所以此命令不需要GLboolean normalized
引數。shader也變為:
//vertex shader
layout(location = 0) in vec3 position;
layout(location = 1) in uvec3 color; //是vec3而不是uvec3
...
void main(void)
{
...
}
如程式碼所示:我們可以在shader中直接引用無符號整數型別的資料了。
傳遞雙精度浮點型資料
有了前面的鋪墊,傳遞double型別的資料很自然就能想到存在類似這樣的命令:
void glVertexArrayAttribLFormat(GLuint vaobj,
GLuint attribindex,
GLint size,
GLenum type,
GLuint relativeoffset);
使用起來也和你想的一樣:
GLfloat trianglePosition[] =
{
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
GLdouble triangleColor[] =
{
1.0, 1.0, 1.0,
0.5, 0.5, 0.5,
0.0, 0.0, 0.0
};
GLuint vbo[2] = { 0 };
glCreateBuffers(2, vbo);
glNamedBufferStorage(vbo[0], sizeof(trianglePosition), trianglePosition, 0);
glNamedBufferStorage(vbo[1], sizeof(triangleColor), triangleColor, 0);
GLuint vao = 0;
glCreateVertexArrays(1, &vao);
glEnableVertexArrayAttrib(vao, 0);
glEnableVertexArrayAttrib(vao, 1);
glVertexArrayVertexBuffer(vao, 3, vbo[0], 0, sizeof(GLfloat) * 3);
glVertexArrayVertexBuffer(vao, 5, vbo[1], 0, sizeof(GLdouble) * 3); //設定vao與binding point關聯的buffer的stride是sizeof(GLdouble)*3
glVertexArrayAttribBinding(vao, 0, 3);
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribLFormat(vao, 1, 3, GL_DOUBLE, 0); //設定data type為GL_DOUBLE
glVertexArrayAttribBinding(vao, 1, 5);
//vertex shader
layout(location = 0) in vec3 position;
layout(location = 1) in dvec3 color; //dvec3表示double型別的向量
...
void main(void)
{
...
}
interleaved attributes
之前我們使用vertex attribute的方式稱為separate attributes,意思是每個vertex attribute分別單獨存在兩個vbo中。或者像這樣,也是separate attribute的變種:
//空間位置和顏色連續存放到一個buffer中
GLfloat triangle[] =
{
-1.0f, -1.0f, //空間位置
1.0f, -1.0f,
0.0f, 1.0f,
1.0f, 0.0f, 0.0f, //顏色
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
};
glNamedBufferStorage(vbo, sizeof(triangle), triangle, 0);
glEnableVertexArrayAttrib(vao, 0);
glEnableVertexArrayAttrib(vao, 1);
glVertexArrayVertexBuffer(vao, 3, vbo, 0, 2 * sizeof(GLfloat));
glVertexArrayAttribFormat(vao, 0, 2, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 0, 3);
glVertexArrayVertexBuffer(vao, 5, vbo, 6 * sizeof(GLfloat), 3 * sizeof(GLfloat)); //顏色的offset是跨過空間位置空間
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 1, 5);
現在我再舉例說明如何使用interleaved attributes,以加深對glVertexArrayVertexBuffer
和glVertexArrayAttribLFormat
的理解。
仍然是繪製一個三角形:
//空間位置和顏色交叉存放
GLfloat triangle[] =
{
-1.0f, -1.0f, //空間位置
1.0f, 0.0f, 0.0f, //顏色
1.0f, -1.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f, 1.0f
};
glNamedBufferStorage(vbo, sizeof(triangle), triangle, 0);
glEnableVertexArrayAttrib(vao, 0);
glEnableVertexArrayAttrib(vao, 1);
glVertexArrayVertexBuffer(vao, 3, vbo, 0, 5 * sizeof(GLfloat));
glVertexArrayAttribFormat(vao, 0, 2, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 0, 3);
glVertexArrayVertexBuffer(vao, 5, vbo, 0, 5 * sizeof(GLfloat));
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat)); //顏色的relativeoffset是跳過頂點位置,即兩個GLfloat
glVertexArrayAttribBinding(vao, 1, 5);
其實顏色的offset也可以指定給glVertexArrayVertexBuffer
的第三個引數offset
,而不是glVertexArrayAttribFormat
的最後一個引數relativeoffset
。
glVertexArrayVertexBuffer(vao, 5, vbo, 2 * sizeof(GLfloat), 5 * sizeof(GLfloat));//指定顏色資料的offset
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 1, 5);
那這offset
和relativeoffset
到底有什麼區別呢?
下面以如下方式繪製一個三角形和一個矩形。
GLfloat triangle_rect[] =
{
//三角形的空間位置和顏色交叉存放
-1.0f, -1.0f, //三角形的空間位置
1.0f, 0.0f, 0.0f, //三角形的顏色
1.0f, -1.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
//矩形的空間位置和顏色也是交叉存放
-0.5f, 0.0f, //矩形的空間位置
0.0f, 0.0f, 0.0f, //矩形的顏色
-0.5f, -1.0f,
0.3f, 0.3f, 0.3f,
0.5f, 0.0f,
0.7f, 0.7f, 0.7f,
0.5f, -1.0f,
1.0f, 1.0f, 1.0f
};
glNamedBufferStorage(vbo, sizeof(triangle_rect), triangle_rect, 0);
glEnableVertexArrayAttrib(triangle_vao, 0);
glEnableVertexArrayAttrib(triangle_vao, 1);
glVertexArrayVertexBuffer(triangle_vao, 3, vbo, 0, 5 * sizeof(GLfloat));
glVertexArrayAttribFormat(triangle_vao, 0, 2, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(triangle_vao, 0, 3);
glVertexArrayVertexBuffer(triangle_vao, 5, vbo, 2 * sizeof(GLfloat), 5 * sizeof(GLfloat));
glVertexArrayAttribFormat(triangle_vao, 1, 3, GL_FLOAT, GL_FALSE, 2 * 0);
glVertexArrayAttribBinding(triangle_vao, 1, 5);
glEnableVertexArrayAttrib(rect_vao, 0);
glEnableVertexArrayAttrib(rect_vao, 1);
glVertexArrayVertexBuffer(rect_vao, 3, vbo, 3 * 5 * sizeof(GLfloat), 5 * sizeof(GLfloat));
glVertexArrayAttribBinding(rect_vao, 0, 3);
glVertexArrayAttribFormat(rect_vao, 0, 2, GL_FLOAT, GL_FALSE, 0);
glVertexArrayVertexBuffer(rect_vao, 5, vbo, 3 * 5 * sizeof(GLfloat), 5 * sizeof(GLfloat)); //矩形顏色的offset為跨過所有的三角形的資料
glVertexArrayAttribBinding(rect_vao, 1, 5);
glVertexArrayAttribFormat(rect_vao, 1, 3, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat)); //矩形顏色的relative offset為跨過自身的空間位置資料
看到上述程式碼,我相信你可能明白一些了:offset往往描述的是跨越到屬於自己的資料區(跨過三角形的所有頂點資料),而在自己的資料區跨越是用relative offset來描述的(跨過矩形本身的空間位置資料)。其實也是有公式的:
//索引某個頂點的attribute公式
location = binding[attrib.binding].memory + // Start of data store in memory
binding[attrib.binding].offset + // Offset of vertex attribute in buffer
binding[attrib.binding].stride * vertex.index + // Start of *this* vertex
vertex.relative_offset; // Start of attribute relative to vertex
頂點的自動補全和截斷
自動補全:
...
glVertexArrayAttribFormat(vao, 1, 1, GL_FLOAT, GL_FALSE, 0); //指定attribute index 1只有一個float分量
//vertex shader
layout(location = 1) in vec4 color; //color的y和z分量被補全為0,w分量為1
...
截斷:
...
glVertexArrayAttribFormat(vao, 1, 4, GL_FLOAT, GL_FALSE, 0); //指定attribute index 1只有一個4個float分量
//vertex shader
layout(location = 1) in vec2 color; //只拿到了x和y分量,z和w直接被丟棄掉了
...
小結
通過這一節,我們掌握瞭如下內容:
- 能夠給Vertex Shader傳遞整數型別的vertex attribute,以及歸一化和不歸一化的區別
- 能夠給Vertex Shader傳遞double型別的vertex attribute
- 學會separate attribute和interleaved attribute的頂點資料組織以及傳遞方式,兩種型別分別有兩種,一共四種
- 瞭解頂點屬性的自動補全和截斷