Home > Development > Tutorial: On the Fly Effect, Material and Model Mapping

Tutorial: On the Fly Effect, Material and Model Mapping

UPDATE: be sure to check out my YouTube channel where you can see live-action of content such as this article as well is other subjects.

 

By using a ModelProcessor custom content pipeline processor you can import 3D assets and automatically have the XNA Model class linked to the appropriate textures and HLSL FX code.  No more individual code for loading effects, models and no more horrid manual mapping (or should I say re-mapping) of effects to models mesh parts.

LoadContent() now looks as clean as:

_stoneyBox = Content.Load<Model>(“models/stoneybox”);

 

So How is This Done?

In the case of FBX file formats, there is a wonderful amount of metadata that is sadly being ignored by our old friend BasicEffect and the standard model import pipeline.  By doing some of our own magic we can access this data and automatically connect shaders, textures and models together.

Add your model to your Content folder, this should be a FBX, at this stage X does not not seem to have all the smarts in it.

Right-click your Solution and choose Add.New Project…

Then in the appearing window, select Content Pipeline Extension Library and do the usual filename stuff, adding it to your solution.

image

This will create a new project with a default class.  Delete what is there and replace with something like the following.

[ContentProcessor(DisplayName = “Custom Effect Model Processor – MickyD”)]
public class CustomEffectModelProcessor : ModelProcessor

{

protected override MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
{
EffectMaterialContent myMaterial = new EffectMaterialContent();

if (material is BasicMaterialContent)
{
Log(context, “Material is basic”);

// do appropriate basic stuff here
}
else if (material is EffectMaterialContent)
{
EffectMaterialContent effectMaterialContent = (EffectMaterialContent) material;

//
// remap effect
//
myMaterial.Effect = new ExternalReference<EffectContent>(effectMaterialContent.Effect.Filename);

// textures
foreach (KeyValuePair<string, ExternalReference<TextureContent>> pair in effectMaterialContent.Textures)
{
string textureKey = pair.Key;
ExternalReference<TextureContent> textureContent = pair.Value;

if (!string.IsNullOrEmpty( textureContent.Filename))
{
myMaterial.Textures.Add(textureKey, material.Textures[textureKey]);
Log(context, “Set texture ‘{0}’ = {1}”, textureKey, textureContent.Filename);
}
}

}

return base.ConvertMaterial(myMaterial, context);
}

Compile your solution making sure everything is fine. OK, now we have a custom pipeline in the solution but nothing is actually using it yet.  We do have that FBX we added before, wouldn’t it be schmick to have it use the pipeline?

Well first we need to add a reference to the pipeline project to our main project.  Once done we get a cool new drop-down option on model’s Content Processor property.  In your main project, right-click Content (NOTE it’s the Content references we want NOT project references), choose Add reference and add a reference to your pipeline project on the Projects tab.

image

If all goes well it should look something like this:

image

Back in your main project (the one with your Content folder holding your assets anyway), right-click your FBX model to bring up the Properties window.  If you look there should be a new choice in the Content Processor field. Fill it out as follows with particular attention to:

  • Content Processor = (whatever your custom effect model processor is)
  • Generate Tangent Frames = true
  • Scale = … (depends on your model)
  • Swap Winding Order = true EDIT: actually leave this to false

image

Now we can load the model in our LoadContent() as follows:

_stoneyBox = Content.Load<Model>(“models/stoneybox”);

Note though that we still need a reference to the effect so that we can pass parameters.  (unless of course your shader has none)

_stoneyEffect = _stoneyBox.Meshes[0].Effects[0];

Note this is the complete reverse of the usual tutorials – which load the model AND effect and then re-apply the effect to the model!

In my draw function I do need to pass though the light position.  I guess if it wasn’t moving (which it wasn’t) I could just do it during LoadContent().  Oh well.

_stoneyEffect.Parameters[“light1Pos”].SetValue(light1Pos);

Here you can see our friend the cube, but with a:

  • diffuse texture map
  • normal bump map
  • diffuse color
  • specular highlights

image

And here’s a shoot of a few buildings.  Each building is a single FBX model with multiple FX (or materials) in XNA (glass, bricks, tar roof)

image

That’s it.  With this technique, there is minimal code required to load your models with the correct textures and effects.  What’s more it is only necessary to add your model to your project – not the textures and effects.  I like to export my models out of 3DS Max directly into my Content/Models folder.  I don’t tell Max to export the textures but rather have it just put the path into the FBX so that the pipeline can load my textures directly out of material library folder at compile time.  This works for me, but understandably it may not suit others.  That is fine.

 

EDIT: The draw code for drawing a XNA model with one or more custom effects is as below:

/// <summary>
///     Draws the with custom effect.
/// </summary>
/// <param name = “model”>The model.</param>
/// <param name = “world”>The world.</param>
/// <param name = “viewMatrix”>The view matrix.</param>
/// <param name = “projectionMatrix”>The projection matrix.</param>
/// <param name = “offset”>The offset.</param>
public static void DrawWithCustomEffect(Model model,
Matrix world,
Matrix viewMatrix,
Matrix projectionMatrix,
Vector3 offset)
{
var transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);

var translation = Matrix.CreateTranslation(offset);
foreach (var mesh in model.Meshes)
{
foreach (var effect in mesh.Effects)
{
var w = transforms[mesh.ParentBone.Index] * world * translation;

effect.CurrentTechnique = effect.Techniques[“Complete”];
effect.Parameters[“wvp”].SetValue(w * viewMatrix * projectionMatrix);
effect.Parameters[“worldI”].SetValue(Matrix.Invert(w));
effect.Parameters[“worldIT”].SetValue(Matrix.Transpose(Matrix.Invert(w)));

effect.Parameters[“viewInv”].SetValue(Matrix.Invert(viewMatrix));

effect.Parameters[“world”].SetValue(w);

foreach (var pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
mesh.Draw();
}
}
}
}

