Vertex Buffer Objects

Vertex Buffer Objects (VBOs) are Buffer Objects that are used for vertex data. Since the storage for buffer objects is allocated by OpenGL, vertex buffer objects are a mechanism for storing vertex data in "fast" memory (i.e. video RAM or AGP RAM, and in the case of PCI Express, video RAM or RAM), thereby allowing for significant increases in vertex throughput between the application and the GPU.

Vertex data can be either vertex attributes and/or indices.

Legacy Note: Versions of OpenGL prior to 3.0 allowed the use of client-side data for vertex rendering, but GL 3.0 deprecated this. Core GL 3.1 and greater forbid client-side vertex data, so VBOs are the only means for rendering.

Inner Workings

Chunks of vertex attribute or index data are encapsulated in vertex buffer objects (VBO). A VBO is an opaque handle. Much like a texture object or display list, you can associate data with a VBO but the actual storage location of the data is hidden from you. The API for creating a VBO is very similar to the texture object APIs. A VBO is created using glGenBuffers() and destroyed using glDeleteBuffers(). Before rendering from a VBO, it has to be made active using glBindBuffer().

Data can be written to a VBO in two ways. The first way is through the GL, using the glBufferData() or glBufferSubData() functions. These functions are conceptually similar to glTexImage*() and glTexSubImage*().

The second option is to obtain a direct pointer to the VBO data and to write the data to this memory area yourself. This technique is called "mapping a buffer". The application calls glMapBuffer() to obtain a pointer to the VBO, then writes data to it, and finishes by calling glUnmapBuffer(). When calling glMapBuffer(), you have to specify the desired access mode that you would like for the data. This can be GL_READ_ONLY, GL_WRITE_ONLY, or GL_READ_WRITE. The meanings of these are self-explanatory. Obviously you should not attempt to read from a write-only mapping or vice versa.

Restrictions

It's important to note that a VBO cannot be rendered from while it is mapped. This restriction exists so that drivers may be free to move the data around as they see fit in order to provide the best performance. This also means that mapping and unmapping the same buffer twice will not necessarily give you back the same pointer. You should never store the pointer to a buffer after you've unmapped that buffer, and you should never pass it to GL (e.g. to glVertexPointer() or related functions).

Also note that it's not possible to map a buffer until memory has been allocated for it. To do so, you have to use glBufferData() at least once. If you don't have any data available at buffer creation time, you can pass a null pointer to allocate uninitialized memory. You can then fill this memory later by mapping the buffer or by using glBufferSubData().

Allocating VBOs

When allocating VBOs, you have to specify your intended usage for them. The first thing you have to specify is what kind of data you will store in the buffer -- this can be either vertex attribute data or glDrawElements index data. You do this by setting the target parameter of glBufferData() to GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER, respectively. The same target also has to be specified when binding or mapping the buffer. The second thing you have to specify is the access pattern you will use for your buffer.

Accessing VBOs

The access pattern is specified as a combination of two settings. The first is how often you intend to modify the data. The three possible settings are:

  • STATIC : You will specify the data only once, then use it many times without modifying it.
  • STREAM : You will modify the data once, then use it once, and repeat this process many times.
  • DYNAMIC : You will specify or modify the data repeatedly, and use it repeatedly after each time you do this.

The second setting indicates what the source and destination of the data will be. There are again three possibilities:

  • DRAW : The data is generated by the application and passed to GL for rendering.
  • COPY : The data is generated by GL, and copied into the VBO to be used for rendering.
  • READ : The data is generated by GL, and read back by the application. It is not used by GL.

The usage parameter of glBufferData() is a combination of these two settings, as shown in this table:

 DRAW COPY READ
STATIC GL_STATIC_DRAW GL_STATIC_COPY GL_STATIC_READ
STREAM GL_STREAM_DRAW GL_STREAM_COPY GL_STREAM_READ
DYNAMIC GL_DYNAMIC_DRAW GL_DYNAMIC_COPY GL_DYNAMIC_READ

Rendering from a VBO (or VBOs) is like rendering from a normal vertex array, and can be done using any of the relevant OpenGL draw functions such as glDrawArrays(), glDrawElements(), glDrawRangeElements(), etc.. There are however two important steps to take:

The first is that you have to make your VBO active by calling glBindBuffer(), taking care to bind the buffer to the correct target.

The second is that you have to pass VBO byte offsets to the relevent GL call(s) instead of CPU memory pointers. For instance, for a vertex attribute VBO containing vertex positions, instead of passing a CPU memory pointer to the ptr parameter of glVertexPointer(), you'd pass the a byte offset into the VBO (e.g. NULL for offset 0). Similarly for a index VBO, you'd pass an offset into the VBO (e.g. NULL for 0) instead of a CPU memory pointer to an index list to the indices parameter of glDrawElements() (or similar draw call).

Sample Code

