} // Lerp #endregion #region Generate Spherical Harmonic from CubeMap /// <summary> /// Generate a spherical harmonic from the faces of a cubemap, treating each pixel as a light source and averaging the result. /// </summary> public static SphericalHarmonicL2 GenerateSphericalHarmonicFromCubeMap(TextureCube cubeMap) { SphericalHarmonicL2 sh = new SphericalHarmonicL2(); // Extract the 6 faces of the cubemap. for (int face = 0; face < 6; face++) { CubeMapFace faceId = (CubeMapFace)face; // Get the transformation for this face, Matrix cubeFaceMatrix; switch (faceId) { case CubeMapFace.PositiveX: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(1, 0, 0), new Vector3(0, 1, 0)); break; case CubeMapFace.NegativeX: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(-1, 0, 0), new Vector3(0, 1, 0)); break; case CubeMapFace.PositiveY: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, 1, 0), new Vector3(0, 0, 1)); break; case CubeMapFace.NegativeY: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, -1, 0), new Vector3(0, 0, -1)); break; case CubeMapFace.PositiveZ: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, 0, -1), new Vector3(0, 1, 0)); break; case CubeMapFace.NegativeZ: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, 0, 1), new Vector3(0, 1, 0)); break; default: throw new ArgumentOutOfRangeException(); } Color[] colorArray = new Color[cubeMap.Size * cubeMap.Size]; cubeMap.GetData(faceId, colorArray); // Extract the spherical harmonic for this face and accumulate it. sh += ExtractSphericalHarmonicForCubeFace(cubeFaceMatrix, colorArray, cubeMap.Size); } //average out over the sphere return(sh.GetWeightedAverageLightInputFromSphere()); } // GenerateSphericalHarmonicFromCubeMap
} // SampleDirection #endregion #region Add /// <summary> /// Add two spherical harmonics together. /// </summary> public static SphericalHarmonicL2 Add(SphericalHarmonicL2 x, SphericalHarmonicL2 y) { return(new SphericalHarmonicL2 { weighting = x.weighting + y.weighting, c0 = { X = x.c0.X + y.c0.X, Y = x.c0.Y + y.c0.Y, Z = x.c0.Z + y.c0.Z }, c1 = { X = x.c1.X + y.c1.X, Y = x.c1.Y + y.c1.Y, Z = x.c1.Z + y.c1.Z }, c2 = { X = x.c2.X + y.c2.X, Y = x.c2.Y + y.c2.Y, Z = x.c2.Z + y.c2.Z }, c3 = { X = x.c3.X + y.c3.X, Y = x.c3.Y + y.c3.Y, Z = x.c3.Z + y.c3.Z }, c4 = { X = x.c4.X + y.c4.X, Y = x.c4.Y + y.c4.Y, Z = x.c4.Z + y.c4.Z }, c5 = { X = x.c5.X + y.c5.X, Y = x.c5.Y + y.c5.Y, Z = x.c5.Z + y.c5.Z }, c6 = { X = x.c6.X + y.c6.X, Y = x.c6.Y + y.c6.Y, Z = x.c6.Z + y.c6.Z }, c7 = { X = x.c7.X + y.c7.X, Y = x.c7.Y + y.c7.Y, Z = x.c7.Z + y.c7.Z }, c8 = { X = x.c8.X + y.c8.X, Y = x.c8.Y + y.c8.Y, Z = x.c8.Z + y.c8.Z } }); } // Add
} // Add #endregion #region Multiply by a scalar /// <summary> /// Multiply a spherical harmonic by a constant scale factor /// </summary> public static SphericalHarmonicL2 Multiply(SphericalHarmonicL2 x, float scale) { return(new SphericalHarmonicL2 { weighting = x.weighting * scale, c0 = { X = x.c0.X * scale, Y = x.c0.Y * scale, Z = x.c0.Z * scale }, c1 = { X = x.c1.X * scale, Y = x.c1.Y * scale, Z = x.c1.Z * scale }, c2 = { X = x.c2.X * scale, Y = x.c2.Y * scale, Z = x.c2.Z * scale }, c3 = { X = x.c3.X * scale, Y = x.c3.Y * scale, Z = x.c3.Z * scale }, c4 = { X = x.c4.X * scale, Y = x.c4.Y * scale, Z = x.c4.Z * scale }, c5 = { X = x.c5.X * scale, Y = x.c5.Y * scale, Z = x.c5.Z * scale }, c6 = { X = x.c6.X * scale, Y = x.c6.Y * scale, Z = x.c6.Z * scale }, c7 = { X = x.c7.X * scale, Y = x.c7.Y * scale, Z = x.c7.Z * scale }, c8 = { X = x.c8.X * scale, Y = x.c8.Y * scale, Z = x.c8.Z * scale } }); } // Multiply
} // Multiply #endregion #region Lerp /// <summary> /// Linear interpolate (Lerp) between two spherical harmonics based on a interpolation factor. /// </summary> /// <param name="factor">Determines the interpolation point. When factor is 1.0, the output will be x, when factor is 0.0, the output will be y</param> public static SphericalHarmonicL2 Lerp(ref SphericalHarmonicL2 x, ref SphericalHarmonicL2 y, float factor) { float xs = factor; float ys = 1.0f - factor; return(new SphericalHarmonicL2 { weighting = x.weighting * xs + y.weighting * ys, c0 = { X = xs * x.c0.X + ys * y.c0.X, Y = xs * x.c0.Y + ys * y.c0.Y, Z = xs * x.c0.Z + ys * y.c0.Z }, c1 = { X = xs * x.c1.X + ys * y.c1.X, Y = xs * x.c1.Y + ys * y.c1.Y, Z = xs * x.c1.Z + ys * y.c1.Z }, c2 = { X = xs * x.c2.X + ys * y.c2.X, Y = xs * x.c2.Y + ys * y.c2.Y, Z = xs * x.c2.Z + ys * y.c2.Z }, c3 = { X = xs * x.c3.X + ys * y.c3.X, Y = xs * x.c3.Y + ys * y.c3.Y, Z = xs * x.c3.Z + ys * y.c3.Z }, c4 = { X = xs * x.c4.X + ys * y.c4.X, Y = xs * x.c4.Y + ys * y.c4.Y, Z = xs * x.c4.Z + ys * y.c4.Z }, c5 = { X = xs * x.c5.X + ys * y.c5.X, Y = xs * x.c5.Y + ys * y.c5.Y, Z = xs * x.c5.Z + ys * y.c5.Z }, c6 = { X = xs * x.c6.X + ys * y.c6.X, Y = xs * x.c6.Y + ys * y.c6.Y, Z = xs * x.c6.Z + ys * y.c6.Z }, c7 = { X = xs * x.c7.X + ys * y.c7.X, Y = xs * x.c7.Y + ys * y.c7.Y, Z = xs * x.c7.Z + ys * y.c7.Z }, c8 = { X = xs * x.c8.X + ys * y.c8.X, Y = xs * x.c8.Y + ys * y.c8.Y, Z = xs * x.c8.Z + ys * y.c8.Z } }); } // Lerp
} // GetWeightedAverageLightInputFromSphere private static SphericalHarmonicL2 ExtractSphericalHarmonicForCubeFace(Matrix faceTransform, Color[] colorDataRgb, int faceSize) { SphericalHarmonicL2 sh = new SphericalHarmonicL2(); // 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. float directionStep = 2.0f / (faceSize - 1.0f); int pixelIndex = 0; float dirY = 1.0f; for (int y = 0; y < faceSize; y++) { SphericalHarmonicL2 lineSh = new SphericalHarmonicL2(); 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; Vector3 rgbFloat; Color rgbm = colorDataRgb[pixelIndex++]; rgbFloat = rgbm.ToVector3(); //Add it to the SH lineSh.AddLight(rgbFloat, direction, weight); dirX += directionStep; } //average the SH if (lineSh.weighting > 0) { lineSh *= 1 / 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 *= 1 / sh.weighting; } return(sh); } // ExtractSphericalHarmonicForCubeFace
} // GetWeightedAverageLightInputFromSphere private static SphericalHarmonicL2 ExtractSphericalHarmonicForCubeFace(Matrix faceTransform, Color[] colorDataRgb, int faceSize) { SphericalHarmonicL2 sh = new SphericalHarmonicL2(); // 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. float directionStep = 2.0f / (faceSize - 1.0f); int pixelIndex = 0; float dirY = 1.0f; for (int y = 0; y < faceSize; y++) { SphericalHarmonicL2 lineSh = new SphericalHarmonicL2(); 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; Vector3 rgbFloat; Color rgbm = colorDataRgb[pixelIndex++]; rgbFloat = rgbm.ToVector3(); //Add it to the SH lineSh.AddLight(rgbFloat, direction, weight); dirX += directionStep; } //average the SH if (lineSh.weighting > 0) lineSh *= 1 / 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 *= 1 / sh.weighting; return sh; } // ExtractSphericalHarmonicForCubeFace
} // Lerp #endregion #region Generate Spherical Harmonic from CubeMap /// <summary> /// Generate a spherical harmonic from the faces of a cubemap, treating each pixel as a light source and averaging the result. /// </summary> public static SphericalHarmonicL2 GenerateSphericalHarmonicFromCubeMap(TextureCube cubeMap) { SphericalHarmonicL2 sh = new SphericalHarmonicL2(); // Extract the 6 faces of the cubemap. for (int face = 0; face < 6; face++) { CubeMapFace faceId = (CubeMapFace)face; // Get the transformation for this face, Matrix cubeFaceMatrix; switch (faceId) { case CubeMapFace.PositiveX: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(1, 0, 0), new Vector3(0, 1, 0)); break; case CubeMapFace.NegativeX: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(-1, 0, 0), new Vector3(0, 1, 0)); break; case CubeMapFace.PositiveY: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, 1, 0), new Vector3(0, 0, 1)); break; case CubeMapFace.NegativeY: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, -1, 0), new Vector3(0, 0, -1)); break; case CubeMapFace.PositiveZ: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, 0, -1), new Vector3(0, 1, 0)); break; case CubeMapFace.NegativeZ: cubeFaceMatrix = Matrix.CreateLookAt(Vector3.Zero, new Vector3(0, 0, 1), new Vector3(0, 1, 0)); break; default: throw new ArgumentOutOfRangeException(); } Color[] colorArray = new Color[cubeMap.Size * cubeMap.Size]; cubeMap.GetData(faceId, colorArray); // Extract the spherical harmonic for this face and accumulate it. sh += ExtractSphericalHarmonicForCubeFace(cubeFaceMatrix, colorArray, cubeMap.Size); } //average out over the sphere return sh.GetWeightedAverageLightInputFromSphere(); } // GenerateSphericalHarmonicFromCubeMap
} // Multiply #endregion #region Lerp /// <summary> /// Linear interpolate (Lerp) between two spherical harmonics based on a interpolation factor. /// </summary> /// <param name="factor">Determines the interpolation point. When factor is 1.0, the output will be x, when factor is 0.0, the output will be y</param> public static SphericalHarmonicL2 Lerp(ref SphericalHarmonicL2 x, ref SphericalHarmonicL2 y, float factor) { float xs = factor; float ys = 1.0f - factor; return new SphericalHarmonicL2 { weighting = x.weighting * xs + y.weighting * ys, c0 = { X = xs * x.c0.X + ys * y.c0.X, Y = xs * x.c0.Y + ys * y.c0.Y, Z = xs * x.c0.Z + ys * y.c0.Z }, c1 = { X = xs * x.c1.X + ys * y.c1.X, Y = xs * x.c1.Y + ys * y.c1.Y, Z = xs * x.c1.Z + ys * y.c1.Z }, c2 = { X = xs * x.c2.X + ys * y.c2.X, Y = xs * x.c2.Y + ys * y.c2.Y, Z = xs * x.c2.Z + ys * y.c2.Z }, c3 = { X = xs * x.c3.X + ys * y.c3.X, Y = xs * x.c3.Y + ys * y.c3.Y, Z = xs * x.c3.Z + ys * y.c3.Z }, c4 = { X = xs * x.c4.X + ys * y.c4.X, Y = xs * x.c4.Y + ys * y.c4.Y, Z = xs * x.c4.Z + ys * y.c4.Z }, c5 = { X = xs * x.c5.X + ys * y.c5.X, Y = xs * x.c5.Y + ys * y.c5.Y, Z = xs * x.c5.Z + ys * y.c5.Z }, c6 = { X = xs * x.c6.X + ys * y.c6.X, Y = xs * x.c6.Y + ys * y.c6.Y, Z = xs * x.c6.Z + ys * y.c6.Z }, c7 = { X = xs * x.c7.X + ys * y.c7.X, Y = xs * x.c7.Y + ys * y.c7.Y, Z = xs * x.c7.Z + ys * y.c7.Z }, c8 = { X = xs * x.c8.X + ys * y.c8.X, Y = xs * x.c8.Y + ys * y.c8.Y, Z = xs * x.c8.Z + ys * y.c8.Z } }; } // Lerp
} // Add #endregion #region Multiply by a scalar /// <summary> /// Multiply a spherical harmonic by a constant scale factor /// </summary> public static SphericalHarmonicL2 Multiply(SphericalHarmonicL2 x, float scale) { return new SphericalHarmonicL2 { weighting = x.weighting * scale, c0 = { X = x.c0.X * scale, Y = x.c0.Y * scale, Z = x.c0.Z * scale }, c1 = { X = x.c1.X * scale, Y = x.c1.Y * scale, Z = x.c1.Z * scale }, c2 = { X = x.c2.X * scale, Y = x.c2.Y * scale, Z = x.c2.Z * scale }, c3 = { X = x.c3.X * scale, Y = x.c3.Y * scale, Z = x.c3.Z * scale }, c4 = { X = x.c4.X * scale, Y = x.c4.Y * scale, Z = x.c4.Z * scale }, c5 = { X = x.c5.X * scale, Y = x.c5.Y * scale, Z = x.c5.Z * scale }, c6 = { X = x.c6.X * scale, Y = x.c6.Y * scale, Z = x.c6.Z * scale }, c7 = { X = x.c7.X * scale, Y = x.c7.Y * scale, Z = x.c7.Z * scale }, c8 = { X = x.c8.X * scale, Y = x.c8.Y * scale, Z = x.c8.Z * scale } }; } // Multiply
} // SampleDirection #endregion #region Add /// <summary> /// Add two spherical harmonics together. /// </summary> public static SphericalHarmonicL2 Add(SphericalHarmonicL2 x, SphericalHarmonicL2 y) { return new SphericalHarmonicL2 { weighting = x.weighting + y.weighting, c0 = { X = x.c0.X + y.c0.X, Y = x.c0.Y + y.c0.Y, Z = x.c0.Z + y.c0.Z }, c1 = { X = x.c1.X + y.c1.X, Y = x.c1.Y + y.c1.Y, Z = x.c1.Z + y.c1.Z }, c2 = { X = x.c2.X + y.c2.X, Y = x.c2.Y + y.c2.Y, Z = x.c2.Z + y.c2.Z }, c3 = { X = x.c3.X + y.c3.X, Y = x.c3.Y + y.c3.Y, Z = x.c3.Z + y.c3.Z }, c4 = { X = x.c4.X + y.c4.X, Y = x.c4.Y + y.c4.Y, Z = x.c4.Z + y.c4.Z }, c5 = { X = x.c5.X + y.c5.X, Y = x.c5.Y + y.c5.Y, Z = x.c5.Z + y.c5.Z }, c6 = { X = x.c6.X + y.c6.X, Y = x.c6.Y + y.c6.Y, Z = x.c6.Z + y.c6.Z }, c7 = { X = x.c7.X + y.c7.X, Y = x.c7.Y + y.c7.Y, Z = x.c7.Z + y.c7.Z }, c8 = { X = x.c8.X + y.c8.X, Y = x.c8.Y + y.c8.Y, Z = x.c8.Z + y.c8.Z } }; } // Add