Sign in to follow this  
jmakitalo

Keeping render objects and visibility determination separated

Recommended Posts

jmakitalo    668
I have usually implemented a quadtree or some other visibility determination system so that it defines a base class, which contains e.g. a virtual draw call and then objects such as a mesh and a particle system inherit this base class. Then as the tree is traversed, draw calls are made as appropriate.

I feel that this makes the quadree too tightly integrated to so many other modules, that it is difficult to deattach it to use the other modules like meshes and particle systems without the quadtree. Is it advisable to pursue such interfacing, that this kind of modules could be easilty used alone if wanted?

What would be a good way to implement a quadtree or other vis system as an independent system? Of course the baseclass approach does make it separate from other modules, but the mesh and particle system modules depend on the quadtree module. Maybe the quadtree could just store a list of bounding boxes for each render object type and the tree traversal would produce lists of indices to visible objects. The game engine could then use these lists to draw visible meshes and particle systems. Then the quadtree, mesh and particle system modules would be independent of each other and only the engine would couple them together. What do you think?

Share this post


Link to post
Share on other sites
lauris71    841
One possible approach I try to use in my engine:
Think about your game world as a database. Each object has set of properties, some read-only, some read-write. Some are stored with object, some calculated on-the-fly.
Then think what kind of queries you have to do with objects. For example rendering involves spatial queries like "list all opaque objects, visible from current camera location, that lie inside view frustum". For game logic you may need queries like "list all NPC characters that see given character".
For complex and frequently used queries you will build indexes - octree is a special kind of spatial index.
In your case I'd still attach bounding box to specific object - because it is spatial property of given object and it is used a lot. But object should neither be an octree node nor "belong" to octree. If an object has a single "true location" or "true parent" at all this should be level loading/caching system - i.e the part of code that can build and destroy object.

Share this post


Link to post
Share on other sites
Hodgman    51234
A virtual base 'Drawable' class isn't a great design here because it couples drawing with culling, but also because it makes the culling system responsible for the order in which things are drawn -- most renderers should have the ability to sort draw-calls themselves for correctness ([i]e.g. transparents after opaques[/i]), special techniques ([i]e.g. offscreen passes[/i]) or performance ([i]e.g. reducing state changes[/i]).

There's no best way to do this, so here's some ideas as food-for-thought --

If you were going to have a virtual 'Drawable' class, I would re-design it so that it doesn't actually draw anything, but instead adds itself to a draw-list that can later be sorted and drawn by your renderer.
e.g. something (pseudo) like:
[code]vector<RenderItem> toBeDrawn;
foreach( o in visibleObjects )
o->AddRenderItems( &toBeDrawn );[/code]
Ideally the culling system would know only about occluders and object bounding volumes, and not know at all about drawables.
However, you could break this idealism a small amount by having Cullables know which Drawables exist within their bounds, but not actually draw them. After determining the visible set of cullables, you can then iterate through them to find the list of visible drawables.
e.g. (pseudo)[code]struct Cullable
{
Aabb bounds;
vector<Drawable*> drawables;//the drawables that are linked to this bounding volume

};
vector<Cullable*> visCullables = GetVisibleObjects();
vector<Drawable*> visDrawables;
foreach( v in vis )
{
foreach( d in v->drawables )
visDrawables.push_back( d );
}
Render( visDrawables );[/code]

To make the culling system fully unaware of the rendering system, you could reverse the above, so that each renderable item instead contains a link to the cullable region that contains it. You can then test the visibility of each cullable to obtain a big list of 0's/1's indicating which regions are visible. You can then iterate all renderables and test whether their cullable is true or false.
e.g.[code]vector<Aabb> boundingVolumes = ...;
struct Drawable
{
int aabb;//an index into the above vector
...// other data - e.g. mesh to be drawn, etc...
}
vector<Drawable> drawables = ...;
vector<bool> visibleCullables = GetVisibilityFlags(boundingVolumes);
vector<Drawable*> visDrawables = ...;
foreach( d in drawables )
{
if( visibleCullables[d->aabb] )
visDrawables->push_back(d);
}
Render( visDrawables );[/code]

Share this post


Link to post
Share on other sites
jmakitalo    668
I agree with Lauris, that the bounding box is used a lot and could be very high in abstraction hierarchy, but vis structure should be kept separate.

