babylon.hdrCubeTexture.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. var __extends = (this && this.__extends) || function (d, b) {
  2. for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
  3. function __() { this.constructor = d; }
  4. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  5. };
  6. var BABYLON;
  7. (function (BABYLON) {
  8. /**
  9. * This represents a texture coming from an HDR input.
  10. *
  11. * The only supported format is currently panorama picture stored in RGBE format.
  12. * Example of such files can be found on HDRLib: http://hdrlib.com/
  13. */
  14. var HDRCubeTexture = (function (_super) {
  15. __extends(HDRCubeTexture, _super);
  16. /**
  17. * Instantiates an HDRTexture from the following parameters.
  18. *
  19. * @param url The location of the HDR raw data (Panorama stored in RGBE format)
  20. * @param scene The scene the texture will be used in
  21. * @param size The cubemap desired size (the more it increases the longer the generation will be) If the size is omitted this implies you are using a preprocessed cubemap.
  22. * @param noMipmap Forces to not generate the mipmap if true
  23. * @param generateHarmonics Specifies wether you want to extract the polynomial harmonics during the generation process
  24. * @param useInGammaSpace Specifies if the texture will be use in gamma or linear space (the PBR material requires those texture in linear space, but the standard material would require them in Gamma space)
  25. * @param usePMREMGenerator Specifies wether or not to generate the CubeMap through CubeMapGen to avoid seams issue at run time.
  26. */
  27. function HDRCubeTexture(url, scene, size, noMipmap, generateHarmonics, useInGammaSpace, usePMREMGenerator) {
  28. if (noMipmap === void 0) { noMipmap = false; }
  29. if (generateHarmonics === void 0) { generateHarmonics = true; }
  30. if (useInGammaSpace === void 0) { useInGammaSpace = false; }
  31. if (usePMREMGenerator === void 0) { usePMREMGenerator = false; }
  32. _super.call(this, scene);
  33. this._useInGammaSpace = false;
  34. this._generateHarmonics = true;
  35. this._isBABYLONPreprocessed = false;
  36. /**
  37. * The texture coordinates mode. As this texture is stored in a cube format, please modify carefully.
  38. */
  39. this.coordinatesMode = BABYLON.Texture.CUBIC_MODE;
  40. /**
  41. * The spherical polynomial data extracted from the texture.
  42. */
  43. this.sphericalPolynomial = null;
  44. /**
  45. * Specifies wether the texture has been generated through the PMREMGenerator tool.
  46. * This is usefull at run time to apply the good shader.
  47. */
  48. this.isPMREM = false;
  49. if (!url) {
  50. return;
  51. }
  52. this.name = url;
  53. this.url = url;
  54. this.hasAlpha = false;
  55. this.isCube = true;
  56. this._textureMatrix = BABYLON.Matrix.Identity();
  57. if (size) {
  58. this._isBABYLONPreprocessed = false;
  59. this._noMipmap = noMipmap;
  60. this._size = size;
  61. this._useInGammaSpace = useInGammaSpace;
  62. this._usePMREMGenerator = usePMREMGenerator &&
  63. scene.getEngine().getCaps().textureLOD &&
  64. this.getScene().getEngine().getCaps().textureFloat &&
  65. !this._useInGammaSpace;
  66. }
  67. else {
  68. this._isBABYLONPreprocessed = true;
  69. this._noMipmap = false;
  70. this._useInGammaSpace = false;
  71. this._usePMREMGenerator = scene.getEngine().getCaps().textureLOD &&
  72. this.getScene().getEngine().getCaps().textureFloat &&
  73. !this._useInGammaSpace;
  74. }
  75. this.isPMREM = this._usePMREMGenerator;
  76. this._texture = this._getFromCache(url, this._noMipmap);
  77. if (!this._texture) {
  78. if (!scene.useDelayedTextureLoading) {
  79. this.loadTexture();
  80. }
  81. else {
  82. this.delayLoadState = BABYLON.Engine.DELAYLOADSTATE_NOTLOADED;
  83. }
  84. }
  85. }
  86. /**
  87. * Occurs when the file is a preprocessed .babylon.hdr file.
  88. */
  89. HDRCubeTexture.prototype.loadBabylonTexture = function () {
  90. var _this = this;
  91. var mipLevels = 0;
  92. var floatArrayView = null;
  93. var mipmapGenerator = (!this._useInGammaSpace && this.getScene().getEngine().getCaps().textureFloat) ? function (data) {
  94. var mips = [];
  95. var startIndex = 30;
  96. for (var level = 0; level < mipLevels; level++) {
  97. mips.push([]);
  98. // Fill each pixel of the mip level.
  99. var faceSize = Math.pow(_this._size >> level, 2) * 3;
  100. for (var faceIndex = 0; faceIndex < 6; faceIndex++) {
  101. var faceData = floatArrayView.subarray(startIndex, startIndex + faceSize);
  102. mips[level].push(faceData);
  103. startIndex += faceSize;
  104. }
  105. }
  106. return mips;
  107. } : null;
  108. var callback = function (buffer) {
  109. // Create Native Array Views
  110. var intArrayView = new Int32Array(buffer);
  111. floatArrayView = new Float32Array(buffer);
  112. // Fill header.
  113. var version = intArrayView[0]; // Version 1. (MAy be use in case of format changes for backward compaibility)
  114. _this._size = intArrayView[1]; // CubeMap max mip face size.
  115. // Update Texture Information.
  116. _this.getScene().getEngine().updateTextureSize(_this._texture, _this._size, _this._size);
  117. // Fill polynomial information.
  118. _this.sphericalPolynomial = new BABYLON.SphericalPolynomial();
  119. _this.sphericalPolynomial.x.copyFromFloats(floatArrayView[2], floatArrayView[3], floatArrayView[4]);
  120. _this.sphericalPolynomial.y.copyFromFloats(floatArrayView[5], floatArrayView[6], floatArrayView[7]);
  121. _this.sphericalPolynomial.z.copyFromFloats(floatArrayView[8], floatArrayView[9], floatArrayView[10]);
  122. _this.sphericalPolynomial.xx.copyFromFloats(floatArrayView[11], floatArrayView[12], floatArrayView[13]);
  123. _this.sphericalPolynomial.yy.copyFromFloats(floatArrayView[14], floatArrayView[15], floatArrayView[16]);
  124. _this.sphericalPolynomial.zz.copyFromFloats(floatArrayView[17], floatArrayView[18], floatArrayView[19]);
  125. _this.sphericalPolynomial.xy.copyFromFloats(floatArrayView[20], floatArrayView[21], floatArrayView[22]);
  126. _this.sphericalPolynomial.yz.copyFromFloats(floatArrayView[23], floatArrayView[24], floatArrayView[25]);
  127. _this.sphericalPolynomial.zx.copyFromFloats(floatArrayView[26], floatArrayView[27], floatArrayView[28]);
  128. // Fill pixel data.
  129. mipLevels = intArrayView[29]; // Number of mip levels.
  130. var startIndex = 30;
  131. var data = [];
  132. var faceSize = Math.pow(_this._size, 2) * 3;
  133. for (var faceIndex = 0; faceIndex < 6; faceIndex++) {
  134. data.push(floatArrayView.subarray(startIndex, startIndex + faceSize));
  135. startIndex += faceSize;
  136. }
  137. var results = [];
  138. var byteArray = null;
  139. // Push each faces.
  140. for (var k = 0; k < 6; k++) {
  141. var dataFace = null;
  142. // If special cases.
  143. if (!mipmapGenerator) {
  144. var j = ([0, 2, 4, 1, 3, 5])[k]; // Transforms +X+Y+Z... to +X-X+Y-Y... if no mipmapgenerator...
  145. dataFace = data[j];
  146. if (!_this.getScene().getEngine().getCaps().textureFloat) {
  147. // 3 channels of 1 bytes per pixel in bytes.
  148. var byteBuffer = new ArrayBuffer(faceSize);
  149. byteArray = new Uint8Array(byteBuffer);
  150. }
  151. for (var i = 0; i < _this._size * _this._size; i++) {
  152. // Put in gamma space if requested.
  153. if (_this._useInGammaSpace) {
  154. dataFace[(i * 3) + 0] = Math.pow(dataFace[(i * 3) + 0], BABYLON.ToGammaSpace);
  155. dataFace[(i * 3) + 1] = Math.pow(dataFace[(i * 3) + 1], BABYLON.ToGammaSpace);
  156. dataFace[(i * 3) + 2] = Math.pow(dataFace[(i * 3) + 2], BABYLON.ToGammaSpace);
  157. }
  158. // Convert to int texture for fallback.
  159. if (byteArray) {
  160. var r = Math.max(dataFace[(i * 3) + 0] * 255, 0);
  161. var g = Math.max(dataFace[(i * 3) + 1] * 255, 0);
  162. var b = Math.max(dataFace[(i * 3) + 2] * 255, 0);
  163. // May use luminance instead if the result is not accurate.
  164. var max = Math.max(Math.max(r, g), b);
  165. if (max > 255) {
  166. var scale = 255 / max;
  167. r *= scale;
  168. g *= scale;
  169. b *= scale;
  170. }
  171. byteArray[(i * 3) + 0] = r;
  172. byteArray[(i * 3) + 1] = g;
  173. byteArray[(i * 3) + 2] = b;
  174. }
  175. }
  176. }
  177. else {
  178. dataFace = data[k];
  179. }
  180. // Fill the array accordingly.
  181. if (byteArray) {
  182. results.push(byteArray);
  183. }
  184. else {
  185. results.push(dataFace);
  186. }
  187. }
  188. return results;
  189. };
  190. this._texture = this.getScene().getEngine().createRawCubeTexture(this.url, this.getScene(), this._size, BABYLON.Engine.TEXTUREFORMAT_RGB, this.getScene().getEngine().getCaps().textureFloat ? BABYLON.Engine.TEXTURETYPE_FLOAT : BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT, this._noMipmap, callback, mipmapGenerator);
  191. };
  192. /**
  193. * Occurs when the file is raw .hdr file.
  194. */
  195. HDRCubeTexture.prototype.loadHDRTexture = function () {
  196. var _this = this;
  197. var callback = function (buffer) {
  198. // Extract the raw linear data.
  199. var data = BABYLON.Internals.HDRTools.GetCubeMapTextureData(buffer, _this._size);
  200. // Generate harmonics if needed.
  201. if (_this._generateHarmonics) {
  202. _this.sphericalPolynomial = BABYLON.Internals.CubeMapToSphericalPolynomialTools.ConvertCubeMapToSphericalPolynomial(data);
  203. }
  204. var results = [];
  205. var byteArray = null;
  206. // Push each faces.
  207. for (var j = 0; j < 6; j++) {
  208. // Create uintarray fallback.
  209. if (!_this.getScene().getEngine().getCaps().textureFloat) {
  210. // 3 channels of 1 bytes per pixel in bytes.
  211. var byteBuffer = new ArrayBuffer(_this._size * _this._size * 3);
  212. byteArray = new Uint8Array(byteBuffer);
  213. }
  214. var dataFace = data[HDRCubeTexture._facesMapping[j]];
  215. // If special cases.
  216. if (_this._useInGammaSpace || byteArray) {
  217. for (var i = 0; i < _this._size * _this._size; i++) {
  218. // Put in gamma space if requested.
  219. if (_this._useInGammaSpace) {
  220. dataFace[(i * 3) + 0] = Math.pow(dataFace[(i * 3) + 0], BABYLON.ToGammaSpace);
  221. dataFace[(i * 3) + 1] = Math.pow(dataFace[(i * 3) + 1], BABYLON.ToGammaSpace);
  222. dataFace[(i * 3) + 2] = Math.pow(dataFace[(i * 3) + 2], BABYLON.ToGammaSpace);
  223. }
  224. // Convert to int texture for fallback.
  225. if (byteArray) {
  226. var r = Math.max(dataFace[(i * 3) + 0] * 255, 0);
  227. var g = Math.max(dataFace[(i * 3) + 1] * 255, 0);
  228. var b = Math.max(dataFace[(i * 3) + 2] * 255, 0);
  229. // May use luminance instead if the result is not accurate.
  230. var max = Math.max(Math.max(r, g), b);
  231. if (max > 255) {
  232. var scale = 255 / max;
  233. r *= scale;
  234. g *= scale;
  235. b *= scale;
  236. }
  237. byteArray[(i * 3) + 0] = r;
  238. byteArray[(i * 3) + 1] = g;
  239. byteArray[(i * 3) + 2] = b;
  240. }
  241. }
  242. }
  243. if (byteArray) {
  244. results.push(byteArray);
  245. }
  246. else {
  247. results.push(dataFace);
  248. }
  249. }
  250. return results;
  251. };
  252. var mipmapGenerator = null;
  253. if (!this._noMipmap &&
  254. this._usePMREMGenerator) {
  255. mipmapGenerator = function (data) {
  256. // Custom setup of the generator matching with the PBR shader values.
  257. var generator = new BABYLON.Internals.PMREMGenerator(data, _this._size, _this._size, 0, 3, _this.getScene().getEngine().getCaps().textureFloat, 2048, 0.25, false, true);
  258. return generator.filterCubeMap();
  259. };
  260. }
  261. this._texture = this.getScene().getEngine().createRawCubeTexture(this.url, this.getScene(), this._size, BABYLON.Engine.TEXTUREFORMAT_RGB, this.getScene().getEngine().getCaps().textureFloat ? BABYLON.Engine.TEXTURETYPE_FLOAT : BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT, this._noMipmap, callback, mipmapGenerator);
  262. };
  263. /**
  264. * Starts the loading process of the texture.
  265. */
  266. HDRCubeTexture.prototype.loadTexture = function () {
  267. if (this._isBABYLONPreprocessed) {
  268. this.loadBabylonTexture();
  269. }
  270. else {
  271. this.loadHDRTexture();
  272. }
  273. };
  274. HDRCubeTexture.prototype.clone = function () {
  275. var size = this._isBABYLONPreprocessed ? null : this._size;
  276. var newTexture = new HDRCubeTexture(this.url, this.getScene(), size, this._noMipmap, this._generateHarmonics, this._useInGammaSpace, this._usePMREMGenerator);
  277. // Base texture
  278. newTexture.level = this.level;
  279. newTexture.wrapU = this.wrapU;
  280. newTexture.wrapV = this.wrapV;
  281. newTexture.coordinatesIndex = this.coordinatesIndex;
  282. newTexture.coordinatesMode = this.coordinatesMode;
  283. return newTexture;
  284. };
  285. // Methods
  286. HDRCubeTexture.prototype.delayLoad = function () {
  287. if (this.delayLoadState !== BABYLON.Engine.DELAYLOADSTATE_NOTLOADED) {
  288. return;
  289. }
  290. this.delayLoadState = BABYLON.Engine.DELAYLOADSTATE_LOADED;
  291. this._texture = this._getFromCache(this.url, this._noMipmap);
  292. if (!this._texture) {
  293. this.loadTexture();
  294. }
  295. };
  296. HDRCubeTexture.prototype.getReflectionTextureMatrix = function () {
  297. return this._textureMatrix;
  298. };
  299. HDRCubeTexture.Parse = function (parsedTexture, scene, rootUrl) {
  300. var texture = null;
  301. if (parsedTexture.name && !parsedTexture.isRenderTarget) {
  302. var size = parsedTexture.isBABYLONPreprocessed ? null : parsedTexture.size;
  303. texture = new BABYLON.HDRCubeTexture(rootUrl + parsedTexture.name, scene, size, texture.generateHarmonics, texture.useInGammaSpace, texture.usePMREMGenerator);
  304. texture.name = parsedTexture.name;
  305. texture.hasAlpha = parsedTexture.hasAlpha;
  306. texture.level = parsedTexture.level;
  307. texture.coordinatesMode = parsedTexture.coordinatesMode;
  308. }
  309. return texture;
  310. };
  311. HDRCubeTexture.prototype.serialize = function () {
  312. if (!this.name) {
  313. return null;
  314. }
  315. var serializationObject = {};
  316. serializationObject.name = this.name;
  317. serializationObject.hasAlpha = this.hasAlpha;
  318. serializationObject.isCube = true;
  319. serializationObject.level = this.level;
  320. serializationObject.size = this._size;
  321. serializationObject.coordinatesMode = this.coordinatesMode;
  322. serializationObject.useInGammaSpace = this._useInGammaSpace;
  323. serializationObject.generateHarmonics = this._generateHarmonics;
  324. serializationObject.usePMREMGenerator = this._usePMREMGenerator;
  325. serializationObject.isBABYLONPreprocessed = this._isBABYLONPreprocessed;
  326. serializationObject.customType = "BABYLON.HDRCubeTexture";
  327. return serializationObject;
  328. };
  329. /**
  330. * Saves as a file the data contained in the texture in a binary format.
  331. * This can be used to prevent the long loading tie associated with creating the seamless texture as well
  332. * as the spherical used in the lighting.
  333. * @param url The HDR file url.
  334. * @param size The size of the texture data to generate (one of the cubemap face desired width).
  335. * @param onError Method called if any error happens during download.
  336. * @return The packed binary data.
  337. */
  338. HDRCubeTexture.generateBabylonHDROnDisk = function (url, size, onError) {
  339. if (onError === void 0) { onError = null; }
  340. var callback = function (buffer) {
  341. var data = new Blob([buffer], { type: 'application/octet-stream' });
  342. // Returns a URL you can use as a href.
  343. var objUrl = window.URL.createObjectURL(data);
  344. // Simulates a link to it and click to dowload.
  345. var a = document.createElement("a");
  346. document.body.appendChild(a);
  347. a.style.display = "none";
  348. a.href = objUrl;
  349. a.download = "envmap.babylon.hdr";
  350. a.click();
  351. };
  352. HDRCubeTexture.generateBabylonHDR(url, size, callback, onError);
  353. };
  354. /**
  355. * Serializes the data contained in the texture in a binary format.
  356. * This can be used to prevent the long loading tie associated with creating the seamless texture as well
  357. * as the spherical used in the lighting.
  358. * @param url The HDR file url.
  359. * @param size The size of the texture data to generate (one of the cubemap face desired width).
  360. * @param onError Method called if any error happens during download.
  361. * @return The packed binary data.
  362. */
  363. HDRCubeTexture.generateBabylonHDR = function (url, size, callback, onError) {
  364. if (onError === void 0) { onError = null; }
  365. // Needs the url tho create the texture.
  366. if (!url) {
  367. return null;
  368. }
  369. // Check Power of two size.
  370. if (!BABYLON.Tools.IsExponentOfTwo(size)) {
  371. return null;
  372. }
  373. var getDataCallback = function (dataBuffer) {
  374. // Extract the raw linear data.
  375. var cubeData = BABYLON.Internals.HDRTools.GetCubeMapTextureData(dataBuffer, size);
  376. // Generate harmonics if needed.
  377. var sphericalPolynomial = BABYLON.Internals.CubeMapToSphericalPolynomialTools.ConvertCubeMapToSphericalPolynomial(cubeData);
  378. // Generate seamless faces
  379. var mipGeneratorArray = [];
  380. // Data are known to be in +X +Y +Z -X -Y -Z
  381. // mipmmapGenerator data is expected to be order in +X -X +Y -Y +Z -Z
  382. mipGeneratorArray.push(cubeData.right); // +X
  383. mipGeneratorArray.push(cubeData.left); // -X
  384. mipGeneratorArray.push(cubeData.up); // +Y
  385. mipGeneratorArray.push(cubeData.down); // -Y
  386. mipGeneratorArray.push(cubeData.front); // +Z
  387. mipGeneratorArray.push(cubeData.back); // -Z
  388. // Custom setup of the generator matching with the PBR shader values.
  389. var generator = new BABYLON.Internals.PMREMGenerator(mipGeneratorArray, size, size, 0, 3, true, 2048, 0.25, false, true);
  390. var mippedData = generator.filterCubeMap();
  391. // Compute required byte length.
  392. var byteLength = 1 * 4; // Raw Data Version int32.
  393. byteLength += 4; // CubeMap max mip face size int32.
  394. byteLength += (9 * 3 * 4); // Spherical polynomial byte length 9 Vector 3 of floats.
  395. // Add data size.
  396. byteLength += 4; // Number of mip levels int32.
  397. for (var level = 0; level < mippedData.length; level++) {
  398. var mipSize = size >> level;
  399. byteLength += (6 * mipSize * mipSize * 3 * 4); // 6 faces of size squared rgb float pixels.
  400. }
  401. // Prepare binary structure.
  402. var buffer = new ArrayBuffer(byteLength);
  403. var intArrayView = new Int32Array(buffer);
  404. var floatArrayView = new Float32Array(buffer);
  405. // Fill header.
  406. intArrayView[0] = 1; // Version 1.
  407. intArrayView[1] = size; // CubeMap max mip face size.
  408. // Fill polynomial information.
  409. sphericalPolynomial.x.toArray(floatArrayView, 2);
  410. sphericalPolynomial.y.toArray(floatArrayView, 5);
  411. sphericalPolynomial.z.toArray(floatArrayView, 8);
  412. sphericalPolynomial.xx.toArray(floatArrayView, 11);
  413. sphericalPolynomial.yy.toArray(floatArrayView, 14);
  414. sphericalPolynomial.zz.toArray(floatArrayView, 17);
  415. sphericalPolynomial.xy.toArray(floatArrayView, 20);
  416. sphericalPolynomial.yz.toArray(floatArrayView, 23);
  417. sphericalPolynomial.zx.toArray(floatArrayView, 26);
  418. // Fill pixel data.
  419. intArrayView[29] = mippedData.length; // Number of mip levels.
  420. var startIndex = 30;
  421. for (var level = 0; level < mippedData.length; level++) {
  422. // Fill each pixel of the mip level.
  423. var faceSize = Math.pow(size >> level, 2) * 3;
  424. for (var faceIndex = 0; faceIndex < 6; faceIndex++) {
  425. floatArrayView.set(mippedData[level][faceIndex], startIndex);
  426. startIndex += faceSize;
  427. }
  428. }
  429. // Callback.
  430. callback(buffer);
  431. };
  432. // Download and process.
  433. BABYLON.Tools.LoadFile(url, function (data) {
  434. getDataCallback(data);
  435. }, null, null, true, onError);
  436. };
  437. HDRCubeTexture._facesMapping = [
  438. "right",
  439. "up",
  440. "front",
  441. "left",
  442. "down",
  443. "back"
  444. ];
  445. return HDRCubeTexture;
  446. }(BABYLON.BaseTexture));
  447. BABYLON.HDRCubeTexture = HDRCubeTexture;
  448. })(BABYLON || (BABYLON = {}));