4
\$\begingroup\$

Lots of game engines and GUI systems have a general Renderer class with functions like:

renderer.drawRect(0, 0, 100, 100);
renderer.drawText(204, 452, "Hello, world!");

I understand how this could easily be done in old immediate-mode OpenGL 2.1, but now that VBO's are the norm, could someone explain how these renderers work internally? They surely aren't creating and deleting a VBO quad everytime drawRect() is called, right?

Keeping one VBO and updating the vertex data is out of the question, as it would be cool to set a different shader, texture or colour in between the drawRect() calls.

Is instancing a (0, 0, 1, 1) rect and then scaling up the way to go?

Any pointers?

\$\endgroup\$
  • \$\begingroup\$ I don't see why keeping one VBO and updating the vertex data would be out of the question for you? Updating a VBO is really not that expensive. It's probably even faster than old-style glVertex calls... \$\endgroup\$ – glampert Dec 3 '14 at 17:14
  • \$\begingroup\$ Using one single vbo you wouldn't be able to use a different shader per quad, right? Or is that incredibly uncommon in day-to-day applications? \$\endgroup\$ – Stijn Frishert Dec 3 '14 at 18:22
  • \$\begingroup\$ Besides, once you use a batch (and the painters algo; no z-ordering), everything has to use that batch. Or your render order screws up. \$\endgroup\$ – Stijn Frishert Dec 3 '14 at 18:23
  • \$\begingroup\$ VBOs are not related to how you shade the geometry. You set the shading properties before a draw call, by setting shader uniforms. Unless of course you are thinking about per-vertex attributes, but usually the only per-vertex attribute you specify, which influences shading, is the vertex color. That can be replaced by a uniform variable. \$\endgroup\$ – glampert Dec 3 '14 at 18:36
  • \$\begingroup\$ But yes, indeed if you batch objects by material then, when drawing 2D without depth, Z order is compromised. You would have to then sort the objects yourself before drawing/setting up the VBO. \$\endgroup\$ – glampert Dec 3 '14 at 18:42
5
\$\begingroup\$

There are many cleaver ways of drawing 2D geometry with modern shader-centric OpenGL. The method that I usually choose and find the most straightforward is with simple batching.

  • On startup, create a system-side buffer of 2D vertexes (the batch). This buffer can have a fixed size or can be resizable (like a std::vector in C++). Also create a VBO with the same size. Indexed or not, its your choice.

  • In a game frame, every drawRect() or drawText() actually writes the vertexes and material properties to this system-side memory buffer.

  • At the end of every frame, map the VBO and upload the batched vertexes to it, or update it using glBuffer[Sub]Data.

  • Render all your UI/2D stuff with a single draw call and clear the system-side vertex batch for the next frame.

This setup has two main advantages:

  1. Reduced number of draw calls, you can draw everything that shares the same material properties with one call.

  2. Single update of the GL buffer: You centralize your update to a single place. This will promote CPU cache efficiency and will also require very few GL API calls. There are several ways to optimize this step even further with newer OpenGL versions.

\$\endgroup\$
  • \$\begingroup\$ Hmm...how would you handle multiple shaders? I reckon you'd keep one array/vector per shader? \$\endgroup\$ – Stijn Frishert Dec 3 '14 at 17:30
  • \$\begingroup\$ @Frishert, just change the shader before a draw call. Each geometry batch usually groups objects that share the same shader/material. You can either use one VBO per batch or even a single VBO for all batches with a bit of extra work... \$\endgroup\$ – glampert Dec 3 '14 at 18:38
2
\$\begingroup\$

Create a program that takes the relevant parameters as uniforms and pass those.

Then the size and position of a rect can be passed as a single vec4 uniform and you only need to pass a single VBO that is 0,0; 0,1; 1,1; 1,0 (or 0,0; 0,1; 1,1; 0,0; 1,1; 1,0 for non indexed draws)

in vec2 input;
varrying texCoords;
uniform vec4 posSize;//xy is the position zw is the size

main(){

    gl_Position = vec4(input*posSize.zw + posSize.xy, 0, 1);
    texCoords = input;
}

Then most of the magic will happen in the fragment shader where you will be sampling textures and such based on texCoords and your uniforms.

\$\endgroup\$
  • \$\begingroup\$ I like this! Basically using one quad vbo as a stamp. I guess the disadvantage would be that you wouldn't be able to use a custom vertex shader, but for some applications that would be fine. \$\endgroup\$ – Stijn Frishert Dec 3 '14 at 17:29
  • \$\begingroup\$ @Frishert what would you want to do in the vertex shader that you couldn't in the fragment shader? \$\endgroup\$ – ratchet freak Dec 3 '14 at 17:36
  • \$\begingroup\$ Hmmm...displacing vertices in a wavey pattern? I'm asking the question because I'm still learning about this. Can't imagine there aren't any real-life uses for a vertex shader, but definitely correct me if I'm wrong. \$\endgroup\$ – Stijn Frishert Dec 3 '14 at 18:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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