This project is read-only.

Extra Semantics for Variable Shader Values

Jan 24, 2010 at 10:59 PM

Would it be overly complex to add in extra semantics for some non constant shader variables based around different values of Game time and perhaps the MaterialLightCollection?

I have been looking at loading up custom shaders from XML files to remove my current need of hard coding values inside a shader override and have come across problems easily setting variable shader values, whilst semantics handle most of the common ones currently game time, in any time format, is not available as far as I can see and would be very useful.

The MaterialLightCollection is perhaps not so easy but would be useful perhaps semantics allowing the filling of arrays of common light values from the light collection based on light type? Position, Color, Specular Color, Attentuation and Direction if applicable? The overall aim to be able to state something like

float : LightDirectionalCount;

Vector3[] : LightDirectionalDirection;

Vector4[] : LightDirectionalColor;

The custom shader code could then loop over the values in the array pull out the matching values and calculate the custom lighting.

The end result would be that custom shader code could then be defined purely via the shader.fx file and via its constant values which are easily serializable. I had a look at the custom tool myself, but I'd have no idea where to even start.

Jan 25, 2010 at 6:22 PM
Edited Jan 25, 2010 at 6:24 PM

It would be fairly tricky to do this, however, you can mark anything with 'GLOBAL' and there is special methods for setting these values (DrawState.SetShaderGlobal).
GLOBAL's are intended for being set once-per-frame or once per run, as they are application global values. They are not that well suited to rapidly changing values. They are also set-by-stringname, which is part of the reason why they are best not used for rapidly changing values.


Also, another (less obvious :) trick is you can have a generated shader implement an interface. Using a magic comment at the start of the shader (Yes! a comment :) you can do this:

You may have seen something like:

//CompilerOptions = InternalClass

Well, there is another similar thing you can do per-technique. This one (I just realised!) is entirely undocumented. Which is an 'oops' on my part.

When defining a technique, you can specify an annotation on the technique. This allows you to provide custom information to the runtime. Xen ignores pretty much all of it (as there is no standards) except one magic option, 'BaseTypes'. If you specify a comma separated list of names, the class that is generated will inherit from those names (they must be interfaces).

Eg:

technique Material0 < string BaseTypes = "ILightsShader,INormalShader"; >
{
    pass
   {
       ....
    }
}

The generated class 'Material0' - or whatever it's called, will then implement ILightsShader and INormalShader.  ILightsShader could (for example) declare a Vector3 property called 'LightDirectionalDirection'. Of course, if the shader doesn't implement the interface, it won't compile. So the interface must match up to what the generated shader will produce.
The advantage is you can then use reflection to get the shader (from your XML config) and set it's values using the interface.

Does that make sense? ;-)

Jan 25, 2010 at 7:30 PM

It should then be possible to have custom shaders implement interfaces that allow the setting of DrawState and MaterialLightCollection values, I can test to see if the shader implements the interface in the drawing code and set the appropriate values.

My only concerns with that would be:

1. This might require alot of testing of the shader to see what interfaces it implements, would this slow things down?

2. What about completely new variables that the user may want to set in his classes? My initial reaction would be to have the CustomShader class expose several sorts of generic variable collections, floats, Vector3, Vector4, Matrices and Textures. If any values are put into these collections then the shader would attempt to bind them to the custom shader based on XML mappings.

Or are you suggesting using reflection to set the values at runtime? It would make life easier if I could simply specify mappings in the XML from DrawState/MaterialLightCollection and any custom collections but I am wary of using reflection per object in the drawing code.

Jan 25, 2010 at 8:38 PM
Edited Jan 25, 2010 at 8:40 PM

Ok I think I understand what you want now :-) And guess what, there is an even easier solution!

Every shader constant declared in a xen shader (known as an attribute) will have a unique ID associated with it's string name. So, for example, "LightDirectionColour" may be associated to the ID 5. This happens as needed, and is unique per instance of the application.

What this means, is that the IShader base interface has a set of methods called 'SetAttribute' (Their implementation is hidden by explicit interface implementation), each of these SetAttribute methods takes in the shader system (DrawState), an integer ID value, and the value to set the attribute. These methods return true if the value was set correctly. They are basically a big switch statement.

Check out the IShader interface or disect some of your generated classes and you will see the avalanche of overloaded SetAttribute methods :-)

So what you could do, is when parsing your input file, and it says "LightColour" is set to 5, you then call state.GetShaderAttributeNameUniqueID("LightColour") - you can then store the returned integer, and use that as the ID when calling SetAttribute() later on at runtime. (Note, the method is only available from DrawState, but a restricted DrawState is provided during LoadContent for just this sort of case).

Jan 25, 2010 at 10:25 PM

Ahh, I spotted the unique IDs but didn't know about the state.GetShaderAttributeNameUniqueID().