CubemapProcessor.cs 11 KB

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