Extending an XNA Framework Standard Processor

XNA Game Studio Express lets you modify or extend the behavior of any of the standard Content Pipeline processors that ship with the product. See Standard Importers and Processors for a description of the standard processors.

Because there are so many asset variants supported by different digital content creation (DCC) tools, it is often useful to be able to modfiy how one of the standard processors operates. The examples illustrate some of the kinds of things you might want to do.

Adding a Scaling Operation to a Processor

There are many reasons why you might want to modify the existing functionality of a standard processor. To give one example, if your source assets and your game are at different scales you might want the processor to scale each model automatically at build time. You can implement such automatic scaling by overriding the Process method of the ModelProcessor class, which generates a Model. In the override, you would first scale the entire scene and then invoke the base class functionality to process as usual.

The following code illustrates this technique:

[ContentProcessor]
class ScalingModelProcessor : ModelProcessor
{
    public override ModelContent Process(
        NodeContent input, ContentProcessorContext context )
    {
        MeshHelper.TransformScene( input, Matrix.CreateScale( 10.0f ) );
        return base.Process( input, context );
    }
}
    

Generating Additional Data

In some cases, you might want to add information to a game asset that a standard processor would not. For example, if a custom effect you want to apply requires tangent or binormal data, you can extend the standard model processor to build this additional data into the asset. To do this, you would override the Process method of the ModelProcessor class. In the override, navigate the NodeContent hierarchy of the game asset, and call CalculateTangentFrames for each MeshContent object you find.

The following code shows how you would do this:

[ContentProcessor]
class ModelProcessorWithTangents : ModelProcessor
{
    public override ModelContent Process( NodeContent input, ContentProcessorContext context )
    {
        GenerateTangentFramesRecursive( input );
        return base.Process( input, context );
    }

    private void GenerateTangentFramesRecursive( NodeContent node )
    {
        MeshContent mesh = node as MeshContent;
        if (mesh != null)
        {
            MeshHelper.CalculateTangentFrames( mesh, VertexChannelNames.TextureCoordinate( 0 ), 
                VertexChannelNames.Tangent( 0 ), VertexChannelNames.Binormal( 0 ) );
        }

        foreach (NodeContent child in node.Children)
        {
            GenerateTangentFramesRecursive( child );
        }
    }
}

Changing the Processors Called for Child Objects

Another technique that can be useful is to override a standard processor and change the way child objects are processed by changing the processors that are used for them.

Consider, for example, the hierarchy of calls through which textures in a model are processed:

One reason that you might want to change how this works is that the ModelTextureProcessor.Process method applies DXT1 or DXT5 compression to all textures it processes. If textures in your game assets are already compressed, you might well wish to avoid a second compression.

Here's how you would prevent compression from being applied to model textures during processing:

  1. Create an override of the standard MaterialProcessor.BuildTexture method, and invoke the TextureProcessor.Process method, which does no compression, instead of ModelTextureProcessor.Process.
  2. Create an override of ModelProcessor.ConvertMaterial that invokes your override of MaterialProcessor.BuildTexture instead of the standard one.

The first of these overrides could be coded as follows:

[ContentProcessor]
class NoCompressionMaterialProcessor : MaterialProcessor
{
    protected override ExternalReference<TextureContent> BuildTexture( 
        string textureName, ExternalReference<TextureContent> texture, ContentProcessorContext context )
    {
        return context.BuildAsset<TextureContent, TextureContent>( texture, "TextureProcessor" );
    }
}

There are several things to note about this code:

  • An ExternalReference is an asset object that is shared between multiple classes, such as a diffuse texture used by more than one material. When is specified, The content manager loads only one copy of an ExternalReference at run time and builds it only once no matter how many references there are to it.
  • The ContentProcessorContext BuildAsset method lets you invoke a processor by name to build the content in an object.
  • Although textureName, the first argument to BuildTexture, is ignored in the override above, you could use it if you wanted to process textures differently depending on normal maps or other criteria.

Given the processor created by your first override above, you could code the second override as follows:

[ContentProcessor]
class NoCompressionModelProcessor : ModelProcessor
{
    protected override MaterialContent ConvertMaterial(
        MaterialContent material, ContentProcessorContext context )
    {
        return context.Convert<MaterialContent, MaterialContent>(
            material, "NoCompressionMaterialProcessor" );
    }
}

Because this override is processing MaterialContent objects in memory rather than ExternalReference objects, it uses the ContentProcessorContext.Convert function instead of BuildAsset to invoke the processor created by your first override.

After building and installing your new NoCompressionModelProcessor (see Installing a Custom Importer or Content Processor), you can assign it to any models whose textures are already compressed and no further compression will be applied to them.

See Also

Overview of the Content Pipeline
Content Pipeline Architecture
Standard Importers and Processors
Installing a Custom Importer or Content Processor