I suggest that you make a VBO for your vertices (vertex, normals, texcoords, perhaps other things) and another VBO for indices (IBO). I suggest that you interleave your vertex attributes for best performance. The order of attributes does not matter for performance because it's just a pointer to a memory location for the GPU.

Make a structure for your vertex attributes in your C++ code :

struct MyVertex
{
float x, y, z;        //Vertex
float nx, ny, nz;     //Normal
float s0, t0;         //Texcoord0
float s1, t1;         //Texcoord1
float s2, t2;         //Texcoord2
float padding[4];
};

The structure's size is 64 bytes. ATI suggests that you align your vertex size for 32 bytes, so I added 4 floats to make it 2 * 32 bytes. For nVidia, I think it doesn't matter

Generate a VBO for vertices. Make it STATIC, meaning we will never change it. It will be stored in VRAM:

glGenBuffers(1, VertexVBOID);

GL_ARRAY_BUFFER is for vertices:

glBindBuffer(GL_ARRAY_BUFFER, VertexVBOID);

The parameter SizeInBytes is how big you want your VBO to be, don't make it too small and don't make it too large. For good performance, allocate something like 1MB or 2MB or 4MB or 8MB and put many objects to be rendered into it. Notice that the pointer is NULL, which means we want GL to allocate memory but not initialize it. If you want to initialize, just pass a pointer to your array which holds the vertex data.

glBufferData(GL_ARRAY_BUFFER, SizeInBytes, NULL, GL_STATIC_DRAW);

Let's assume you used NULL. You can use glBufferSubData to upload all your vertices to the VBO or just upload some vertices. This is nice if you want to update only some of the vertices, but right not, we keep the example simple with lots of comments.

MyVertex pvertices[XXX];
//Fill the pvertices array
pvertices[0].x=0.0;
pvertices[0].y=0.0;
pvertices[0].z=0.0;
pvertices[0].nx=0.0;
pvertices[0].ny=0.0;
pvertices[0].nz=1.0;
pvertices[0].s0=0.0;
pvertices[0].t0=0.0;
//etc....
glBufferSubData(GL_ARRAY_BUFFER, offsetInByte, SizeInBytes, &pvertices[0].x);

Now it's time to make a VBO for the indices. The same API for vertices is used but instead of calling it a VBO, we can call it a index buffer object - IBO.

glGenBuffers(1, &IndexVBOID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBOID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, SizeInBytes, NULL, GL_STATIC_DRAW);
ushort pindices[YYY];
pindices[0]=0;
pindices[1]=5;
//etc...
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offsetInByte, SizeInBytes, pindices);


For best performance, they suggest that you used 16 bit unsigned integers (short).
32 bit unsigned integer is the other option.
Don't use anything ridiculous like unsigned byte.

Now it's time to render. I have not included the part about setting up shaders or binding shaders.

Make the following definition in your C++ code:

define BUFFER_OFFSET(i) ((char *)NULL + (i))

Bind your VBO. P.S., Our VBO is already bound actually.

glBindBuffer(GL_ARRAY_BUFFER, VertexVBOID);

Call your gl***Pointer functions to make a INTERLEAVED ARRAY. The pointers are relative to the zero point of the VBO. (missing: calls to glEnableClientState(..) )

glVertexPointer(3, GL_FLOAT, 64, BUFFER_OFFSET(0));
glNormalPointer(GL_FLOAT, 64, BUFFER_OFFSET(12));
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(24));
glClientActiveTexture(GL_TEXTURE1);
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(32));
glClientActiveTexture(GL_TEXTURE2);
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(40));

Bind your IBO. P.S., Our IBO is already bound actually.

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBOID);

Draw and pass the pointer relative to the zero point of the IBO.

glDrawRangeElements(GL_TRIANGLES, x, y, z, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));

And in the end, (missing: calls to glDisableClientState(..))


When you are done using the VBO, release the handles

glDeleteBuffers(1, &VertexVBOID);
glDeleteBuffers(1, &IndexVBOID);

In summary:

  • Make your vertex structure.
    Make it multiple of 32 bytes in size.
  • Make a VBO.
  • Make an IBO.
    Use 16 bit integer.
  • Try not to make many redundant calls to GL to maximize performance.
  • Don't use glInterleaved array because almost nobody uses it and it's limited. You can read about it here: Common Mistakes

I haven't talked about glMapBuffer and glUnmapBuffer, but they're easy to use.

