Jump to content
  • Advertisement
Sign in to follow this  
matt77hias

Game loop on the scene graph

This topic is 535 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

I created a base class SceneNode which has a Transform, at most one parent SceneNode and some child SceneNodes (allowing to go both up and down in this custom tree structure). A complete scene (and its scene graph) is fully defined by one SceneNode. So far I have two derived classes: CameraNode and ModelNode containing a Camera and Model, respectively

 

Other features like colliders, audio sources, etc. I will probably add in one collection of "components" in the SceneNode (becomes a bit bloated this way). The same for scripts (which can inherit pre/update/post methods). This way I can add stuff to the CameraNode and perform a lookup (for instance in scripts) to retrieve certain components. This is a bit similar to Unity3D's scripting I guess.

 

The Model base class consists of a Mesh and Material.

The Mesh base class consists of a vertex and index buffer. I pass an ID3D11Device2 (construction) and ID3D11DeviceContext2 to create and bind the buffers, respectively.

The Material base class consists of a PixelShader and VertexShader (subclasses can contain some further specifics like diffuse color or texture). I pass an ID3D11Device2 (construction) to create the shader.

(I possibly need to enforce some connection between the Model, Mesh, Material, VertexShader and mesh loading functions to support multiple vertex input layouts).

 

The scripting part would then consist of traversing the scene graph and executing the update methods of all enabled scripts found on the way. Maybe, I can first gather all SceneNodes by flattening the scene graph and then traverse them to avoid scripting updates changing the scene graph from breaking the traversal.

The rendering part would consist of first adding the camera matrices (ordinary single pass forward rendering so far) to some constant buffers and then rendering all enabled ModelNodes (UpdateSubresource, VSSetShader, PSSetShader, DrawIndexed, etc.).

 

Can someone give some feedback on this design? My D3D11 knowledge is still in its infancy: it is easy to setup everything for a single camera, mesh and shader, but in the presence of multiple shaders and meshes (using different vertex types) it becomes less tractable for me.

Edited by matt77hias

Share this post


Link to post
Share on other sites
Advertisement

allowing to go both up and down in this custom tree structure

Do you need that feature? You can probably get by with just one or the other. Note that if, for example, you use child pointers only, then if you're doing some kind of recursive update function down the tree, you can still pass the "this" pointer into your children's update functions, so they have access to their parent if required.
Personally I get by storing parent pointers only.

So far I have two derived classes: CameraNode and ModelNode containing a Camera and Model, respectively.

I would not use inheritance for this. Instead, I would have Camera and Model each contain a pointer to a SceneNode or a pointer to a Transform. 
I would have the 'scene graph' have no knowledge of the other systems that depend on it whatsoever -- it's just a transform hierarchy. You can update that hierarchy, and then use the updated transforms to cull the bounding volumes of the scene. The results of that culling stage then let you know which cameras/models/etc are visible and must be drawn.

The Model base class consists of a Mesh and Material.

A model should probably contain a collection of Mesh+Material pairs -- many models will be made up of multiple parts.

The Mesh base class consists of a vertex and index buffer

The Draw* functions take an offset into the vertex/index buffers as an argument. This lets the model own the vertex/index buffers, and each mesh can exist at an offset within that buffer.

Share this post


Link to post
Share on other sites
 Do you need that feature? You can probably get by with just one or the other. Note that if, for example, you use child pointers only, then if you're doing some kind of recursive update function down the tree, you can still pass the "this" pointer into your children's update functions, so they have access to their parent if required.
Personally I get by storing parent pointers only.

Currently, I have implemented a Visitor pattern for general purposes which visits the scene graph in a depth-first fashion. But if I remove all but the traversal functionality, like you said, an iterator or visitor without the double dispatch suffice which is more performant since no dynamic dispatch is needed. The only time I actually go up the tree is while calculating the world-to-object or object-to-world matrix of a node (which couldn't be called directly anymore without some accumulator parameter if I remove the parent connection).

 

 A model should probably contain a collection of Mesh+Material pairs -- many models will be made up of multiple parts.

But a mesh still consists of one material? (and at most one texture?)

 

 

The Draw* functions take an offset into the vertex/index buffers as an argument. This lets the model own the vertex/index buffers, and each mesh can exist at an offset within that buffer.

But this only allows model reuse if you want to use the same material? I thought to abstract all the geometry in the mesh and all the appearance in the material, allowing to reuse (only pointer) the mesh and material indepently?

Edited by matt77hias

Share this post


Link to post
Share on other sites

You could implement a dirty bit for finding the world matrix of a node. Basically when your traversing through the tree, if a dirty bit has been set on a node (set anytime it's world matrix has changed), you will pass an isdirty boolean to all its children, so they will now all update their own world matrices: http://gameprogrammingpatterns.com/dirty-flag.html

 

Models are usually made up of more than one mesh, where a lot of the time the meshes are separated by material. So a model should contain a list of Mesh + Material pairs like Hodgman said. The model does not have to own these meshes (or materials), it only needs pointers to them. This way, you might have two different models using the same group of meshes but with different materials.As a simple example, think of a cube where all sides have a different material/texture. Each side will require it's own draw call, so each side is it's own mesh with a material. So that cube Model would have a list of 6 Mesh + Material pairs, one for each side

 

This is why you use pointers and do not let the model own the mesh. You may want to think about how you will implement instancing too.

Share this post


Link to post
Share on other sites

Models are usually made up of more than one mesh, where a lot of the time the meshes are separated by material. So a model should contain a list of Mesh + Material pairs like Hodgman said. The model does not have to own these meshes (or materials), it only needs pointers to them. This way, you might have two different models using the same group of meshes but with different materials.As a simple example, think of a cube where all sides have a different material/texture. Each side will require it's own draw call, so each side is it's own mesh with a material. So that cube Model would have a list of 6 Mesh + Material pairs, one for each side   This is why you use pointers and do not let the model own the mesh. You may want to think about how you will implement instancing too.
 I use ComPtr< ID3D11Buffer >s. Originally, I stored them with the Mesh, but since Models contain multiple Meshes, I will store them with the Model and use an index like Hodgman said above. So I will use something like list< pair<SharedPtr< Mesh< VertexType > >, SharedPtr< Material< VertexType > > > > at the Model< VertexType > class.

Share this post


Link to post
Share on other sites

You could implement a dirty bit for finding the world matrix of a node. Basically when your traversing through the tree, if a dirty bit has been set on a node (set anytime it's world matrix has changed), you will pass an isdirty boolean to all its children, so they will now all update their own world matrices: http://gameprogrammingpatterns.com/dirty-flag.html
As a consequence, you need to cache instead of create the object-to-world matrices on demand (instead of only the local object-to-parent matrices)?

 

Can this approach affect the scripting? What if a transform is mutated and accessed again in the same script?

Share this post


Link to post
Share on other sites

Ok, still some questions about customization of an engine:

 

How many vertex formats/input layouts does one normally support? One, a few fixed or fully customizable?

How many shader input layouts (constant buffers, etc.) does one normally support? A few fixed or fully customizable? With fixed, I mean TextureShader, SolidShader, BlinnPhongShader, etc. where the user just needs to provide the right arguments since they could differ versus one uniform interface?

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.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!