CubemapProcessor.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // CubemapProcessor.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using Microsoft.Xna.Framework;
  12. using Microsoft.Xna.Framework.Graphics;
  13. using Microsoft.Xna.Framework.Content.Pipeline;
  14. using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
  15. #endregion
  16. namespace CustomModelEffectPipeline
  17. {
  18. /// <summary>
  19. /// Custom content pipeline processor converts regular
  20. /// 2D images into reflection cubemaps.
  21. /// </summary>
  22. [ContentProcessor]
  23. public class CubemapProcessor : ContentProcessor<TextureContent, TextureCubeContent>
  24. {
  25. const int cubemapSize = 64;
  26. /// <summary>
  27. /// Converts an arbitrary 2D image into a reflection cubemap.
  28. /// </summary>
  29. public override TextureCubeContent Process(TextureContent input,
  30. ContentProcessorContext context)
  31. {
  32. // Chain to the TexturePlusAlpha processor, which will merge a separate alpha map
  33. // into our source image. This data is used by the EnvironmentMapEffect to display
  34. // specular highlights, when the specular amount slider is turned up.
  35. input = context.Convert<TextureContent, TextureContent>(input, "TexturePlusAlphaProcessor");
  36. // Convert the input data to Color format, for ease of processing.
  37. input.ConvertBitmapType(typeof(PixelBitmapContent<Color>));
  38. // Mirror the source image from left to right.
  39. PixelBitmapContent<Color> mirrored;
  40. mirrored = MirrorBitmap((PixelBitmapContent<Color>)input.Faces[0][0]);
  41. // Create the six cubemap faces.
  42. TextureCubeContent cubemap = new TextureCubeContent();
  43. cubemap.Faces[(int)CubeMapFace.NegativeZ] = CreateSideFace(mirrored, 0);
  44. cubemap.Faces[(int)CubeMapFace.NegativeX] = CreateSideFace(mirrored, 1);
  45. cubemap.Faces[(int)CubeMapFace.PositiveZ] = CreateSideFace(mirrored, 2);
  46. cubemap.Faces[(int)CubeMapFace.PositiveX] = CreateSideFace(mirrored, 3);
  47. cubemap.Faces[(int)CubeMapFace.PositiveY] = CreateTopFace(mirrored);
  48. cubemap.Faces[(int)CubeMapFace.NegativeY] = CreateBottomFace(mirrored);
  49. // Calculate mipmap data.
  50. cubemap.GenerateMipmaps(true);
  51. return cubemap;
  52. }
  53. /// <summary>
  54. /// Our source data is just a regular 2D image, but to make a good
  55. /// cubemap we need this to wrap on all sides without any visible seams.
  56. /// An easy way of making an image wrap from left to right is simply to
  57. /// put a mirrored copy of the image next to the original. The point
  58. /// where the image mirrors is still pretty obvious, but for a reflection
  59. /// map this will be good enough.
  60. /// </summary>
  61. static PixelBitmapContent<Color> MirrorBitmap(PixelBitmapContent<Color> source)
  62. {
  63. int width = source.Width * 2;
  64. PixelBitmapContent<Color> mirrored;
  65. mirrored = new PixelBitmapContent<Color>(width, source.Height);
  66. for (int y = 0; y < source.Height; y++)
  67. {
  68. for (int x = 0; x < source.Width; x++)
  69. {
  70. Color color = source.GetPixel(x, y);
  71. mirrored.SetPixel(x, y, color);
  72. mirrored.SetPixel(width - x - 1, y, color);
  73. }
  74. }
  75. return mirrored;
  76. }
  77. /// <summary>
  78. /// The four side faces of the cubemap are easy to create: we just copy
  79. /// out the appropriate region from the middle of the source bitmap.
  80. /// </summary>
  81. static BitmapContent CreateSideFace(PixelBitmapContent<Color> source,
  82. int cubeSide)
  83. {
  84. PixelBitmapContent<Color> result;
  85. result = new PixelBitmapContent<Color>(cubemapSize, cubemapSize);
  86. Rectangle sourceRegion = new Rectangle(source.Width * cubeSide / 4,
  87. source.Height / 3,
  88. source.Width / 4,
  89. source.Height / 3);
  90. Rectangle destinationRegion = new Rectangle(0, 0, cubemapSize, cubemapSize);
  91. BitmapContent.Copy(source, sourceRegion, result, destinationRegion);
  92. return result;
  93. }
  94. /// <summary>
  95. /// We have to do a lot of stretching and warping to create the top
  96. /// and bottom faces of the cubemap. To keep the result nicely free
  97. /// of jaggies, we do this computation on a larger version of the
  98. /// bitmap, then scale down the final result to antialias it.
  99. /// </summary>
  100. const int multisampleScale = 4;
  101. /// <summary>
  102. /// Folds four flaps inward from the top of the source bitmap,
  103. /// to create the top face of the cubemap.
  104. /// </summary>
  105. static BitmapContent CreateTopFace(PixelBitmapContent<Color> source)
  106. {
  107. PixelBitmapContent<Color> result;
  108. result = new PixelBitmapContent<Color>(cubemapSize * multisampleScale,
  109. cubemapSize * multisampleScale);
  110. int right = cubemapSize * multisampleScale - 1;
  111. ScaleTrapezoid(source, 0, -1, result, right, 0, -1, 0, 0, 1);
  112. ScaleTrapezoid(source, 1, -1, result, 0, 0, 0, 1, 1, 0);
  113. ScaleTrapezoid(source, 2, -1, result, 0, right, 1, 0, 0, -1);
  114. ScaleTrapezoid(source, 3, -1, result, right, right, 0, -1, -1, 0);
  115. return BlurCubemapFace(result);
  116. }
  117. /// <summary>
  118. /// Folds four flaps inward from the bottom of the source bitmap,
  119. /// to create the bottom face of the cubemap.
  120. /// </summary>
  121. static BitmapContent CreateBottomFace(PixelBitmapContent<Color> source)
  122. {
  123. PixelBitmapContent<Color> result;
  124. result = new PixelBitmapContent<Color>(cubemapSize * multisampleScale,
  125. cubemapSize * multisampleScale);
  126. int right = cubemapSize * multisampleScale - 1;
  127. ScaleTrapezoid(source, 0, 1, result, right, right, -1, 0, 0, -1);
  128. ScaleTrapezoid(source, 1, 1, result, 0, right, 0, -1, 1, 0);
  129. ScaleTrapezoid(source, 2, 1, result, 0, 0, 1, 0, 0, 1);
  130. ScaleTrapezoid(source, 3, 1, result, right, 0, 0, 1, -1, 0);
  131. return BlurCubemapFace(result);
  132. }
  133. /// <summary>
  134. /// Worker function for folding and stretching a flap from the source
  135. /// image to make up one quarter of the top or bottom cubemap faces.
  136. /// </summary>
  137. static void ScaleTrapezoid(PixelBitmapContent<Color> source,
  138. int cubeSide, int cubeY,
  139. PixelBitmapContent<Color> destination,
  140. int destinationX, int destinationY,
  141. int xDirection1, int yDirection1,
  142. int xDirection2, int yDirection2)
  143. {
  144. int size = destination.Width;
  145. // Compute the source x location.
  146. int baseSourceX = cubeSide * source.Width / 4;
  147. // Copy the image data one row at a time.
  148. for (int row = 0; row < size / 2; row++)
  149. {
  150. // Compute the source y location.
  151. int sourceY;
  152. if (cubeY < 0)
  153. sourceY = source.Height / 3;
  154. else
  155. sourceY = source.Height * 2 / 3;
  156. sourceY += cubeY * row * source.Height / 3 / (size / 2);
  157. // Stretch this row from the source to destination.
  158. int x = destinationX;
  159. int y = destinationY;
  160. int rowLength = size - row * 2;
  161. for (int i = 0; i < rowLength; i++)
  162. {
  163. int sourceX = baseSourceX + i * source.Width / 4 / rowLength;
  164. Color color = source.GetPixel(sourceX, sourceY);
  165. destination.SetPixel(x, y, color);
  166. x += xDirection1;
  167. y += yDirection1;
  168. }
  169. // Advance to the start of the next row.
  170. destinationX += xDirection1 + xDirection2;
  171. destinationY += yDirection1 + yDirection2;
  172. }
  173. }
  174. /// <summary>
  175. /// The top and bottom cubemap faces will have a nasty discontinuity
  176. /// in the middle where the four source image flaps meet. We can cover
  177. /// this up by applying a blur filter to the problematic area.
  178. /// </summary>
  179. static BitmapContent BlurCubemapFace(PixelBitmapContent<Color> source)
  180. {
  181. // Create two temporary bitmaps.
  182. PixelBitmapContent<Vector4> temp1, temp2;
  183. temp1 = new PixelBitmapContent<Vector4>(cubemapSize, cubemapSize);
  184. temp2 = new PixelBitmapContent<Vector4>(cubemapSize, cubemapSize);
  185. // Antialias by shrinking the larger generated image to the final size.
  186. BitmapContent.Copy(source, temp1);
  187. // Apply the blur in two passes, first horizontally, then vertically.
  188. ApplyBlurPass(temp1, temp2, 1, 0);
  189. ApplyBlurPass(temp2, temp1, 0, 1);
  190. // Convert the result back to Color format.
  191. PixelBitmapContent<Color> result;
  192. result = new PixelBitmapContent<Color>(cubemapSize, cubemapSize);
  193. BitmapContent.Copy(temp1, result);
  194. return result;
  195. }
  196. /// <summary>
  197. /// Applies a single pass of a separable box filter, blurring either
  198. /// along the x or y axis. This could give much higher quality results
  199. /// if we used a gaussian filter kernel rather than this simplistic box,
  200. /// but this is good enough to get the job done.
  201. /// </summary>
  202. static void ApplyBlurPass(PixelBitmapContent<Vector4> source,
  203. PixelBitmapContent<Vector4> destination,
  204. int dx, int dy)
  205. {
  206. int cubemapCenter = cubemapSize / 2;
  207. for (int y = 0; y < cubemapSize; y++)
  208. {
  209. for (int x = 0; x < cubemapSize; x++)
  210. {
  211. // How far is this texel from the center of the image?
  212. int xDist = cubemapCenter - x;
  213. int yDist = cubemapCenter - y;
  214. int distance = (int)Math.Sqrt(xDist * xDist + yDist * yDist);
  215. // Blur more in the center, less near the edges.
  216. int blurAmount = Math.Max(cubemapCenter - distance, 0) / 8;
  217. // Accumulate source texel values.
  218. Vector4 blurredValue = Vector4.Zero;
  219. for (int i = -blurAmount; i <= blurAmount; i++)
  220. {
  221. blurredValue += source.GetPixel(x + dx * i, y + dy * i);
  222. }
  223. // Average them to calculate a blurred result.
  224. blurredValue /= blurAmount * 2 + 1;
  225. destination.SetPixel(x, y, blurredValue);
  226. }
  227. }
  228. }
  229. }
  230. }