Skip to main content
GameDev.net gamedev.net
🔒 Locked

Multiple shaders

Started by Endar Jul 15, 2006 at 10:19 AM 19 replies 4.1k views
Original Post
Endar
Endar
My current material system allows for only one texture and one shader per material, and an object usually only has 1 material (or for an entity with specific triangle groups, 1 material per group), and I've recently realised (quite obviously to some of you, I would imagine) that I'll be needing more than a single shader for an object. How would I go about this? Please note that I'm talking more about the method of combining the shaders so they are rendered correctly and not the data structures required. Is there anything you guys can tell me? Or is there a good reference site for something like this?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
deathkrush
deathkrush
As far as I know, there are two reasons to have multiple shaders per material: multipass rendering and LOD shaders. In multipass rendering, you have a base pass shader, a lighting pass shader, a combine shader, whatever you need. Each one of these shaders can have a number of LOD shaders associated with it.

The problem with having so many shaders per material is that they all can potentially have different number of constants and the constants could be differently named, so it becomes a nightmare to set up the shader constants. One way to solve that is to use hard-coded registers for your shader constants, so you don't need to worry about the variable names. I'm not sure how well it works with different graphics cards on PC, but I've seen this method used it a console game.

Another way to solve that problem is to have a data-driven shader system, where each shader is treated the same by your renderer. Each shader has a metafile associated with it, this metafile describes how to set up the shader constants. That way, the only thing that the renderer has to do is read this metafile and blindly set up shader constants according to the metafile.

I hope you find these suggestions useful.
deathkrushPS3/Xbox360 Graphics Programmer, Mass Media.Completed Projects: Stuntman Ignition (PS3), Saints Row 2 (PS3), Darksiders(PS3, 360)
Endar
Endar
Quote:
Original post by deathkrush
As far as I know, there are two reasons to have multiple shaders per material: multipass rendering and LOD shaders. In multipass rendering, you have a base pass shader, a lighting pass shader, a combine shader, whatever you need. Each one of these shaders can have a number of LOD shaders associated with it.

The problem with having so many shaders per material is that they all can potentially have different number of constants and the constants could be differently named, so it becomes a nightmare to set up the shader constants. One way to solve that is to use hard-coded registers for your shader constants, so you don't need to worry about the variable names. I'm not sure how well it works with different graphics cards on PC, but I've seen this method used it a console game.

Another way to solve that problem is to have a data-driven shader system, where each shader is treated the same by your renderer. Each shader has a metafile associated with it, this metafile describes how to set up the shader constants. That way, the only thing that the renderer has to do is read this metafile and blindly set up shader constants according to the metafile.

I hope you find these suggestions useful.


I'm not completely certain exactly what you mean by "data-driven."

Could I see an example of a metafile for a shader? I have a feeling that once I see an example, at least partial understanding would dawn on me.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
griffin2000
griffin2000
Quote:


Another way to solve that problem is to have a data-driven shader system, where each shader is treated the same by your renderer. Each shader has a metafile associated with it, this metafile describes how to set up the shader constants. That way, the only thing that the renderer has to do is read this metafile and blindly set up shader constants according to the metafile.


Another commonly used was of doing this is using annotation and semantics. So rather than always binding the Local-to-Screen mtx to register 4 or to the variable called LtoS you look for a the semantic WORLDVIEWPROJECTION (and after compiling your shader you look for this semantic and bind that parameter to a handle in your shader class so any shader can set-up the WVP matrix without knowing the details of the shader).

There is standard set of these semantics (DXSAS) but the documentation is very sketchy:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/directx9_c/dx9_graphics_reference_effects_dxsas.asp








griffin2000
griffin2000
That said... My reading of the OP is that you need multiple materials-per-object, not multiple shaders-per-material. If this is the case you just need to sub-divide you objects (at export or load-time) into sections where each section has a single material (so a car object could be sub-divided into two sections, one with a glass material for the windows, one with a metal material for the rest of the car).
Endar
Endar
I'm not sure that I explained myself correctly in the original post.

Just say I have a shader that performs per-pixel lighting, and another that makes it's object look like glass. How do I use these for a single object? And by object I mean that a mesh. So, this could be a triangle group of a more complex object, or it could be somthing simple like a cube. It's just a mesh with more than one shader.

I'm fairly sure that I'm trying to talk about rendering with multiple passes instead of several shaders for a single pass to render an object.

