Effect interfaces in XNA Game Studio 4.0

Effect interfaces in XNA Game Studio 4.0

  • Comments 10

This article is prerecorded. Shawn is away (on honeymoon). Replies to comments will be delayed.

Typical XNA model drawing code goes something like:

    Matrix[] transforms = new Matrix[model.Bones.Count];

    model.CopyAbsoluteBoneTransformsTo(transforms);

    foreach (ModelMesh mesh in model.Meshes)
    {
        foreach (BasicEffect effect in mesh.Effects)
        {
            effect.World = transforms[mesh.ParentBone.Index] * world;
            effect.View = view;
            effect.Projection = projection;
        }

        mesh.Draw();
    }

But this only works as long as you limit yourself to BasicEffect. If there are any other types of effect attached to the model, the "foreach (BasicEffect effect in mesh.Effects)" part will throw, in which case you need something like:

    foreach (ModelMesh mesh in model.Meshes)
    {
        foreach (Effect effect in mesh.Effects)
        {
            if (effect is BasicEffect)
            {
                // as before
            }
            else
            {
                effect.Parameters["World"].SetValue(transforms[mesh.ParentBone.Index] * world);
                effect.Parameters["View"].SetValue(view);
                effect.Parameters["Projection"].SetValue(projection);
            }
        }

        mesh.Draw();
    }

How to set parameters for a custom effect depends entirely on the effect in question. Many people use naming conventions like the above example. Others use effect annotations to build powerful data binding mechanisms.

When we added SkinnedEffect, EnvironmentMapEffect, DualTextureEffect, and AlphaTestEffect, model drawing code got ugly. Instead of a single "if (effect is BasicEffect)" test, robust implementations had to separately handle each of the five built-in effect types. Yuck.

We fixed this by adding three new interfaces:

    public interface IEffectMatrices
    {
        Matrix World { get; set; }
        Matrix View { get; set; }
        Matrix Projection { get; set; }
    }

    public interface IEffectFog
    {
        bool FogEnabled { get; set; }
        float FogStart { get; set; }
        float FogEnd { get; set; }
        Vector3 FogColor { get; set; }
    }

    public interface IEffectLights
    {
        bool LightingEnabled { get; set; }
        Vector3 AmbientLightColor { get; set; }
        DirectionalLight DirectionalLight0 { get; }
        DirectionalLight DirectionalLight1 { get; }
        DirectionalLight DirectionalLight2 { get; }
        void EnableDefaultLighting();
    }

All five built-in effects implement IEffectMatrices and IEffectFog, while BasicEffect, SkinnedEffect, and EnvironmentMapEffect also implement IEffectLights. This allows models using any combination of these effects to be drawn with a single piece of code:

    Matrix[] transforms = new Matrix[model.Bones.Count];

    model.CopyAbsoluteBoneTransformsTo(transforms);

    foreach (ModelMesh mesh in model.Meshes)
    {
        foreach (IEffectMatrices effect in mesh.Effects)
        {
            effect.World = transforms[mesh.ParentBone.Index] * world;
            effect.View = view;
            effect.Projection = projection;
        }

        mesh.Draw();
    }

We also added a new helper method, which does the same thing as the above example, handling the CopyAbsoluteBoneTransformsTo and foreach ModelMesh shenanigans for you:

    model.Draw(world, view, projection);

This only works if all the effects used by the model implement IEffectMatrices. It will throw an exception if there are other custom effects, in which case you still need to use an old style ModelMesh.Draw loop, setting up the custom effects directly yourself.

  • Why was the Effect class not updated to include these Interfaces? I am sure there could be some kind of runtime check if the shader dose not have the correct properties.

    Sounds like the OnApply() you talked about a few posts ago could be used to map all these interfaces to custom effects properties if it could not be done automatically.

  • Then why update the Effect class?

    A derived class can simple impliment the interfaces and override OnApply() Plus add some of its own interfaces etc. I guess an SAS(?) Annotations Effect class would be cool, but this doesnt have to be from the XNA team...

    But for a larger game engine the way many things are bound is non-trivial and game specific. eg how to choose/define lights/projective textures/instancing etc. At least in my engine there is a heap of code trying to solve this problem.

  • This interface solution looks strange to me. Every time add new parameters in fx file,we have to modify c# source code to create new interface, not flexible for larger project or engine. In my current engine,i use something like TryUpdateParameter(semantic,value).

  • clayman: This approach is statically verified by the compiler instead of checked at runtime and doesn't add any overhead unless you use the interfaces. A TryUpdateParameter method like you suggest doesn't have those advantages.

    Though to be honest, I doubt the difference matters much :)

  • The advantage of a TryUpdateParameter is that you can use it with many effects in a flexible wat, all you have to ensure is that you update the union of all parameters.

    The TryUpdateParameter approach can be fairly efficient if you use bit flags or something to decide which parameters you need to set in the OnApply() method.

    However, this doesnt exclude the use of interfaces etc. Its more or less an orthogonal issue. You can have a mixture of effects, some relying just on the interface approach and some using the TryUpdateParameter() approach in addition.

  • nice.

    Shawn happy honeymoon from

    Michael Hansen

    and i may hope that you visit a small tropical island

    from me to you , a nice demo for the xbox360

    http://evofxstudio.net/Default.aspx

    Best regards

    Michael

  • Thats a nice water rendering effect Michael. Care to share any tips or references for it? The clouds are also quite nice.

  • > Why was the Effect class not updated to include these Interfaces? I am sure there could be some kind of runtime check if the shader dose not have the correct properties.

    C# is a statically typed language, so it's not really appropriate to use interfaces for functionality that can vary at runtime depending on what data is loaded into an instance of a type. There is no runtime way to express something like "if (effect is IEffectFog)" where support for this interface may dynamically change without changing the underlying type of the object.

    Also, there isn't any single "right" way to do databinding for arbitrary effect parameters. Some people use a naming convention, others use semantics, while others use SAS style annotations. All are valid, and all can be reasonably implemented in an Effect subclass (which could also implement these new interfaces), but it's far from obvious to us right now that one of these approaches is sufficiently more "right" than the others to be worth baking into the core of the framework.

    Data binding for effect parameters is definitely an interesting problem, but far from trivial to solve in a universal and robust way, and was out of scope for this version.

  • > This interface solution looks strange to me. Every time add new parameters in fx file,we have to modify c# source code to create new interface

    Only if you want to use interfaces. These interfaces are an addition to the existing EffectParameter mechanism, not a replacement for it. They are intended to make the easy things easy, rather than to handle all the hard things for more advanced scenarios.

  • > We also added a new helper method, which does the same thing as the above example, handling the CopyAbsoluteBoneTransformsTo and foreach ModelMesh shenanigans for you:

    I'm curious - is there any batching done under the hood so this won't unnecessarily create garbage? Making the transforms[] array for each model static and precalculating it when loading them was one of the biggests optimizations I had to do to reduce garbage.

Page 1 of 1 (10 items)
Leave a Comment
  • Please add 3 and 6 and type the answer here:
  • Post