Hodgman, I think the last code example is closest to what I would like to have, except that I'm not entirely sure if indexes to vectors should be the way
to implement it.

One problem is also that I have a scene editor built into my engine and the editing is integrated to the scene graph that contains list of the
base classes. It works quite fine in most cases, but it gets ugly e.g. at the point of saving the scene data. The scene must know (up to a point), what
kind of objects there are in the scene, since they can be associated with different kind of data. This kind of ruins the idea of object abstraction. My solution
has been to assign some variable to the objects that actually tells if the the object is a mesh or a sprite, but this is not cool.

Another thing is that in my engine mesh objects require more subtle rendering compared to sprites and particle systems. The mesh objects
should be sorted by materials, level of detail etc. and the scene system first binds vertex arrays and sets up materials for one type of meshes
and then calls the draw routines of all the meshes of this type. The meshes are also made up of groups, each having specific material. The
scene system must account for this to draw the meshes efficiently, but an abstraction that takes this into account is an overkill for
all the other kind of objects. I'm thinking if I should make a specific kind of scene management system for the meshes and a simpler one
for all the other kind of objects.

Share this post


Link to post
Share on other sites
Hodgman    51234
[quote name='jmakitalo' timestamp='1333909943' post='4929367']scene graph that contains list of the base classes[/quote]Can you describe this? "[i]Scene graphs[/i]" have a bad name in the industry and can be the root of inefficiencies and coupling problems... Also, using inheritance should be pretty rare, so if you've got many things inheriting the same base class, it could be part of your problem.
[quote]I'm thinking if I should make a specific kind of scene management system for the meshes and a simpler one for all the other kind of objects.[/quote]Is scene management the same as your culling/visibility management system? If so, then once you've de-coupled culling from rendering, then you won't have a scene for meshes/particles, you'll have a scene for culling objects.[quote]except that I'm not entirely sure if indexes to vectors should be the way to implement it.[/quote]It's just pseudo-code to get some ideas across; the indices could well be pointers if you preferred.

Share this post


Link to post
Share on other sites
jmakitalo    668
I think I should put here some code to give the idea how my code has been structured for now:

[CODE]
// quadtree.hh
class CBaseObject
{
bool visible;

virtual BBox getBoundingBox();
virtual int getNumGroups();
virtual void bindGroup(int i);
virtual void unbindGroup(int i);
virtual void drawGroup(int i);
};
class CQuadNode
{
vector<CBaseObject*> pObjects;
CQuadNode *children[4];
};
class CQuadTree
{
CQuadNode *root;
};
// mesh.hh
#include "quadtree.hh"
class CMeshInstance : public CBaseObject
{
CMesh *pData;

BBox getBoundingBox();
int getNumGroups();
void bindGroup(int i);
void unbindGroup(int i);
void drawGroup(int i);
};
// Particle system
#include "quadtree.hh"
class CParticleSystemInstance : public CBaseObject
{
CParticleSystem *pData;

BBox getBoundingBox();
int getNumGroups();
void bindGroup(int i);
void unbindGroup(int i);
void drawGroup(int i);
};
// Scene
#include "quadtree.hh"
class CObject : public CBaseObject
{
vector3f position;
vector3f rotation;
vector3f scale;
bool selected;
};
class CScene
{
list<CObject> objects;
CQuadtree *qt;

void draw();
};
[/CODE]

So basically the scene does not know about the meshes and particle systems, just about the quadtree and the base class. But the scene is used also for in-game editing (changing object positions etc.) and it should be able to save the scene to a file. Then it needs to be aware of the meshes and particle systems. Also the meshes require the ability to handle groups, but most other systems, such as the particle emitter, does not. Additionally, although not shown here, meshes need specific shaders to be loaded and enabled prior to rendering and I have embedded this to the scene module, which is not very good now that I think of it.

Share this post


Link to post
Share on other sites
L. Spiro    25622
You are correct, it is not.
Leave behind any thoughts about a scene for now and think only inside a little black box that represents a model.

Any model, drawable or not, has some form of bounding box, perhaps optional collision data, and probably vertices, perhaps in pool form or perhaps expanded. At this point we don’t care what the purpose of the data is. Collision detection? Rendering? Who cares. We now have CModel.

