babylon.cubemapToSphericalPolynomial.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. module BABYLON {
  2. class FileFaceOrientation {
  3. public name: string;
  4. public worldAxisForNormal: Vector3; // the world axis corresponding to the normal to the face
  5. public worldAxisForFileX: Vector3; // the world axis corresponding to texture right x-axis in file
  6. public worldAxisForFileY: Vector3; // the world axis corresponding to texture down y-axis in file
  7. public constructor(name: string, worldAxisForNormal: Vector3, worldAxisForFileX: Vector3, worldAxisForFileY: Vector3) {
  8. this.name = name;
  9. this.worldAxisForNormal = worldAxisForNormal;
  10. this.worldAxisForFileX = worldAxisForFileX;
  11. this.worldAxisForFileY = worldAxisForFileY;
  12. }
  13. };
  14. /**
  15. * Helper class dealing with the extraction of spherical polynomial dataArray
  16. * from a cube map.
  17. */
  18. export class CubeMapToSphericalPolynomialTools {
  19. private static FileFaces: FileFaceOrientation[] = [
  20. new FileFaceOrientation("right", new Vector3(1, 0, 0), new Vector3(0, 0, -1), new Vector3(0, -1, 0)), // +X east
  21. new FileFaceOrientation("left", new Vector3(-1, 0, 0), new Vector3(0, 0, 1), new Vector3(0, -1, 0)), // -X west
  22. new FileFaceOrientation("up", new Vector3(0, 1, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1)), // +Y north
  23. new FileFaceOrientation("down", new Vector3(0, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 0, -1)), // -Y south
  24. new FileFaceOrientation("front", new Vector3(0, 0, 1), new Vector3(1, 0, 0), new Vector3(0, -1, 0)), // +Z top
  25. new FileFaceOrientation("back", new Vector3(0, 0, -1), new Vector3(-1, 0, 0), new Vector3(0, -1, 0))// -Z bottom
  26. ];
  27. /**
  28. * Converts a texture to the according Spherical Polynomial data.
  29. * This extracts the first 3 orders only as they are the only one used in the lighting.
  30. *
  31. * @param texture The texture to extract the information from.
  32. * @return The Spherical Polynomial data.
  33. */
  34. public static ConvertCubeMapTextureToSphericalPolynomial(texture: BaseTexture) {
  35. if (!texture.isCube) {
  36. // Only supports cube Textures currently.
  37. return null;
  38. }
  39. var size = texture.getSize().width;
  40. var right = texture.readPixels(0);
  41. var left = texture.readPixels(1);
  42. var up: Nullable<ArrayBufferView>;
  43. var down: Nullable<ArrayBufferView>;
  44. if (texture.isRenderTarget) {
  45. up = texture.readPixels(3);
  46. down = texture.readPixels(2);
  47. }
  48. else {
  49. up = texture.readPixels(2);
  50. down = texture.readPixels(3);
  51. }
  52. var front = texture.readPixels(4);
  53. var back = texture.readPixels(5);
  54. var gammaSpace = texture.gammaSpace;
  55. // Always read as RGBA.
  56. var format = Engine.TEXTUREFORMAT_RGBA;
  57. var type = Engine.TEXTURETYPE_UNSIGNED_INT;
  58. if (texture.textureType && texture.textureType !== Engine.TEXTURETYPE_UNSIGNED_INT) {
  59. type = Engine.TEXTURETYPE_FLOAT;
  60. }
  61. var cubeInfo: CubeMapInfo = {
  62. size,
  63. right,
  64. left,
  65. up,
  66. down,
  67. front,
  68. back,
  69. format,
  70. type,
  71. gammaSpace,
  72. };
  73. return this.ConvertCubeMapToSphericalPolynomial(cubeInfo);
  74. }
  75. /**
  76. * Converts a cubemap to the according Spherical Polynomial data.
  77. * This extracts the first 3 orders only as they are the only one used in the lighting.
  78. *
  79. * @param cubeInfo The Cube map to extract the information from.
  80. * @return The Spherical Polynomial data.
  81. */
  82. public static ConvertCubeMapToSphericalPolynomial(cubeInfo: CubeMapInfo): SphericalPolynomial {
  83. var sphericalHarmonics = new SphericalHarmonics();
  84. var totalSolidAngle = 0.0;
  85. // The (u,v) range is [-1,+1], so the distance between each texel is 2/Size.
  86. var du = 2.0 / cubeInfo.size;
  87. var dv = du;
  88. // The (u,v) of the first texel is half a texel from the corner (-1,-1).
  89. var minUV = du * 0.5 - 1.0;
  90. for (var faceIndex = 0; faceIndex < 6; faceIndex++) {
  91. var fileFace = this.FileFaces[faceIndex];
  92. var dataArray = (<any>cubeInfo)[fileFace.name];
  93. var v = minUV;
  94. // TODO: we could perform the summation directly into a SphericalPolynomial (SP), which is more efficient than SphericalHarmonic (SH).
  95. // This is possible because during the summation we do not need the SH-specific properties, e.g. orthogonality.
  96. // Because SP is still linear, so summation is fine in that basis.
  97. const stride = cubeInfo.format === Engine.TEXTUREFORMAT_RGBA ? 4 : 3;
  98. for (var y = 0; y < cubeInfo.size; y++) {
  99. var u = minUV;
  100. for (var x = 0; x < cubeInfo.size; x++) {
  101. // World direction (not normalised)
  102. var worldDirection =
  103. fileFace.worldAxisForFileX.scale(u).add(
  104. fileFace.worldAxisForFileY.scale(v)).add(
  105. fileFace.worldAxisForNormal);
  106. worldDirection.normalize();
  107. var deltaSolidAngle = Math.pow(1.0 + u * u + v * v, -3.0 / 2.0);
  108. var r = dataArray[(y * cubeInfo.size * stride) + (x * stride) + 0];
  109. var g = dataArray[(y * cubeInfo.size * stride) + (x * stride) + 1];
  110. var b = dataArray[(y * cubeInfo.size * stride) + (x * stride) + 2];
  111. // Handle Integer types.
  112. if (cubeInfo.type === Engine.TEXTURETYPE_UNSIGNED_INT) {
  113. r /= 255;
  114. g /= 255;
  115. b /= 255;
  116. }
  117. // Handle Gamma space textures.
  118. if (cubeInfo.gammaSpace) {
  119. r = Math.pow(Scalar.Clamp(r), ToLinearSpace);
  120. g = Math.pow(Scalar.Clamp(g), ToLinearSpace);
  121. b = Math.pow(Scalar.Clamp(b), ToLinearSpace);
  122. }
  123. var color = new Color3(r, g, b);
  124. sphericalHarmonics.addLight(worldDirection, color, deltaSolidAngle);
  125. totalSolidAngle += deltaSolidAngle;
  126. u += du;
  127. }
  128. v += dv;
  129. }
  130. }
  131. // Solid angle for entire sphere is 4*pi
  132. var sphereSolidAngle = 4.0 * Math.PI;
  133. // Adjust the solid angle to allow for how many faces we processed.
  134. var facesProcessed = 6.0;
  135. var expectedSolidAngle = sphereSolidAngle * facesProcessed / 6.0;
  136. // Adjust the harmonics so that the accumulated solid angle matches the expected solid angle.
  137. // This is needed because the numerical integration over the cube uses a
  138. // small angle approximation of solid angle for each texel (see deltaSolidAngle),
  139. // and also to compensate for accumulative error due to float precision in the summation.
  140. var correctionFactor = expectedSolidAngle / totalSolidAngle;
  141. sphericalHarmonics.scale(correctionFactor);
  142. sphericalHarmonics.convertIncidentRadianceToIrradiance();
  143. sphericalHarmonics.convertIrradianceToLambertianRadiance();
  144. return SphericalPolynomial.getSphericalPolynomialFromHarmonics(sphericalHarmonics);
  145. }
  146. }
  147. }