Foo.
I'm begginning to find that much of the theory about scene graphs that you find on the net is great for drawing pretty graphs and giving presentations that make you sound smart, but very little of it actually applies in a useful manner. No one likes to talk about the nitty gritty (but very important!) details. For example, how does one (cleanly) get a scenegraph to work with a renderer in a nice, modular environment?
The details:
I've got a fairly nice scene graph set up, which manages our scene objects very nicely. Everything renderable or interact-able is plugged into it. Cameras, lights, static and dynamic meshs, transforms, etc. The basic code is pretty simple:
class CNode
{
public:
CNode( void );
virtual ~CNode( void );
int Attach( CNode* node );
int Remove( CNode* node );
void UpdateNode( void );
void RenderNode( void );
private:
virtual void Update( void );
virtual void Render( void );
CNode* next;
CNode* child;
protected:
uint type;
};
Everything that can be attached to the scene graph inherits this class. We attach nodes to eachother using the Attach function, and the Remove function removes any instances of the specified node that occur below the node it's passed into. (Most of the time the Root node)
UpdateNode and RenderNode are simple recursive function calls that call their own Update/Render function, then that of their child node and finally their next node. (Basically it's a depth first tree)
The render function is where our problems come to light, though. Up until recently, I had it so that each type of node was responsible for handling it's own rendering. It worked great except for one problem: This meant putting API calls (OpenGL in this case) directly into the nodes. So, for example, the Camera node would contain a direct call to glLoadMatrixf().
There are several problems with this approach: For one it tied the code directly and inseprably with a single rendering API. Though I have no need for it, I would
like to leave the doors open for a DirectX implementation in the future. (Or even a software renderer if I got feeling REALLY ambitious! ^_^) It also meant that it was very difficult to track rendering states across the board, and even harder to insure that one objects render routine wasn't going to interfere with anothers. These problems, along with a few asthetic values and the prompting of some friends helping me with the code pushed me to change the rendering architecture to a more modular and independant design.
So, here's our new Render interface (or at least a simplified version):
class CRenderWorld
{
public:
virtual int Create( HWND hWnd, CViewParams *params ) = NULL;
virtual int Destroy( void ) = NULL;
virtual void Begin( void ) = NULL;
virtual void End( void ) = NULL;
virtual void Render( CNode *scene ) = NULL;
virtual void Flip( void ) = NULL;
protected:
CCamera* camera;
CLight* light;
//etc.....
};
The idea, obviously, is that we create one set of basic functions to interface with the rendering, and then create multiple render classes inherited from this to handle individual render pipelines. For example, I would have a CRenderWorldGL class that is an OpenGL implementation of this. To specify the exact renderer you simply say:
CRenderWorld *render = new CRenderWorldGL;
Pretty basic OOP stuff, yes?
Okay, here comes the fun bits. First off: Obviously at this point we're stuck with providing a lot of new methods for each node to pass out the nessicary information to the renderer, which means that when programming new nodes you have to anticipate which information the renderer will need, which may be different from renderer to renderer. To an extent this is unavoidable with the OOP style of programming, but it's made more aggravating here by the needs of the system.
Secondly: For every new node that is added, we now are faced with going into the core rendering code and adding new render routines to accomidate the new node type. In my opinion this kills a lot of the usefulness of the scene graph, since a big plus (in my mind) was the ability to add new items in a very simple and modular manner.
In the end, I feel like I traded one set of problems for an entirely new set of them. I feel that there must be a better way of handling this, and am wondering how others with more complete rendering systems have handled this. Is there a more elegant way, or am I simply facing the cold hard reality of how it's done and just don't know it yet?
(If you'd like any more details on how my code is structured just ask, I simply feel like this post is long enough as it it and don't want to scare anyone away ^_^)
// The user formerly known as Tojiro67445, formerly known as Toji [smile]