Jump to content
  • Advertisement
Sign in to follow this  
pauls_1979

Relationship between ISceneNode and IRenderable

This topic is 4411 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Okay I'm sure everyone's getting pretty bored of scene management questions by now but I've trawled the forums and not really found a solution that fits my needs. I think I have a come up with a reasonable solution of my own, but I want to run it by a few people to make sure I'm heading in the right direction. Basically I want to be able to traverse my scene graph and create a list of renderable objects. Once the traversal is complete the render list should be sorted by material and in order of distance from the camera. My plan is to create IRenderable and ISceneNode interface classes.
class IRenderable
{
	virtual void Render() = 0;
	
	Material	*pMaterial;
	
	Matrix	*pTransform;
};

class ISceneNode
{
	virtual void Render();

	// Name, transform, parent / child links, bounding box etc. You know the drill, no point listing then all here.
};


Objects that inherit from ISceneNode (e.g. CMesh) may contain several renderable objects (inherited from IRenderable of course). When traversing the scene graph I will call each visible scene node's render function, which should in turn add the relevant renderable objects to the render list.
class CMeshAtomic : public IRenderable
{
	void Render()
	{
		// Render geometry
	}
};

class CMesh : public ISceneNode
{
	void Render()
	{
		// Add all atomics to list of renderable objects
	}

	std::list<CMeshAtomic>	atomics;
};


The biggest reservation I have is that the ISceneNode::Render function may be a bit misleading and could easily be confused with IRenderable::Render. Also, I'm struggling to find a good way of adding objects rendered using immediate mode (e.g. bounding boxes for debugging) to a scene? What do you think? Any help and or constructive criticism would be greatly appreciated.

Share this post


Link to post
Share on other sites
Advertisement
ISceneNode::Render seems accurate - it renders things, even if it forks the responsibility off on it's children. (The rest of my post is based on this assumption, ignore it if I've misinterpreted what ISceneNode::Render and clarify what exactly does it do)

That not all Render()ables are IRenderable stinks of a naming flaw, though - ISceneNode should be IRenderable, since it is Render()able. Since material isn't shared by all Render()ables, that probably belongs in a seperate class.

Refactoring, I'd end up with something like:

class IRenderable {
public:
virtual ~IRenderable() {}
virtual void Render() const = 0;
virtual const Matrix& Transformation() const = 0;
};

class ISceneNode : public IRenderable {
std::list< IRenderable* > children; //just for synopsis
public:
virtual void Render(); //std::for_each( children.begin() , children.end() , std::mem_fun( & IRenderable::Render ) );
//name, bounding box, transformation implementation
//children management accessors
//etc
};

class AtomicMesh : public IRenderable {
Material* material; //if this is used in more classes, we could refactor this into IMaterialedRenderable
public:
virtual void Render();
};

class Mesh : public ISceneNode {
//no more "atomics" list - that's been pulled up into SceneNode
public:
//mesh-specific children management accessors here - e.g.:
// LoadMesh( filename ) would call:
// SceneNode::Add( new AtomicMesh( ... ) ) as needed [or equivilant].
};


Share this post


Link to post
Share on other sites
Don't know if it will help you, but I use a different approach.
I have a ISceneNode interface, like you. But I don't use a IRenderable method. Instead, I use a SRenderable structure which contains everything needed to render something (texture ids, vb, ib ids, etc.) and in the ISceneNode::Render() method, I send those structures to a Renderer, instead of rendering things.

Then, this renderer can sort the SRenderables, and render them. I prefer this approach : you have only one rendering entry point, and the advantage is that the renderer has a global view of everything that is rendered. For the moment, I don't really need this, but I usually prefer the most open methods ^^


struct SRenderable
{
unsigned int _vb; ///< id of the vertex buffer
unsigned int _ib; ///< id of the index buffer
// ...
};

class ISceneNode
{
protected:
ISceneNode(void); ///< protected to avoid instanciation of ISceneNode
virtual ~ISceneNode(void);

public:
// default implementation of OnRender :
virtual void OnRender(void)
{
for (RenderableIterator iter = m_Renderables.begin(); iter != m_Renderables.end(); ++iter)
CRenderer::AddRenderable(*iter);
}

private:
std::vector<SRenderable *> m_Renderables;
typedef std::vector<SRenderable *>::iterator RenderableIterator;
};

