Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I have a game engine that currently uses inheritance to provide a generic interface to do rendering:

class renderable
{
public:
    void render();
};

Each class calls the gl_* functions itself, this makes the code hard to optimize and hard to implement something like setting the quality of rendering:

class sphere : public renderable
{
public:
    void render()
    {
        glDrawElements(...);
    }
};

I was thinking about implementing a system where I would create a Renderer class that would render my objects:

class sphere
{
    void render( renderer* r )
    {
        r->renderme( *this );
    }
};

class renderer
{
    renderme( sphere& sphere )
    {
         // magically get render resources here
         // magically render a sphere here
    }
};

My main problem is where should I store the VBOs and where should I Create them when using this method? Should I even use this approach or stick to the current one, perhaps something else?

PS: I already asked this question on SO but got no proper answers.

share|improve this question
    
The component/entity system is fairly common and byte56 gives a great explanation here; gamedev.stackexchange.com/questions/31473/… –  OriginalDaemon Oct 23 '13 at 15:20
    
Note that rendering of most objects will look the same. I think that almost everything can be rendered as - load mesh, set materials, render mesh. With deferred rendering even lights are done this way. When terrain is split to chunks it can also be drawn this way. –  Kikaimaru Oct 23 '13 at 23:47
    
@Kikaimaru Well, not everything, for example a particle system doesn't fit this case, also internally generated meshes(GUI, Deferred rendering light shapes) are not exactly 'loaded'. –  akaltar Oct 24 '13 at 4:55
    
@akaltar True particle system would have to have custom rendering (or at least its mesh wouldnt get loaded the same way as other meshes). But light shapes can be loaded as other meshes, and I dont think that gui should be even part of renderer, its something completley different (as long as you dont wont to render 3d gui in game world). Btw by load I didnt meant load from file system, i meant just that you need to somehow get mesh data. –  Kikaimaru Oct 24 '13 at 9:58
    
@Kikaimaru For me that's enough reason to be using a separate component for rendering and separate for mesh loading –  akaltar Oct 24 '13 at 11:32

3 Answers 3

up vote 5 down vote accepted

You should have both, Inheritance and Visitor-Patter. This will be useful when you render all your objects like (c++11):

for( auto & prop: proplist ){
    prop->render( renderer );
}

The easy way is to store the VBO and other data in the object to be rendered, and the renderer will use this data to draw it.

class renderer {
    void renderme( sphere& sphere ){
        // magically get render resources here
        auto & vertexes = sphere->getVertexes();

        // magically render a sphere here
        glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vertexes );

        // ... etc
    }
};

E.g: The sphere has both, vertexes, normals and textures. But if the renderer is drawing in wire-frame, only will query the vertexes, but if it's also doing lighting it also will get the normals and textures from the sphere. Also, you can separate it into different renderers, wireframeRenderer and FullRenderer, and pass them as the visitor in different moments. And the Sphere code doesn't change.

share|improve this answer
    
I am going to use this method in my engine as it seems very flexible and (finally) gives me a way to properly separate shaders from my rendering code. –  akaltar Oct 23 '13 at 13:30

My experience is that it's better to have a discrete renderer, and package the renderables as pure data for the most part (with the exception of data accessor methods).

This is for exactly the reasons you've described -- ultimately, rendering is a pipeline, from which each renderable benefits in different ways / at different points. So this way, you can structure the pipeline, and pass the data through that pipeline, with different part of the pipeline applying selectively to each renderable based on what it is.

Yes, the VBOs form part of each renderable object, and you probably want to maintain separate concepts of meshes, models and so on, so that you can copy new vertex data into that VBO on the fly, and the renderer simply picks up those changes and acts on them. It will become clear to you as you tackle it, notwithstanding a couple of conceptual hiccups; every engine is going to have subtly different architectural considerations based on what the engine in question is built to achieve.

share|improve this answer

Let's first check what is the problem with inheritance in your case:

  1. Introduces tight coupling between classes making it harder to maintain and optimize.
  2. Your use of inheritance doesn't obey Liskov_substitution principle which states that types and their sub-types should be used interchangeably without altering any of the desirable properties of that program, for example is sphere used the same as renderer?

Second what is the visitor pattern when should it be used ?

Is a way of separating an algorithm from an object structure on which it operates.

Sounds neat right? Well, not quite.. depending on your application you may or may not use it.

I will use it if:

  • I have a complex structure (tree/graph) that I need to traverse and render and I need to separate the traverse from the actual rendering algorithm.
  • I have multiple rendering algorithms and multiple renderable types I want to perform double dispatch.

I won't use it if I have:

  • Simple structure that I can loop through.
  • You only have one rendering algorithm per type for example billboards use an algorithm and 3d meshes use another algorithm.

My recommendation is:

  • Use an ObjectRenderer that references the actual mesh (aggregation) and submits the actual rendering calls.

  • The ObjectRenderer owns VBO so you can map a mesh or multiple meshes/sub-meshes (read:batching) for a single VBO.

  • You can make the object ObjectRenderer polymorphic so each ObjectRenderer subtype can implement it's own algorithm ( e.g. BillboardRenderer, ParticlesRenderer, MeshRenderer).

  • You can also make separate ObjectRenderer classes not subtype of a base type, and should be handled differently by the Renderer.

    class ObjectRenderer
    {
    // or use a list of meshes
    ObjectRenderer( Mesh* mesh );
    
    private:
    Material* material;
    Mesh* mesh;
    VBO* vbo;  // the object renderer owns the VBO
    };
    

The object renderer is not a singleton, use one instance for a single mesh or group of meshes depending on the actual data. Create another class Renderer that stores a list of the actual ObjectRenderer(s) and can simply call the appropriate algorithm for each ObjectRenderer.

Renderer
{ 

void Render( )
{
  //for each Object call appropraite rendering algorithm.
}

std::vector<ObjectRenderer*> m_renderObjects; // one list in case of polymophic ObjectRenderer

// OR 

 std::vector<ObjectRenderer*> m_renderObjects;
 std::vector<ParticlesRenderer*> m_particles;  // multiple list for different types so you can make the Renderer deal with different types differently. (e.g. sorting)
};
share|improve this answer
    
Your post has very useful tips, but the conclusion is somewhat unclear and doesn't seem right. –  akaltar Oct 23 '13 at 13:29
    
can you clarify what was unclear or not right? –  concept3d Oct 23 '13 at 13:37
    
The meaning of the sentence"To explain my recommendation use ObjectRenderer class this is..." is unclear to me.(I understood what you wanted to say, but only vaguely). The code is otherwise fine. –  akaltar Oct 23 '13 at 13:48
    
I edited the answer I hope it makes the idea clearer. –  concept3d Oct 23 '13 at 15:20

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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