#region File Description
//-----------------------------------------------------------------------------
// NormalMapProcessor.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
#endregion
namespace SpriteShaderPipeline
{
///
/// Custom content processor converts greyscale displacement bitmaps into
/// normalmap format. In the source image, use black for pixels that are
/// further away and white for ones that are bumped upward. The output
/// texture contains three-component normal vectors. This processor works
/// best if the source bitmap is slightly blurred.
///
[ContentProcessor]
public class NormalMapProcessor : ContentProcessor
{
// Controls how extreme the output normalmap should be.
const float bumpSize = 4f;
///
/// Converts a greyscale displacement bitmap into normalmap format.
///
public override TextureContent Process(TextureContent input,
ContentProcessorContext context)
{
// Convert the input bitmap to Vector4 format, for ease of processing.
input.ConvertBitmapType(typeof(PixelBitmapContent));
PixelBitmapContent bitmap;
bitmap = (PixelBitmapContent)input.Faces[0][0];
// Calculate normalmap vectors.
ConvertGreyToAlpha(bitmap);
ConvertAlphaToNormals(bitmap);
// Convert the result into NormalizedByte4 format.
input.ConvertBitmapType(typeof(PixelBitmapContent));
return input;
}
///
/// Copies greyscale color information into the alpha channel.
///
static void ConvertGreyToAlpha(PixelBitmapContent bitmap)
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
Vector4 value = bitmap.GetPixel(x, y);
// Copy a greyscale version of the RGB data into the alpha channel.
float greyscale = (value.X + value.Y + value.Z) / 3;
value.W = greyscale;
bitmap.SetPixel(x, y, value);
}
}
}
///
/// Using height data stored in the alpha channel, computes normalmap
/// vectors and stores them in the RGB portion of the bitmap.
///
static void ConvertAlphaToNormals(PixelBitmapContent bitmap)
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
// Look up the heights to either side of this pixel.
float left = GetHeight(bitmap, x - 1, y);
float right = GetHeight(bitmap, x + 1, y);
float top = GetHeight(bitmap, x, y - 1);
float bottom = GetHeight(bitmap, x, y + 1);
// Compute gradient vectors, then cross them to get the normal.
Vector3 dx = new Vector3(1, 0, (right - left) * bumpSize);
Vector3 dy = new Vector3(0, 1, (bottom - top) * bumpSize);
Vector3 normal = Vector3.Cross(dx, dy);
normal.Normalize();
// Store the result.
float alpha = GetHeight(bitmap, x, y);
bitmap.SetPixel(x, y, new Vector4(normal, alpha));
}
}
}
///
/// Helper for looking up height values from the bitmap alpha channel,
/// clamping if the specified position is off the edge of the bitmap.
///
static float GetHeight(PixelBitmapContent bitmap, int x, int y)
{
if (x < 0)
{
x = 0;
}
else if (x >= bitmap.Width)
{
x = bitmap.Width - 1;
}
if (y < 0)
{
y = 0;
}
else if (y >= bitmap.Height)
{
y = bitmap.Height - 1;
}
return bitmap.GetPixel(x, y).W;
}
}
}