Mesh manager and frustrum culling

Started by
10 comments, last by L. Spiro 12 years, 6 months ago
I'm designing a 3D engine with a mesh manager that will pull the vertex and index buffer from an object instance that links to a file with the data, and group them according to how it's shaded so the gpu will do fewer context switching. So with this system, when you determine that an object is outside the frame, what would be a way to make sure it's not drawn? If I'm understanding right, the vertices and indices buffer are sent once, and I'm not sure if there would be any sort of accounting information to go with the vertices and indices.

Edit: Mesh Renderer*
Advertisement
If I'm understanding your question correctly, your mesh should have some kind of bounding volume which you would pass to your frustum culler. The bounding volume could be anything really but usually it's an AABB or Sphere. If the bounding volume is outside the frustum then you simply don't draw the mesh (don't send primitives to the GPU with DrawPrimitive* call).

If I'm understanding your question correctly, your mesh should have some kind of bounding volume which you would pass to your frustum culler. The bounding volume could be anything really but usually it's an AABB or Sphere. If the bounding volume is outside the frustum then you simply don't draw the mesh (don't send primitives to the GPU with DrawPrimitive* call).


I'm thinking much lower level than that. The mesh manager will just group vertex and index data to prevent a lot of gpu context switching, and tell the gpu to draw them. The culler is higher level. Once the culler determines that something is outside the frustum, how do I make sure that those objects don't get drawn.

If my design is off though, what would be a better way to handle it?
I will give a brief description of my general approach to this:

  • Each scene node has a bounding volume (axis aligned box) based on what render objects (like a mesh) are attached to the node.
  • If the bounding volume of a scene node is within the frustum then a render request will be generated for each render object attached to the visible node.
  • Scene nodes can be hierarchical. Meaning that you can implement visibility tree algorithms such as Octrees or Quadtrees.
  • Each render request contains a vertex buffer, material, and a transformation matrix.
  • The renderer takes an array of render requests which it will sort for minimal context switching before rendering each vertex buffer.


The benefits to this approach:

  • The scene only cares about the origins, orientations, and bounding volumes and does not depend on any render objects (such as a mesh, light, etc).
  • You can attach identical render objects to different scene nodes (they will end up sharing the same vertex buffer and material).
  • The renderer does not depend on any render objects either, it just worries about rendering vertex buffers at given transformations along with their material properties.


Here is an example (Ptr is a smart pointer similar to shared_ptr):



// Get mesh from resource cache.
Ptr<Mesh> someMesh = resourceCache->get<Mesh>("Mesh.mesh");

// Create a node and attach the mesh (bounding volume for node is computed).
Ptr<SceneNode> node1 = scene.createNode(Vector3(10, 0, 0));
node1->attachRenderObject(someMesh);

// Create another node and add the same mesh, but rotate the node.
Ptr<SceneNode> node2 = scene.createNode(Vector3(-10, 0, 0));
node2->rotate(Vector3(0, 1, 0), Angle::fromDegrees(45));
node2->attachRenderObject(someMesh);

// Every frame...
scene.updateRenderQueue(frameDelta); // Update which nodes are visible.
RenderQueue/8 renderQueue = scene.getRenderQueue(); // Get the queue of render request that was build based on visible nodes.

// Render everything in the queue to the main window.
renderer->renderAll(mainWindow, camera, renderQueue);

I will give a brief description of my general approach to this:

  • Each scene node has a bounding volume (axis aligned box) based on what render objects (like a mesh) are attached to the node.
  • If the bounding volume of a scene node is within the frustum then a render request will be generated for each render object attached to the visible node.
  • Scene nodes can be hierarchical. Meaning that you can implement visibility tree algorithms such as Octrees or Quadtrees.
  • Each render request contains a vertex buffer, material, and a transformation matrix.
  • The renderer takes an array of render requests which it will sort for minimal context switching before rendering each vertex buffer.


The benefits to this approach:

  • The scene only cares about the origins, orientations, and bounding volumes and does not depend on any render objects (such as a mesh, light, etc).
  • You can attach identical render objects to different scene nodes (they will end up sharing the same vertex buffer and material).
  • The renderer does not depend on any render objects either, it just worries about rendering vertex buffers at given transformations along with their material properties.


Here is an example (Ptr is a smart pointer similar to shared_ptr):



I guess mesh manager is the wrong title for it. What I'm working on is actually just the renderer. How do I make sure that the particular vertices aren't rendered for an object that is outside the frustum after I cull it, and after the buffers are created.

