Rendering a Model with a custom Effect

Rendering a Model with a custom Effect

Rate This
  • Comments 8

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.

  • Hi Shawn,

    Thank you for this sample. This is almost what I have searched for.

    But, just create an constant “MyEffect.fx” Effect, well it’s ok for a sample. It would be better to use a dependency to the input material. If I use some kind of factory class, e.g. MaterialLibManager to create the Effect, it would be nice to do something like this :

    protected override ModelContent ConvertMaterial(ModelContent material, ContentProcessorContext context)

    {

    EffectMaterialContent myMaterial = MaterialLibManager.Convert(material.Name);

    If (myMaterial == null)

    {

    return base.ConvertMaterial(material, context);

    }

    return base.ConvertMaterial(myMaterial, context);

    }

    So this leads again to the ‘not imported material name problem’. The MaterialLibManager class could contain a list of valid names (maybe loaded via xml file) and appropriate preset parameter.

    To use just the fx - file name is not sufficient, because there could be different materials that use the same fx-file (e.g. Wood and Rubber uses  phongbump.fx) with just some other parameter values.

    So, some kind of “keydata” that comes from the model tool to the XNA Framework.

    PS:

    I guess the first override method in your example should be the Process method (as you mention) not the ConvertMaterial ;-)

  • You actually don't normally need to override Process to alter the materials: just ConvertMaterial is enough.

    Regarding getting more information about each material, our importers do their best to pull through as much data as we can find, but in this regard we're pretty much at the mercy of the file formats that we are reading from.

    The X format unfortunately doesn't include name strings for materials, so all we get there is the effect settings.

    FBX has a significantly richer system for exporting metadata, so if you import from FBX, we will be able to populate the Name property correctly for materials.

  • Hi Shawn. As said above, this is great if you want to hardcode the effect, but it doesn't address having different effects.

    In 3ds MAX, for example, you can explicitly set effect parameters, as well as the effect and the textures. How would one go about extracting that data, and setting the appropriate Parameters[], as well as loading in the textures needed by the effects, in a content processor and/or importer? By the way, I'm using FBX for my models.

    ~~Pon

  • FBX doesn't (currently, this will hopefully change in the near future) support the Max effects implementation, but hopefully Autodesk will add that soon. It has good metadata for pulling through material names, which you could use to re-populate the effect data in your processor, but you won't be able to fully automate this from the Max material since that data isn't in the FBX file.

    If you are setting up your effects directly in Max, .X might be a better format since that does currently support materials based on D3D Effects (but then it doesn't include the material name data: swings and roundabouts really, neither format is quite perfect yet...)

  • Hi Shawn,

    >but then it doesn't include the material name data (inside the X File)

    I'm sorry but this is not right.

    You get the material names exported with the Panda and kw exporter.  

    example :

    Material BasicRed {    

    0.780392;0.200000;0.121569;1.000000;;

    3.200000;

    0.000000;0.000000;0.000000;;

    0.000000;0.000000;0.000000;;

    }

    Actually every x-file template can have a name and it is not difficult to get it via MDX XFileData object. Well, mixing XNA with MDX... I don't think that's a good way, but there ist no XFile class in XNA. And for the content processing you can add the Direct3DX assembly, but is this compatible with a Xbox360 build?

    Another file format that support fx Shader parameter and names is the collada format.

    Native collada support in XNA would be great,

    but I think there are allready some user importer around.

  • フォーラムの質問を見て、今まで書いていなかったことに気づいたので遅ればせながらカスタムエフェクトの使い方を説明します。 例によって例の如く、コピー元は Shawn Hargreaves氏の投稿 からです。

  • Hi Shawn,

    I am not understand this:

       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);

       }

    it seems that, we add an "Image Texture" of basicMaterial to effectMaterial, however , a model not noly contians "Image Texture" but also contains "DiffuseColor Texture" etc , so if i should add the these manually ? just like:

    myMaterial.Textures.Add("DiffuseColor", basicMaterial.DiffuseColor);

    myMaterial.Textures.Add("EmissiveColor", basicMaterial.EmissiveColor);

    …………

    or the processor will add these Automatically?

  • I realize since this blog entry is kinda old, but really need help :(

    I'm trying to make a custom processor that'll save the texture filename in the tag of the ModelMeshPart, but I'm stuck as I can't figure out where to get the actual filename of the texture.

    Any help or guidance to the right *direction* would be greatly appreciated. Thanks :)

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