Now we want to render some of those. Enter CDrawableModel : public CModel.
CDrawableModel introduces a concept completely foreign to CModel: graphics.
It creates vertex and index buffers for all the parts of the model (using smaller classes to organize each part of course) and holds shared pointers to the shaders it needs.

Notice, though, how these objects manage themselves. Having a scene module handle this will be a mess when you introduce particles, terrain, etc. Each new type of object has a special way for drawing itself, and trying to put all these special cases in one spot will be unmanageable and fast.


Another point is that the above classes are shared. They contain the raw data that instances use for rendering themselves. With that said we can get out of the box and think about scenes.

A scene knows what CActor or CEntity is.
It isn’t bothered when it receives a “CModelInstance : public CActor” or “CDrawableModelInstance : public CModelInstance”.
It only needs to have a method for determining if something can be drawn at render time so it can cast it accordingly, or else you end up with CActor having an interface for every possible type of functionality that everything inheriting from it will ever need.

Time to render.
Run through your oct-tree and gather only renderable objects into an array. An array that avoids constant re-allocation by always growing and never shrinking. Pointers to the objects are gathered.
Models are composed of a bunch of smaller meshes, and each mesh may have multiple vertex/index buffers. These little parts are what you actually gather.
Opaque parts go in one list, translucent in another.
Sort the list by fewest major render state changes for opaque. Swapping textures and shaders are the 2 things you want to avoid most, so make them high on your priority list.

Objects should then render themselves in that order. The scene manager/module has no business rendering for others. It simply pulls the strings orchestrates the render process from a manager’s point of view. That’s what managers do. They boss people around but the people have to do their own work.


L. Spiro

Share this post


Link to post
Share on other sites
jmakitalo    668
The following structure seems better, but there are still some problems:

[CODE]
// cullable.hh


class CCullable
{
public:
vector3f getOrigin() const;
vector3f getBoxSize() const;
};

// quadtree.hh


#include "cullable.hh"

class CQuadNode
{
private:
// The outer vector is for groupping cullables by their kind.
// I want to cull meshes, particle systems and so on with a single tree.
vector<vector<CCullable*>> pCullables;
CQuadNode *children[4];
};


class CQuadTree
{
private:
CQuadNode *root;

public:
void insertCullables(const vector<CCullable*> &cullables, int group);

// Return indices to visible cullables for each group.
vector<vector<int>> frustumCull(vector3f cameraPos, vector3f cameraDir, float fov);

// This can be used to speed up line-object intersection tests.
vector<vector<int>> lineIntersect(vector3f start, vector3f end);
};

// transformable.hh


class CTransformable
{
protected:
vector3f origin;
vector3f rotation;
vector3f scale;
bool deleted;
bool selected;

public:
void translate(const vector3f &v);
void rotate(const vector3f &v);
void scale(const vector3f &v);
};

// Selects such indices that refer to transformables that are at given range from the camera.
vector<int> transformablesInRange(const vector<CTransformable*> &t, const vector<int> &indices, vector3f cameraPos, float range);


// These are useful for editing a scene.
vector<CTransformable*> getSelectedTransformables(const vector<CTransformable*> &t);
void translateTransformables(const vector<CTransformable*> &t, vector3f v);
void rotateTransformables(const vector<CTransformable*> &t, vector3f v);
void scaleTransformables(const vector<CTransformable*> &t, vector3f v);

// drawable.hh


class CDrawable
{
public:
virtual int getNumGroups();
virtual void bindGroup(int i);
virtual void unbindGroup(int i);
virtual void drawGroup(int i);

// Returns an integer that can be used to sort group bindings.
// For all drawables with the same data id, bindGroup is called only
// once per group index and only drawGroup is invoked for each such group.
virtual int getDataID();
};

// Sort indices by data id. The indices refer to the drawables vector.
void sortDrawables(const vector<CDrawable*> &drawables, vector<int> &indices);


// Draws the drawables pointed by indices. Calls to bindGroup are minimized if
// sortDrawables has been invoked before.
void drawDrawables(const vector<CDrawable*> &drawables, const vector<int> &indices);

// mesh.hh