Share this post


Link to post
Share on other sites
Thanks for the input guys, much appreciated.

MaulingMonkey, your assumption is correct. In most situations ISceneNode::Render() will pass responsibility on to a set of IRenderable objects, although I have tried to leave the functionality open and generic so that it would be easy for other programmers to render debugging info (such as bounding boxes) for a scene node by simply calling immediate mode functions from within it's Render() function (rather than having to create and setup a new IRenderable object specifically for this purpose). Hope that makes sense??? I agree that if ISceneNode is to have a Render() function it should really be a renderable itself, but this seems a little unnecessary. Perhaps what I should really be doing is re-naming ISceneNode::Render() to ISceneNode::PrepRenderables() or something similar, that way I could keep the material variable within IRenderable, which would make sorting renderables by material much easier.

Paic, your suggestion is something I had already considered and I am warming to it now I see that I'm not the only one to have thought of it. The only reservation I have is regarding the lack of flexibility (when compared to using IRenderables), but I think perhaps that's just me trying to over complicate things and really it provides more than enough flexibility.

I plan to go with Paic's method because it sounds a little more straight forward. There are so many other things to consider with scene management and rendering that I want to keep things reasonably simple, a least for now. I may re-name SRenderable to something like SPrimitiveBatch because if I have something named renderable, it suggests that all objects that can be rendered should be renderables. But that's not really important, it's just what makes sense to me.

Share this post


Link to post
Share on other sites
class ISceneNode
{
virtual std::vector<IRenderable*> getRenderables();
};


How does this sound? I don't think a scene node should care about a Render method, becasue to a scene node that doesn't make sense. At least to me.

Disclaimer - I'm no scene expert!

Share this post


Link to post
Share on other sites
In the theory, you're right ... but in practice (in an editor in my case) almost every scene node needs something to be drawn (the cameras need to draw a dummy on screen so the user know where is is, same for lights, etc.) So I decided to go this way ^^
But the principle is the same so the exact implementation is just a matter of taste :)

Share this post


Link to post
Share on other sites
Quote:

class ISceneNode
{
virtual std::vector<IRenderable*> getRenderables();
};

How does this sound? I don't think a scene node should care about a Render method, becasue to a scene node that doesn't make sense. At least to me.

Disclaimer - I'm no scene expert!


I had also considered this but it would mean an ISceneNode object would have no way of determining how it's IRenderable objects are rendered, something which would be useful for level of detail meshes amongs other things.


class CMesh : public ISceneNode
{
void Render()
{
if(distanceFromCamera > lodDistance)
{
// Render lod1Atomics
}
else
{
// Render lod2Atomics
}
}

std::list<CMeshAtomic> lod1Atomics, lod2Atomics;
};




Of course if there's a better way of doing this I'd be willing to give it a try.

Share this post


Link to post
Share on other sites
In my last engine, ISceneNode had-a IRenderable. In my current engine, IRenderable refers to ISceneNode, and I prefer it this way. Basically, it came down to "Why should a scene node know anything about drawing? It's purely a positional thing." The renderable thing cares about position and orientation, so it asks the scene node, but the scene node is completely ignorant about drawing anything.

Similarly, last time around, my cameras were derived from scene nodes, which I consider in retrospect to be a mistake. Now, cameras refer to scene nodes, but don't derive from them.

So, also, in my way of doing things, multiple renderables can refer to the same scene node.

As for LOD, I'd recommend that you simply treat things generically (ignoring any code or logic flaws - I just typed this in):


class IRenderable
{
public:
IRenderable(INode &node) : mNode(&node) {}
virtual tSphere BoundingSphere(void) const = 0;
virtual void Render(void) const = 0;

protected:
ptr<INode> mNode;
};

class ILODRenderable : public IRenderable
{
public:
typedef pair<float, ptr<IRenderable> > tLOD;

virtual void Render(void) const
{
if (mLODs.empty())
return;

float coverage = mNode->ComputeCoverage(BoundingSphere());
tLOD::const_iterator it;
for (it = mLODs.begin(); it != mLODs.end(); ++it)
{
const tLOD &lod = *it;
if (lod.first > coverage)
{
lod.second->Render();
return;
}
}
(mLODs.end() - 1).second->Render();
}

private:
vector<tLOD> mLODs;
};

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!