Vertex Attributes and Layouts in
P5
简单来说,Opengl管线的工作流程就是为我们的显卡提供数据,然后存储在GPU上的内存里。GPU内存包含了我们想要绘制的所有数据。然后我们使用shader(在GPU上执行的一种程序),读取这部分数据,然后在屏幕上显示下来。
有代表性的是,我们实际上绘制图形使用的是一个Vertex buffer,即存储在GPU上的一部分内存buffer。当shader实际上开始读取Vertex buffer时,它需要知道buffer的布局layout,比如说buffer里面有什么,是以答对浮点数,这些浮点数决定着每个顶点的位置。你也有像坐标、纹理、法线之类的东西。
我们必须告诉opengl里面有什么,它是如何布局的,因为如果我们不这样做。然后opengl就会说,这只是一堆字节,就和C++一样(比如我写了一个float我给它分配一些东西,我们就知道内存里有一个浮点数,因为我告诉c++这是一个浮点数,如果我用void把它赋值给整数,如果我把它赋值给Int指针,如果我取流的内存地址,并将其赋给结束指针,然后取消它的引用,我就会获得完全不同的东西,因为它interpreting memory不同)在这里也是同样。
告诉Opengl每流的12个字节是3个4字节的浮点数,这是我的位置,接下来的8个字节是我的纹理坐标,一个浮点数x一个浮点数给y或者是u和v,如果你想像这样子调用它。我们需要一个方法告诉opengl,这是我们的内存布局,这是确切的整个顶点属性指针能为我们做的,这是一个函数,在opengl里面叫glVertex()
,一个真指针。
在着色器那边,你也需要去i匹配上布局,这布局是你用C++类定义在cpu里面的。
顶点
一个顶点和位置无关,顶点就只是一个在几何体上的点,人们在视觉上对他们的看法显然是由它的位置决定。所以如果我画一个三角形,你就会这样啊三个顶点,但顶点不是位置,一个顶点不是一个位置,一个顶点可以包含比一个位置更多的东西,所以入如果你告诉我有一个顶点,它是0.5 0.5,这并没有告诉我任何事情。因为你是在说明我说的顶点位置的意思,很多人像这样子,但是它们是明显错误的。因为顶点可以包含比一个位置要多很多的数据,尽管它通常用来包含位置,所以我是想说清楚,当我说顶点位置时,如果我说了一个顶点,我的意思是这一整个点的数据组成了这个顶点,它可以包括这个位置的纹理,坐标,法线,颜色,按法线切线,所有这些东西都可以在一个顶点里面,所以只是位置,如果你只是参考位置,你应该叫它位置顶点。所以这就是为什么我这里获取这个浮点数作为位置而不是顶点,因为那一刻它们只是位置,尽管我往里面加了东西。
位置.png
我们这里有三个顶点,然后我们生成了一个缓存,绑定,然后把它放进去,或者我应该说复制这些位置到这个实际的缓存里面,我们这里已经通过指明一个指针向它定义了,它们有多大,这就是我们做的。
image.png
opengl是不知道的,如果你看这代码,我们可以清楚看到,我们每个顶点有两个浮点数,这就是x和y的位置,opengl是不知道的,这对你来说是很明显的,因为你是在看着代码,opengl是不知道发生了说明,但如果我们突然引入了第三个坐标。换一句话说,如果我想要一个三维坐标系,而不是像我们这里获取的二维的,如果我这样做,你是能知道明白的,那opengl是如何知道明白的呢?
所以我们获取的是说明,这我们需要告诉opengl布局,这就是我们要做的,如果我们往下翻一下。我们绑定了缓存,我们填了数据进去,在你填数据进去之前你不需要这样做,在你可以做之后也不用这样做,你可以想什么时候做就什么时候做只要这个buffer is actually bind。
当我们能够告诉Opengl我们buffer的布局是什么,
glVertexAttribPointer()
基本上我们的着色器读取这些东西,通过一个索引。Its picture likes array,但是类型和数组might be different,这就是索引。关于你实际引用属性的索引。所有的这些东西,通过维持属性,当我提高过,一个顶点的组成比只是位置要多,位置,纹理,坐标,法线,颜色无论哪一样东西,像一个位置都是一个属性。所以这个索引只是告诉我们,嘿,这个属性是什么索引。
假设一下,我有三个属性,我又位置,纹理,法线,我需要说我像位置在index0,纹理在Index1,然后法线在index2 所以当我开始从gpu那边,从着色器那边开始读取时,我会到达缓存,我可以简单地引用它们,通过嘿index2放在这里,我知道这是我的法线,因为这是我定义的布局,这就是一个索引。它只是在你缓存里面的实际索引。size这里可能会引起误解,你可以看到这里很清楚地说明了它必须是1,2,3,4所以这与字节大小或者内存实际占有无关。它基本就是一个数目,所以我的意思是当这里我们指定类型,像我们实际用的顶点位置GL_FLOAT,这就是有多少浮点数要提供的数字。所以这里很明显,每个顶点我们有两个浮点数,所以这里的大小会是2,因为我们bascally有了两浮点数的顶点组件,这代表了位置,如果我们切换到了三维坐标系统,就像我之前提到的,我们要将它设置成3。我刚才解释的基本上是数据类型,我们提供GL_FLOAT是我们想要的。
normolized这又一点点难办,我们不需要真的担心这些,尤其是如果我们在解决浮点数,因为它们已经是标准的,但它实际是用来。(我们刚才说,我们指定了一个字节,在0到255之间,因为这是颜色的值,在我们着色器里面这需要作为一个浮点数转换成0-1之间。这就是shader实际为我们做的,这不是某样东西的例子,你可以自己在cpu上面试一下,你也可以让opengl做) GL_FALSE
stride和pointer这是大部分人比较疑惑的。在文档的描述,stride是连续通用顶点属性之间的字节偏移量。实际上,stride就是每个顶点之间的字节数量。所以,再说一次现在再我们的例子里面,如果我们有一个位置,纹理,法线,我们就说位置他是一个三维顶点组件,然后纹理是二维,然后法线又是三维的。我们有3个float,12字节,2float给纹理,8字节,有12字节给了法线,所以32字节就是我们的stride,因为实际上,这是每个顶点的大小,这是很重要的,因为如果Opengl也会调用步幅,如果我们想从一个顶点跳到下一个顶点,例如,我想看index1,所以是从0开始,0是我们的第一个顶点,我想要跳到第二个顶点也就是Index1我需要在缓存里走32字节。所以我们有一个指针,指向缓存的开始,然后我走32字节,我到了下一个顶点的开始,这就是stride。
pointer另一方面,这是对第一个组件的偏移。pointer就是一个指针,在实际的属性里面,所以它已经在顶点的空间里面了。你不用担心你有多少个顶点,假装你只有一个顶点,我有一个顶点,然后里面我有位置、纹理、坐标和法线。这就是那些东西在字节里面的每一个偏移,对于位置来说它的偏移是0,因为我们缓存的第一个字节,我们顶点的第一个字节,就是位置,然后我们往前走12字节,我们到达了纹理坐标的开始,所以纹理坐标属性这个值就应该是12,因为从开始的地方到这里是12字节。最后8字节是我们到顶点的法线,所以20字节从开始到顶点法线,因此顶点法线属性指针的这个值就应该是20。现在,代替刚才写的像0,12,20这些东西,可能有点迷惑,不仅如此如果你重新排列你的布局或者别的你必须重新计算或手动操作的,有一个宏叫general demonstrated???C++里面给提供了计算便宜的宏。你可以在一个结构体或类里面找到并使用它,你可以像stride一样,你可以用宏之类的东西来自动完成这些工作。所以你不用往里面加像12,32这样的值。我只是向你解释这是什么。
把缓存绑定起来,这是很重要的,我将写glVertexAttribPointer(),第一个参数是index
现在,这是一个简单的例子,我们只有位置,所以首先我们只有一个顶点属性,所以我们只需要调用这个函数1次,因为我们指定一个单一属性索引,当然会是0,因为这是第一个属性,size记住size是组件数目2,Type ,不管我们是否希望它们被规范化它们已经是流了,它们已经在我们想要的空间里了,所以GL_FALSE,接下来是stride。sizeof(float)2。 指针是数字,(const void)8
这就是我们需要实现顶点属性glEnableV,,这是禁止生成顶点属性的,回到我们的代码里,我喜欢在我写指针前这样做,它不会实际影响到opengl。当然作为哦状态机,它不是要检查它是否被启用。
image.pngimage.png