Using Multiple Vertex Shader Inputs
周一到周五,每天一篇,北京时间早上7点准时更新~
As you have learned, you can get OpenGL to feed data into your vertex shaders and use data you've placed in buffer objects. You can also declare multiple inputs to your vertex shaders, and assign each one a unique location that can be used to refer to it. Combining these things together means that you can get OpenGL to provide data to multiple vertex shader inputs simultaneously. Consider the input declarations to a vertex shader shown in Listing 5.6.
你已经学会了使用使用缓冲区为shader输入数据,你也可以为你的vertex shader定义多个输入的属性。结合这些所学的东西,就意味着你可以同时给shader输入多组数据。 让我们来看看清单5.6的代码
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
Listing 5.6: Declaring two inputs to a vertex shader
清单5.6申明了vertex shader的两个输入数据
If you have a linked program object whose vertex shader has multiple inputs, you can determine the locations of those inputs by calling
如果你生成了一个有多组输入数据的GPU程序,那么你可以通过下面的API来获取某一个输入数据的绑定位置。
GLint glGetAttribLocation(GLuint program,const GLchar * name);
Here, program is the name of the program object containing the vertex shader and name is the name of the vertex attribute. In our example declarations of Listing 5.6, passing "position" to glGetAttribLocation() will cause it to return 0, and passing "color" will cause it to return 1. Passing something that is not the name of a vertex shader input will cause glGetAttribLocation() to return −1. Of course, if you always specify locations for your vertex attributes in your shader code, then glGetAttribLocation() should return whatever you specified. If you don't specify locations in shader code, OpenGL will assign locations for you, and those locations will be returned by glGetAttribLocation(). There are two ways to connect vertex shader inputs to your application's data, referred to as separate attributes and interleaved attributes. When attributes are separate, they are located either in different buffers or at least at different locations in the same buffer. For example, if you want to feed data into two vertex attributes, you could create two buffer objects, bind each to a different vertex buffer binding with a call to glVertexArrayVertexBuffer(), and then specify the two indices of the two vertex buffer binding points that you used when you call glVertexArrayAttribBinding() for each. Alternatively, you could place the data at different offsets within the same buffer, bind it to a single vertex buffer binding with one call to glVertexArrayVertexBuffer(), and then call glVertexArrayAttribBinding() for both attributes, passing the same binding index to each. Listing 5.7 shows this approach.
第一个参数是GPU程序,第二个参数是顶点属性的名字。在我们清单5.6的代码中,名字这个参数如果传position,那么该API会返回0,如果传color,那么该API会返回1. 如果你传了一个不存在的名字,那么该API返回-1.当然,如果你在shader代码中去定义了属性的绑定位置,那么你总会拿到跟你设置的那些位置一致的返回值。如果你不去在shader里 设置属性的绑定位置,那么OpenGL将会为你分配这些位置,你通过这个API就可以获取到那些位置。有两种方法可以让你给shader中的属性传输数据,一种是separate,另一种叫interleaved。 当使用separate方式传输数据的时候,你的数据会存放在不同的缓冲区对象里,或者存储在同一个缓冲区对象里的不同位置。比如你希望给shader中的两个属性传输数据,那么你可以创建两个缓冲区对象, 然后调用glVertexArrayVertexBuffer去将它们各自绑定到不同的缓冲区的绑定节点,然后通过glVertexArrayAttribBingding分别为它们指定两个它们使用的索引。 另外,你也可以把数据放在同一个缓冲区的不同位置,然后使用跟上面相同的方法去做这件事。清单5.7展示了如何做到这些操作
GLuint buffer[2];
GLuint vao;
static const GLfloat positions[] = { ... };
static const GLfloat colors[] = { ... };
// Create the vertex array object
glCreateVertexArrays(1, &vao)
// Get create two buffers
glCreateBuffers(2, &buffer[0]);
// Initialize the first buffer
glNamedBufferStorage(buffer[0], sizeof(positions), positions, 0);
// Bind it to the vertex array - offset zero, stride = sizeof(vec3)
glVertexArrayVertexBuffer(vao, 0, buffer[0], 0, sizeof(vmath::vec3));
// Tell OpenGL what the format of the attribute is
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0);
// Tell OpenGL which vertex buffer binding to use for this attribute
glVertexArrayAttribBinding(vao, 0, 0);
// Enable the attribute
glEnableVertexArrayAttrib(vao, 0);
// Perform similar initialization for the second buffer
glNamedBufferStorage(buffer[1], sizeof(colors), colors, 0);
glVertexArrayVertexBuffer(vao, 1, buffer[1], 0, sizeof(vmath::vec3));
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 1, 1);
glEnableVertexAttribArray(1);
Listing 5.7: Multiple separate vertex attributes
清单5.7:通过separate方式为顶点属性传入数据
In both cases of separate attributes, we have used tightly packed arrays of data to feed both attributes. This is effectively structure-of-arrays (SoA) data. We have a set of tightly packed, independent arrays of data. However, it's also possible to use an array of-structures (AoS) form of data. Consider how the following structure might represent a single vertex:
两种separate的数据传输方式我们都用到了非常紧凑的数据块。当然,使用AOS也是可以的,我们来看看下面这样的结构体。
struct vertex
{
// Position
float x;
float y;
float z;
// Color
float r;
float g;
float b;
};
Now we have two inputs to our vertex shader (position and color) interleaved together in a single structure. Clearly, if we make an array of these structures, we have an AoS layout for our data. To represent this with calls to glVertexArrayVertexBuffer(), we have to use its stride parameter. The stride parameter tells OpenGL how far apart in bytes the beginning of each vertex's data is. If we leave it as 0, OpenGL will use the same data for every vertex. However, to use the vertex structure declared above, we can simply use sizeof(vertex) for the stride parameter and everything will work out. Listing 5.8 shows the code to do this
现在我们使用interleaved方式为shader的两个属性输入数据。很明显,如果我们有一组这样的结构体类型的数据,我们使用的是AOS的数据块。我们使用glVertexArrayVertexBuffer来设置的时候,我们 需要用到它的stride参数。stride参数告诉OpenGL两个vertex之间的间隔,如果这个参数我们写0,那么所有的顶点都使用的是同一个数据。使用这个数据结构是很简单的,我们直接使用sizeof(vertex)来赋值 给stride参数。清单5.8展示了刚才讨论的这种方式的样本代码
GLuint vao;
GLuint buffer;
static const vertex vertices[] = { ... };
// Create the vertex array object
glCreateVertexArrays(1, &vao);
// Allocate and initialize a buffer object
glCreateBuffers(1, &buffer);
glNamedBufferStorage(buffer, sizeof(vertices), vertices, 0);
// Set up two vertex attributes - first positions
glVertexArrayAttribBinding(vao, 0, 0);
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex,x));
glEnableVertexArrayAttrib(0);
// Now colors
glVertexArrayAttribBinding(vao, 1, 0);
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex,
r));
glEnableVertexArrayAttrib(1);
// Finally, bind our one and only buffer to the vertex array object
glVertexArrayVertexBuffer(vao, 0, buffer);
Listing 5.8: Multiple interleaved vertex attributes
清单5.8:使用interleaved方式为shader的多个属性输入数据
After executing the code in Listing 5.8, you can bind the vertex array object and start pulling data from the buffers bound to it. After the vertex format information has been set up with calls to glVertexArrayAttribFormat(), you can change the vertex buffers that are bound with further calls to glVertexArrayAttribBinding(). If you want to render a lot of geometry stored in different buffers but with similar vertex formats, simply call glVertexArrayAttribBinding() to switch buffers and start drawing from them.
当执行完毕了清单5.8的代码之后,你就可以拿着它去画画了。在使用glVertexArrayAttribFormat设置好了缓冲区之后,你可以调用glVertexArrayAttribBinding去修改当前的缓冲区对象。 如果你想渲染很多内存相似,却存储在不同缓冲区里的几何形体时,你可以简单调用glVertexArrayAttribBinding去切换输入缓冲区,然后直接调用绘图函数。
Loading Objects from Files(从文件加载物体)
As you can see, you could potentially use a large number of vertex attributes in a single vertex shader. As we progress through various techniques, you will see that we'll regularly use four or five vertex attributes, and possibly more. Filling buffers with data to feed all of these attributes and then setting up the vertex array object and all of the vertex attribute pointers can be a chore. Further, encoding all of your geometry data directly in your application isn't practical for anything but the simplest models. Therefore, it makes sense to store model data in files and load it into your application. There are plenty of model file formats out there, and most modeling programs support several of the more common formats.
如你所见,你可能在shader里需要使用大量的顶点属性。随着学习的深入,我们时常需要经常用到四五个顶点属性,或许更多。手工的去干这个活很麻烦。另外, 直接在应用程序里手写这些几何数据,会让你内分泌失调。因此,从文件加载模型这个选择看起来非常不错。模型的种类有很多,很多建模软件都支持一些比较常见的模型格式。
For the purpose of this book, we have devised a simple object file definition called an .SBM file, which stores the information we need without being either too simple or too overly engineered. Complete documentation for the format is found in Appendix B, "The SBM File Format." The sb7 framework also includes a loader for this model format, called sb7::object. To load an object file, create an instance of sb7::object and call its load function as follows:
为了学习的需要,我们来使用一个后缀叫SBM的模型格式。该格式的完整格式描述在附录B。我们教程的框架代码提供加载该格式的代码,sb7::object。你可以通过下面的代码加载一个sbm的模型
sb7::object my_object;
my_object.load("filename.sbm");
If this operation is successful, the model will be loaded into the instance of sb7::object and you will be able to render it. During loading, the class will create and set up the object's vertex array object and then configure all of the vertex attributes contained in the model file. The class also includes a render function that binds the object's vertex array object and calls the appropriate drawing command. For example, calling
如果加载成功,你之后就可以渲染它了。在加载的时候,这个类会配置好VAO以及所有的顶点属性。这个类还包括了渲染的API接口,里面绑定了VAO之后,然后调用了绘图函数。比如下面这样
my_object.render();
will render a single copy of the object with the current shaders. In many of the examples in the remainder of this book, we'll simply use our object loader to load object files (several of which are included with the book's source code) and render them.
上面的接口就会使用当前的shader去渲染该模型。在本书剩余的内容中,我们将使用这个模型类去加载并渲染模型。
本日的翻译就到这里,明天见,拜拜~~
第一时间获取最新桥段,请关注东汉书院以及图形之心公众号
东汉书院,等你来玩哦