// loc contains shader attribute and uniform locations.
CMeshShader *loadMeshShaders(CMeshShaderLoc &loc);
void freeMeshShaders(CMeshShader *sh);
void bindMeshShaders(CMeshShader *sh);
void unbindMeshShaders(CMeshShader *sh);

class CMeshInstance
{
protected:
CMesh *pData[maxLevelsOfDetail];
int currentLevelOfDetail;

public:
// Here's a problem: mesh instance needs loc, but the generic drawGroup in CDrawable
// cannot account for this in a generic way.
void bindGroup(int index, CMeshShaderLoc &loc);
void bindGroup(int index);
void drawGroup(int index, CMeshShaderLoc &loc);
int getNumGroups();
};


// engine.hh

#include "quadtree.hh"
#include "transformable.hh"
#include "drawable.hh"
#include "mesh.hh"
#include "particle.hh"


class CMeshObject : public CCullable, public CTransformable, public CDrawable, public CMeshInstance
{
public:
// Not sure if this hack works. Mesh sorting would be based on data address.
int getDataID(){
return (int)pData[currentLevelOfDetail];
}
};

class CParticleObject : public CCullable, public CTransformable, public CDrawable, public CParticleInstance
{
public:
int getDataID(){
// ...
}
};


class CEngine
{
private:
CQuadTree *qt;

vector<CMeshObject*> meshObjects;
vector<CParticleObject*> particleObjects;
public:
bool init();
void draw();
};


// engine.cpp

bool CEngine::init()
{
// ...

qt->insertCullables(meshObjects, 0);
qt->insertCullables(particleObjects, 1);

// ...
}

void CEngine::draw()
{
vector<vector<int>> objectIndices;
objectIndices = qt->frustumCull(cameraPos, cameraDir, fov);

vector<int> meshIndices;
meshIndices = transformablesInRange(meshObjects, objectIndices[0], cameraPos, range);
sortDrawables(meshObjects, meshIndices);
bindMeshShaders(meshShaders);
drawDrawables(meshObjects, meshIndices);
unbindMeshShaders(meshShaders);

vector<int> particleIndices;
particleIndices = transformablesInRange(particleObjects, objectIndices[1], cameraPos, range);
sortDrawables(particleObjects, particleIndices);
bindParticleShaders(particleShaders);
drawDrawables(particleObjects, particleIndices);
unbindParticleShaders(particleShaders);
}
[/CODE]

Integer vectors seem here appropritate, since if e.g. quadtree would return a vector of CCullables from frustumCull, then
using that data for further culling or even drawing would not be directly possible.

I'm not sure if I like the cullable grouping scheme of the quadtree, but I cannot think of another way of handling
different object types in a single tree. On the other hand, using a dedicated tree for each object type seems wasteful.

The greatest problem in the above is passing the shader information to the drawing routines, as the CDrawables is
being used.

Share this post


Link to post
Share on other sites
jmakitalo    668
Ok, maybe the methods of CDrawable could have GLint *loc as argument to pass arbitrary shader locations. These would then be also passed to drawDrawables function. But let's say that the drawables in question want to implement object space normal mapping. Then they require the object space camera and light positions, so these transformation would have to be done in drawDrawables. In what kind of abstractions could these be included in the CDrawable? It gets messy.

Share this post


Link to post
Share on other sites
lauris71    841
[quote name='jmakitalo' timestamp='1334089154' post='4929989']
Ok, maybe the methods of CDrawable could have GLint *loc as argument to pass arbitrary shader locations. These would then be also passed to drawDrawables function. But let's say that the drawables in question want to implement object space normal mapping. Then they require the object space camera and light positions, so these transformation would have to be done in drawDrawables. In what kind of abstractions could these be included in the CDrawable? It gets messy.
[/quote]
In my engine I made the actual draw call a virtual method of Material object instead of Mesh object.
I.e.
Mesh::display() calls scheduleRender, the latter appends small RenderData structure to render list. RenderData contains links to index and vertex buffers, backlinks to mesh and material, sorting keys and some other data
After sorting etc. a Material::render is called
The reason is, that only material instance (which manages shaders) knows, what actual data it requires (vertexes, normals, texcoods, colors, all kinds of matrices, special textures, lights, reflections, sky, aerial thickness etc. etc.)

Share this post


