Note: the original English version is preferred
concept
- In this section, we will discuss some more interesting buffer functions and how we can use texture objects to store a large amount of data (the texture part has not been completed).
- When we bind a buffer to GL_ARRAY_BUFFER is a vertex array buffer, but we can also easily bind it to GL_ELEMENT_ARRAY_BUFFER. OpenGL internally stores a buffer for each target, and handles the buffer in different ways according to different targets.
- So far, we have been calling the glBufferData function to fill the memory managed by the buffer object. This function will allocate a piece of memory and add data to this memory. If we set its data parameter to NULL, the function will only allocate memory, but will not fill it. This is useful when we need to reserve a specific size of memory and then go back to the buffer to fill it bit by bit.
Details
- Before: just bind memory and fill a whole block of memory
glBindVertexArray(skyboxVAO); glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
- In addition to filling the entire buffer with one function call, we can also use glBufferSubData to fill a specific area of the buffer. This function requires a buffer target, an offset, the size of the data, and the data itself as its parameters. The difference in this function is that we can provide an offset to specify where to start filling the buffer. This allows us to insert or update a portion of the buffer memory. It should be noted that the buffer needs enough allocated memory, so glBufferData must be called before calling glBufferSubData for a buffer.
- glBufferSubData:
Fills the memory managed by the buffer object
Allocate a piece of memory and add data to it
If the data parameter is set to NULL, only memory is allocated and not populated
glBufferSubData must be called before buffering a specific area
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // Range: [24, 24 + sizeof(data)]
- glMapBuffer
//Another way to import data into the buffer is to request a pointer to the buffer memory and directly copy the data into the buffer. //By calling the glMapBuffer function, OpenGL will return the memory pointer of the current binding buffer for us to operate: float data[] = { 0.5f, 1.0f, -0.35f ... }; glBindBuffer(GL_ARRAY_BUFFER, buffer); //Note that binding is required before processing // Get pointer void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);//Objectives and methods // Copy data to memory memcpy(ptr, data, sizeof(data)); // Remember to tell OpenGL that we no longer need this pointer glUnmapBuffer(GL_ARRAY_BUFFER);
Batch vertex attributes
- Previously: in the processing VAO of mesh.h
// set the vertex attribute pointers // vertex Positions glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); // vertex normals glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); // vertex texture coords glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); // vertex tangent glEnableVertexAttribArray(3); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent)); // vertex bitangent glEnableVertexAttribArray(4); glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent)); glBindVertexArray(0);
- Now with glVertexAttribPointer: we can specify the attribute layout of the buffer contents of the vertex array.
In the vertex array buffer, we interleave the attributes, that is, we place the position, discovery and / or texture coordinates of each vertex closely together. Now that we know more about buffering, we can take another approach.
What we can do is to batch the vector data of each attribute type into a large block instead of interleaving them. Unlike the staggered layout 123123123, we will use the batched method 111122223333.
When loading vertex data from a file, you usually get a position array, a normal array and / or a texture coordinate array. It takes some effort to convert these arrays into a large interleaved data array. Using a batch approach would be a simpler solution
//in batches float positions[] = { ... }; float normals[] = { ... }; float tex[] = { ... }; // Fill buffer glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions); glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals); glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
- In this way, we can directly pass the attribute array to the buffer as a whole without processing them in advance. We can still combine them into a large array and use glBufferData to fill the buffer, but glBufferSubData is more appropriate for this kind of work.
We also need to update the vertex attribute pointer to reflect these changes: - When updating the vertex attribute pointer, pay attention to distinguish the distance and size, which increases step by step with the increase of content
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions))); glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
Copy buffer
- After your buffer has been filled with data, you may want to share the data with other buffers, or you may want to copy the contents of the buffer to another buffer.
glCopyBufferSubData makes it relatively easy to copy data from one buffer to another.
//The readtarget and writetarget parameters need to be filled with the buffer targets of the replication source and replication target. //For example, we can put VERTEX_ARRAY_BUFFER buffer copy to VERTEX_ELEMENT_ARRAY_BUFFER buffer, set these buffer targets as read and write targets respectively. //The buffers currently bound to these buffer targets will be affected. void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);
- But what if we want two different buffers for reading and writing data to be vertex array buffers (both reading and writing are one)? We cannot bind two buffers to the same buffer target at the same time. For this reason, OpenGL provides us with two other buffer targets called GL_COPY_READ_BUFFER and GL_COPY_WRITE_BUFFER. Next, we can bind the required buffer to the two buffer targets, and take the two targets as the readtarget and writetarget parameters.
//Next, glCopyBufferSubData reads the size data from the readtarget and writes it to the writeoffset offset of the writetarget buffer. //The following example shows how to copy two vertex array buffers: float vertexData[] = { ... }; glBindBuffer(GL_COPY_READ_BUFFER, vbo1); glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
//We can also bind the writetarget buffer only to one of the new buffer target types float vertexData[] = { ... }; glBindBuffer(GL_ARRAY_BUFFER, vbo1); glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));