• 11
• 9
• 11
• 9
• 11
• entries
422
1540
• views
489635

# Whats this? discussing something (potentially) use

169 views

Useful tricks with annotations

I mentioned a while back that I wasn't happy with these "liquid glass" reflect/refract water effects. Sure they've got the "cool" factor, but now that everyone and their pet n00b (you do have a pet n00b, dont you?) has implemented it in their engine I think its time to try again.

My primary problem with "liquid glass" is quite simple - I don't seem to come across much of it in the real world. Maybe we have different water in the UK (or, if you believe the news we might actually have no water!) or it might just be that the effect is more artistic licence than it is accurate.

I've recently been setting up a framework to experiment with water effects - I don't expect to come up with any innovative jaw-dropping results but I would like to see what happens when you tweak some factors in the equations.

This journal entry came about as a result of trying to hook up a basic DXUT GUI with the underlying .fx file. I want lots of knobs and dials so I can mess around with the effect(s), some can be runtime constants but others need to be compile-time constant.

I'll describe my chosen solution - its not ground-breaking or highly original and I'm sure some of you will already use similar. This is more because I can and on the off-chance it might give you some ideas if you haven't already tried this stuff out [smile]

The problem

I use uniform declarations for compile-time switches (that the compiler will dead-code eliminate for me). I specify these as part of my declaration:

//A simple on/off switch:technique MethodWithThingEnabled{    pass p0    {        VertexShader = compile vs_2_0 vs( true );    }}technique MethodWithoutThingEnabled{    pass p0    {        VertexShader = compile vs_2_0 vs( false );    }}

I'd then have my application extract the D3DXHANDLE's of the two techniques and have a simple application-controller switch (e.g. SetTechnique( bFlag ? hWith : hWithout )).

It gets ugly real fast. A recent example:

technique ToneMapping    { /* ... */ }technique ToneMappingWithBilinear    { /* ... */ }technique ToneMapWithoutLensEffect    { /* ... */ }technique LensEffectsNoToneMapping    { /* ... */ }technique LensEffectsNoToneMappingWithBilinear    { /* ... */ }

I'm sure I dont have to list why the above sucks for anything more than a simple sample, but here are a few:

• Naming conventions. Seem nice on paper, but far too easy to deviate from and generally screw up. Trying to write a decent engine to parse techniques based on the names following specific conventions doesn't sound like fun.

• Not very dynamic. You have to change the application code (and probably recompile at least some of it) in order to add a new technique and/or variation. Ouch.

• Writing fall-back paths is harder. It'd be nice if we could assume everyone was already using SM4 hardware with all the fixed-caps glory, but at the time of writing its very likely (if not required) to support several different shader models and probably various performance/quality levels. Using hard-coded names (or some other name-based approach) to solving this is ugly.

The solution

The solution is both simple and elegant - annotate each technique with more "computer friendly" information. This is easily achieved by using the annotations built into the effect framework (refer to Standard Annotations and Semantics for some examples of how far this can go).

If we load in and compile an effect file we can iterate through each technique and use annotations to build up an internal database. This database simply maps the GUI properties to a D3DXHANDLE.

Example:
technique MyPetN00b<    bool isWaterEffect      = true;    bool isRefracting       = false;    bool isReflecting       = true;    bool isScalingWithDepth = false;    bool isDebug            = true;>{    /* ... */}

The name of the technique becomes unimportant (unless you want it to appear in log-files and/or error messages) and any semantic meaning once hidden in the technique's name is now clearly expressed as a series of boolean flags.

The implementation

Assuming a clean load and compilation of the effect we need the application to load in the information. Firstly some utility fluff:

struct WaterEffectDescription{    BOOL        reflects;    BOOL        refracts;    BOOL        depthScale;    BOOL        debug;    D3DXHANDLE  techniqueHandle;};typedef std::vector< WaterEffectDescription > WaterEffectVector;WaterEffectVector g_vWaterEffects;// Two simple predicate functions - do exactly what they imply!BOOL    TechniqueHasFlag( D3DXHANDLE hTechnique, LPCSTR pFlagName );BOOL    BooleanFlagValue( D3DXHANDLE hTechnique, LPCSTR pFlagName );

We can use the following to scan the techniques in the file. It'd probably be more robust to use ID3DXEffect::FindNextValidTechnique() but this will do:
D3DXEFFECT_DESC EffectDesc;if( SUCCEEDED( g_pEffect->GetDesc( &EffectDesc ) ) ){    for( UINT iTechnique = 0; iTechnique < EffectDesc.Techniques; ++iTechnique )    {        D3DXHANDLE hTechnique = g_pEffect->GetTechnique( iTechnique );        // Examine the annotations within 'hTechnique' here and         // either accept or reject accordingly.    }}

The acceptance tests make simple use of the TechniqueHasFlag() and BooleanFlagValue() predicates (post a comment or send me a PM if you need this code, but it is as simple as it sounds!). The code simply builds up a WaterEffectDescription with a copy of each annotation and then WaterEffectVector::push_back()'s it into our list for later use.

Provided we define an equality operator (of the form bool operator== (const WaterEffectDescription& lhs, const WaterEffectDescription& rhs )) we can make trivial use of std::find() to look up information from our database. Its unlikely that the database will store so many WaterEffectDescription's that we require anything more than a brute-force/linear search.

//A simple example:WaterEffectDescription d = { TRUE, TRUE, TRUE, FALSE, NULL };WaterEffectVector::iterator technique = std::find( g_vWaterEffects.begin(), g_vWaterEffects.end(), d );if( technique != g_vWaterEffects.end() ){    // We found a match!    OutputDebugString( L"(INFO) Water effect was found.\n" );}else{    // Database does *NOT* recognise this state, either    // report an error or just ignore the UI state change...    OutputDebugString( L"(ERROR) Water effect was not found.\n" );}

In real usage the definition of 'd' would correspond with whatever the new GUI state is in.

My current code only examines this database when a GUI element is changed such that if the new GUI state has no matching technique the change is ignored. It wouldn't require a huge amount more work to run a test on the change before the GUI accepts to make sure the user is transitioning towards a valid state - possibly enabling/disabling GUI elements as appropriate.

Conclusion

I like this approach - seems to suit both the application code and the effect format. With a bit of imagination this could go a lot further - as you can probably see I dont check (or care about) duplicate technique signitures for example. You could try and add some sort of "cost" and "quality" annotations to help choose the optimal technique for a given piece of hardware...

I'd be interested in any comments (click here to leave one) regarding similar (or better/different) uses of this sort of thing..

Awesome entry, tons of good information. I'm very interested in seeing what kind of water you come up with as well. What type of reflection are you thinking about using? Planar/cube maps/both?

Keep up the good work.

- Dan

Quote:
 Original post by dgreen02 Awesome entry, tons of good information.
Thanks [smile]

Quote:
 Original post by dgreen02 I'm very interested in seeing what kind of water you come up with as well. What type of reflection are you thinking about using? Planar/cube maps/both?
At least initially I'm going to stick to simple planar reflection. My primary interest is how to create the water effect given appropriate inputs. I figure that once I've explored the possibilities for blending/simulating the water I can "drop in" a cube-map lookup alternative without much change to the core mathematics/algorithm.

Cheers,
Jack