Tips and tricks

  • Mapping a buffer may cause your data to be lost.
  • Array pointers are only treated as offsets if a buffer object is bound. Binding 0 returns to normal behaviour (i.e. direct pointers).
  • Multiple buffers can be mapped simultaneously.
  • All vertex attributes do not necessarily need to be in the same buffer object.
  • Calling glBufferData with a NULL pointer before uploading new data can improve performance (tells the driver you don't care about the old contents)

Size of a VBO/IBO
How small or how large should a VBO be?

You can make it as small as you like but it is better to put many objects into one VBO and attempt to reduce the number of calls you make to glBindBuffer and glVertexPointer and other GL functions.

You can also make it as large as you want but keep in mind that if it is too large, it might not be stored in VRAM or perhaps the driver won't allocate your VBO and give you a GL_OUT_OF_MEMORY.

1MB to 4MB is a nice size according to one nVidia document. The driver can do memory management more easily. It should be the same case for all other implementations as well like ATI/AMD, Intel, SiS.
Formatting VBO Data

VBOs are quite flexible in how you use them. For instance, there are a number of ways you can represent vertex attribute data in VBOs:
(VVVV) (NNNN) (CCCC)
One option for using them would be, for each batch (draw call) allocate a separate VBO per vertex attribute. This is certainly possible. If you have vertex, normal, and color as vertex attributes, pictorially this is: (VVVV) (NNNN) (CCCC)
(VVVVNNNNCCCC)
Another approach is to store the vertex attribute blocks in a batch, one right after the other, in the same block and stuff them all in the same VBO. When specifying the vertex attributes via gl*Pointer() calls you'd pass byte offsets into the VBO to the ptr parameters. Pictorially, this is: (VVVVNNNNCCCC).
(VNCVNCVNCVNC)
Yet another approach is to interleave the vertex attributes for each vertex in a batch, and then store each of these interleaved vertex blocks sequentially, again combining all the vertex attributes into a single buffer. As before, you'd pass byte offsets into the VBO to the gl*Pointer() ptr parameters, but you'd also use the stride parameter to ensure each vertex attribute array access only touched elements for that attribute array. Pictorially, this option is: (VNCVNCVNCVNC)

Now this is just a single batch. There's also nothing stopping you from storing the vertex attribute data for multiple batches inside a single VBO or set of VBOs.

Now what's best for performance? That's going to depend on the specific GL hardware and driver, but we can apply a little common sense to help steer our choices. First, combining multiple batches often rendered side-by-side into a single VBO or set of VBOs makes sense because this can result in fewer buffer binds inside the application (assuming the application implements lazy VBO buffer binds). Also, for static vertex attribute data, interleaving the vertex data in the VBO makes sense because this results in better memory locality and streaming inside the GPU. However, if we need to dynamically update a single vertex attribute in a batch every so often, we'll likely want to pull this vertex attribute off into its own VBO so we can update it by itself.

Note that when interleaving vertex attribute data some folks say there is gain to be had by aligning blocks of interleaved vertex data on 32-byte-aligned boundaries (keep in mind, there's nothing stopping you from inserting dead space into a VBO, if you're using the stride parameter to your pointer set calls). However, I personally have not seen any significant gain from doing this.

Vertex, normals, texcoords
Should you create a separate VBO for each? Would you lose performance?

If your data is static, then make 1 VBO for best performance. Be sure to interleave your vertex attribute data in the VBO and make the data block for each vertex a multiple of 32 bytes for good cache line coherence. See the other VBO page because it explains these details.

If one of the vertex attributes is dynamic, such as the vertex positions, you could store this in separate VBO.

By dynamic, we mean that you will be updating the VBO every frame. Perhaps you want to compute the new vertices on the CPU. Perhaps you are doing some kind of water simulation. etc.

No, you don't lose much performance if you use separate VBOs. It would be on the order of 5% but your testing might show otherwise.
EXAMPLE: Multiple Vertex Attribute VBOs Per Batch
  //Binding the vertex
  glBindBuffer(GL_ARRAY_BUFFER, vertexVBOID);
  glVertexPointer(3, GL_FLOAT, sizeof(float)*3, NULL);  //Vertex start position address

  //Bind normal and texcoord
  glBindBuffer(GL_ARRAY_BUFFER, otherVBOID);
  glNormalPointer(GL_FLOAT, sizeof(float)*6, NULL); //Normal start position address
  glTexCoordPointer(2, GL_FLOAT, sizeof(float)*6, sizeof(float*3);  //Texcoord start position address
Dynamic VBO
If the contents of your VBO will be dynamic, should you call glBufferData or glBufferSubData (or glMapBuffer)?

If you will be updating a small section, use glBufferSubData. If you will update the entire VBO, use glBufferData (this information reportedly comes from a nVidia document). However, another approach reputed to work well when updating an entire buffer is to call glBufferData with a NULL pointer, and then glBufferSubData with the new contents. The NULL pointer to glBufferData lets the driver know you don't care about the previous contents so it's free to substitute a totally different buffer, and that helps the driver pipeline uploads more efficiently.

Another thing you can do is double buffered VBO. This means you make 2 VBOs. On frame N, you update VBO 2 and you render with VBO 1. On frame N+1, you update VBO 1 and you render from VBO 2. This also gives a nice boost in performance for nVidia and ATI/AMD.

转载于:https://www.cnblogs.com/weizhixiao/archive/2009/11/18/5697369.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值