At the moment, this is my material class:
struct Material{public:	std::string name;			///< The name of the material	int id;				///< The material's id	video::ITexture* texture;		///< The texture for the material	video::IShader* shader;		///< The shader for the material	video::CColor ambient;		///< Ambient material color	video::CColor diffuse;		///< Diffuse material color	video::CColor specular;		///< Specular material color	video::CColor emissive;		///< Emissive material color	float shininess;			///< The shininess factor (from 0.0f to 128.0f)	float transparency;		///< The transparency factor	bool reflective;			///< If this object can reflect the world	util::CPlane clippingPlane;		///< The clipping plane for the material (for reflections, etc, etc)	// misc constructors, etc, etc};


That's what I've got so far, and now I'm wondering if I'll have to make the pointer to a shader object into a list, or whether I have to seperate shaders from more general attributes like lighting attributes and textures in order to have a well working system.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
griffin2000
griffin2000
Now do you need to apply the two shaders to the same bit of the mesh ? If the same triangle is rendered twice once with one material, then again with another, then you need to do multipass rendering.

The material structure doesn't nessacarily need to change for multipass rendering. If you look at the FX file you can include several passes within the same shader..

technique MyTechnique{  pass p0  {    //Vertex and pixel function for first pass  }  pass p1  {    //Vertex and pixel function for second pass  }};


Within your shader class you could then include a list of handles to passes (or a list of techinques with a list of passes in each technique). Edit: Actually on DX you only access passes via an index so you only need to store the number of passes each technique has in the shader class.

Though, as I said, if all you need to do is apply a different shader from one triangle to the next (e.g. a different shader on each face of a cube). Then you don't need multipass rendering you just need to subdivide your mesh into sections.
CrazyCdn
CrazyCdn
Umm... You can remove the public in your struct too... Since it's the only thing you can have it's kinda redundant ;-)

Do yourself a huge favor, don't limit yourself to one texture/shader per Material either. Just make them vectors and give your engine freedom :)
"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin
Key_46
Key_46
i think that is less complex create subsets for the meshes and apply separated materials for each subset.
Endar
Endar
griffin2000: that was what I was aiming for. Just say I want per-pixel lighting and bump mapping, and I have both as shader programs. I'm going to need to run the lighting as a first pass, and then the bump mapping as a second pass, right?

I have no idea how to set up dynamic multipass rendering like this for my engine.

Anyone have a hint on where to start?
Quote:
Actually on DX you only access passes via an index so you only need to store the number of passes each technique has in the shader class.

I am using OpenGL, so I'm not quite sure what you meant by this.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
griffin2000
griffin2000
Quote:
Original post by Endar
griffin2000: that was what I was aiming for. Just say I want per-pixel lighting and bump mapping, and I have both as shader programs. I'm going to need to run the lighting as a first pass, and then the bump mapping as a second pass, right?

I have no idea how to set up dynamic multipass rendering like this for my engine.

Anyone have a hint on where to start?


As a first pass simply set up your FX file to have multiple passes (you may also want to set up some blend modes for the second pass).
technique bumpdemo_arb {  pass p0 {    FragmentProgram =      compile arbfp1 firstPassPixelShader(...);    VertexProgram =      compile arbvp1 firstPassVertexShader(...);  }  pass p1 {    FragmentProgram =      compile arbfp1 secondPassPixelShader(...);    VertexProgram =      compile arbvp1 secondPassVertexShader(...);  }}



Then for each object render it once for each pass (see code below, taken from the example that comes with the Nvidia CGFX compiler).


Quote:
Actually on DX you only access passes via an index so you only need to store the number of passes each technique has in the shader class.

I am using OpenGL, so I'm not quite sure what you meant by this.


On DX on PC there is no concept of pass handle (there are technique, parameter, etc. handles but no class handles).

I think the OpenGL CGFX API does store a handle to a pass (CGpass) but I don't know if it will save you anything by storing this in your shader class. At least the CGFX example code does not...

  CGpass pass;.. //Set up parameters for object  pass = cgGetFirstPass(myCgTechnique);//  while (pass) {    cgSetPassState(pass);    //Do rendering    cgResetPassState(pass);    pass = cgGetNextPass(pass);  }
Endar
Endar
Is this anything more than having a list of attributes for a series of passes, and then setting them and then drawing the object once for every set of attributes?

Is this something that would be too difficult to write and set up myself? Am I dreaming? Or would it just take a very long time?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Endar
Endar
Boo!
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Endar
Endar
Okay, well since no one has replied yet, I'll bump with a question, so maybe I can get 2 questions answered.

I'm wondering if when I'm implementing multi-pass, whether I should be writing a shader solely for texturing. Because, otherwise, won't I have to implement the texturing in every vertex and fragment shader that I write? I mean, granted, it's a total of like, 4 lines of code, but still.

Would I even be able to do that? Just have the first shader perform texturing and then do passes with the lighting shader, and anything else that the user wants. Is this just silly?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
SimplifiedComplexity
SimplifiedComplexity
Neat idea for the multiple passes, but do you really need to have multiple passes for what you're trying to accomplish? Rendering multiple shaders is effectively rendering multiple objects, as all vertex information is re-sent to the graphics card EACH pass. Doing this unneedlessly is extremely inefficient. If I understand what you are after, why can't you combine your multiple shaders into a single shader, so that that desired effect is rendered in a single pass? You can apply more than one effect with one pass...I would only use the multiple passes for effects that absolute require it.

If you're talking about having one mesh, split up by multiple subsets(mesh with multiple textures/materials) and wanting to use a different shader per subset, then I think you're getting quite complex. A not so great (ie Terrible) solution would be to begin the first shader, render all subsets that require it and skip over the subsets that don't. End the first shader, then start the second shader, render all that require it and so on and so on. Looping through each of the meshes subsets each time for every different shader you want to use. Again a terrible not so great solution.

A better way...split each subset into a new mesh/vertex-index buffer, and render each as separate objects with the correct material/texture.

Just looking at the material structure you've created, I have a suggestion to maybe make it a little bit more dynamic. You talk about wanting a bump map shader - obviously they require a base texture as well as a bump map texture. The base texture is the only one that actually needs material attributes like ambient spec and diffuse - why not have a list of the textures - maybe an stl map with a string as the key instead of the one pointer?

map;
texList["base"] = ...
texList["bump"] = ...

This way you wouldn't be wasting space by having the unrequired variables. Maybe create a naming convention for all of your textures and make youre shader get and set them automatically depending on need. - Of course all depending on the way you have the rest of everything set up...

Anywho, hopefully that helps
Endar
Endar
Well, I'm looking for a way to use a glass shader with the shader I have for per-pixel lighting (once I've found out how to write a glass shader :D ). And I also want to be able to set up a system for my graphics engine that will make it easy to add more things, like bump mapping, normal mapping, parallax mapping, etc, hopefully all without writing a shader that has to do per-pixel lighting and the thing I'm trying to implement.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
SimplifiedComplexity
SimplifiedComplexity
Again, I would stay away from using multiple passes unless absolutely necessary. Combining two shaders into one is fairly trivial, and will save you time not only in render speed, but also in attempting to make your engine support what you want. If you decide that this is the only way to do it, then don't be suprised when your render speed is less than exceptional - especially if you are looking to perform something as complex as parallex occlusion mapping.
superpig
superpig
Quote:
Original post by Endar
without writing a shader that has to do per-pixel lighting and the thing I'm trying to implement.


Have you considered writing per-pixel lighting or glass effects as free-standing functions, and then writing a shader that just calls those functions? It means you're still writing one shader per combination, but each shader would just call a couple of functions, doing no real work itself.

It also avoids the problem you would have had about combining shader outputs: given the result of the per-pixel lighting function, and the result of the glass function, how do you combine those two? It's a problem you need to solve no matter how you actually run the functions (separate passes, single passes, whatever) and hand-coding a shader to "chain" them together gives you complete control over how it should be done.

Also, a word or two in defence of multipass rendering: You don't resend all your geometry to the graphics hardware if you're using VBOs (and you should be using VBOs), and parallax occlusion mapping is not a good example of something that suffers under multipass as it typically has low-weight vertex programs and allows for low-frequency geometry. [smile]
Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse
Endar
Endar
Quote:
Original post by superpig
Quote:
Original post by Endar
without writing a shader that has to do per-pixel lighting and the thing I'm trying to implement.


Have you considered writing per-pixel lighting or glass effects as free-standing functions, and then writing a shader that just calls those functions? It means you're still writing one shader per combination, but each shader would just call a couple of functions, doing no real work itself.

It also avoids the problem you would have had about combining shader outputs: given the result of the per-pixel lighting function, and the result of the glass function, how do you combine those two? It's a problem you need to solve no matter how you actually run the functions (separate passes, single passes, whatever) and hand-coding a shader to "chain" them together gives you complete control over how it should be done.

Also, a word or two in defence of multipass rendering: You don't resend all your geometry to the graphics hardware if you're using VBOs (and you should be using VBOs), and parallax occlusion mapping is not a good example of something that suffers under multipass as it typically has low-weight vertex programs and allows for low-frequency geometry. [smile]


That sounds like it is the way to go. I'm definately going to look into that more. :D

Anyway, to the subject of VBO's, I'm using vertex arrays. How is the speed different? I'm guessing that every time you render, the graphics card needs to load and process the vertices. Even though I'm using vertex arrays and it only processes each vertex once, that's still something.

What about if I was trying to have an animated model as a VBO. Wouldn't that entail loading it to the graphics card after every update in animation? Wouldn't the constant copying take away the speed boost that it would have gotten? Are VBO's best used for static geometery (note: I'm not counting rotations and translations as animation), like level geometry or level objects that have no animation, such as a chair, or a desk?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
OrangyTang
OrangyTang
Quote:
Original post by Endar
Anyway, to the subject of VBO's, I'm using vertex arrays. How is the speed different? I'm guessing that every time you render, the graphics card needs to load and process the vertices. Even though I'm using vertex arrays and it only processes each vertex once, that's still something.

I did some (small) experiments with VBOs - because this was for sprites the contents of the VBO would change and have to be re-uploaded each frame. Even with thousands of sprites I didn't see a speed difference for a single rendering.

However even if the data is changing every frame you can get some advantages when it comes to multi-pass stuff. Rendering the sprites twice each frame (where the data only needs to be sent once) was showing considerable speed differences.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.