sphericalPolynomial.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /* eslint-disable @typescript-eslint/naming-convention */
  2. import { Vector3 } from "../Maths/math.vector.js";
  3. import { TmpVectors } from "./math.js";
  4. // https://dickyjim.wordpress.com/2013/09/04/spherical-harmonics-for-beginners/
  5. // http://silviojemma.com/public/papers/lighting/spherical-harmonic-lighting.pdf
  6. // https://www.ppsloan.org/publications/StupidSH36.pdf
  7. // http://cseweb.ucsd.edu/~ravir/papers/envmap/envmap.pdf
  8. // https://www.ppsloan.org/publications/SHJCGT.pdf
  9. // https://www.ppsloan.org/publications/shdering.pdf
  10. // https://google.github.io/filament/Filament.md.html#annex/sphericalharmonics
  11. // https://patapom.com/blog/SHPortal/
  12. // https://imdoingitwrong.wordpress.com/2011/04/14/spherical-harmonics-wtf/
  13. // Using real SH basis:
  14. // m>0 m m
  15. // y = sqrt(2) * K * P * cos(m*phi) * cos(theta)
  16. // l l l
  17. //
  18. // m<0 m |m|
  19. // y = sqrt(2) * K * P * sin(m*phi) * cos(theta)
  20. // l l l
  21. //
  22. // m=0 0 0
  23. // y = K * P * trigono terms
  24. // l l l
  25. //
  26. // m (2l + 1)(l - |m|)!
  27. // K = sqrt(------------------)
  28. // l 4pi(l + |m|)!
  29. //
  30. // and P by recursion:
  31. //
  32. // P00(x) = 1
  33. // P01(x) = x
  34. // Pll(x) = (-1^l)(2l - 1)!!(1-x*x)^(1/2)
  35. // ((2l - 1)x[Pl-1/m]-(l + m - 1)[Pl-2/m])
  36. // Plm(x) = ---------------------------------------
  37. // l - m
  38. // Leaving the trigonometric terms aside we can precompute the constants to :
  39. const SH3ylmBasisConstants = [
  40. Math.sqrt(1 / (4 * Math.PI)),
  41. -Math.sqrt(3 / (4 * Math.PI)),
  42. Math.sqrt(3 / (4 * Math.PI)),
  43. -Math.sqrt(3 / (4 * Math.PI)),
  44. Math.sqrt(15 / (4 * Math.PI)),
  45. -Math.sqrt(15 / (4 * Math.PI)),
  46. Math.sqrt(5 / (16 * Math.PI)),
  47. -Math.sqrt(15 / (4 * Math.PI)),
  48. Math.sqrt(15 / (16 * Math.PI)), // l22
  49. ];
  50. // cm = cos(m * phi)
  51. // sm = sin(m * phi)
  52. // {x,y,z} = {cos(phi)sin(theta), sin(phi)sin(theta), cos(theta)}
  53. // By recursion on using trigo identities:
  54. const SH3ylmBasisTrigonometricTerms = [
  55. () => 1,
  56. (direction) => direction.y,
  57. (direction) => direction.z,
  58. (direction) => direction.x,
  59. (direction) => direction.x * direction.y,
  60. (direction) => direction.y * direction.z,
  61. (direction) => 3 * direction.z * direction.z - 1,
  62. (direction) => direction.x * direction.z,
  63. (direction) => direction.x * direction.x - direction.y * direction.y, // l22
  64. ];
  65. // Wrap the full compute
  66. const applySH3 = (lm, direction) => {
  67. return SH3ylmBasisConstants[lm] * SH3ylmBasisTrigonometricTerms[lm](direction);
  68. };
  69. // Derived from the integration of the a kernel convolution to SH.
  70. // Great explanation here: https://patapom.com/blog/SHPortal/#about-distant-radiance-and-irradiance-environments
  71. const SHCosKernelConvolution = [Math.PI, (2 * Math.PI) / 3, (2 * Math.PI) / 3, (2 * Math.PI) / 3, Math.PI / 4, Math.PI / 4, Math.PI / 4, Math.PI / 4, Math.PI / 4];
  72. /**
  73. * Class representing spherical harmonics coefficients to the 3rd degree
  74. */
  75. export class SphericalHarmonics {
  76. constructor() {
  77. /**
  78. * Defines whether or not the harmonics have been prescaled for rendering.
  79. */
  80. this.preScaled = false;
  81. /**
  82. * The l0,0 coefficients of the spherical harmonics
  83. */
  84. this.l00 = Vector3.Zero();
  85. /**
  86. * The l1,-1 coefficients of the spherical harmonics
  87. */
  88. this.l1_1 = Vector3.Zero();
  89. /**
  90. * The l1,0 coefficients of the spherical harmonics
  91. */
  92. this.l10 = Vector3.Zero();
  93. /**
  94. * The l1,1 coefficients of the spherical harmonics
  95. */
  96. this.l11 = Vector3.Zero();
  97. /**
  98. * The l2,-2 coefficients of the spherical harmonics
  99. */
  100. this.l2_2 = Vector3.Zero();
  101. /**
  102. * The l2,-1 coefficients of the spherical harmonics
  103. */
  104. this.l2_1 = Vector3.Zero();
  105. /**
  106. * The l2,0 coefficients of the spherical harmonics
  107. */
  108. this.l20 = Vector3.Zero();
  109. /**
  110. * The l2,1 coefficients of the spherical harmonics
  111. */
  112. this.l21 = Vector3.Zero();
  113. /**
  114. * The l2,2 coefficients of the spherical harmonics
  115. */
  116. this.l22 = Vector3.Zero();
  117. }
  118. /**
  119. * Adds a light to the spherical harmonics
  120. * @param direction the direction of the light
  121. * @param color the color of the light
  122. * @param deltaSolidAngle the delta solid angle of the light
  123. */
  124. addLight(direction, color, deltaSolidAngle) {
  125. TmpVectors.Vector3[0].set(color.r, color.g, color.b);
  126. const colorVector = TmpVectors.Vector3[0];
  127. const c = TmpVectors.Vector3[1];
  128. colorVector.scaleToRef(deltaSolidAngle, c);
  129. c.scaleToRef(applySH3(0, direction), TmpVectors.Vector3[2]);
  130. this.l00.addInPlace(TmpVectors.Vector3[2]);
  131. c.scaleToRef(applySH3(1, direction), TmpVectors.Vector3[2]);
  132. this.l1_1.addInPlace(TmpVectors.Vector3[2]);
  133. c.scaleToRef(applySH3(2, direction), TmpVectors.Vector3[2]);
  134. this.l10.addInPlace(TmpVectors.Vector3[2]);
  135. c.scaleToRef(applySH3(3, direction), TmpVectors.Vector3[2]);
  136. this.l11.addInPlace(TmpVectors.Vector3[2]);
  137. c.scaleToRef(applySH3(4, direction), TmpVectors.Vector3[2]);
  138. this.l2_2.addInPlace(TmpVectors.Vector3[2]);
  139. c.scaleToRef(applySH3(5, direction), TmpVectors.Vector3[2]);
  140. this.l2_1.addInPlace(TmpVectors.Vector3[2]);
  141. c.scaleToRef(applySH3(6, direction), TmpVectors.Vector3[2]);
  142. this.l20.addInPlace(TmpVectors.Vector3[2]);
  143. c.scaleToRef(applySH3(7, direction), TmpVectors.Vector3[2]);
  144. this.l21.addInPlace(TmpVectors.Vector3[2]);
  145. c.scaleToRef(applySH3(8, direction), TmpVectors.Vector3[2]);
  146. this.l22.addInPlace(TmpVectors.Vector3[2]);
  147. }
  148. /**
  149. * Scales the spherical harmonics by the given amount
  150. * @param scale the amount to scale
  151. */
  152. scaleInPlace(scale) {
  153. this.l00.scaleInPlace(scale);
  154. this.l1_1.scaleInPlace(scale);
  155. this.l10.scaleInPlace(scale);
  156. this.l11.scaleInPlace(scale);
  157. this.l2_2.scaleInPlace(scale);
  158. this.l2_1.scaleInPlace(scale);
  159. this.l20.scaleInPlace(scale);
  160. this.l21.scaleInPlace(scale);
  161. this.l22.scaleInPlace(scale);
  162. }
  163. /**
  164. * Convert from incident radiance (Li) to irradiance (E) by applying convolution with the cosine-weighted hemisphere.
  165. *
  166. * ```
  167. * E_lm = A_l * L_lm
  168. * ```
  169. *
  170. * In spherical harmonics this convolution amounts to scaling factors for each frequency band.
  171. * This corresponds to equation 5 in "An Efficient Representation for Irradiance Environment Maps", where
  172. * the scaling factors are given in equation 9.
  173. */
  174. convertIncidentRadianceToIrradiance() {
  175. // Constant (Band 0)
  176. this.l00.scaleInPlace(SHCosKernelConvolution[0]);
  177. // Linear (Band 1)
  178. this.l1_1.scaleInPlace(SHCosKernelConvolution[1]);
  179. this.l10.scaleInPlace(SHCosKernelConvolution[2]);
  180. this.l11.scaleInPlace(SHCosKernelConvolution[3]);
  181. // Quadratic (Band 2)
  182. this.l2_2.scaleInPlace(SHCosKernelConvolution[4]);
  183. this.l2_1.scaleInPlace(SHCosKernelConvolution[5]);
  184. this.l20.scaleInPlace(SHCosKernelConvolution[6]);
  185. this.l21.scaleInPlace(SHCosKernelConvolution[7]);
  186. this.l22.scaleInPlace(SHCosKernelConvolution[8]);
  187. }
  188. /**
  189. * Convert from irradiance to outgoing radiance for Lambertian BDRF, suitable for efficient shader evaluation.
  190. *
  191. * ```
  192. * L = (1/pi) * E * rho
  193. * ```
  194. *
  195. * This is done by an additional scale by 1/pi, so is a fairly trivial operation but important conceptually.
  196. */
  197. convertIrradianceToLambertianRadiance() {
  198. this.scaleInPlace(1.0 / Math.PI);
  199. // The resultant SH now represents outgoing radiance, so includes the Lambert 1/pi normalisation factor but without albedo (rho) applied
  200. // (The pixel shader must apply albedo after texture fetches, etc).
  201. }
  202. /**
  203. * Integrates the reconstruction coefficients directly in to the SH preventing further
  204. * required operations at run time.
  205. *
  206. * This is simply done by scaling back the SH with Ylm constants parameter.
  207. * The trigonometric part being applied by the shader at run time.
  208. */
  209. preScaleForRendering() {
  210. this.preScaled = true;
  211. this.l00.scaleInPlace(SH3ylmBasisConstants[0]);
  212. this.l1_1.scaleInPlace(SH3ylmBasisConstants[1]);
  213. this.l10.scaleInPlace(SH3ylmBasisConstants[2]);
  214. this.l11.scaleInPlace(SH3ylmBasisConstants[3]);
  215. this.l2_2.scaleInPlace(SH3ylmBasisConstants[4]);
  216. this.l2_1.scaleInPlace(SH3ylmBasisConstants[5]);
  217. this.l20.scaleInPlace(SH3ylmBasisConstants[6]);
  218. this.l21.scaleInPlace(SH3ylmBasisConstants[7]);
  219. this.l22.scaleInPlace(SH3ylmBasisConstants[8]);
  220. }
  221. /**
  222. * update the spherical harmonics coefficients from the given array
  223. * @param data defines the 9x3 coefficients (l00, l1-1, l10, l11, l2-2, l2-1, l20, l21, l22)
  224. * @returns the spherical harmonics (this)
  225. */
  226. updateFromArray(data) {
  227. Vector3.FromArrayToRef(data[0], 0, this.l00);
  228. Vector3.FromArrayToRef(data[1], 0, this.l1_1);
  229. Vector3.FromArrayToRef(data[2], 0, this.l10);
  230. Vector3.FromArrayToRef(data[3], 0, this.l11);
  231. Vector3.FromArrayToRef(data[4], 0, this.l2_2);
  232. Vector3.FromArrayToRef(data[5], 0, this.l2_1);
  233. Vector3.FromArrayToRef(data[6], 0, this.l20);
  234. Vector3.FromArrayToRef(data[7], 0, this.l21);
  235. Vector3.FromArrayToRef(data[8], 0, this.l22);
  236. return this;
  237. }
  238. /**
  239. * update the spherical harmonics coefficients from the given floats array
  240. * @param data defines the 9x3 coefficients (l00, l1-1, l10, l11, l2-2, l2-1, l20, l21, l22)
  241. * @returns the spherical harmonics (this)
  242. */
  243. updateFromFloatsArray(data) {
  244. Vector3.FromFloatsToRef(data[0], data[1], data[2], this.l00);
  245. Vector3.FromFloatsToRef(data[3], data[4], data[5], this.l1_1);
  246. Vector3.FromFloatsToRef(data[6], data[7], data[8], this.l10);
  247. Vector3.FromFloatsToRef(data[9], data[10], data[11], this.l11);
  248. Vector3.FromFloatsToRef(data[12], data[13], data[14], this.l2_2);
  249. Vector3.FromFloatsToRef(data[15], data[16], data[17], this.l2_1);
  250. Vector3.FromFloatsToRef(data[18], data[19], data[20], this.l20);
  251. Vector3.FromFloatsToRef(data[21], data[22], data[23], this.l21);
  252. Vector3.FromFloatsToRef(data[24], data[25], data[26], this.l22);
  253. return this;
  254. }
  255. /**
  256. * Constructs a spherical harmonics from an array.
  257. * @param data defines the 9x3 coefficients (l00, l1-1, l10, l11, l2-2, l2-1, l20, l21, l22)
  258. * @returns the spherical harmonics
  259. */
  260. static FromArray(data) {
  261. const sh = new SphericalHarmonics();
  262. return sh.updateFromArray(data);
  263. }
  264. // Keep for references.
  265. /**
  266. * Gets the spherical harmonics from polynomial
  267. * @param polynomial the spherical polynomial
  268. * @returns the spherical harmonics
  269. */
  270. static FromPolynomial(polynomial) {
  271. const result = new SphericalHarmonics();
  272. result.l00 = polynomial.xx.scale(0.376127).add(polynomial.yy.scale(0.376127)).add(polynomial.zz.scale(0.376126));
  273. result.l1_1 = polynomial.y.scale(0.977204);
  274. result.l10 = polynomial.z.scale(0.977204);
  275. result.l11 = polynomial.x.scale(0.977204);
  276. result.l2_2 = polynomial.xy.scale(1.16538);
  277. result.l2_1 = polynomial.yz.scale(1.16538);
  278. result.l20 = polynomial.zz.scale(1.34567).subtract(polynomial.xx.scale(0.672834)).subtract(polynomial.yy.scale(0.672834));
  279. result.l21 = polynomial.zx.scale(1.16538);
  280. result.l22 = polynomial.xx.scale(1.16538).subtract(polynomial.yy.scale(1.16538));
  281. result.l1_1.scaleInPlace(-1);
  282. result.l11.scaleInPlace(-1);
  283. result.l2_1.scaleInPlace(-1);
  284. result.l21.scaleInPlace(-1);
  285. result.scaleInPlace(Math.PI);
  286. return result;
  287. }
  288. }
  289. /**
  290. * Class representing spherical polynomial coefficients to the 3rd degree
  291. */
  292. export class SphericalPolynomial {
  293. constructor() {
  294. /**
  295. * The x coefficients of the spherical polynomial
  296. */
  297. this.x = Vector3.Zero();
  298. /**
  299. * The y coefficients of the spherical polynomial
  300. */
  301. this.y = Vector3.Zero();
  302. /**
  303. * The z coefficients of the spherical polynomial
  304. */
  305. this.z = Vector3.Zero();
  306. /**
  307. * The xx coefficients of the spherical polynomial
  308. */
  309. this.xx = Vector3.Zero();
  310. /**
  311. * The yy coefficients of the spherical polynomial
  312. */
  313. this.yy = Vector3.Zero();
  314. /**
  315. * The zz coefficients of the spherical polynomial
  316. */
  317. this.zz = Vector3.Zero();
  318. /**
  319. * The xy coefficients of the spherical polynomial
  320. */
  321. this.xy = Vector3.Zero();
  322. /**
  323. * The yz coefficients of the spherical polynomial
  324. */
  325. this.yz = Vector3.Zero();
  326. /**
  327. * The zx coefficients of the spherical polynomial
  328. */
  329. this.zx = Vector3.Zero();
  330. }
  331. /**
  332. * The spherical harmonics used to create the polynomials.
  333. */
  334. get preScaledHarmonics() {
  335. if (!this._harmonics) {
  336. this._harmonics = SphericalHarmonics.FromPolynomial(this);
  337. }
  338. if (!this._harmonics.preScaled) {
  339. this._harmonics.preScaleForRendering();
  340. }
  341. return this._harmonics;
  342. }
  343. /**
  344. * Adds an ambient color to the spherical polynomial
  345. * @param color the color to add
  346. */
  347. addAmbient(color) {
  348. TmpVectors.Vector3[0].copyFromFloats(color.r, color.g, color.b);
  349. const colorVector = TmpVectors.Vector3[0];
  350. this.xx.addInPlace(colorVector);
  351. this.yy.addInPlace(colorVector);
  352. this.zz.addInPlace(colorVector);
  353. }
  354. /**
  355. * Scales the spherical polynomial by the given amount
  356. * @param scale the amount to scale
  357. */
  358. scaleInPlace(scale) {
  359. this.x.scaleInPlace(scale);
  360. this.y.scaleInPlace(scale);
  361. this.z.scaleInPlace(scale);
  362. this.xx.scaleInPlace(scale);
  363. this.yy.scaleInPlace(scale);
  364. this.zz.scaleInPlace(scale);
  365. this.yz.scaleInPlace(scale);
  366. this.zx.scaleInPlace(scale);
  367. this.xy.scaleInPlace(scale);
  368. }
  369. /**
  370. * Updates the spherical polynomial from harmonics
  371. * @param harmonics the spherical harmonics
  372. * @returns the spherical polynomial
  373. */
  374. updateFromHarmonics(harmonics) {
  375. this._harmonics = harmonics;
  376. this.x.copyFrom(harmonics.l11);
  377. this.x.scaleInPlace(1.02333).scaleInPlace(-1);
  378. this.y.copyFrom(harmonics.l1_1);
  379. this.y.scaleInPlace(1.02333).scaleInPlace(-1);
  380. this.z.copyFrom(harmonics.l10);
  381. this.z.scaleInPlace(1.02333);
  382. this.xx.copyFrom(harmonics.l00);
  383. TmpVectors.Vector3[0].copyFrom(harmonics.l20).scaleInPlace(0.247708);
  384. TmpVectors.Vector3[1].copyFrom(harmonics.l22).scaleInPlace(0.429043);
  385. this.xx.scaleInPlace(0.886277).subtractInPlace(TmpVectors.Vector3[0]).addInPlace(TmpVectors.Vector3[1]);
  386. this.yy.copyFrom(harmonics.l00);
  387. this.yy.scaleInPlace(0.886277).subtractInPlace(TmpVectors.Vector3[0]).subtractInPlace(TmpVectors.Vector3[1]);
  388. this.zz.copyFrom(harmonics.l00);
  389. TmpVectors.Vector3[0].copyFrom(harmonics.l20).scaleInPlace(0.495417);
  390. this.zz.scaleInPlace(0.886277).addInPlace(TmpVectors.Vector3[0]);
  391. this.yz.copyFrom(harmonics.l2_1);
  392. this.yz.scaleInPlace(0.858086).scaleInPlace(-1);
  393. this.zx.copyFrom(harmonics.l21);
  394. this.zx.scaleInPlace(0.858086).scaleInPlace(-1);
  395. this.xy.copyFrom(harmonics.l2_2);
  396. this.xy.scaleInPlace(0.858086);
  397. this.scaleInPlace(1.0 / Math.PI);
  398. return this;
  399. }
  400. /**
  401. * Gets the spherical polynomial from harmonics
  402. * @param harmonics the spherical harmonics
  403. * @returns the spherical polynomial
  404. */
  405. static FromHarmonics(harmonics) {
  406. const result = new SphericalPolynomial();
  407. return result.updateFromHarmonics(harmonics);
  408. }
  409. /**
  410. * Constructs a spherical polynomial from an array.
  411. * @param data defines the 9x3 coefficients (x, y, z, xx, yy, zz, yz, zx, xy)
  412. * @returns the spherical polynomial
  413. */
  414. static FromArray(data) {
  415. const sp = new SphericalPolynomial();
  416. Vector3.FromArrayToRef(data[0], 0, sp.x);
  417. Vector3.FromArrayToRef(data[1], 0, sp.y);
  418. Vector3.FromArrayToRef(data[2], 0, sp.z);
  419. Vector3.FromArrayToRef(data[3], 0, sp.xx);
  420. Vector3.FromArrayToRef(data[4], 0, sp.yy);
  421. Vector3.FromArrayToRef(data[5], 0, sp.zz);
  422. Vector3.FromArrayToRef(data[6], 0, sp.yz);
  423. Vector3.FromArrayToRef(data[7], 0, sp.zx);
  424. Vector3.FromArrayToRef(data[8], 0, sp.xy);
  425. return sp;
  426. }
  427. }
  428. //# sourceMappingURL=sphericalPolynomial.js.map