Sign in to follow this  
acid2

Shader system , or a state tree?

Recommended Posts

Hey everyone. Designing myself yet another game engine - this one is hopefully going to be my best designed one, I have another one that I can fill up with crap like working out how to do HDR :P I have a kernel system, rendering pipeline, settings manager and some other stuff at the moment and I really like how things are going. However, I've hit a wall (metaphoricaly, obviously). It's down to materials again. The 2 approaches I'm aware of are material trees and shaders. For people who are unfamiliar with them, I'll go over them quickly; or at least, my understanding. Material Tree These system organise the rendering into a tree that is traversed. Afaik, this is regarded as the most optimal system. Here's an example material tree for an example scene with 4 boxes. 2 are bumpmapped with bricks (1 is transparent), 1 has EMBM enabled and one is "special", some type of multipass shader.
 Bump map shader
    Main pass
       Rocks Diffuse Texture
          Rocks Normal Map
             Cube 1
             Transparancy
                 Cube 2
 EMBM shader
     EMBM pass
       Reflection Cube Map
          Normal Map
                 Cube 3
 Special shader
     Pass 0
          Cube 4
     Pass 1
          Cube 4
 Sky shader
    Sky pass
       Sky model
There are obviously other ways of representing this. The problems I see however, is its often very bare. Maybe its just my example but most branches at most have 2-3 children. I think this leads to a bit more time spent traversing the tree, when we could be rendering. Also, its very hard to edit, because you have to go changing and traversing to the node you need. The advantages are, its extensibile, because you can introduce new nodes wherever you want and should be confident that the other changes will remain ok. Everything is very seperate, which I kinda like. Also, its very simple to sort, in fact - it basically has to happen. Shader system Do not think soley of pixel/vertex shaders here. I use this term in the sense of a type of description associated with a model. A shader is loaded and this has parameters, and requests certain textures and other resources from appropriate managers. Because we now have all settings together, editing becomes much easier. We can just get the shader (by id, by name, etc) and then make changes. The problems do arise with sorting though. Although here is a solution that has just occured to me. Shaders have parameters of a "ShaderParameter" type. This is a templated (well, generic - im a c# user) type that contains the setting and a cost value. Then, when it comes to sorting, we go through each shader, and then somehow merge them and sort by the cost of the parameters I suppose what I'm really leaning towards is shader's for descriptions, and material tree's for the implementation in the engine. What are your thoughts?

Share this post


Link to post
Share on other sites
I use method #2 - shader based system with a set of rendering parameters carried around by each object that can be rendered. Within this rendering parameter structure, there are things like texture IDs, effect IDs (I use .fx files), and whatever else you can think of.

Then for sorting, I use a std::list of objects and use a custom sorting object to sort them however I want - even different sorting routines based on the current hardware. It works out pretty good, and is adaptable to just about any situation.

Share this post


Link to post
Share on other sites
Yea, that sounds good Jason - mind going into a bit more depth on your rendering pass (the sorting stuff too)? Is the std::list a list of things your going to render, or settings that your going to render with?

Share this post


Link to post
Share on other sites
I use the 2nd approcah too but my design is a bit more complicated ;

A drawable node references an appearance object. Each appearance class has an appearance shader class, and a shared appearance shader class associated with it by a plugin system.
- When the rendering pipeline encounters a drawable node for the first time, it creates one instance of the appearance shader dedicated to it.
- When the rendering pipeline encounters an appearance for the first time, it creates one instance of the shared appearance shader dedicated to it.
These shaders reuse or create render passes and subpass (it's a tree) when they are created. Each render pass has its own sorting system.

After that, the appearance shader is called when the render frame is updated for either :
- entering the render frame (the node becomes visible),
- updating the render frame (the node is visible and needs update),
- exiting the render frame (the node becomes invisible).
In these method, it will add/update/remove render commands to the render passes.

When the render frame is executed, the render passes are sorted according to their sorting policy.

I use the usual sorting policy ;
- most passes are state sorted ; render commands which are instance of ISingleStateRenderCommand are asked for their state and sorted based on it (by texture and GPU shader). I do not sort front to back since it happened to be more expensive on my scenes.
- the translucent pass is back to front sorted.

Sorting happens to be rather cheap with this design since the render passes are kept from one render frame to the next render frame. Therefore the render commands are mostly sorted and a quicksort resolves in nearly O(n) most of the time.

Vincent

Share this post


Link to post
Share on other sites
Quote:
Original post by acid2
Yea, that sounds good Jason - mind going into a bit more depth on your rendering pass (the sorting stuff too)? Is the std::list a list of things your going to render, or settings that your going to render with?
Sure. The std::list is basically just a list of objects that are going to be rendered. I essentially just have a renderSetup method for each of my renderable objects that gets called through the scene graph interfaces if the object is going to be rendered this frame. Most of the time these are the actual game objects that are visible in the current view. Each object tests itself against the renderer's view, current render pass type, etc. to see if it should be added to the list. If so, it submits itself to the renderer to be added to its list.

Once the objects are added, I use a 'CRenderSort' object on the std::list. So it looks something like:

std::list<CObject*> m_RenderList;

//
//...add items from scene graph
//

CRenderSort sortObject( iType ); // create sorting object on the stack
m_RenderList.sort( sortObject ); // sort the render list with the object


The constructor for CRenderSort takes a type parameter for specializing the sorting functions. So you can sort by effect in some cases, or by distance from the camera, or whatever else method you need for the latest graphical method that you want to implement.

Once the list is sorted, the renderer just iterates through the list and calls the draw method for each of the objects. The whole thing is pretty flexible, and I haven't run into any situations where I can't use it for what I want. Do you think this can help you out?

Share this post


Link to post
Share on other sites
I do almost exactly the same as jason : I have a std::vector of renderable objects (which are sent to the renderer during the scengraph traversal) and before rendering, I simply do a std::sort (I provide the comparison function) on the array of renderable objects, and then simply render them.

Share this post


Link to post
Share on other sites
In fact, I do exactly the same as Jason as well.

The only difference is that I have several lists (one per render pass), and that I keep these lists from one frame to the next frame.

Vincent

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