6
$\begingroup$

Especially when rendering particle effects, the same object needs to be rendered several times with slightly modified properties. But these changes are often limited to properties like pose or textures, and are not in the geometry of the object itself.

A different pose (translation, rotation) is merely a matrix multiplication with the vertices, and a different texture with an applied texture atlas are merely different texture coordinates.

Nevertheless each of the particles needs to be drawn and I currently do that by calling glDrawElements for every particle, while setting uniforms appropriately. Until now this is sufficient for rendering the scene in real-time, but for more complex scenes or simply for more particles emitted by the particle system this could easily lead to dropping frame rates.

So, is there a way to reduce the number of draw calls for very similar objects?

$\endgroup$
2
  • 1
    $\begingroup$ I know there is Instancing in OpenGL, that's why I also posted an answer here. But maybe there is also some other way to achieve the same result. $\endgroup$
    – Nero
    Commented Aug 16, 2015 at 16:28
  • 1
    $\begingroup$ Seems you were right... :) $\endgroup$ Commented Aug 16, 2015 at 21:17

2 Answers 2

8
$\begingroup$

There are many, many ways to draw things in OpenGL, so this is naturally confusing sometimes. The first method you describe, setting the shader parameters and issuing one draw call per object is usually the most inefficient, due to the high API overhead. The second one, using instanced drawing is a much smarter approach for objects with the same parameters.

When it comes to particles, specifically, there are two other methods which I'm aware of and have tested:

  • The first one, more traditional and easier to implement, is to generate a unique quadrilateral for each particle in the application code. Then use one of the several optimized buffer streaming paths of OpenGL to upload this data and issue a single draw call. This is the most straightforward method and provides good results. It will involve very few API calls if you can map the vertex/index buffers (glMapBuffer/MapBufferRange).

  • Another method is to move the whole thing to a shader program, using Transform Feedback. This method is a little more complicated to get up and running, but you can find a lot of references on the subject, such as this tutorial. This one should be the optimal path, since it moves the whole simulation to the GPU.

Those are some of the optimized ways of rendering particle effects. But OpenGL provides several other rendering paths that are better for different cases, one of such is indirect draw, which isn't available on ES at the moment, but is probably one of the fastest drawing paths available on modern PC OpenGL. Transform Feedback also requires a geometry shader, so it is not available for current OpenGL-ES.

For more on the optimized rendering paths of OpenGL, I recommend watching this very good presentation by NVidia: Approaching Zero Driver Overhead in OpenGL. At the end of the talk, they show a very interesting benchmark of several methods and how they compare with the Direct3D equivalents.

$\endgroup$
4
$\begingroup$

In OpenGL ES there is Instancing which provides allows for rendering one object multiple times.

When using Instancing, you can use uniform arrays to provide different information, e.g. a transformation matrix, for each of the particles. In the shader you can then use gl_InstanceID to distinguish between the individual particles and pick the appropriate index from the uniform array.

attribute vec4 position;
uniform mat4 transformations[16];
// other attributes, uniforms, etc
void main() {
    // ...
    gl_Position = ... * transformations[gl_InstanceID] * position;
}

And you need to draw your objects using glDrawElementsInstanced which takes the number of objects as an additional parameter (compared to glDrawElements).


Note: In OpenGL ES 2, you need to use gl_InstanceIDEXT and glDrawElementsInstancedEXT as it is an extension there. Also you have to enable it in the shader using

#extension GL_EXT_draw_instanced : enable
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.