Customizing how models build their textures

Originally posted to Shawn Hargreaves Blog on MSDN, Wednesday, November 22, 2006

When the content pipeline builds a model, by default it will use the ModelTextureProcessor to convert all the textures used on that model.

 

There are many reasons why you might want to override this behavior:

All of these customizations are pretty easy once you understand the basic approach. I'll give an example of how to automatically correct non-power-of-two texture sizes.

 

First, we need to make a custom processor that will convert textures in the way we want. There is quite a lot of code here, but it should all be fairly self explanatory. Note how it logs a warning if the input texture size was invalid, so you can tell which textures are being automatically resized:

    [ContentProcessor]
class MyTextureProcessor : ContentProcessor<TextureContent, TextureContent>
{
public override TextureContent Process(TextureContent input,
ContentProcessorContext context)
{
// Convert the input to standard Color format, for ease of processing.
input.ConvertBitmapType(typeof(PixelBitmapContent<Color>));

foreach (MipmapChain imageFace in input.Faces)
{
for (int i = 0; i < imageFace.Count; i++)
{
PixelBitmapContent<Color> mip = (PixelBitmapContent<Color>)imageFace[i];

// Apply color keying.
mip.ReplaceColor(Color.Magenta, Color.TransparentBlack);

// Make sure the image is a power of two in size.
int width = MakePowerOfTwo(mip.Width);
int height = MakePowerOfTwo(mip.Height);

if ((width != mip.Width) || (height != mip.Height))
{
context.Logger.LogWarning(null, input.Identity,
"Bitmap was not a power of two. Scaled from {0}x{1} to {2}x{3}.",
mip.Width, mip.Height, width, height);

PixelBitmapContent<Color> scaledMip;
scaledMip = new PixelBitmapContent<Color>(width, height);

BitmapContent.Copy(mip, scaledMip);

imageFace[i] = scaledMip;
}
}
}

input.GenerateMipmaps(false);

// Compress the output texture.
input.ConvertBitmapType(typeof(Dxt1BitmapContent));

return input;
}


static int MakePowerOfTwo(int value)
{
int bit = 1;

while (bit < value)
bit *= 2;

return bit;
}
}

Second, we need to make a custom material processor that will call into our custom texture processor. This one is very simple, but for more complex scenarios you could examine the textureName parameter and use it to decide which texture processor should be invoked:

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

The final step is a custom model processor that will call into our custom material processor. Specify this processor for your model, and it will no longer be a problem if that model references textures of invalid sizes:

    [ContentProcessor]
class MyModelProcessor : ModelProcessor
{
protected override MaterialContent ConvertMaterial(MaterialContent material,
ContentProcessorContext context)
{
return context.Convert<MaterialContent,
MaterialContent>(material, "MyMaterialProcessor");
}
}
Blog index   -   Back to my homepage