babylon.environmentTextureTools.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. module BABYLON {
  2. /**
  3. * Raw texture data and descriptor sufficient for WebGL texture upload
  4. */
  5. export interface EnvironmentTextureInfo {
  6. /**
  7. * Version of the environment map
  8. */
  9. version: number;
  10. /**
  11. * Width of image
  12. */
  13. width: number;
  14. /**
  15. * Irradiance information stored in the file.
  16. */
  17. irradiance: any;
  18. /**
  19. * Specular information stored in the file.
  20. */
  21. specular: any;
  22. }
  23. /**
  24. * Defines One Image in the file. It requires only the position in the file
  25. * as well as the length.
  26. */
  27. interface BufferImageData {
  28. /**
  29. * Length of the image data.
  30. */
  31. length: number;
  32. /**
  33. * Position of the data from the null terminator delimiting the end of the JSON.
  34. */
  35. position: number;
  36. }
  37. /**
  38. * Defines the specular data enclosed in the file.
  39. * This corresponds to the version 1 of the data.
  40. */
  41. interface EnvironmentTextureSpecularInfoV1 {
  42. /**
  43. * Defines where the specular Payload is located. It is a runtime value only not stored in the file.
  44. */
  45. specularDataPosition?: number;
  46. /**
  47. * This contains all the images data needed to reconstruct the cubemap.
  48. */
  49. mipmaps: Array<BufferImageData>
  50. }
  51. /**
  52. * Defines the required storage to save the environment irradiance information.
  53. */
  54. interface EnvironmentTextureIrradianceInfoV1 {
  55. polynomials: boolean;
  56. l00: Array<number>;
  57. l1_1: Array<number>;
  58. l10: Array<number>;
  59. l11: Array<number>;
  60. l2_2: Array<number>;
  61. l2_1: Array<number>;
  62. l20: Array<number>;
  63. l21: Array<number>;
  64. l22: Array<number>;
  65. x: Array<number>;
  66. y: Array<number>;
  67. z: Array<number>;
  68. xx: Array<number>;
  69. yy: Array<number>;
  70. zz: Array<number>;
  71. yz: Array<number>;
  72. zx: Array<number>;
  73. xy: Array<number>;
  74. }
  75. /**
  76. * Sets of helpers addressing the serialization and deserialization of environment texture
  77. * stored in a BabylonJS env file.
  78. * Those files are usually stored as .env files.
  79. */
  80. export class EnvironmentTextureTools {
  81. /**
  82. * Magic number identifying the env file.
  83. */
  84. private static _MagicBytes = [0x86, 0x16, 0x87, 0x96, 0xf6, 0xd6, 0x96, 0x36];
  85. /**
  86. * Gets the environment info from an env file.
  87. * @param data The array buffer containing the .env bytes.
  88. * @returns the environment file info (the json header) if successfully parsed.
  89. */
  90. public static GetEnvInfo(data: ArrayBuffer): Nullable<EnvironmentTextureInfo> {
  91. let dataView = new DataView(data);
  92. let pos = 0;
  93. for (let i = 0; i < EnvironmentTextureTools._MagicBytes.length; i++) {
  94. if (dataView.getUint8(pos++) !== EnvironmentTextureTools._MagicBytes[i]) {
  95. Tools.Error('Not a babylon environment map');
  96. return null;
  97. }
  98. }
  99. // Read json manifest - collect characters up to null terminator
  100. let manifestString = '';
  101. let charCode = 0x00;
  102. while ((charCode = dataView.getUint8(pos++))) {
  103. manifestString += String.fromCharCode(charCode);
  104. }
  105. let manifest: EnvironmentTextureInfo = JSON.parse(manifestString);
  106. if (manifest.specular) {
  107. // Extend the header with the position of the payload.
  108. manifest.specular.specularDataPosition = pos;
  109. }
  110. return manifest;
  111. }
  112. /**
  113. * Creates an environment texture from a loaded cube texture.
  114. * @param texture defines the cube texture to convert in env file
  115. * @return a promise containing the environment data if succesfull.
  116. */
  117. public static CreateEnvTextureAsync(texture: CubeTexture): Promise<ArrayBuffer> {
  118. let internalTexture = texture.getInternalTexture();
  119. if (!internalTexture) {
  120. return Promise.reject("The cube texture is invalid.");
  121. }
  122. if (!texture._prefiltered) {
  123. return Promise.reject("The cube texture is invalid (not prefiltered).");
  124. }
  125. let engine = internalTexture.getEngine();
  126. if (engine && engine.premultipliedAlpha) {
  127. return Promise.reject("Env texture can only be created when the engine is created with the premultipliedAlpa option.");
  128. }
  129. let canvas = engine.getRenderingCanvas();
  130. if (!canvas) {
  131. return Promise.reject("Env texture can only be created when the engine is associated to a canvas.");
  132. }
  133. let textureType = Engine.TEXTURETYPE_FLOAT;
  134. if (!engine.getCaps().textureFloatRender) {
  135. textureType = Engine.TEXTURETYPE_HALF_FLOAT;
  136. if (!engine.getCaps().textureHalfFloatRender) {
  137. return Promise.reject("Env texture can only be created when the browser supports half float or full float rendering.");
  138. }
  139. }
  140. let cubeWidth = internalTexture.width;
  141. let hostingScene = new Scene(engine);
  142. let specularTextures: { [key: number]: ArrayBuffer } = { };
  143. let promises: Promise<void>[] = [];
  144. // Read and collect all mipmaps data from the cube.
  145. let mipmapsCount = Scalar.Log2(internalTexture.width);
  146. mipmapsCount = Math.round(mipmapsCount);
  147. for (let i = 0; i <= mipmapsCount; i++) {
  148. let faceWidth = Math.pow(2, mipmapsCount - i);
  149. // All faces of the cube.
  150. for (let face = 0; face < 6; face++) {
  151. let data = texture.readPixels(face, i);
  152. // Creates a temp texture with the face data.
  153. let tempTexture = engine.createRawTexture(data, faceWidth, faceWidth, Engine.TEXTUREFORMAT_RGBA, false, false, Texture.NEAREST_SAMPLINGMODE, null, textureType);
  154. // And rgbmEncode them.
  155. let promise = new Promise<void>((resolve, reject) => {
  156. let rgbmPostProcess = new PostProcess("rgbmEncode", "rgbmEncode", null, null, 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, undefined, Engine.TEXTURETYPE_UNSIGNED_INT, undefined, null, false);
  157. rgbmPostProcess.getEffect().executeWhenCompiled(() => {
  158. rgbmPostProcess.onApply = (effect) => {
  159. effect._bindTexture("textureSampler", tempTexture);
  160. }
  161. // As the process needs to happen on the main canvas, keep track of the current size
  162. let currentW = engine.getRenderWidth();
  163. let currentH = engine.getRenderHeight();
  164. // Set the desired size for the texture
  165. engine.setSize(faceWidth, faceWidth);
  166. hostingScene.postProcessManager.directRender([rgbmPostProcess], null);
  167. // Reading datas from WebGL
  168. canvas!.toBlob((blob) => {
  169. let fileReader = new FileReader();
  170. fileReader.onload = (event) => {
  171. let arrayBuffer = event.target!.result as ArrayBuffer;
  172. specularTextures[i * 6 + face] = arrayBuffer;
  173. resolve();
  174. };
  175. fileReader.readAsArrayBuffer(blob!);
  176. });
  177. // Reapply the previous canvas size
  178. engine.setSize(currentW, currentH);
  179. });
  180. });
  181. promises.push(promise);
  182. }
  183. }
  184. // Once all the textures haves been collected as RGBM stored in PNGs
  185. return Promise.all(promises).then(() => {
  186. // We can delete the hosting scene keeping track of all the creation objects
  187. hostingScene.dispose();
  188. // Creates the json header for the env texture
  189. let info: EnvironmentTextureInfo = {
  190. version: 1,
  191. width: cubeWidth,
  192. irradiance: this._CreateEnvTextureIrradiance(texture),
  193. specular: {
  194. mipmaps: []
  195. }
  196. };
  197. // Sets the specular image data information
  198. let position = 0;
  199. for (let i = 0; i <= mipmapsCount; i++) {
  200. for (let face = 0; face < 6; face++) {
  201. let byteLength = specularTextures[i * 6 + face].byteLength;
  202. info.specular.mipmaps.push({
  203. length: byteLength,
  204. position: position
  205. });
  206. position += byteLength;
  207. }
  208. }
  209. // Encode the JSON as an array buffer
  210. let infoString = JSON.stringify(info);
  211. let infoBuffer = new ArrayBuffer(infoString.length + 1);
  212. let infoView = new Uint8Array(infoBuffer); // Limited to ascii subset matching unicode.
  213. for (let i= 0, strLen = infoString.length; i < strLen; i++) {
  214. infoView[i] = infoString.charCodeAt(i);
  215. }
  216. // Ends up with a null terminator for easier parsing
  217. infoView[infoString.length] = 0x00;
  218. // Computes the final required size and creates the storage
  219. let totalSize = EnvironmentTextureTools._MagicBytes.length + position + infoBuffer.byteLength;
  220. let finalBuffer = new ArrayBuffer(totalSize);
  221. let finalBufferView = new Uint8Array(finalBuffer);
  222. let dataView = new DataView(finalBuffer);
  223. // Copy the magic bytes identifying the file in
  224. let pos = 0;
  225. for (let i = 0; i < EnvironmentTextureTools._MagicBytes.length; i++) {
  226. dataView.setUint8(pos++, EnvironmentTextureTools._MagicBytes[i]);
  227. }
  228. // Add the json info
  229. finalBufferView.set(new Uint8Array(infoBuffer), pos);
  230. pos += infoBuffer.byteLength;
  231. // Finally inserts the texture data
  232. for (let i = 0; i <= mipmapsCount; i++) {
  233. for (let face = 0; face < 6; face++) {
  234. let dataBuffer = specularTextures[i * 6 + face];
  235. finalBufferView.set(new Uint8Array(dataBuffer), pos);
  236. pos += dataBuffer.byteLength;
  237. }
  238. }
  239. // Voila
  240. return finalBuffer;
  241. });
  242. }
  243. /**
  244. * Creates a JSON representation of the spherical data.
  245. * @param texture defines the texture containing the polynomials
  246. * @return the JSON representation of the spherical info
  247. */
  248. private static _CreateEnvTextureIrradiance(texture: CubeTexture) : Nullable<EnvironmentTextureIrradianceInfoV1> {
  249. let polynmials = texture.sphericalPolynomial;
  250. if (polynmials == null) {
  251. return null;
  252. }
  253. return {
  254. polynomials: true,
  255. x: [polynmials.x.x, polynmials.x.y, polynmials.x.z],
  256. y: [polynmials.y.x, polynmials.y.y, polynmials.y.z],
  257. z: [polynmials.z.x, polynmials.z.y, polynmials.z.z],
  258. xx: [polynmials.xx.x, polynmials.xx.y, polynmials.xx.z],
  259. yy: [polynmials.yy.x, polynmials.yy.y, polynmials.yy.z],
  260. zz: [polynmials.zz.x, polynmials.zz.y, polynmials.zz.z],
  261. yz: [polynmials.yz.x, polynmials.yz.y, polynmials.yz.z],
  262. zx: [polynmials.zx.x, polynmials.zx.y, polynmials.zx.z],
  263. xy: [polynmials.xy.x, polynmials.xy.y, polynmials.xy.z]
  264. } as any;
  265. }
  266. /**
  267. * Uploads the texture info contained in the env file to te GPU.
  268. * @param texture defines the internal texture to upload to
  269. * @param arrayBuffer defines the buffer cotaining the data to load
  270. * @param info defines the texture info retrieved through the GetEnvInfo method
  271. * @returns a promise
  272. */
  273. public static UploadLevelsAsync(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): Promise<void> {
  274. if (info.version !== 1) {
  275. Tools.Warn('Unsupported babylon environment map version "' + info.version + '"');
  276. }
  277. let specularInfo = info.specular as EnvironmentTextureSpecularInfoV1;
  278. if (!specularInfo) {
  279. // Nothing else parsed so far
  280. return Promise.resolve();
  281. }
  282. // Double checks the enclosed info
  283. let mipmapsCount = Scalar.Log2(info.width);
  284. mipmapsCount = Math.round(mipmapsCount) + 1;
  285. if (specularInfo.mipmaps.length !== 6 * mipmapsCount) {
  286. Tools.Warn('Unsupported specular mipmaps number "' + specularInfo.mipmaps.length + '"');
  287. }
  288. // Gets everything ready.
  289. let engine = texture.getEngine();
  290. let expandTexture = false;
  291. let generateNonLODTextures = false;
  292. let rgbmPostProcess: Nullable<PostProcess> = null;
  293. let cubeRtt: Nullable<InternalTexture> = null;
  294. let lodTextures: Nullable<{ [lod: number]: BaseTexture}> = null;
  295. let caps = engine.getCaps();
  296. texture.format = Engine.TEXTUREFORMAT_RGBA;
  297. texture.type = Engine.TEXTURETYPE_UNSIGNED_INT;
  298. texture.samplingMode = Texture.TRILINEAR_SAMPLINGMODE;
  299. // Add extra process if texture lod is not supported
  300. if (!caps.textureLOD) {
  301. expandTexture = false;
  302. generateNonLODTextures = true;
  303. lodTextures = { };
  304. }
  305. // in webgl 1 there are no ways to either render or copy lod level information for float textures.
  306. else if (engine.webGLVersion < 2) {
  307. expandTexture = false;
  308. }
  309. // If half float available we can uncompress the texture
  310. else if (caps.textureHalfFloatRender && caps.textureHalfFloatLinearFiltering) {
  311. expandTexture = true;
  312. texture.type = Engine.TEXTURETYPE_HALF_FLOAT;
  313. }
  314. // If full float available we can uncompress the texture
  315. else if (caps.textureFloatRender && caps.textureFloatLinearFiltering) {
  316. expandTexture = true;
  317. texture.type = Engine.TEXTURETYPE_FLOAT;
  318. }
  319. // Expand the texture if possible
  320. if (expandTexture) {
  321. // Simply run through the decode PP
  322. rgbmPostProcess = new PostProcess("rgbmDecode", "rgbmDecode", null, null, 1, null, Texture.TRILINEAR_SAMPLINGMODE, engine, false, undefined, texture.type, undefined, null, false);
  323. texture._isRGBM = false;
  324. texture.invertY = false;
  325. cubeRtt = engine.createRenderTargetCubeTexture(texture.width, {
  326. generateDepthBuffer: false,
  327. generateMipMaps: true,
  328. generateStencilBuffer: false,
  329. samplingMode: Texture.TRILINEAR_SAMPLINGMODE,
  330. type: texture.type,
  331. format: Engine.TEXTUREFORMAT_RGBA
  332. });
  333. }
  334. else {
  335. texture._isRGBM = true;
  336. texture.invertY = true;
  337. // In case of missing support, applies the same patch than DDS files.
  338. if (generateNonLODTextures) {
  339. let mipSlices = 3;
  340. let scale = texture._lodGenerationScale;
  341. let offset = texture._lodGenerationOffset;
  342. for (let i = 0; i < mipSlices; i++) {
  343. //compute LOD from even spacing in smoothness (matching shader calculation)
  344. let smoothness = i / (mipSlices - 1);
  345. let roughness = 1 - smoothness;
  346. let minLODIndex = offset; // roughness = 0
  347. let maxLODIndex = Scalar.Log2(info.width) * scale + offset; // roughness = 1
  348. let lodIndex = minLODIndex + (maxLODIndex - minLODIndex) * roughness;
  349. let mipmapIndex = Math.round(Math.min(Math.max(lodIndex, 0), maxLODIndex));
  350. let glTextureFromLod = new InternalTexture(engine, InternalTexture.DATASOURCE_TEMP);
  351. glTextureFromLod.isCube = true;
  352. glTextureFromLod.invertY = true;
  353. glTextureFromLod.generateMipMaps = false;
  354. engine.updateTextureSamplingMode(Texture.LINEAR_LINEAR, glTextureFromLod);
  355. // Wrap in a base texture for easy binding.
  356. let lodTexture = new BaseTexture(null);
  357. lodTexture.isCube = true;
  358. lodTexture._texture = glTextureFromLod;
  359. lodTextures![mipmapIndex] = lodTexture;
  360. switch (i) {
  361. case 0:
  362. texture._lodTextureLow = lodTexture;
  363. break;
  364. case 1:
  365. texture._lodTextureMid = lodTexture;
  366. break;
  367. case 2:
  368. texture._lodTextureHigh = lodTexture;
  369. break;
  370. }
  371. }
  372. }
  373. }
  374. let promises: Promise<void>[] = [];
  375. // All mipmaps
  376. for (let i = 0; i < mipmapsCount; i++) {
  377. // All faces
  378. for (let face = 0; face < 6; face++) {
  379. // Retrieves the face data
  380. let imageData = specularInfo.mipmaps[i * 6 + face];
  381. let bytes = new Uint8Array(arrayBuffer, specularInfo.specularDataPosition! + imageData.position, imageData.length);
  382. // Constructs an image element from bytes
  383. let blob = new Blob([bytes], { type: 'image/png' });
  384. let url = URL.createObjectURL(blob);
  385. let image = new Image();
  386. image.src = url;
  387. // Enqueue promise to upload to the texture.
  388. let promise = new Promise<void>((resolve, reject) => {;
  389. image.onload = () => {
  390. if (expandTexture) {
  391. let tempTexture = engine.createTexture(null, true, true, null, Texture.NEAREST_SAMPLINGMODE, null,
  392. (message) => {
  393. reject(message);
  394. },
  395. image);
  396. rgbmPostProcess!.getEffect().executeWhenCompiled(() => {
  397. // Uncompress the data to a RTT
  398. rgbmPostProcess!.onApply = (effect) => {
  399. effect._bindTexture("textureSampler", tempTexture);
  400. effect.setFloat2("scale", 1, 1);
  401. }
  402. engine.scenes[0].postProcessManager.directRender([rgbmPostProcess!], cubeRtt, true, face, i);
  403. // Cleanup
  404. engine.restoreDefaultFramebuffer();
  405. tempTexture.dispose();
  406. window.URL.revokeObjectURL(url);
  407. resolve();
  408. });
  409. }
  410. else {
  411. engine._uploadImageToTexture(texture, face, i, image);
  412. // Upload the face to the none lod texture support
  413. if (generateNonLODTextures) {
  414. let lodTexture = lodTextures![i];
  415. if (lodTexture) {
  416. engine._uploadImageToTexture(lodTexture._texture!, face, 0, image);
  417. }
  418. }
  419. resolve();
  420. }
  421. };
  422. image.onerror = (error) => {
  423. reject(error);
  424. };
  425. });
  426. promises.push(promise);
  427. }
  428. }
  429. // Once all done, finishes the cleanup and return
  430. return Promise.all(promises).then(() => {
  431. // Relase temp RTT.
  432. if (cubeRtt) {
  433. engine._releaseFramebufferObjects(cubeRtt);
  434. cubeRtt._swapAndDie(texture);
  435. }
  436. // Relase temp Post Process.
  437. if (rgbmPostProcess) {
  438. rgbmPostProcess.dispose();
  439. }
  440. // Flag internal texture as ready in case they are in use.
  441. if (generateNonLODTextures) {
  442. if (texture._lodTextureHigh && texture._lodTextureHigh._texture) {
  443. texture._lodTextureHigh._texture.isReady = true;
  444. }
  445. if (texture._lodTextureMid && texture._lodTextureMid._texture) {
  446. texture._lodTextureMid._texture.isReady = true;
  447. }
  448. if (texture._lodTextureLow && texture._lodTextureLow._texture) {
  449. texture._lodTextureLow._texture.isReady = true;
  450. }
  451. }
  452. });
  453. }
  454. /**
  455. * Uploads spherical polynomials information to the texture.
  456. * @param texture defines the texture we are trying to upload the information to
  457. * @param arrayBuffer defines the array buffer holding the data
  458. * @param info defines the environment texture info retrieved through the GetEnvInfo method
  459. */
  460. public static UploadPolynomials(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): void {
  461. if (info.version !== 1) {
  462. Tools.Warn('Unsupported babylon environment map version "' + info.version + '"');
  463. }
  464. let irradianceInfo = info.irradiance as EnvironmentTextureIrradianceInfoV1;
  465. if (!irradianceInfo) {
  466. return;
  467. }
  468. //harmonics now represent radiance
  469. texture._sphericalPolynomial = new SphericalPolynomial();
  470. if (irradianceInfo.polynomials) {
  471. EnvironmentTextureTools._UploadSP(irradianceInfo, texture._sphericalPolynomial);
  472. }
  473. else {
  474. // convert From SH to SP.
  475. EnvironmentTextureTools._ConvertSHIrradianceToLambertianRadiance(irradianceInfo);
  476. EnvironmentTextureTools._ConvertSHToSP(irradianceInfo, texture._sphericalPolynomial);
  477. }
  478. }
  479. /**
  480. * Upload spherical polynomial coefficients to the texture
  481. * @param polynmials Spherical polynmial coefficients (9)
  482. * @param outPolynomialCoefficents Polynomial coefficients (9) object to store result
  483. */
  484. private static _UploadSP(polynmials: EnvironmentTextureIrradianceInfoV1, outPolynomialCoefficents: SphericalPolynomial) {
  485. outPolynomialCoefficents.x.x = polynmials.x[0];
  486. outPolynomialCoefficents.x.y = polynmials.x[1];
  487. outPolynomialCoefficents.x.z = polynmials.x[2];
  488. outPolynomialCoefficents.y.x = polynmials.y[0];
  489. outPolynomialCoefficents.y.y = polynmials.y[1];
  490. outPolynomialCoefficents.y.z = polynmials.y[2];
  491. outPolynomialCoefficents.z.x = polynmials.z[0];
  492. outPolynomialCoefficents.z.y = polynmials.z[1];
  493. outPolynomialCoefficents.z.z = polynmials.z[2];
  494. //xx
  495. outPolynomialCoefficents.xx.x = polynmials.xx[0];
  496. outPolynomialCoefficents.xx.y = polynmials.xx[1];
  497. outPolynomialCoefficents.xx.z = polynmials.xx[2];
  498. outPolynomialCoefficents.yy.x = polynmials.yy[0];
  499. outPolynomialCoefficents.yy.y = polynmials.yy[1];
  500. outPolynomialCoefficents.yy.z = polynmials.yy[2];
  501. outPolynomialCoefficents.zz.x = polynmials.zz[0];
  502. outPolynomialCoefficents.zz.y = polynmials.zz[1];
  503. outPolynomialCoefficents.zz.z = polynmials.zz[2];
  504. //yz
  505. outPolynomialCoefficents.yz.x = polynmials.yz[0];
  506. outPolynomialCoefficents.yz.y = polynmials.yz[1];
  507. outPolynomialCoefficents.yz.z = polynmials.yz[2];
  508. outPolynomialCoefficents.zx.x = polynmials.zx[0];
  509. outPolynomialCoefficents.zx.y = polynmials.zx[1];
  510. outPolynomialCoefficents.zx.z = polynmials.zx[2];
  511. outPolynomialCoefficents.xy.x = polynmials.xy[0];
  512. outPolynomialCoefficents.xy.y = polynmials.xy[1];
  513. outPolynomialCoefficents.xy.z = polynmials.xy[2];
  514. }
  515. /**
  516. * Convert from irradiance to outgoing radiance for Lambertian BDRF, suitable for efficient shader evaluation.
  517. * L = (1/pi) * E * rho
  518. *
  519. * This is done by an additional scale by 1/pi, so is a fairly trivial operation but important conceptually.
  520. * @param harmonics Spherical harmonic coefficients (9)
  521. */
  522. private static _ConvertSHIrradianceToLambertianRadiance(harmonics: any): void {
  523. let scaleFactor = 1 / Math.PI;
  524. // The resultant SH now represents outgoing radiance, so includes the Lambert 1/pi normalisation factor but without albedo (rho) applied
  525. // (The pixel shader must apply albedo after texture fetches, etc).
  526. harmonics.l00[0] *= scaleFactor;
  527. harmonics.l00[1] *= scaleFactor;
  528. harmonics.l00[2] *= scaleFactor;
  529. harmonics.l1_1[0] *= scaleFactor;
  530. harmonics.l1_1[1] *= scaleFactor;
  531. harmonics.l1_1[2] *= scaleFactor;
  532. harmonics.l10[0] *= scaleFactor;
  533. harmonics.l10[1] *= scaleFactor;
  534. harmonics.l10[2] *= scaleFactor;
  535. harmonics.l11[0] *= scaleFactor;
  536. harmonics.l11[1] *= scaleFactor;
  537. harmonics.l11[2] *= scaleFactor;
  538. harmonics.l2_2[0] *= scaleFactor;
  539. harmonics.l2_2[1] *= scaleFactor;
  540. harmonics.l2_2[2] *= scaleFactor;
  541. harmonics.l2_1[0] *= scaleFactor;
  542. harmonics.l2_1[1] *= scaleFactor;
  543. harmonics.l2_1[2] *= scaleFactor;
  544. harmonics.l20[0] *= scaleFactor;
  545. harmonics.l20[1] *= scaleFactor;
  546. harmonics.l20[2] *= scaleFactor;
  547. harmonics.l21[0] *= scaleFactor;
  548. harmonics.l21[1] *= scaleFactor;
  549. harmonics.l21[2] *= scaleFactor;
  550. harmonics.l22[0] *= scaleFactor;
  551. harmonics.l22[1] *= scaleFactor;
  552. harmonics.l22[2] *= scaleFactor;
  553. }
  554. /**
  555. * Convert spherical harmonics to spherical polynomial coefficients
  556. * @param harmonics Spherical harmonic coefficients (9)
  557. * @param outPolynomialCoefficents Polynomial coefficients (9) object to store result
  558. */
  559. private static _ConvertSHToSP(harmonics: any, outPolynomialCoefficents: SphericalPolynomial) {
  560. let rPi = 1 / Math.PI;
  561. //x
  562. outPolynomialCoefficents.x.x = 1.02333 * harmonics.l11[0] * rPi;
  563. outPolynomialCoefficents.x.y = 1.02333 * harmonics.l11[1] * rPi;
  564. outPolynomialCoefficents.x.z = 1.02333 * harmonics.l11[2] * rPi;
  565. outPolynomialCoefficents.y.x = 1.02333 * harmonics.l1_1[0] * rPi;
  566. outPolynomialCoefficents.y.y = 1.02333 * harmonics.l1_1[1] * rPi;
  567. outPolynomialCoefficents.y.z = 1.02333 * harmonics.l1_1[2] * rPi;
  568. outPolynomialCoefficents.z.x = 1.02333 * harmonics.l10[0] * rPi;
  569. outPolynomialCoefficents.z.y = 1.02333 * harmonics.l10[1] * rPi;
  570. outPolynomialCoefficents.z.z = 1.02333 * harmonics.l10[2] * rPi;
  571. //xx
  572. outPolynomialCoefficents.xx.x = (0.886277 * harmonics.l00[0] - 0.247708 * harmonics.l20[0] + 0.429043 * harmonics.l22[0]) * rPi;
  573. outPolynomialCoefficents.xx.y = (0.886277 * harmonics.l00[1] - 0.247708 * harmonics.l20[1] + 0.429043 * harmonics.l22[1]) * rPi;
  574. outPolynomialCoefficents.xx.z = (0.886277 * harmonics.l00[2] - 0.247708 * harmonics.l20[2] + 0.429043 * harmonics.l22[2]) * rPi;
  575. outPolynomialCoefficents.yy.x = (0.886277 * harmonics.l00[0] - 0.247708 * harmonics.l20[0] - 0.429043 * harmonics.l22[0]) * rPi;
  576. outPolynomialCoefficents.yy.y = (0.886277 * harmonics.l00[1] - 0.247708 * harmonics.l20[1] - 0.429043 * harmonics.l22[1]) * rPi;
  577. outPolynomialCoefficents.yy.z = (0.886277 * harmonics.l00[2] - 0.247708 * harmonics.l20[2] - 0.429043 * harmonics.l22[2]) * rPi;
  578. outPolynomialCoefficents.zz.x = (0.886277 * harmonics.l00[0] + 0.495417 * harmonics.l20[0]) * rPi;
  579. outPolynomialCoefficents.zz.y = (0.886277 * harmonics.l00[1] + 0.495417 * harmonics.l20[1]) * rPi;
  580. outPolynomialCoefficents.zz.z = (0.886277 * harmonics.l00[2] + 0.495417 * harmonics.l20[2]) * rPi;
  581. //yz
  582. outPolynomialCoefficents.yz.x = 0.858086 * harmonics.l2_1[0] * rPi;
  583. outPolynomialCoefficents.yz.y = 0.858086 * harmonics.l2_1[1] * rPi;
  584. outPolynomialCoefficents.yz.z = 0.858086 * harmonics.l2_1[2] * rPi;
  585. outPolynomialCoefficents.zx.x = 0.858086 * harmonics.l21[0] * rPi;
  586. outPolynomialCoefficents.zx.y = 0.858086 * harmonics.l21[1] * rPi;
  587. outPolynomialCoefficents.zx.z = 0.858086 * harmonics.l21[2] * rPi;
  588. outPolynomialCoefficents.xy.x = 0.858086 * harmonics.l2_2[0] * rPi;
  589. outPolynomialCoefficents.xy.y = 0.858086 * harmonics.l2_2[1] * rPi;
  590. outPolynomialCoefficents.xy.z = 0.858086 * harmonics.l2_2[2] * rPi;
  591. }
  592. }
  593. }