Link to post
Share on other sites
jmakitalo    668
[quote name='Lauris Kaplinski' timestamp='1334154315' post='4930258']
In my engine I made the actual draw call a virtual method of Material object instead of Mesh object.
I.e.
Mesh::display() calls scheduleRender, the latter appends small RenderData structure to render list. RenderData contains links to index and vertex buffers, backlinks to mesh and material, sorting keys and some other data
After sorting etc. a Material::render is called
The reason is, that only material instance (which manages shaders) knows, what actual data it requires (vertexes, normals, texcoods, colors, all kinds of matrices, special textures, lights, reflections, sky, aerial thickness etc. etc.)
[/quote]

That is a good insight.

With this in mind, I have redesigned what I pasted earlier. So what if CDrawable would consist of virtual functions for binding all sorts of arrays. It would allow passing shader varying and uniform locations.

This gets a bit lengthy, but hopefully it is not too messy.

The beef is in the function drawDrawables, which can draw culled and sorted object instances while shaders, geometry and culling are well decoupled in the code.

[CODE]


// cullable.hh

// Just a very basic class to be used by a quadtree or some other vis.
class CCullable
{
public:
vector3f getOrigin() const;
vector3f getBoxSize() const;
vector3f getAlignedBoxSize() const;
};

// quadtree.hh

#include "cullable.hh"

class CQuadNode
{
private:
// The outer vector is for groupping cullables by their kind.
// I want to cull meshes, particle systems and so on with a single tree.
vector<vector<CCullable*>> pCullables;
CQuadNode *children[4];
};

class CQuadTree
{
private:
CQuadNode *root;

public:
void insertCullables(const vector<CCullable*> &cullables, int group);

// Return indices to visible cullables for each group.
vector<vector<int>> frustumCull(vector3f cameraPos, vector3f cameraDir, float fov, float range);
};

// transformable.hh

// This acts as a base class for object instances.
class CTransformable
{
protected:
vector3f origin;
vector3f rotation;
vector3f scale;
bool deleted;
bool selected;

matrix44f_c trans, invTrans;

void buildMatrices();

public:
void translate(const vector3f &v);
void rotate(const vector3f &v);
void scale(const vector3f &v);

void loadMatrix();
matrix44f_c getMatrix();
matrix44f_c getInvMatrix();
};

// These are useful for editing a scene.
vector<CTransformable*> getSelectedTransformables(const vector<CTransformable*> &t);
void translateTransformables(const vector<CTransformable*> &t, vector3f v);
void rotateTransformables(const vector<CTransformable*> &t, vector3f v);
void scaleTransformables(const vector<CTransformable*> &t, vector3f v);

// shader.hh

// This acts as a base class for specific shaders.
// The methods here allow the abstract function drawDrawables to tell
// drawables, what data the shader requires.
class CShaderBase : CShader
{
public:
virtual bool requireTexCoords() const;
virtual bool requireNormals() const;
virtual bool requireTangents() const;
virtual bool requireWeights() const;
virtual bool requireBoneindices() const;
virtual bool requireTexture() const;

virtual GLint getTangentLoc() const;
virtual GLint getWeightLoc() const;
virtual GLint getBoneIndexLoc() const;

virtual void bindTexture(CTexture *pTex);

virtual void loadObjectSpaceUniforms(matrix44f_c invTrans);
};

// drawable.hh

#include "transformable.hh"
#include "shader.hh"

// This is a base class for drawable object instances, that may
// share some common data. A drawable is allowed to consist of groups.
// The idea is that each group is associated with one texture/material.
class CDrawable : public CTransformable
{
public:
virtual int getNumGroups();
virtual void bindGroupVertexBuffer(int index);
virtual void bindGroupNormalBuffer(int index);
virtual void bindGroupTexCoordBuffer(int index);
virtual void bindGroupTangentBuffer(int index, GLint loc);
virtual void bindGroupWeightBuffer(int index, GLint loc);
virtual void bindGroupBoneBuffer(int index, GLint loc);
virtual void bindGroupIndexBuffer(int index);
virtual void drawGroup(int index);
virtual CTexture *getGroupTexture(int index);

// Allow the drawable to load uniforms for a shader.
// These can be e.g. bone matrices for skinned meshes.
virtual void loadGroupUniforms(int index, GLint *locArray, int numLoc);

// Returns an integer that can be used to sort group bindings.
// For all drawables with the same data id, bindGroup is called only
// once per group index and only drawGroup is invoked for each such group.
virtual int getDataID();
};

