sphericalPolynomial.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. import { Vector3 } from "../Maths/math.vector";
  2. import { Nullable } from "../types";
  3. import { Color3 } from '../Maths/math.color';
  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)), // l00
  41. -Math.sqrt(3 / (4 * Math.PI)), // l1_1
  42. Math.sqrt(3 / (4 * Math.PI)), // l10
  43. -Math.sqrt(3 / (4 * Math.PI)), // l11
  44. Math.sqrt(15 / (4 * Math.PI)), // l2_2
  45. -Math.sqrt(15 / (4 * Math.PI)), // l2_1
  46. Math.sqrt(5 / (16 * Math.PI)), // l20
  47. -Math.sqrt(15 / (4 * Math.PI)), // l21
  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. (direction: Vector3) => 1, // l00
  56. (direction: Vector3) => direction.y, // l1_1
  57. (direction: Vector3) => direction.z, // l10
  58. (direction: Vector3) => direction.x, // l11
  59. (direction: Vector3) => direction.x * direction.y, // l2_2
  60. (direction: Vector3) => direction.y * direction.z, // l2_1
  61. (direction: Vector3) => 3 * direction.z * direction.z - 1, // l20
  62. (direction: Vector3) => direction.x * direction.z, // l21
  63. (direction: Vector3) => direction.x * direction.x - direction.y * direction.y, // l22
  64. ];
  65. // Wrap the full compute
  66. const applySH3 = (lm: number, direction: Vector3) => {
  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 = [
  72. Math.PI,
  73. 2 * Math.PI / 3,
  74. 2 * Math.PI / 3,
  75. 2 * Math.PI / 3,
  76. Math.PI / 4,
  77. Math.PI / 4,
  78. Math.PI / 4,
  79. Math.PI / 4,
  80. Math.PI / 4,
  81. ];
  82. /**
  83. * Class representing spherical harmonics coefficients to the 3rd degree
  84. */
  85. export class SphericalHarmonics {
  86. /**
  87. * Defines whether or not the harmonics have been prescaled for rendering.
  88. */
  89. public preScaled = false;
  90. /**
  91. * The l0,0 coefficients of the spherical harmonics
  92. */
  93. public l00: Vector3 = Vector3.Zero();
  94. /**
  95. * The l1,-1 coefficients of the spherical harmonics
  96. */
  97. public l1_1: Vector3 = Vector3.Zero();
  98. /**
  99. * The l1,0 coefficients of the spherical harmonics
  100. */
  101. public l10: Vector3 = Vector3.Zero();
  102. /**
  103. * The l1,1 coefficients of the spherical harmonics
  104. */
  105. public l11: Vector3 = Vector3.Zero();
  106. /**
  107. * The l2,-2 coefficients of the spherical harmonics
  108. */
  109. public l2_2: Vector3 = Vector3.Zero();
  110. /**
  111. * The l2,-1 coefficients of the spherical harmonics
  112. */
  113. public l2_1: Vector3 = Vector3.Zero();
  114. /**
  115. * The l2,0 coefficients of the spherical harmonics
  116. */
  117. public l20: Vector3 = Vector3.Zero();
  118. /**
  119. * The l2,1 coefficients of the spherical harmonics
  120. */
  121. public l21: Vector3 = Vector3.Zero();
  122. /**
  123. * The l2,2 coefficients of the spherical harmonics
  124. */
  125. public l22: Vector3 = Vector3.Zero();
  126. /**
  127. * Adds a light to the spherical harmonics
  128. * @param direction the direction of the light
  129. * @param color the color of the light
  130. * @param deltaSolidAngle the delta solid angle of the light
  131. */
  132. public addLight(direction: Vector3, color: Color3, deltaSolidAngle: number): void {
  133. var colorVector = new Vector3(color.r, color.g, color.b);
  134. var c = colorVector.scale(deltaSolidAngle);
  135. this.l00 = this.l00.add(c.scale(applySH3(0, direction)));
  136. this.l1_1 = this.l1_1.add(c.scale(applySH3(1, direction)));
  137. this.l10 = this.l10.add(c.scale(applySH3(2, direction)));
  138. this.l11 = this.l11.add(c.scale(applySH3(3, direction)));
  139. this.l2_2 = this.l2_2.add(c.scale(applySH3(4, direction)));
  140. this.l2_1 = this.l2_1.add(c.scale(applySH3(5, direction)));
  141. this.l20 = this.l20.add(c.scale(applySH3(6, direction)));
  142. this.l21 = this.l21.add(c.scale(applySH3(7, direction)));
  143. this.l22 = this.l22.add(c.scale(applySH3(8, direction)));
  144. }
  145. /**
  146. * Scales the spherical harmonics by the given amount
  147. * @param scale the amount to scale
  148. */
  149. public scaleInPlace(scale: number): void {
  150. this.l00.scaleInPlace(scale);
  151. this.l1_1.scaleInPlace(scale);
  152. this.l10.scaleInPlace(scale);
  153. this.l11.scaleInPlace(scale);
  154. this.l2_2.scaleInPlace(scale);
  155. this.l2_1.scaleInPlace(scale);
  156. this.l20.scaleInPlace(scale);
  157. this.l21.scaleInPlace(scale);
  158. this.l22.scaleInPlace(scale);
  159. }
  160. /**
  161. * Convert from incident radiance (Li) to irradiance (E) by applying convolution with the cosine-weighted hemisphere.
  162. *
  163. * ```
  164. * E_lm = A_l * L_lm
  165. * ```
  166. *
  167. * In spherical harmonics this convolution amounts to scaling factors for each frequency band.
  168. * This corresponds to equation 5 in "An Efficient Representation for Irradiance Environment Maps", where
  169. * the scaling factors are given in equation 9.
  170. */
  171. public convertIncidentRadianceToIrradiance(): void {
  172. // Constant (Band 0)
  173. this.l00.scaleInPlace(SHCosKernelConvolution[0]);
  174. // Linear (Band 1)
  175. this.l1_1.scaleInPlace(SHCosKernelConvolution[1]);
  176. this.l10.scaleInPlace(SHCosKernelConvolution[2]);
  177. this.l11.scaleInPlace(SHCosKernelConvolution[3]);
  178. // Quadratic (Band 2)
  179. this.l2_2.scaleInPlace(SHCosKernelConvolution[4]);
  180. this.l2_1.scaleInPlace(SHCosKernelConvolution[5]);
  181. this.l20.scaleInPlace(SHCosKernelConvolution[6]);
  182. this.l21.scaleInPlace(SHCosKernelConvolution[7]);
  183. this.l22.scaleInPlace(SHCosKernelConvolution[8]);
  184. }
  185. /**
  186. * Convert from irradiance to outgoing radiance for Lambertian BDRF, suitable for efficient shader evaluation.
  187. *
  188. * ```
  189. * L = (1/pi) * E * rho
  190. * ```
  191. *
  192. * This is done by an additional scale by 1/pi, so is a fairly trivial operation but important conceptually.
  193. */
  194. public convertIrradianceToLambertianRadiance(): void {
  195. this.scaleInPlace(1.0 / Math.PI);
  196. // The resultant SH now represents outgoing radiance, so includes the Lambert 1/pi normalisation factor but without albedo (rho) applied
  197. // (The pixel shader must apply albedo after texture fetches, etc).
  198. }
  199. /**
  200. * Integrates the reconstruction coefficients directly in to the SH preventing further
  201. * required operations at run time.
  202. *
  203. * This is simply done by scaling back the SH with Ylm constants parameter.
  204. * The trigonometric part being applied by the shader at run time.
  205. */
  206. public preScaleForRendering(): void {
  207. this.preScaled = true;
  208. this.l00.scaleInPlace(SH3ylmBasisConstants[0]);
  209. this.l1_1.scaleInPlace(SH3ylmBasisConstants[1]);
  210. this.l10.scaleInPlace(SH3ylmBasisConstants[2]);
  211. this.l11.scaleInPlace(SH3ylmBasisConstants[3]);
  212. this.l2_2.scaleInPlace(SH3ylmBasisConstants[4]);
  213. this.l2_1.scaleInPlace(SH3ylmBasisConstants[5]);
  214. this.l20.scaleInPlace(SH3ylmBasisConstants[6]);
  215. this.l21.scaleInPlace(SH3ylmBasisConstants[7]);
  216. this.l22.scaleInPlace(SH3ylmBasisConstants[8]);
  217. }
  218. /**
  219. * Constructs a spherical harmonics from an array.
  220. * @param data defines the 9x3 coefficients (l00, l1-1, l10, l11, l2-2, l2-1, l20, l21, l22)
  221. * @returns the spherical harmonics
  222. */
  223. public static FromArray(data: ArrayLike<ArrayLike<number>>): SphericalHarmonics {
  224. const sh = new SphericalHarmonics();
  225. Vector3.FromArrayToRef(data[0], 0, sh.l00);
  226. Vector3.FromArrayToRef(data[1], 0, sh.l1_1);
  227. Vector3.FromArrayToRef(data[2], 0, sh.l10);
  228. Vector3.FromArrayToRef(data[3], 0, sh.l11);
  229. Vector3.FromArrayToRef(data[4], 0, sh.l2_2);
  230. Vector3.FromArrayToRef(data[5], 0, sh.l2_1);
  231. Vector3.FromArrayToRef(data[6], 0, sh.l20);
  232. Vector3.FromArrayToRef(data[7], 0, sh.l21);
  233. Vector3.FromArrayToRef(data[8], 0, sh.l22);
  234. return sh;
  235. }
  236. // Keep for references.
  237. /**
  238. * Gets the spherical harmonics from polynomial
  239. * @param polynomial the spherical polynomial
  240. * @returns the spherical harmonics
  241. */
  242. public static FromPolynomial(polynomial: SphericalPolynomial): SphericalHarmonics {
  243. var result = new SphericalHarmonics();
  244. result.l00 = polynomial.xx.scale(0.376127).add(polynomial.yy.scale(0.376127)).add(polynomial.zz.scale(0.376126));
  245. result.l1_1 = polynomial.y.scale(0.977204);
  246. result.l10 = polynomial.z.scale(0.977204);
  247. result.l11 = polynomial.x.scale(0.977204);
  248. result.l2_2 = polynomial.xy.scale(1.16538);
  249. result.l2_1 = polynomial.yz.scale(1.16538);
  250. result.l20 = polynomial.zz.scale(1.34567).subtract(polynomial.xx.scale(0.672834)).subtract(polynomial.yy.scale(0.672834));
  251. result.l21 = polynomial.zx.scale(1.16538);
  252. result.l22 = polynomial.xx.scale(1.16538).subtract(polynomial.yy.scale(1.16538));
  253. result.l1_1.scaleInPlace(-1);
  254. result.l11.scaleInPlace(-1);
  255. result.l2_1.scaleInPlace(-1);
  256. result.l21.scaleInPlace(-1);
  257. result.scaleInPlace(Math.PI);
  258. return result;
  259. }
  260. }
  261. /**
  262. * Class representing spherical polynomial coefficients to the 3rd degree
  263. */
  264. export class SphericalPolynomial {
  265. private _harmonics: Nullable<SphericalHarmonics>;
  266. /**
  267. * The spherical harmonics used to create the polynomials.
  268. */
  269. public get preScaledHarmonics(): SphericalHarmonics {
  270. if (!this._harmonics) {
  271. this._harmonics = SphericalHarmonics.FromPolynomial(this);
  272. }
  273. if (!this._harmonics.preScaled) {
  274. this._harmonics.preScaleForRendering();
  275. }
  276. return this._harmonics;
  277. }
  278. /**
  279. * The x coefficients of the spherical polynomial
  280. */
  281. public x: Vector3 = Vector3.Zero();
  282. /**
  283. * The y coefficients of the spherical polynomial
  284. */
  285. public y: Vector3 = Vector3.Zero();
  286. /**
  287. * The z coefficients of the spherical polynomial
  288. */
  289. public z: Vector3 = Vector3.Zero();
  290. /**
  291. * The xx coefficients of the spherical polynomial
  292. */
  293. public xx: Vector3 = Vector3.Zero();
  294. /**
  295. * The yy coefficients of the spherical polynomial
  296. */
  297. public yy: Vector3 = Vector3.Zero();
  298. /**
  299. * The zz coefficients of the spherical polynomial
  300. */
  301. public zz: Vector3 = Vector3.Zero();
  302. /**
  303. * The xy coefficients of the spherical polynomial
  304. */
  305. public xy: Vector3 = Vector3.Zero();
  306. /**
  307. * The yz coefficients of the spherical polynomial
  308. */
  309. public yz: Vector3 = Vector3.Zero();
  310. /**
  311. * The zx coefficients of the spherical polynomial
  312. */
  313. public zx: Vector3 = Vector3.Zero();
  314. /**
  315. * Adds an ambient color to the spherical polynomial
  316. * @param color the color to add
  317. */
  318. public addAmbient(color: Color3): void {
  319. var colorVector = new Vector3(color.r, color.g, color.b);
  320. this.xx = this.xx.add(colorVector);
  321. this.yy = this.yy.add(colorVector);
  322. this.zz = this.zz.add(colorVector);
  323. }
  324. /**
  325. * Scales the spherical polynomial by the given amount
  326. * @param scale the amount to scale
  327. */
  328. public scaleInPlace(scale: number) {
  329. this.x.scaleInPlace(scale);
  330. this.y.scaleInPlace(scale);
  331. this.z.scaleInPlace(scale);
  332. this.xx.scaleInPlace(scale);
  333. this.yy.scaleInPlace(scale);
  334. this.zz.scaleInPlace(scale);
  335. this.yz.scaleInPlace(scale);
  336. this.zx.scaleInPlace(scale);
  337. this.xy.scaleInPlace(scale);
  338. }
  339. /**
  340. * Gets the spherical polynomial from harmonics
  341. * @param harmonics the spherical harmonics
  342. * @returns the spherical polynomial
  343. */
  344. public static FromHarmonics(harmonics: SphericalHarmonics): SphericalPolynomial {
  345. var result = new SphericalPolynomial();
  346. result._harmonics = harmonics;
  347. result.x = harmonics.l11.scale(1.02333).scale(-1);
  348. result.y = harmonics.l1_1.scale(1.02333).scale(-1);
  349. result.z = harmonics.l10.scale(1.02333);
  350. result.xx = harmonics.l00.scale(0.886277).subtract(harmonics.l20.scale(0.247708)).add(harmonics.l22.scale(0.429043));
  351. result.yy = harmonics.l00.scale(0.886277).subtract(harmonics.l20.scale(0.247708)).subtract(harmonics.l22.scale(0.429043));
  352. result.zz = harmonics.l00.scale(0.886277).add(harmonics.l20.scale(0.495417));
  353. result.yz = harmonics.l2_1.scale(0.858086).scale(-1);
  354. result.zx = harmonics.l21.scale(0.858086).scale(-1);
  355. result.xy = harmonics.l2_2.scale(0.858086);
  356. result.scaleInPlace(1.0 / Math.PI);
  357. return result;
  358. }
  359. /**
  360. * Constructs a spherical polynomial from an array.
  361. * @param data defines the 9x3 coefficients (x, y, z, xx, yy, zz, yz, zx, xy)
  362. * @returns the spherical polynomial
  363. */
  364. public static FromArray(data: ArrayLike<ArrayLike<number>>): SphericalPolynomial {
  365. const sp = new SphericalPolynomial();
  366. Vector3.FromArrayToRef(data[0], 0, sp.x);
  367. Vector3.FromArrayToRef(data[1], 0, sp.y);
  368. Vector3.FromArrayToRef(data[2], 0, sp.z);
  369. Vector3.FromArrayToRef(data[3], 0, sp.xx);
  370. Vector3.FromArrayToRef(data[4], 0, sp.yy);
  371. Vector3.FromArrayToRef(data[5], 0, sp.zz);
  372. Vector3.FromArrayToRef(data[6], 0, sp.yz);
  373. Vector3.FromArrayToRef(data[7], 0, sp.zx);
  374. Vector3.FromArrayToRef(data[8], 0, sp.xy);
  375. return sp;
  376. }
  377. }