Thursday, December 29, 2016

oop - Architecture to draw many different objects in OpenGL


I have some objects that I want to draw. I am not sure how I can create my architecture in a way where I can draw everything as fast as possible.


As example:


class MyObject
{

float[] vertices;
float[] colors;
float[] textures;

public MyObject()
{
//fill every buffer with data
}

public void Draw()

{
//set shader
//set shader uniforms etc.

//foreach buffer
BindBuffer();
VertexAttribPointer();

DrawArrays();
}

}

In this way, I can draw every object like myObject.Draw after it is constructed. The problem would be, that if I have 5000 objects, I have 5000 draw calls. I need to set the shader every time even if I loop over the 5000 objects because I don't know if they use the same shaders.


Another way:


class MyObject
{
bool needsToBeChanged;

public MyObject()
{

//set my edge points
needsToBeChanged = true;
}

void ChangeMyObject() //e.g. I changed the height of my object
{
needsToBeChanged = true;
}
}


class GLWindow
{
float[] vertices;
float[] colors;
float[] textures;

GameLoop()
{
foreach(MyObject myObj in allMyObjects)
{

if(myObj.needsToBeChanged)
{
//get objects points for drawing it and set the global buffers up

myObj.needsToBeChanged = false;
}
}

DrawArrays(); //draw only once (because every data is in the buffer)
}

}

Only 1 draw call for everything. I guess this will be faster than first method. The problem on this code is the big buffer that contains everything. Let's imagine we have inherited from MyObject and want to draw objects in different shapes. Now the Loop gets more complicated. You may have to split it because you need another shader for example for text. Now you have 2 arrays... And with other changes the Loop becomes more and more complicated and harder to maintain.


What solutions (beside the both) are available for this kind of problem? Can I use the first way or is there a better way where I can reduce the draw calls? How is it solved in other applications?



Answer



Generally speaking, you will need a separate draw call for each set of objects that have a different material (by material I mean a different shader, texture or other shading parameters).


So the obvious approach is to group objects that have the same material, so that they can be rendered together, avoiding the expensive state changes. This is normally done by batching objects that share the same material. A data structure that helps achieving this is the scene graph.


Once you have batched objects with the same material you eliminate redundant state changes. The shader program and textures are now set outside and a batch is rendered. Conceptually, this would look like this:



for each batch in batches

set common material properties
for each object in batch
draw object
end for
end for

But you still have multiple draw calls within each batch. This could be resolved if all objects resided inside the same Vertex Buffer. Packing everything in the same VB is feasible and might be the best option in some cases. Modern OpenGL, however, provides instanced rendering (see a tutorial here), which is a very good solution to reducing the number of draw calls.


Overall, some parts of your rendering will probably be better optimized with simple batching or a scene graph, while other will be more efficient if implemented using instancing. You should profile and test to discover the ideal approach. But bare in mind that this is an optimization step, therefore, you should first worry about making the game/software work, then you optimize it.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...