Edit: So If the renderer is only tasked with rendering vertex buffers, what would be the best way to tell the renderer not to render this object because it's outside the frustum. Is this a thing I need to do? And should the renderer group different vertices by materials or is that the job of another class?

[quote name='Shael' timestamp='1318290136' post='4871254']
If I'm understanding your question correctly, your mesh should have some kind of bounding volume which you would pass to your frustum culler. The bounding volume could be anything really but usually it's an AABB or Sphere. If the bounding volume is outside the frustum then you simply don't draw the mesh (don't send primitives to the GPU with DrawPrimitive* call).


I'm thinking much lower level than that. The mesh manager will just group vertex and index data to prevent a lot of gpu context switching, and tell the gpu to draw them. The culler is higher level. Once the culler determines that something is outside the frustum, how do I make sure that those objects don't get drawn.

If my design is off though, what would be a better way to handle it?
[/quote]

I don't really understand what you mean. When the culler determines something is outside the frustum then you simply just don't draw it.

This is just example code but this is what I'm referring to:
[source lang=cpp]
void DrawMeshes()
{
foreach (mesh in mesharray)
{
// only draw mesh if it's inside the frustum
if (frustum.Contains(mesh.aabb))
{
Draw(mesh);
}
}
}
[/source]

You should be organising your mesh data similar to the ideas presented in this thread: http://www.gamedev.n...for-model-data/

I'm not sure what your "mesh manager" is exactly but either way you should be obtaining a list of mesh instances which are used to form a list of render packets which get sent to the renderer. A render packet is the most basic information required to draw something. Eg. vertex/index buffer and material. The renderer can then go through this list and render each item taking into account state changes, etc.


I don't really understand what you mean. When the culler determines something is outside the frustum then you simply just don't draw it.

This is just example code but this is what I'm referring to:
[source lang=cpp]
void DrawMeshes()
{
foreach (mesh in mesharray)
{
// only draw mesh if it's inside the frustum
if (frustum.Contains(mesh.aabb))
{
Draw(mesh);
}
}
}
[/source]

You should be organising your mesh data similar to the ideas presented in this thread: http://www.gamedev.n...for-model-data/

I'm not sure what your "mesh manager" is exactly but either way you should be obtaining a list of mesh instances which are used to form a list of render packets which get sent to the renderer. A render packet is the most basic information required to draw something. Eg. vertex/index buffer and material. The renderer can then go through this list and render each item taking into account state changes, etc.




Sorry, mesh manager wasn't the correct term for it, it is a class that handles just the vertex data and renders them based on materials. I want to group the vertices based on materials. If I'm not mistaken, the way you have it done is that the gpu will do a context switch for each mesh. Should this be avoided in order to make the code easier for rendering by grouping them? So what I will have multiple buffers, one for each type of material that is going to be used on the vertices. What would be the best way to handle the list? Build it as you go down the list to render each frame, or pre-make the list and just have switches for when the vertices associated with a mesh is outside the frustum?
My last paragraph is basically what you're after but I'll try explain it a little more.

You should have some higher level Model/Mesh management where a mesh would share vertex/index data and have a material to describe how the mesh should be rendered. Some other higher level class like a "scene manager" would then determine which meshes need to be drawn by checking their bounding volumes against the frustum. For each visible mesh you would take the basic rendering information and form a temporary render atom/packet object which is later sent to the Renderer in a sorted batch.

The Renderers job is then to take the batch of render atoms and use them to perform the low level draw calls to send the primitives to the GPU. While doing this process it should also be taking into account state changes to reduce them. Eg. don't change vb/ib or shader if the previous render atom has the same as the current one about to be rendered.

My last paragraph is basically what you're after but I'll try explain it a little more.

You should have some higher level Model/Mesh management where a mesh would share vertex/index data and have a material to describe how the mesh should be rendered. Some other higher level class like a "scene manager" would then determine which meshes need to be drawn by checking their bounding volumes against the frustum. For each visible mesh you would take the basic rendering information and form a temporary render atom/packet object which is later sent to the Renderer in a sorted batch.

The Renderers job is then to take the batch of render atoms and use them to perform the low level draw calls to send the primitives to the GPU. While doing this process it should also be taking into account state changes to reduce them. Eg. don't change vb/ib or shader if the previous render atom has the same as the current one about to be rendered.


Ah, I see now. So there's a temporary packet involved. And it's the renderer's job to group the packets to take into account state changes. Is building the list of things to be rendered by materials on the fly relatively fast?
That's just one way of doing it and you would need to profile it to see how it performs but I couldn't imagine it'd affect performance that much if done right. There's a really good thread here which covers more advanced techniques.

This topic is closed to new replies.

Advertisement