// Sort indices by data id. The indices refer to the drawables vector.
void sortDrawables(const vector<CDrawable*> &drawables, vector<int> &indices);


// The indices point to the drawables vector, but indices.size() may be less than
// drawables.size() as some may be culled. Indices should also be sorted by data ID.
void drawDrawables(const vector<CDrawable*> &drawables, const vector<int> &indices, CShaderBase *sh)
{
int prevDataID = -1;

for(int i=0; i<indices.size(); i++){
CDrawable *p = drawables[indices[i]];
int dataID = p->getDataID();

sh->loadObjectSpaceUniforms(p->getInvMatrix());

// This function is a simplified version of what would be used.
// As the indices may be sorted by dataID, one should loop through
// groups first and then object instances and not vise versa as in here.

for(int j=0; j<p->getNumGroups(); j++){

// If object source data changes, bind the new data.
if(dataID!=prevDataID){
p->bindGroupVertexBuffer(j);
if(sh->requireTexCoords())
p->bindTexCoordBuffer(j);
if(sh->requireNormals())
p->bindNormalBuffer(j);
if(sh->requireTangents())
p->bindTangentBuffer(j, sh->getTangentLoc());
if(sh->requireWeights())
p->bindWeightBuffer(j, sh->getWeightLoc());
if(sh->requireBoneIndices())
p->bindBoneIndexBuffer(j, sh->getBoneIndexLoc());
if(sh->requireTexture())
sh->bindTexture(p->getGroupTexture(j));
}

p->loadGroupUniforms(j, sh->getUniformLocArray(), sh->getNumUniformLoc());

glPushMatrix();
p->loadMatrix();

p->drawGroup(j);

glPopMatrix();
}

prevDataID = dataID;
}
}

// mesh.hh

// I don't want to make this module and class to be involved with
// any of the above modules. Coupling is done in the engine module.
class CMeshInstance
{
protected:
CMesh *pData[maxLevelsOfDetail]; // These are pointers to the actual shared data.
int currentLevelOfDetail;

public:
int getNumGroups();

void bindGroupVertexBuffer(int index);
void bindGroupNormalBuffer(int index);
void bindGroupTexCoordBuffer(int index);
void bindGroupTangentBuffer(int index, GLint loc);
void bindGroupWeightBuffer(int index, GLint loc);
void bindGroupBoneBuffer(int index, GLint loc);
void setGroupSkinMatricesUniform(int index, GLint loc, int maxBones);
void bindGroupIndexBuffer(int index);
void drawGroup(int index);
};

// engine.hh

class CShaderStaticMeshDirLight : public CShaderBase
{
// Implement the virtual routines here.


// e.g.
vector3f cameraPosWorldSpace;
vector3f lightDirWorldSpace;

void loadObjectSpaceUniforms(matrix44f_c invTrans);
};

#include "quadtree.hh"
#include "drawable.hh"
#include "mesh.hh"

// This couples the mesh instance to everything else.
class CMeshObject : public CCullable, public CDrawable, public CMeshInstance
{
public:
// Not sure if this hack works. Mesh sorting would be based on data address.
int getDataID(){
return (int)pData[currentLevelOfDetail];
}

void loadUniforms(int index, GLint *locArray, int numLoc){
setGroupSkinMatricesUniform(index, locArray[0], 50);
}
};

class CEngine
{
private:
CQuadTree *qt;

vector<CMeshObject*> meshObjects;

CShaderStaticMeshDirLight *shaderStaticMeshDirLight;

public:
bool init();
void draw();
};

bool CEngine::init()
{
// ...

qt->insertCullables(meshObjects, 0);

// ...
}

void CEngine::draw()
{
// Frustum cull all objects with quadtree.
vector<vector<int>> objectIndices;
objectIndices = qt->frustumCull(cameraPos, cameraDir, fov, range);

// Sort visible mesh objects according to data ID:s.
vector<int> &meshIndices = objectIndices[0];
sortDrawables(meshObjects, meshIndices);

// Draw meshes visible & sorted meshes and use directional light shader.
shaderStaticMeshDirLight->bind();
shaderStaticMeshDirLight->setCameraPos(cameraPos);
shaderStaticMeshDirLight->setLightDir(lightDir);

drawDrawables(meshObjects, meshIndices, shaderStaticMeshDirLight);

shaderStaticMeshDirLight->unbind();
}
[/CODE]