Advertisements
Categories: Development Tags: , , , ,
  1. MickyD
    2009/08/27 at 5:12 am

    I omitted the Log(…) method I am using from the code above so here it is:

    ///
    /// Logs the specified context.
    ///
    /// The context.
    /// The format.
    /// The args.
    private void Log(ContentProcessorContext context, string format, params object[] args)
    {
    string message = string.Format(“CustomEffectModelProcessor: ” + format, args);
    context.Logger.LogImportantMessage(message);
    }

    Like

  2. Mario
    2009/09/12 at 1:15 am

    Hey, I appreciate the reply back on Lumonix’s ShaderFX site! I thought I replied to it too, but it never showed up so just wanted to say thanks!

    Now, I’m gonna give your stuff a run through this weekend! I’ll see about hassling you further!

    Like

    • MickyD
      2009/09/12 at 1:23 am

      Anytime Mario, glad I could help in some small way.

      Good luck this weekend. I’m going to try some stuff too!

      Like

  3. Ben
    2010/04/23 at 3:36 am

    Hi there, I’ve tried using this in my project, but I am getting errors when trying to process models that have multiple materials assigned to them. I have an effect file that the processor is using, but some other materials in the fbx model don’t use that effect, and use just a normal blinn / phong with just a diffuse texture and a specular value.

    Is there a way to have these materials accounted for automatically? I’ve tried a similar loop as the effectMaterialContent.Textures loop, but some BasicMaterialContent materials fail, and I’m not sure why… If I don’t do anything when the effect is a BasicEffect, then I get “is an EffectMaterialContent, but its Effect is null.” error…

    Any help would be greatly apreciated!
    Cheers!

    Like

    • MickyD
      2010/04/23 at 5:29 am

      hi ben,
      yes, i’ve been a bit lazy for in my code above when I detect that the effect would be ‘basic’ (see the BasicMaterialContent above) i’ve not actually done anything about it – it was on my todo list i guess. i’ll try to update a fix soon.

      in the meantime, you could in your modeller program of choice, create/use a ‘phong/blinn’ effect and incorporate that in your model as a ‘custom’ effect. then the processor will treat it as a custom effect and remap accordingly. though it won’t use XNA’s BasicEffect, it will be cutom but low-bandwidth. its a temporary solution perhaps. that might work.

      hope it does and watch this space for updates.
      cheers,
      MickyD

      Like

      • Ben
        2010/04/23 at 6:00 am

        Thanks, and thank you for the quick reply!
        I’ve got it to build doing this – not sure how correct it is.

        EffectMaterialContent myMaterial = new EffectMaterialContent();
                    
                    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);
        
                        return base.ConvertMaterial(basicMaterial, context); 
                    }
                    else if (material is EffectMaterialContent)
                    {
                        EffectMaterialContent effectMaterial = (EffectMaterialContent)material;
                        string effectPath = Path.GetFullPath("Shaders/Xtown1.fx");
                        
                        myMaterial.Effect = new ExternalReference<EffectContent>(effectPath);
        

        Just out of interest what setup do you use to get fbx models into xna?

        I’m struggling at the moment, trying to use Maya 2008 with the latest fbx2010 exporter, into XNA 3.1. I’ve had success with the customModelEffect processor, and have entirely basicEffect models and entirely one-material shadered models in there and looking great. Just as soon as I put blinns and a shader on I get problems.

        Thank you!

        Like

  4. MickyD
    2010/04/23 at 6:46 am

    (seems WP wont let me reply to msgs indented 2 levels)

    Hi Ben,

    Gratz! Your code looks good. My first impressions was that perhaps we needed to let the code exit via the ‘return base.ConvertMaterial(myMaterial, context); ‘ but maybe because its ‘basic’ we don’t need to? I’ve not done that – but since your code works its probably academic 🙂

    my setup is:

    3DS Max 2010 –> latest FBX exporter –> model –> XNA default FBX importer
    –> lumonix ShaderFX –> effect –> ‘my custom processor’ XNA

    its been a while but its entirely possible that ive not tried to mix phong and custom on the same model – i either had all phong or all custom. ill give it a whirl this weekend.

    have a good one
    Cheers,
    MickyD

    Like

    • Ben
      2010/04/23 at 6:28 pm

      Yeah, the code in the basic material if block returns with the result of the Convert on the basic material – rather than using the base.convertMaterial at the bottom of the function. I will try and swap that with base.Convert(myMaterial) and see what happens there.

      I too used shaderFX to create my 3-light normal/diff/spec shader, then found that I had to modify it a bit to make maya understand it. “Unrecognised semantics”…

      Thanks again for your time and I look forward to hearing how you get on. I have a test model I can send you if you want to drop me an email…

      Cheers,
      Ben

      Like

      • MickyD
        2010/04/30 at 9:38 pm

        im pretty sure this should be possible because you can make models that have multiple custom effects. create your custom effect in the modeller and just leave everything else to the default lighting model. when you go to save we should have a combination of custom and standard.

        now i think you have the content pipeline covered, but you may need to do the following in your actual game when you go to render:

        foreach (ModelMesh mesh in model.Meshes)
        {
        if (mesh.Effects[0] is BasicEffect)
        {
        foreach (BasicEffect effect in mesh.Effects)
        { … }
        } else { < do custom stuff }
        }

        Like

  5. 2010/11/28 at 11:02 pm

    My dad has been writing a book precisely on point with this blog, I have emailed him the web address so perhaps he could pick up a couple pointers. Wonderful Job.

    Like

  6. Hilary Hakkinen
    2010/11/30 at 7:00 am

    Rather cool blog you’ve got here. Thanks the author for it. I like such topics and everything connected to this matter. I would like to read a bit more soon.

    Like

  7. ninja76
    2011/03/09 at 5:07 am

    Can you share your Draw code? If my current DrawModel code uses BasicEffect what do I need to change to you your new pipeline?

    Thanks!

    Like

    • MickyD
      2011/03/13 at 1:15 pm

      i’ve updated the above post at the end thanks buddy

      Like

  8. 2011/06/28 at 7:05 am

    Could you post the Draw code as well, I have no Idea how to do it. I’m using ShaderFX and i’m trying to understand how this whole thing works with custom FX files.

    Like

    • MickyD
      2011/07/24 at 3:26 pm

      i’ve updated the above post at the end thanks buddy

      Like

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: