/// <summary> /// <para>Reads in a 2D texture in RGBM format, and extracts the cube faces.</para> /// <para>It also generates a Spherical Harmonic from the input, to approximate indirect lighting</para> /// </summary> public RgbmCubeMap(Texture2D sourceRgbmImage2D, Texture2D sourceRgbmAlphaImage2D, float maxRange) { //the two textures represent the RGB and M values of an RGBM texture. They have //been separated to make it easier to view their colour channels. //This is somewhat wasteful however. Normally it would be wise to combine them into one texture. if (sourceRgbmImage2D == null || sourceRgbmAlphaImage2D == null) throw new ArgumentNullException(); if (sourceRgbmImage2D.Width != sourceRgbmImage2D.Height * 6 || sourceRgbmAlphaImage2D.Width != sourceRgbmImage2D.Width || sourceRgbmAlphaImage2D.Height != sourceRgbmImage2D.Height) throw new ArgumentException("Invalid size"); //expected to have 6 cube faces if (sourceRgbmImage2D.Format != SurfaceFormat.Color || sourceRgbmAlphaImage2D.Format != SurfaceFormat.Color) throw new ArgumentException("Unexpected image format"); if (maxRange <= 0.0f) throw new ArgumentException("MaxRange"); this.MaxRange = maxRange; //extract the RGB pixels Color[] pixelDataRGB = new Color[sourceRgbmImage2D.Width * sourceRgbmImage2D.Height]; sourceRgbmImage2D.GetData(pixelDataRGB); //extract the M pixels Color[] pixelDataM = new Color[sourceRgbmAlphaImage2D.Width * sourceRgbmAlphaImage2D.Height]; sourceRgbmAlphaImage2D.GetData(pixelDataM); //at this point, the source textures are no longer needed //extract the faces from the cubemap, //and generate a spherical harmonic based off it's colours //width/height of each face of the cubemap int cubeFaceSize = sourceRgbmImage2D.Height; this.SphericalHarmonic = new Xen.Ex.SphericalHarmonicL2RGB(); this.cubeMap = new TextureCube(sourceRgbmImage2D.GraphicsDevice, cubeFaceSize, false, SurfaceFormat.Color); int textureLineStride = sourceRgbmImage2D.Width; //storage for the decoded cubemap faces Vector3[][] cubeFaces = new Vector3[6][]; //temporary storage of a single face of the cube Color[] singleFaceRGBM = new Color[cubeFaceSize * cubeFaceSize]; //extract the 6 faces of the cubemap. for (int face = 0; face < 6; face++) { CubeMapFace faceId = (CubeMapFace)face; //cube face data Vector3[] singleFaceRGB = new Vector3[cubeFaceSize * cubeFaceSize]; cubeFaces[face] = singleFaceRGB; //each face is stored next to each other in the 2D texture int startPixel = cubeFaceSize * face; //copy the face from the 2D texture data into singleFace int writeIndex = 0; int readIndex = startPixel; for (int y = 0; y < cubeFaceSize; y++) // each hoizontal line { for (int x = 0; x < cubeFaceSize; x++) // each pixel in the line { Color encodedRgb = pixelDataRGB[readIndex + x]; Color encodedM = pixelDataM[readIndex + x]; //combine to get the RGBM value Color rgbm = new Color(encodedRgb.R, encodedRgb.G, encodedRgb.B, encodedM.R); //decode the pixel Vector3 rgb = RgbmTools.DecodeRGBM(rgbm, maxRange); //convert to linear rgb = rgb * rgb; //store singleFaceRGB[writeIndex + x] = rgb; singleFaceRGBM[writeIndex + x] = rgbm; } //jump to the next line readIndex += textureLineStride; writeIndex += cubeFaceSize; } //write the pixels into the cubemap cubeMap.SetData(faceId, singleFaceRGBM); } //Generate the SH from the cubemap faces this.SphericalHarmonic = Xen.Ex.SphericalHarmonicL2RGB.GenerateSphericalHarmonicFromCubeMap(cubeFaces); }
private static Xen.Ex.SphericalHarmonicL2RGB ExtractSphericalHarmonicForCubeFace(Matrix faceTransform, Vector3[] colourDataRGB, int faceSize) { Xen.Ex.SphericalHarmonicL2RGB sh = new Xen.Ex.SphericalHarmonicL2RGB(); //For each pixel in the face, generate it's SH contribution. //Treat each pixel in the cube as a light source, which gets added to the SH. //This is used to generate an indirect lighting SH for the scene. //See the remarks in SphericalHarmonicL2RGB for more detals. float directionStep = 2.0f / (faceSize - 1.0f); int pixelIndex = 0; float dirY = 1.0f; for (int y = 0; y < faceSize; y++) { Xen.Ex.SphericalHarmonicL2RGB lineSh = new Xen.Ex.SphericalHarmonicL2RGB(); float dirX = -1.0f; for (int x = 0; x < faceSize; x++) { //the direction to the pixel in the cube Vector3 direction = new Vector3(dirX, dirY, 1); Vector3.TransformNormal(ref direction, ref faceTransform, out direction); //length of the direction vector float length = direction.Length(); //approximate area of the pixel (pixels close to the cube edges appear smaller when projected) float weight = 1.0f / length; //normalise: direction.X *= weight; direction.Y *= weight; direction.Z *= weight; //decode the RGBM colour Vector3 rgb = colourDataRGB[pixelIndex++]; //Add it to the SH lineSh.AddLight(ref rgb, ref direction, weight); dirX += directionStep; } //average the SH if (lineSh.Weighting > 0) lineSh /= lineSh.Weighting; //add the line to the full SH //(SH is generated line by line to ease problems with floating point accuracy loss) sh += lineSh; dirY -= directionStep; } if (sh.Weighting > 0) sh /= sh.Weighting; return sh; }
private static Xen.Ex.SphericalHarmonicL2RGB ExtractSphericalHarmonicForCubeFace(Matrix faceTransform, Vector3[] colourDataRGB, int faceSize) { Xen.Ex.SphericalHarmonicL2RGB sh = new Xen.Ex.SphericalHarmonicL2RGB(); //For each pixel in the face, generate it's SH contribution. //Treat each pixel in the cube as a light source, which gets added to the SH. //This is used to generate an indirect lighting SH for the scene. //See the remarks in SphericalHarmonicL2RGB for more detals. float directionStep = 2.0f / (faceSize - 1.0f); int pixelIndex = 0; float dirY = 1.0f; for (int y = 0; y < faceSize; y++) { Xen.Ex.SphericalHarmonicL2RGB lineSh = new Xen.Ex.SphericalHarmonicL2RGB(); float dirX = -1.0f; for (int x = 0; x < faceSize; x++) { //the direction to the pixel in the cube Vector3 direction = new Vector3(dirX, dirY, 1); Vector3.TransformNormal(ref direction, ref faceTransform, out direction); //length of the direction vector float length = direction.Length(); //approximate area of the pixel (pixels close to the cube edges appear smaller when projected) float weight = 1.0f / length; //normalise: direction.X *= weight; direction.Y *= weight; direction.Z *= weight; //decode the RGBM colour Vector3 rgb = colourDataRGB[pixelIndex++]; //Add it to the SH lineSh.AddLight(ref rgb, ref direction, weight); dirX += directionStep; } //average the SH if (lineSh.Weighting > 0) { lineSh /= lineSh.Weighting; } //add the line to the full SH //(SH is generated line by line to ease problems with floating point accuracy loss) sh += lineSh; dirY -= directionStep; } if (sh.Weighting > 0) { sh /= sh.Weighting; } return(sh); }
/// <summary> /// <para>Reads in a 2D texture in RGBM format, and extracts the cube faces.</para> /// <para>It also generates a Spherical Harmonic from the input, to approximate indirect lighting</para> /// </summary> public RgbmCubeMap(Texture2D sourceRgbmImage2D, Texture2D sourceRgbmAlphaImage2D, float maxRange) { //the two textures represent the RGB and M values of an RGBM texture. They have //been separated to make it easier to view their colour channels. //This is somewhat wasteful however. Normally it would be wise to combine them into one texture. if (sourceRgbmImage2D == null || sourceRgbmAlphaImage2D == null) { throw new ArgumentNullException(); } if (sourceRgbmImage2D.Width != sourceRgbmImage2D.Height * 6 || sourceRgbmAlphaImage2D.Width != sourceRgbmImage2D.Width || sourceRgbmAlphaImage2D.Height != sourceRgbmImage2D.Height) { throw new ArgumentException("Invalid size"); //expected to have 6 cube faces } if (sourceRgbmImage2D.Format != SurfaceFormat.Color || sourceRgbmAlphaImage2D.Format != SurfaceFormat.Color) { throw new ArgumentException("Unexpected image format"); } if (maxRange <= 0.0f) { throw new ArgumentException("MaxRange"); } this.MaxRange = maxRange; //extract the RGB pixels Color[] pixelDataRGB = new Color[sourceRgbmImage2D.Width * sourceRgbmImage2D.Height]; sourceRgbmImage2D.GetData(pixelDataRGB); //extract the M pixels Color[] pixelDataM = new Color[sourceRgbmAlphaImage2D.Width * sourceRgbmAlphaImage2D.Height]; sourceRgbmAlphaImage2D.GetData(pixelDataM); //at this point, the source textures are no longer needed //extract the faces from the cubemap, //and generate a spherical harmonic based off it's colours //width/height of each face of the cubemap int cubeFaceSize = sourceRgbmImage2D.Height; this.SphericalHarmonic = new Xen.Ex.SphericalHarmonicL2RGB(); this.cubeMap = new TextureCube(sourceRgbmImage2D.GraphicsDevice, cubeFaceSize, 1, TextureUsage.None, SurfaceFormat.Color); int textureLineStride = sourceRgbmImage2D.Width; //storage for the decoded cubemap faces Vector3[][] cubeFaces = new Vector3[6][]; //temporary storage of a single face of the cube Color[] singleFaceRGBM = new Color[cubeFaceSize * cubeFaceSize]; //extract the 6 faces of the cubemap. for (int face = 0; face < 6; face++) { CubeMapFace faceId = (CubeMapFace)face; //cube face data Vector3[] singleFaceRGB = new Vector3[cubeFaceSize * cubeFaceSize]; cubeFaces[face] = singleFaceRGB; //each face is stored next to each other in the 2D texture int startPixel = cubeFaceSize * face; //copy the face from the 2D texture data into singleFace int writeIndex = 0; int readIndex = startPixel; for (int y = 0; y < cubeFaceSize; y++) // each hoizontal line { for (int x = 0; x < cubeFaceSize; x++) // each pixel in the line { Color encodedRgb = pixelDataRGB[readIndex + x]; Color encodedM = pixelDataM[readIndex + x]; //combine to get the RGBM value Color rgbm = new Color(encodedRgb.R, encodedRgb.G, encodedRgb.B, encodedM.R); //decode the pixel Vector3 rgb = RgbmTools.DecodeRGBM(rgbm, maxRange); //convert to linear rgb = rgb * rgb; //store singleFaceRGB[writeIndex + x] = rgb; singleFaceRGBM[writeIndex + x] = rgbm; } //jump to the next line readIndex += textureLineStride; writeIndex += cubeFaceSize; } //write the pixels into the cubemap cubeMap.SetData(faceId, singleFaceRGBM); } //Generate the SH from the cubemap faces this.SphericalHarmonic = Xen.Ex.SphericalHarmonicL2RGB.GenerateSphericalHarmonicFromCubeMap(cubeFaces); }