Edit: a few typos from the code.

Share this post


Link to post
Share on other sites
lauris71    841
I do not understand, how do you plan to get object indices from cullables? If I understand correctly those will be distributed between Quadtree nodes and thus the index inside single node cullable list does not match the global index.

[code] // Not sure if this hack works. Mesh sorting would be based on data address.
int getDataID(){
return (int)pData[currentLevelOfDetail];
}[/code]
On 64 bit system you will discard higher 32 bits of address and the result may not be unique.

What is the reason that you have:
Shader::bindTexture
but
Drawable::bindTexCoordBuffer
It would seem more logical for me, if all shader binding would be done in shader and Drawable would provide only buffer locations

Share this post


Link to post
Share on other sites
L. Spiro    25622
[quote name='Lauris Kaplinski' timestamp='1334154315' post='4930258']
The reason is, that only material instance (which manages shaders) knows, what actual data it requires (vertexes, normals, texcoods, colors, all kinds of matrices, special textures, lights, reflections, sky, aerial thickness etc. etc.)[/quote]
I disagree. The model itself is the only thing that knows everything about how it needs to be rendered.
And this is an important fact because it allows the model to switch between different vertex buffer combinations for different purposes. For example, generating a shadow map. Why would you send the normals, UV coordinates, tangents, and bitangents when all you really need to send is the vertices?

Multiple streams have some overhead, but less overhead than sending 40 bytes of unnecessary data across the bus.

Meshes know what their materials are, and materials are [b]not[/b] to be tied too tightly to the vertex buffers etc., or else you will need an entirely new material to handle the above case when all that is really necessary is a single new shader that is shared between all meshes (all meshes will be using the same shader during shadow-map generation).

There is a logical connection between shaders and vertex buffers, since shaders are expecting input from vertex buffers, but aside from matching inputs these too are decoupled as much as possible. Any system that attempts to perform some kind of coupling between material data, shaders, and vertex/index buffers is inflexible and should be avoided. These things needs to be as dynamic as possible not only for flexibility but as mentioned above for optimization.


L. Spiro

Share this post


Link to post
Share on other sites
jmakitalo    668
[quote name='Lauris Kaplinski' timestamp='1334354061' post='4931067']
I do not understand, how do you plan to get object indices from cullables? If I understand correctly those will be distributed between Quadtree nodes and thus the index inside single node cullable list does not match the global index.
[/quote]

You're right, that would not work. So maybe I just throw away the cullables class and use plain indices:

[CODE]
class CQuadNode
{
private:
// Vector for each group.
vector<int> *indices;
CQuadNode *children[4];
bool leaf;
};

class CQuadObject
{
vector2f origin;
vector2f boxSize;
int seq;
};

class CQuadTree
{
private:
CQuadNode *root;
int seq;
int ngroups;

// Vector for each group.
vector<CQuadObject> *objects;

// Indices that have passed the last operation.
// Vector for each group.
vector<int> *indices;

public:
bool allocateGroups(int n);

// Insert to objects vector.
bool insertObjects(const vector<CQuadObject> &_objects, int group);

// Perform frustum culling. Results are stored to indices.
void frustumCull(vector3f cameraPos, vector3f cameraDir, float fov, float range);

void lineIntersect(vector3f s, vector3f e);
void cylinderIntersect(vector3f s, vector3f e, float rad);

// Get index vector to query results from last operation.
vector<int> &getIndices(int group);
};
[/CODE]

I also changed it so that e.g. frustumCull does not return an index vector, but each quadtree query holds the result in the indices vector. A reference to this can then be obtained by using getIndices. This avoids copying the vector.

[quote name='Lauris Kaplinski' timestamp='1334354061' post='4931067']
On 64 bit system you will discard higher 32 bits of address and the result may not be unique.
[/quote]

Ok, so using long int should do?

