9
\$\begingroup\$

I am writing an OpenGL 3D game. There will be tons of triangles for terrain and objects in use.

I am studying from the OpenGL official guide and the first presented method is to call a function glVertex after the glBegin for each vertex you want to draw.

However this method sounds quite antique and inefficient when you have to draw thousands of triangles. I guess there are methods to store in the graphic card memory the information that does not change at every frame so that when you render each frame that information is already there. While for more "unstable" information probably you have to send new vertex data every frame and so a different tecnique might be more efficient.

Coming to my question: I would like a clear explanation of the most modern and efficient OpenGL functions and tecniques used to send vertex information to the hardware and to give instruction to render.

What are the best ways and OpenGL tools to send the order to render vertex data that seldom changes during the frames (i think of terrain and objects) and what it the best way to send vertex data that changes a lot during frames?

\$\endgroup\$
2
  • \$\begingroup\$ You are right, glBegin() and glEnd() are part of the immediate mode rendering and pretty old fashioned. Nowaday one would use for example VBOs. How exactly and what you use is dependent on you scenario though! \$\endgroup\$
    – thalador
    Commented Jun 3, 2013 at 11:07
  • \$\begingroup\$ I am also a student trying to learn OpenGL. I know that you will need to use Vertex Buffer Objects (VBOs) to draw vertexes faster. While I just don't know enough to tell you how to do that I can give you a link to a great (free online) book that does. I have skimmed it and made a little triangle but it is really complex. Arcsynthesis Modern OpenGL Tutorial I'm going through LazyFoo's tutorials right now. They start with the glBegin(), glEnd() fixed function pipeline then move into more modern OpenGL using the fixed function pipeline as a foundation. I'll j \$\endgroup\$
    – benbot
    Commented Jun 3, 2013 at 13:43

1 Answer 1

21
\$\begingroup\$

Broadly, the answer is "it depends." The way one would send particle updates is quite a bit different than the way you would send a series of GPU-skinned models.

Vertex/Index Buffers

In the general sense, though, everything these days is done with a VBO (vertex buffer object). The old immediate-mode API (glBegin/glEnd) is probably just implemented as a fat wrapper around the driver's internal vertex buffer system.

For static objects, create a static VBO and fill it with your vertex data. If any of the vertices are shared (usually the case), you probably also want to create an index buffer. That both reduces the need to send the same data more than once for changing meshes (potentially saving on transfer bandwidth) and on processing (saving on vertex shading time). Note that you draw with different functions when making indexed vs non-indexed draws.

For dynamic objects, do the same, albeit with a dynamic set of buffers.

Advanced Notes

For larger pieces like terrain, you probably won't to break up the mesh into multiple pieces. Making the GPU render a hundred million triangles when only two hundred thousand of them are visible is a huge waste, especially if they're unsorted and there's lots of overdraw and wasted fragment shader invocations. Break up the mesh into large chunks and then only render those that are within the view frustrum. There's also various more advanced culling techniques you can use to weed out chunks that might be in the frustrum but are entirely behind a hill or building or something. Keeping draw call count down is good, but there's a balance to be had (which you have to find for your specific app/hardware) between minimizing draw calls and minimizing the drawing of hidden geometry.

One of the key things to keep in mind with a GPU buffer is that you cannot write to it while the GPU is reading from it. You need to let the driver know that it's okay to discard the old copy of the buffer (when it's done) and to give you a new one (if the old one is busy). There is of course for a long time was no function is OpenGL to do this (now there is InvalidateBufferData for GL 4.3 and some older implementations as an extension). Rather, there is a non-standard but common behavior most drivers implement. Do this to discard the buffer before updating it:

glBindBuffer(GL_ARRAY_BUFFER, my_vbo);
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_DYNAMIC_DRAW);

Of course change GL_ARRAY_BUFFER and GL_DYNAMIC_DRAW to the appropriate values for the buffer. Static buffers won't be updated (or shouldn't be) so you're unlikely to need to worry about discard for such a buffer.

Note that it might be faster to use glBufferData or glBufferSubData or it might be faster with glMapBuffer. It really depends on the driver and hardware. Current generation PC hardware will probably be fastest with glBufferData but test to be sure.

Another technique is to use instancing. Instancing allows you to make a single draw call that draws multiple copies of the data in a vertex/index buffer. If you had, say, 100 identical rocks then you would want to draw them all in one go rather than making 100 independent draws.

When instancing you need to put per-instance data in another buffer (like each individual's object position). This can either be a uniform buffer (constant buffer in D3D terminology) or a texture buffer or a per-instance vertex attribute. Again, which is faster depends. Per-instance attributes are probably faster and definitely way easier to use, but many common GL implementations still don't support glBindingAttribDivisor so you'll have to see if it's available to use and if it really is faster (some older drivers emulated instancing by appending buffers and it ended up being slower to use instancing on them than to make independent draw calls and there's no standard way to find out... the joys of using OpenGL).

There are also algorithms for vertex cache optimization, which is the act of ordering vertices/indices in your buffers to play will with the vertex cache on modern GPUs. A GPU will only run the shader for a vertex then cache it in the vertex cache but it might have to be evicted too early to make room for other vertices. (Say, two triangles both share a vertex but there are 100 other triangles drawn between them; the shared vertex will probably end up being wastefully evaluated by the vertex shader twice.)

Some of these features require a new-enough version of GL or GLES. GLES2 did not support instancing, for example.

Always Profile

Again, if you care about performance, test each possible method and see which is faster for your app on your target hardware. Not only will different hardware/drivers from different manufacturers be different but some whole classes of hardware are innately different. A typical mobile GPU is a very different beast from a typical discrete desktop GPU. Techniques that are "best" on one will not necessarily be best on another.

When it comes to performance, always be a skeptic.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .