PDA

View Full Version : How to sort draw and shader calls for multiple models?



Patsch
11-17-2016, 05:41 PM
Hey everyone,

I recently started programming with openGL and managed to create a model class and draw textured models in virtual space.

Now I want to start drawing all the graphics that my game includes. I came up with several thoughts about where and when to draw, but I am highly uncertain about the difference in performance of each approach:

First Approach: Having a "Unit" Class, which has a "Model" variable. The Class has a draw() function, that draws the Model at the Position specified in Unit. Within the Game loop i call "draw()" for every Unit in a vector container (c++).
Issues with this: First of all, I have to access "Model" via Unit, wouldnt it be much faster to have a vector that only contains Model objects? Second: Since I want to use more than one shader, I would have to set the shader in each and every draw method, since I dont know too much about the order of Units in the vector.

Second Approach: Having Vector containers for each shader in the class of the gameloop: calling the shader and then looping through all the models that should be drawn with the shader. Reduces the Shader calls, but on the other hand makes it necessary to loop through all objects for each shader. Also makes all the render calls in the main loop and not all over the place, but i dont know if that makes any difference regarding performance?

Is there some kind of recommendet best practice? Id really appreciate your help!

Silence
11-17-2016, 11:10 PM
This is not really OpenGL related. But if you want to make a game, I highly suggest you to look for space partitioning structures, or (which might fit you better, at least for now), scene graphs. Google for them.

GClements
11-18-2016, 02:30 AM
Second Approach: Having Vector containers for each shader in the class of the gameloop: calling the shader and then looping through all the models that should be drawn with the shader. Reduces the Shader calls, but on the other hand makes it necessary to loop through all objects for each shader. Also makes all the render calls in the main loop and not all over the place, but i dont know if that makes any difference regarding performance?

Is there some kind of recommendet best practice?

If you're concerned about rendering performance, the main factor is not to partition the world into "objects".

Your scene consists of some number of triangles. To render all of those triangles, you need one or more draw calls (glDrawElements() etc). All other factors being equal, the fewer draw calls the better. The time spent rendering can be partitioned into sections according to what it's proportional to: per draw call, per vertex, per triangle, per fragment. The last three are largely[1] dictated by what you're trying to draw, but the first one depends upon how you go about drawing it.

[1] But not entirely; you can reduce the per-fragment overheads significantly by taking advantage of early-depth-test optimisation, either by rendering the major occluders from front to back, or using a depth-only pre-pass.

So start by figuring out how you could draw your entire scene with a single draw call. That means that all of the vertex attributes have to be in a single set of buffers, all of the texture data must fit into no more textures than there are texture units, a single shader must suffice for the entire scene, and any uniforms must have constant values.

Then figure out if there are ways in which partitioning the data would improve performance, or at least not reduce it substantially. E.g. if some of the geometry won't use normal maps, then you can use a shader which doesn't bother calculating the normal-space transformation. If that applies to enough geometry, the savings will outweigh the overhead of switching shaders (which necessitates splitting the rendering off into a separate draw call).

But don't split rendering into multiple draw calls just so that you can force the rendering into some "OOP 101" structure. Structure the data around what's convenient for rendering, don't structure the rendering around a data structure which was designed without regard to rendering efficiency.

If you've only just started with OpenGL, all this may seem too complicated. In which case, you might want to think about getting a couple of years' practice with OpenGL before worrying too much about efficiency or embarking upon projects where efficiency is likely to matter.

john_connor
11-18-2016, 03:11 AM
basically a game is structured like this (each frame):
1. process input (control items etc)
2. simulate (physics, collisions, etc)
3. process output (rendering, sound, etc)

when you reach step 3, you can render the whole game world at once
instead of calling "Model.draw();" for each thing, you only call it once
you need to describe what to draw

how about:


struct GameWorld
{
Camera camera;
vector<Object> someobjects;
vector<Light> somelights;
};


each "Object" has only position / rotation / size, not vertices itself (otherwise would it be a waste of memory)



class Renderer
{
public:

void Render(const GameWorld& world);

private:

vector<Model> somemodels;

Shader shader;
VertexArray vao;
Buffer vertices;
Buffer materials;
Buffer lights;

};


your application has 1 "Renderer" which contains all the models vertices / buffers / shaders / materials / etc
it is responsible for drawing the whole gameworld, you only need to provide a "world"
each "Object" has a "int modelindex" variable which allows the Renderer to determine which model to render for what object

if all your model share a common "vertex" layout, like:


struct Vertex
{
vec3 Position;
vec2 TexCoord;
vec3 Normal;
};

... then its sufficient to have only 1 shader and 1 vertex array object and 1 (static vertex) buffer object
put all the vertices for all models into 1 big buffer (and store the "vertexoffset" / "vertexcount" pairs for each model)

https://www.opengl.org/wiki/Vertex_Specification_Best_Practices


your render method could then look like that:


void Renderer::Render(const GameWord& world)
{
// clear screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(shader);
glBindVertexArray(vao);

// set camera matrices
// set light sources

// render each model
for (auto& object : world.someobjects)
{
// set "model-to-world matrix" for object

// get model type
Model model = somemodels[object.modelindex];

// get vertexoffset and vertexcount for model
int vertexoffset = model.vertexoffset;
int vertexcount= model.vertexcount;

glDrawArrays(GL_TRIANGLES, vertexoffset, vertexcount);
}

glBindVertexArray(0);
glUseProgram(0);
}


tha way you can mimimize the gl-function calls which is generally a good idea
here i've made an example:
https://sites.google.com/site/john87connor/advanced-opengl-tutorials/tutorial-01-3d-scene-camera

to improve performance, render those object which are closer to the camera first (for depth-test)

Grognard
11-18-2016, 08:33 AM
You want to try and draw every unit of a TYPE together, not each one. You should have just a few different kinds of classes needed to render various objects or even just one. Each time you change material/shader you will have to have another rendering batch basically.