#region File Description
//-----------------------------------------------------------------------------
// CubemapProcessor.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
#endregion
namespace CustomModelEffectPipeline
{
///
/// Custom content pipeline processor converts regular
/// 2D images into reflection cubemaps.
///
[ContentProcessor]
public class CubemapProcessor : ContentProcessor
{
const int cubemapSize = 256;
///
/// Converts an arbitrary 2D image into a reflection cubemap.
///
public override TextureCubeContent Process(TextureContent input,
ContentProcessorContext context)
{
// Convert the input data to Color format, for ease of processing.
input.ConvertBitmapType(typeof(PixelBitmapContent));
// Mirror the source image from left to right.
PixelBitmapContent mirrored;
mirrored = MirrorBitmap((PixelBitmapContent)input.Faces[0][0]);
// Create the six cubemap faces.
TextureCubeContent cubemap = new TextureCubeContent();
cubemap.Faces[(int)CubeMapFace.NegativeZ] = CreateSideFace(mirrored, 0);
cubemap.Faces[(int)CubeMapFace.NegativeX] = CreateSideFace(mirrored, 1);
cubemap.Faces[(int)CubeMapFace.PositiveZ] = CreateSideFace(mirrored, 2);
cubemap.Faces[(int)CubeMapFace.PositiveX] = CreateSideFace(mirrored, 3);
cubemap.Faces[(int)CubeMapFace.PositiveY] = CreateTopFace(mirrored);
cubemap.Faces[(int)CubeMapFace.NegativeY] = CreateBottomFace(mirrored);
// Calculate mipmap data.
cubemap.GenerateMipmaps(true);
// Compress the cubemap into DXT1 format.
cubemap.ConvertBitmapType(typeof(Dxt1BitmapContent));
return cubemap;
}
///
/// Our source data is just a regular 2D image, but to make a good
/// cubemap we need this to wrap on all sides without any visible seams.
/// An easy way of making an image wrap from left to right is simply to
/// put a mirrored copy of the image next to the original. The point
/// where the image mirrors is still pretty obvious, but for a reflection
/// map this will be good enough.
///
static PixelBitmapContent MirrorBitmap(PixelBitmapContent source)
{
int width = source.Width * 2;
PixelBitmapContent mirrored;
mirrored = new PixelBitmapContent(width, source.Height);
for (int y = 0; y < source.Height; y++)
{
for (int x = 0; x < source.Width; x++)
{
Color color = source.GetPixel(x, y);
mirrored.SetPixel(x, y, color);
mirrored.SetPixel(width - x - 1, y, color);
}
}
return mirrored;
}
///
/// The four side faces of the cubemap are easy to create: we just copy
/// out the appropriate region from the middle of the source bitmap.
///
static BitmapContent CreateSideFace(PixelBitmapContent source,
int cubeSide)
{
PixelBitmapContent result;
result = new PixelBitmapContent(cubemapSize, cubemapSize);
Rectangle sourceRegion = new Rectangle(source.Width * cubeSide / 4,
source.Height / 3,
source.Width / 4,
source.Height / 3);
Rectangle destinationRegion = new Rectangle(0, 0, cubemapSize, cubemapSize);
BitmapContent.Copy(source, sourceRegion, result, destinationRegion);
return result;
}
///
/// We have to do a lot of stretching and warping to create the top
/// and bottom faces of the cubemap. To keep the result nicely free
/// of jaggies, we do this computation on a larger version of the
/// bitmap, then scale down the final result to antialias it.
///
const int multisampleScale = 4;
///
/// Folds four flaps inward from the top of the source bitmap,
/// to create the top face of the cubemap.
///
static BitmapContent CreateTopFace(PixelBitmapContent source)
{
PixelBitmapContent result;
result = new PixelBitmapContent(cubemapSize * multisampleScale,
cubemapSize * multisampleScale);
int right = cubemapSize * multisampleScale - 1;
ScaleTrapezoid(source, 0, -1, result, right, 0, -1, 0, 0, 1);
ScaleTrapezoid(source, 1, -1, result, 0, 0, 0, 1, 1, 0);
ScaleTrapezoid(source, 2, -1, result, 0, right, 1, 0, 0, -1);
ScaleTrapezoid(source, 3, -1, result, right, right, 0, -1, -1, 0);
return BlurCubemapFace(result);
}
///
/// Folds four flaps inward from the bottom of the source bitmap,
/// to create the bottom face of the cubemap.
///
static BitmapContent CreateBottomFace(PixelBitmapContent source)
{
PixelBitmapContent result;
result = new PixelBitmapContent(cubemapSize * multisampleScale,
cubemapSize * multisampleScale);
int right = cubemapSize * multisampleScale - 1;
ScaleTrapezoid(source, 0, 1, result, right, right, -1, 0, 0, -1);
ScaleTrapezoid(source, 1, 1, result, 0, right, 0, -1, 1, 0);
ScaleTrapezoid(source, 2, 1, result, 0, 0, 1, 0, 0, 1);
ScaleTrapezoid(source, 3, 1, result, right, 0, 0, 1, -1, 0);
return BlurCubemapFace(result);
}
///
/// Worker function for folding and stretching a flap from the source
/// image to make up one quarter of the top or bottom cubemap faces.
///
static void ScaleTrapezoid(PixelBitmapContent source,
int cubeSide, int cubeY,
PixelBitmapContent destination,
int destinationX, int destinationY,
int xDirection1, int yDirection1,
int xDirection2, int yDirection2)
{
int size = destination.Width;
// Compute the source x location.
int baseSourceX = cubeSide * source.Width / 4;
// Copy the image data one row at a time.
for (int row = 0; row < size / 2; row++)
{
// Compute the source y location.
int sourceY;
if (cubeY < 0)
sourceY = source.Height / 3;
else
sourceY = source.Height * 2 / 3;
sourceY += cubeY * row * source.Height / 3 / (size / 2);
// Stretch this row from the source to destination.
int x = destinationX;
int y = destinationY;
int rowLength = size - row * 2;
for (int i = 0; i < rowLength; i++)
{
int sourceX = baseSourceX + i * source.Width / 4 / rowLength;
Color color = source.GetPixel(sourceX, sourceY);
destination.SetPixel(x, y, color);
x += xDirection1;
y += yDirection1;
}
// Advance to the start of the next row.
destinationX += xDirection1 + xDirection2;
destinationY += yDirection1 + yDirection2;
}
}
///
/// The top and bottom cubemap faces will have a nasty discontinuity
/// in the middle where the four source image flaps meet. We can cover
/// this up by applying a blur filter to the problematic area.
///
static BitmapContent BlurCubemapFace(PixelBitmapContent source)
{
// Create two temporary bitmaps.
PixelBitmapContent temp1, temp2;
temp1 = new PixelBitmapContent(cubemapSize, cubemapSize);
temp2 = new PixelBitmapContent(cubemapSize, cubemapSize);
// Antialias by shrinking the larger generated image to the final size.
BitmapContent.Copy(source, temp1);
// Apply the blur in two passes, first horizontally, then vertically.
ApplyBlurPass(temp1, temp2, 1, 0);
ApplyBlurPass(temp2, temp1, 0, 1);
// Convert the result back to Color format.
PixelBitmapContent result;
result = new PixelBitmapContent(cubemapSize, cubemapSize);
BitmapContent.Copy(temp1, result);
return result;
}
///
/// Applies a single pass of a separable box filter, blurring either
/// along the x or y axis. This could give much higher quality results
/// if we used a gaussian filter kernel rather than this simplistic box,
/// but this is good enough to get the job done.
///
static void ApplyBlurPass(PixelBitmapContent source,
PixelBitmapContent destination,
int dx, int dy)
{
int cubemapCenter = cubemapSize / 2;
for (int y = 0; y < cubemapSize; y++)
{
for (int x = 0; x < cubemapSize; x++)
{
// How far is this texel from the center of the image?
int xDist = cubemapCenter - x;
int yDist = cubemapCenter - y;
int distance = (int)Math.Sqrt(xDist * xDist + yDist * yDist);
// Blur more in the center, less near the edges.
int blurAmount = Math.Max(cubemapCenter - distance, 0) / 8;
// Accumulate source texel values.
Vector4 blurredValue = Vector4.Zero;
for (int i = -blurAmount; i <= blurAmount; i++)
{
blurredValue += source.GetPixel(x + dx * i, y + dy * i);
}
// Average them to calculate a blurred result.
blurredValue /= blurAmount * 2 + 1;
destination.SetPixel(x, y, blurredValue);
}
}
}
}
}