[quote name='Lauris Kaplinski' timestamp='1334354061' post='4931067']
What is the reason that you have:
Shader::bindTexture
but
Drawable::bindTexCoordBuffer
It would seem more logical for me, if all shader binding would be done in shader and Drawable would provide only buffer locations
[/quote]

Well, Drawable::bindTexCoordBuffer binds the texture coordinate buffer, as the mesh knows the location of the buffer, but the shader
does not. The mesh has the texture, but it doesn't know where to bind this, but the shader does, so drawDrawables can then
get the texture from the mesh group and call Shader::bindTexture. But your suggestion sounds good,
I will consider it.

Share this post


Link to post
Share on other sites
jmakitalo    668
[quote name='L. Spiro' timestamp='1334376472' post='4931107']
I disagree. The model itself is the only thing that knows everything about how it needs to be rendered.
And this is an important fact because it allows the model to switch between different vertex buffer combinations for different purposes. For example, generating a shadow map. Why would you send the normals, UV coordinates, tangents, and bitangents when all you really need to send is the vertices?
[/quote]

[left]I think that my approach allows to take this into account quite well. One just calls[/left]

[left][CODE]
drawDrawables(meshObjects, meshIndices, shaderStaticMeshDirLight);
[/CODE][/left]

[left]for drawing meshes with directional light and[/left]

[left][CODE]
drawDrawables(meshObjects, meshIndices, shaderStaticMeshShadowmap);
[/CODE][/left]

[left]for drawing things for shadowmap. For static meshes a shader is probably not needed, but for skinned meshes it is. Also,[/left]
[left]the shadowmap shader can pass false for all the require* methods, since indeed, only vertex locations are needed.[/left]

Share this post


Link to post
Share on other sites
lauris71    841
[quote name='L. Spiro' timestamp='1334376472' post='4931107']
[quote name='Lauris Kaplinski' timestamp='1334154315' post='4930258']
The reason is, that only material instance (which manages shaders) knows, what actual data it requires (vertexes, normals, texcoods, colors, all kinds of matrices, special textures, lights, reflections, sky, aerial thickness etc. etc.)[/quote]
I disagree. The model itself is the only thing that knows everything about how it needs to be rendered.
And this is an important fact because it allows the model to switch between different vertex buffer combinations for different purposes. For example, generating a shadow map. Why would you send the normals, UV coordinates, tangents, and bitangents when all you really need to send is the vertices?
[/quote]
I do not have to send normals, unless needed. What happens is:[list]
[*]Mesh creates render structure, which lists available buffers
[*]Material knows which buffers it needs and sends only those to pipeline
[/list]
In my case single material can use more than one shader - depending on requested rendering type. For example shadow map is usually done by simple depth shader invoked by various materials. But still there are differences:[list]
[*]Plain color or simple textured material only has to send vertexes to depth shader
[*]Textured masked material has to send UV coordinates and textures to textured depth shader because it needs transparency mask
[*]Everything animated in vertex shader needs specialized depth shader
[*]etc.
[/list]
[quote name='Lauris Kaplinski' timestamp='1334154315' post='4930258']
Meshes know what their materials are, and materials are [b]not[/b] to be tied too tightly to the vertex buffers etc., or else you will need an entirely new material to handle the above case when all that is really necessary is a single new shader that is shared between all meshes (all meshes will be using the same shader during shadow-map generation).
[/quote]
I found that the data managed by mesh objects (vertex and index buffers) is much more simple and homogenous than the data required and managed by materials/shaders. Thus I found it much easier to move all OpenGL state code into materials and keep meshes as mostly dumb data containers. I do not find it inherently inflexible - the same functionality is simply implemented in another place.

Share this post


Link to post
Share on other sites
jmakitalo    668
Thanks all for your insights. I have now pretty much made the heavy changes to my engine and I think I'm pretty happy with it.

So now basically, when it comes to the original question, the quadtree is completely isolated from everything else. This decoupling is achieved basically by using just index vectors, not any base classes or pointers. Although this might not be along the C++ principles (vaguely stated), it does its job.

Now, my meshes and the like provide methods for binding separately different buffers. Shaders tell what they need and these two are combined in a drawing function which, basically takes as input the shader, a vector of e.g. mesh objects and a vector of indices to these objects. These indices can then be parsed by quadtree and state sorting routines before drawing.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this