Rendering a Model with a custom Effect

Originally posted to Shawn Hargreaves Blog on MSDN, Thursday, December 7, 2006

If you want to use your own effect for model rendering, you have basically two choices. You could just let the content pipeline do its stuff and then replace the output data with your own effect at runtime, or you could use a custom processor to specify your custom effect while the model is being built. The second option is more flexible and usually leads to more efficient runtime game code, so that's what I"m going to talk about here.

The first step is to make a custom processor by deriving from ModelProcessor and overriding the Process method:

    [ContentProcessor]
    public class CustomEffectModelProcessor : ModelProcessor
    {
        protected override MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
        {
            return base.ConvertMaterial(material, context);
        }
    }

Secondly, you need to change this method to construct a new instance of EffectMaterialContent, set it to point at your custom effect file, and return this new material in place of the original:

    [ContentProcessor]
    public class CustomEffectModelProcessor : ModelProcessor
    {
        protected override MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
        {
            EffectMaterialContent myMaterial = new EffectMaterialContent();

            string effectPath = Path.GetFullPath("MyEffect.fx");

            myMaterial.Effect = new ExternalReference<EffectContent>(effectPath);

            return base.ConvertMaterial(myMaterial, context);
        }
    }

Note that there is no need to add MyEffect.fx to your Game Studio project when using this technique. It will be built and loaded automatically when it is referenced by your processor, and the system is smart enough to only build the effect once even if it is used on many different models.

In the above example we are using Path.GetFullPath to look for the effect source file in the current directory. You might want to change this to look in some particular location where you keep your effects, or if you also override the Process method, you could store the root NodeContent parameter and use it to look for effects in the same directory as the model that is currently being built:

    string directory = Path.GetDirectoryName(rootNode.Identity.SourceFilename);

    string effectPath = Path.Combine(directory, "MyEffect.fx");

An effect without any parameter settings is not very interesting, so we probably also want to copy across some information like what texture we should be using from the input material. You may want to edit some values at this point, or perhaps add new parameters and textures that weren't part of the original material:

    if (material is BasicMaterialContent)
    {
        BasicMaterialContent basicMaterial = (BasicMaterialContent)material;

        // You can set textures for the effect to use
        myMaterial.Textures.Add("DiffuseTexture", basicMaterial.Texture);

        // And you can also set any arbitrary effect parameters at this point
        myMaterial.OpaqueData.Add("Shininess", basicMaterial.SpecularPower * 10);
        myMaterial.OpaqueData.Add("BumpSize", 42);
    }
    else if (material is EffectMaterialContent)
    {
        EffectMaterialContent effectMaterial = (EffectMaterialContent)material;

        // todo: put something interesting here if you want
        // or don't bother if you don't want
        // whatever really...
    }
    else
        throw new Exception("huh? this is very odd");

And that's it! When you load the resulting model into your game, it will come in already set up with the effects you specified, and with all the right parameters and textures already configured, so you can proceed to draw your model in the usual way without the game needing to care what effects the processor has specified.

Blog index   -   Back to my homepage