babylon.pmremgenerator.ts 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. //_______________________________________________________________
  2. // Extracted from CubeMapGen:
  3. // https://code.google.com/archive/p/cubemapgen/
  4. //
  5. // Following https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering/
  6. //_______________________________________________________________
  7. namespace BABYLON.Internals {
  8. /**
  9. * The bounding box information used during the conversion process.
  10. */
  11. class CMGBoundinBox {
  12. private static MAX = Number.MAX_VALUE;
  13. private static MIN = Number.MIN_VALUE;
  14. public min: Vector3;
  15. public max: Vector3;
  16. constructor() {
  17. this.min = new Vector3(0, 0, 0);
  18. this.max = new Vector3(0, 0, 0);
  19. this.clear()
  20. }
  21. public clear(): void {
  22. this.min.x = CMGBoundinBox.MAX;
  23. this.min.y = CMGBoundinBox.MAX;
  24. this.min.z = CMGBoundinBox.MAX;
  25. this.max.x = CMGBoundinBox.MIN;
  26. this.max.y = CMGBoundinBox.MIN;
  27. this.max.z = CMGBoundinBox.MIN;
  28. }
  29. public augment(x: number, y: number, z: number): void {
  30. this.min.x = Math.min(this.min.x, x);
  31. this.min.y = Math.min(this.min.y, y);
  32. this.min.z = Math.min(this.min.z, z);
  33. this.max.x = Math.max(this.max.x, x);
  34. this.max.y = Math.max(this.max.y, y);
  35. this.max.z = Math.max(this.max.z, z);
  36. }
  37. public clampMin(x: number, y: number, z: number): void {
  38. this.min.x = Math.max(this.min.x, x);
  39. this.min.y = Math.max(this.min.y, y);
  40. this.min.z = Math.max(this.min.z, z);
  41. }
  42. public clampMax(x: number, y: number, z: number): void {
  43. this.max.x = Math.min(this.max.x, x);
  44. this.max.y = Math.min(this.max.y, y);
  45. this.max.z = Math.min(this.max.z, z);
  46. }
  47. public empty(): boolean {
  48. if ((this.min.x > this.max.y) ||
  49. (this.min.y > this.max.y) ||
  50. (this.min.z > this.max.y)) {
  51. return true;
  52. }
  53. else {
  54. return false;
  55. }
  56. }
  57. }
  58. /**
  59. * Helper class to PreProcess a cubemap in order to generate mipmap according to the level of blur
  60. * required by the glossinees of a material.
  61. *
  62. * This only supports the cosine drop power as well as Warp fixup generation method.
  63. *
  64. * This is using the process from CubeMapGen described here:
  65. * https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering/
  66. */
  67. export class PMREMGenerator {
  68. private static CP_MAX_MIPLEVELS = 16;
  69. private static CP_UDIR = 0;
  70. private static CP_VDIR = 1;
  71. private static CP_FACEAXIS = 2;
  72. //used to index cube faces
  73. private static CP_FACE_X_POS = 0;
  74. private static CP_FACE_X_NEG = 1;
  75. private static CP_FACE_Y_POS = 2;
  76. private static CP_FACE_Y_NEG = 3;
  77. private static CP_FACE_Z_POS = 4;
  78. private static CP_FACE_Z_NEG = 5;
  79. //used to index image edges
  80. // NOTE.. the actual number corresponding to the edge is important
  81. // do not change these, or the code will break
  82. //
  83. // CP_EDGE_LEFT is u = 0
  84. // CP_EDGE_RIGHT is u = width-1
  85. // CP_EDGE_TOP is v = 0
  86. // CP_EDGE_BOTTOM is v = height-1
  87. private static CP_EDGE_LEFT = 0;
  88. private static CP_EDGE_RIGHT = 1;
  89. private static CP_EDGE_TOP = 2;
  90. private static CP_EDGE_BOTTOM = 3;
  91. //corners of CUBE map (P or N specifys if it corresponds to the
  92. // positive or negative direction each of X, Y, and Z
  93. private static CP_CORNER_NNN = 0;
  94. private static CP_CORNER_NNP = 1;
  95. private static CP_CORNER_NPN = 2;
  96. private static CP_CORNER_NPP = 3;
  97. private static CP_CORNER_PNN = 4;
  98. private static CP_CORNER_PNP = 5;
  99. private static CP_CORNER_PPN = 6;
  100. private static CP_CORNER_PPP = 7;
  101. private static _vectorTemp: Vector4 = new Vector4(0, 0, 0, 0);
  102. //3x2 matrices that map cube map indexing vectors in 3d
  103. // (after face selection and divide through by the
  104. // _ABSOLUTE VALUE_ of the max coord)
  105. // into NVC space
  106. //Note this currently assumes the D3D cube face ordering and orientation
  107. private static _sgFace2DMapping = [
  108. //XPOS face
  109. [[0, 0, -1], //u towards negative Z
  110. [0, -1, 0], //v towards negative Y
  111. [1, 0, 0]], //pos X axis
  112. //XNEG face
  113. [[0, 0, 1], //u towards positive Z
  114. [0, -1, 0], //v towards negative Y
  115. [-1, 0, 0]], //neg X axis
  116. //YPOS face
  117. [[1, 0, 0], //u towards positive X
  118. [0, 0, 1], //v towards positive Z
  119. [0, 1, 0]], //pos Y axis
  120. //YNEG face
  121. [[1, 0, 0], //u towards positive X
  122. [0, 0, -1], //v towards negative Z
  123. [0, -1, 0]], //neg Y axis
  124. //ZPOS face
  125. [[1, 0, 0], //u towards positive X
  126. [0, -1, 0], //v towards negative Y
  127. [0, 0, 1]], //pos Z axis
  128. //ZNEG face
  129. [[-1, 0, 0], //u towards negative X
  130. [0, -1, 0], //v towards negative Y
  131. [0, 0, -1]], //neg Z axis
  132. ];
  133. //------------------------------------------------------------------------------
  134. // D3D cube map face specification
  135. // mapping from 3D x,y,z cube map lookup coordinates
  136. // to 2D within face u,v coordinates
  137. //
  138. // --------------------> U direction
  139. // | (within-face texture space)
  140. // | _____
  141. // | | |
  142. // | | +Y |
  143. // | _____|_____|_____ _____
  144. // | | | | | |
  145. // | | -X | +Z | +X | -Z |
  146. // | |_____|_____|_____|_____|
  147. // | | |
  148. // | | -Y |
  149. // | |_____|
  150. // |
  151. // v V direction
  152. // (within-face texture space)
  153. //------------------------------------------------------------------------------
  154. //Information about neighbors and how texture coorrdinates change across faces
  155. // in ORDER of left, right, top, bottom (e.g. edges corresponding to u=0,
  156. // u=1, v=0, v=1 in the 2D coordinate system of the particular face.
  157. //Note this currently assumes the D3D cube face ordering and orientation
  158. private static _sgCubeNgh =
  159. [
  160. //XPOS face
  161. [[PMREMGenerator.CP_FACE_Z_POS, PMREMGenerator.CP_EDGE_RIGHT],
  162. [PMREMGenerator.CP_FACE_Z_NEG, PMREMGenerator.CP_EDGE_LEFT],
  163. [PMREMGenerator.CP_FACE_Y_POS, PMREMGenerator.CP_EDGE_RIGHT],
  164. [PMREMGenerator.CP_FACE_Y_NEG, PMREMGenerator.CP_EDGE_RIGHT]],
  165. //XNEG face
  166. [[PMREMGenerator.CP_FACE_Z_NEG, PMREMGenerator.CP_EDGE_RIGHT],
  167. [PMREMGenerator.CP_FACE_Z_POS, PMREMGenerator.CP_EDGE_LEFT],
  168. [PMREMGenerator.CP_FACE_Y_POS, PMREMGenerator.CP_EDGE_LEFT],
  169. [PMREMGenerator.CP_FACE_Y_NEG, PMREMGenerator.CP_EDGE_LEFT]],
  170. //YPOS face
  171. [[PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_TOP],
  172. [PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_TOP],
  173. [PMREMGenerator.CP_FACE_Z_NEG, PMREMGenerator.CP_EDGE_TOP],
  174. [PMREMGenerator.CP_FACE_Z_POS, PMREMGenerator.CP_EDGE_TOP]],
  175. //YNEG face
  176. [[PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_BOTTOM],
  177. [PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_BOTTOM],
  178. [PMREMGenerator.CP_FACE_Z_POS, PMREMGenerator.CP_EDGE_BOTTOM],
  179. [PMREMGenerator.CP_FACE_Z_NEG, PMREMGenerator.CP_EDGE_BOTTOM]],
  180. //ZPOS face
  181. [[PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_RIGHT],
  182. [PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_LEFT],
  183. [PMREMGenerator.CP_FACE_Y_POS, PMREMGenerator.CP_EDGE_BOTTOM],
  184. [PMREMGenerator.CP_FACE_Y_NEG, PMREMGenerator.CP_EDGE_TOP]],
  185. //ZNEG face
  186. [[PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_RIGHT],
  187. [PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_LEFT],
  188. [PMREMGenerator.CP_FACE_Y_POS, PMREMGenerator.CP_EDGE_TOP],
  189. [PMREMGenerator.CP_FACE_Y_NEG, PMREMGenerator.CP_EDGE_BOTTOM]]
  190. ];
  191. //The 12 edges of the cubemap, (entries are used to index into the neighbor table)
  192. // this table is used to average over the edges.
  193. private static _sgCubeEdgeList = [
  194. [PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_LEFT],
  195. [PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_RIGHT],
  196. [PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_TOP],
  197. [PMREMGenerator.CP_FACE_X_POS, PMREMGenerator.CP_EDGE_BOTTOM],
  198. [PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_LEFT],
  199. [PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_RIGHT],
  200. [PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_TOP],
  201. [PMREMGenerator.CP_FACE_X_NEG, PMREMGenerator.CP_EDGE_BOTTOM],
  202. [PMREMGenerator.CP_FACE_Z_POS, PMREMGenerator.CP_EDGE_TOP],
  203. [PMREMGenerator.CP_FACE_Z_POS, PMREMGenerator.CP_EDGE_BOTTOM],
  204. [PMREMGenerator.CP_FACE_Z_NEG, PMREMGenerator.CP_EDGE_TOP],
  205. [PMREMGenerator.CP_FACE_Z_NEG, PMREMGenerator.CP_EDGE_BOTTOM]
  206. ];
  207. //Information about which of the 8 cube corners are correspond to the
  208. // the 4 corners in each cube face
  209. // the order is upper left, upper right, lower left, lower right
  210. private static _sgCubeCornerList = [
  211. [PMREMGenerator.CP_CORNER_PPP, PMREMGenerator.CP_CORNER_PPN, PMREMGenerator.CP_CORNER_PNP, PMREMGenerator.CP_CORNER_PNN], // XPOS face
  212. [PMREMGenerator.CP_CORNER_NPN, PMREMGenerator.CP_CORNER_NPP, PMREMGenerator.CP_CORNER_NNN, PMREMGenerator.CP_CORNER_NNP], // XNEG face
  213. [PMREMGenerator.CP_CORNER_NPN, PMREMGenerator.CP_CORNER_PPN, PMREMGenerator.CP_CORNER_NPP, PMREMGenerator.CP_CORNER_PPP], // YPOS face
  214. [PMREMGenerator.CP_CORNER_NNP, PMREMGenerator.CP_CORNER_PNP, PMREMGenerator.CP_CORNER_NNN, PMREMGenerator.CP_CORNER_PNN], // YNEG face
  215. [PMREMGenerator.CP_CORNER_NPP, PMREMGenerator.CP_CORNER_PPP, PMREMGenerator.CP_CORNER_NNP, PMREMGenerator.CP_CORNER_PNP], // ZPOS face
  216. [PMREMGenerator.CP_CORNER_PPN, PMREMGenerator.CP_CORNER_NPN, PMREMGenerator.CP_CORNER_PNN, PMREMGenerator.CP_CORNER_NNN] // ZNEG face
  217. ];
  218. private _outputSurface: ArrayBufferView[][] = [];
  219. private _normCubeMap: ArrayBufferView[];
  220. private _filterLUT: ArrayBufferView[];
  221. private _numMipLevels: number = 0;
  222. /**
  223. * Constructor of the generator.
  224. *
  225. * @param input The different faces data from the original cubemap in the order X+ X- Y+ Y- Z+ Z-
  226. * @param inputSize The size of the cubemap faces
  227. * @param outputSize The size of the output cubemap faces
  228. * @param maxNumMipLevels The max number of mip map to generate (0 means all)
  229. * @param numChannels The number of channels stored in the cubemap (3 for RBGE for instance)
  230. * @param isFloat Specifies if the input texture is in float or int (hdr is usually in float)
  231. * @param specularPower The max specular level of the desired cubemap
  232. * @param cosinePowerDropPerMip The amount of drop the specular power will follow on each mip
  233. * @param excludeBase Specifies wether to process the level 0 (original level) or not
  234. * @param fixup Specifies wether to apply the edge fixup algorythm or not
  235. */
  236. constructor(public input: ArrayBufferView[],
  237. public inputSize: number,
  238. public outputSize: number,
  239. public maxNumMipLevels: number,
  240. public numChannels: number,
  241. public isFloat: boolean,
  242. public specularPower: number,
  243. public cosinePowerDropPerMip: number,
  244. public excludeBase: boolean,
  245. public fixup: boolean) {
  246. }
  247. /**
  248. * Launches the filter process and return the result.
  249. *
  250. * @return the filter cubemap in the form mip0 [faces1..6] .. mipN [faces1..6]
  251. */
  252. public filterCubeMap(): ArrayBufferView[][] {
  253. // Init cubemap processor
  254. this.init();
  255. // Filters the cubemap
  256. this.filterCubeMapMipChain();
  257. // Returns the filtered mips.
  258. return this._outputSurface;
  259. }
  260. private init(): void {
  261. var i: number;
  262. var j: number;
  263. var mipLevelSize: number;
  264. //if nax num mip levels is set to 0, set it to generate the entire mip chain
  265. if (this.maxNumMipLevels == 0) {
  266. this.maxNumMipLevels = PMREMGenerator.CP_MAX_MIPLEVELS;
  267. }
  268. //first miplevel size
  269. mipLevelSize = this.outputSize;
  270. //Iterate over mip chain, and init ArrayBufferView for mip-chain
  271. for (j = 0; j < this.maxNumMipLevels; j++) {
  272. this._outputSurface.length++;
  273. this._outputSurface[j] = [];
  274. //Iterate over faces for output images
  275. for (i = 0; i < 6; i++) {
  276. this._outputSurface[j].length++;
  277. // Initializes a new array for the output.
  278. if (this.isFloat) {
  279. this._outputSurface[j][i] = new Float32Array(mipLevelSize * mipLevelSize * this.numChannels);
  280. }
  281. else {
  282. this._outputSurface[j][i] = new Uint32Array(mipLevelSize * mipLevelSize * this.numChannels);
  283. }
  284. }
  285. //next mip level is half size
  286. mipLevelSize >>= 1;
  287. this._numMipLevels++;
  288. //terminate if mip chain becomes too small
  289. if (mipLevelSize == 0) {
  290. this.maxNumMipLevels = j;
  291. return;
  292. }
  293. }
  294. }
  295. //--------------------------------------------------------------------------------------
  296. //Cube map filtering and mip chain generation.
  297. // the cube map filtereing is specified using a number of parameters:
  298. // Filtering per miplevel is specified using 2D cone angle (in degrees) that
  299. // indicates the region of the hemisphere to filter over for each tap.
  300. //
  301. // Note that the top mip level is also a filtered version of the original input images
  302. // as well in order to create mip chains for diffuse environment illumination.
  303. // The cone angle for the top level is specified by a_BaseAngle. This can be used to
  304. // generate mipchains used to store the resutls of preintegration across the hemisphere.
  305. //
  306. // Then the mip angle used to genreate the next level of the mip chain from the first level
  307. // is a_InitialMipAngle
  308. //
  309. // The angle for the subsequent levels of the mip chain are specified by their parents
  310. // filtering angle and a per-level scale and bias
  311. // newAngle = oldAngle * a_MipAnglePerLevelScale;
  312. //
  313. //--------------------------------------------------------------------------------------
  314. private filterCubeMapMipChain(): void {
  315. // First, take count of the lighting model to modify SpecularPower
  316. // var refSpecularPower = (a_MCO.LightingModel == CP_LIGHTINGMODEL_BLINN || a_MCO.LightingModel == CP_LIGHTINGMODEL_BLINN_BRDF) ? a_MCO.SpecularPower / GetSpecularPowerFactorToMatchPhong(a_MCO.SpecularPower) : a_MCO.SpecularPower;
  317. // var refSpecularPower = this.specularPower; // Only Phong BRDF yet. This explains the line below using this.specularpower.
  318. //Cone angle start (for generating subsequent mip levels)
  319. var currentSpecularPower = this.specularPower;
  320. //Build filter lookup tables based on the source miplevel size
  321. this.precomputeFilterLookupTables(this.inputSize);
  322. // Note that we need to filter the first level before generating mipmap
  323. // So LevelIndex == 0 is base filtering hen LevelIndex > 0 is mipmap generation
  324. for (var levelIndex = 0; levelIndex < this._numMipLevels; levelIndex++) {
  325. // TODO : Write a function to copy and scale the base mipmap in output
  326. // I am just lazy here and just put a high specular power value, and do some if.
  327. if (this.excludeBase && (levelIndex == 0)) {
  328. // If we don't want to process the base mipmap, just put a very high specular power (this allow to handle scale of the texture).
  329. currentSpecularPower = 100000.0;
  330. }
  331. // Special case for cosine power mipmap chain. For quality requirement, we always process the current mipmap from the top mipmap
  332. var srcCubeImage = this.input;
  333. var dstCubeImage = this._outputSurface[levelIndex];
  334. var dstSize = this.outputSize >> levelIndex;
  335. // Compute required angle.
  336. var angle = this.getBaseFilterAngle(currentSpecularPower);
  337. // filter cube surfaces
  338. this.filterCubeSurfaces(srcCubeImage, this.inputSize, dstCubeImage, dstSize, angle, currentSpecularPower);
  339. // fix seams
  340. if (this.fixup) {
  341. this.fixupCubeEdges(dstCubeImage, dstSize);
  342. }
  343. // Decrease the specular power to generate the mipmap chain
  344. // TODO : Use another method for Exclude (see first comment at start of the function
  345. if (this.excludeBase && (levelIndex == 0)) {
  346. currentSpecularPower = this.specularPower;
  347. }
  348. currentSpecularPower *= this.cosinePowerDropPerMip;
  349. }
  350. }
  351. //--------------------------------------------------------------------------------------
  352. // This function return the BaseFilterAngle require by PMREMGenerator to its FilterExtends
  353. // It allow to optimize the texel to access base on the specular power.
  354. //--------------------------------------------------------------------------------------
  355. private getBaseFilterAngle(cosinePower: number): number {
  356. // We want to find the alpha such that:
  357. // cos(alpha)^cosinePower = epsilon
  358. // That's: acos(epsilon^(1/cosinePower))
  359. const threshold = 0.000001; // Empirical threshold (Work perfectly, didn't check for a more big number, may get some performance and still god approximation)
  360. var angle = 180.0;
  361. angle = Math.acos(Math.pow(threshold, 1.0 / cosinePower));
  362. angle *= 180.0 / Math.PI; // Convert to degree
  363. angle *= 2.0; // * 2.0f because PMREMGenerator divide by 2 later
  364. return angle;
  365. }
  366. //--------------------------------------------------------------------------------------
  367. //Builds the following lookup tables prior to filtering:
  368. // -normalizer cube map
  369. // -tap weight lookup table
  370. //
  371. //--------------------------------------------------------------------------------------
  372. private precomputeFilterLookupTables(srcCubeMapWidth: number): void {
  373. var srcTexelAngle: number;
  374. var iCubeFace: number;
  375. //clear pre-existing normalizer cube map
  376. this._normCubeMap = [];
  377. //Normalized vectors per cubeface and per-texel solid angle
  378. this.buildNormalizerSolidAngleCubemap(srcCubeMapWidth);
  379. }
  380. //--------------------------------------------------------------------------------------
  381. //Builds a normalizer cubemap, with the texels solid angle stored in the fourth component
  382. //
  383. //Takes in a cube face size, and an array of 6 surfaces to write the cube faces into
  384. //
  385. //Note that this normalizer cube map stores the vectors in unbiased -1 to 1 range.
  386. // if _bx2 style scaled and biased vectors are needed, uncomment the SCALE and BIAS
  387. // below
  388. //--------------------------------------------------------------------------------------
  389. private buildNormalizerSolidAngleCubemap(size: number) {
  390. var iCubeFace: number;
  391. var u: number;
  392. var v: number;
  393. //iterate over cube faces
  394. for (iCubeFace = 0; iCubeFace < 6; iCubeFace++) {
  395. //First three channels for norm cube, and last channel for solid angle
  396. this._normCubeMap.push(new Float32Array(size * size * 4));
  397. //fast texture walk, build normalizer cube map
  398. var facesData = this.input[iCubeFace];
  399. for (v = 0; v < size; v++) {
  400. for (u = 0; u < size; u++) {
  401. var vect = this.texelCoordToVect(iCubeFace, u, v, size, this.fixup);
  402. this._normCubeMap[iCubeFace][(v * size + u) * 4 + 0] = vect.x;
  403. this._normCubeMap[iCubeFace][(v * size + u) * 4 + 1] = vect.y;
  404. this._normCubeMap[iCubeFace][(v * size + u) * 4 + 2] = vect.z;
  405. var solidAngle = this.texelCoordSolidAngle(iCubeFace, u, v, size);
  406. this._normCubeMap[iCubeFace][(v * size + u) * 4 + 4] = solidAngle;
  407. }
  408. }
  409. }
  410. }
  411. //--------------------------------------------------------------------------------------
  412. // Convert cubemap face texel coordinates and face idx to 3D vector
  413. // note the U and V coords are integer coords and range from 0 to size-1
  414. // this routine can be used to generate a normalizer cube map
  415. //--------------------------------------------------------------------------------------
  416. // SL BEGIN
  417. private texelCoordToVect(faceIdx: number, u: number, v: number, size: number, fixup: boolean): Vector4 {
  418. var nvcU: number;
  419. var nvcV: number;
  420. var tempVec: Vector4;
  421. // Change from original AMD code
  422. // transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
  423. // + 0.5f is for texel center addressing
  424. nvcU = (2.0 * (u + 0.5) / size) - 1.0;
  425. nvcV = (2.0 * (v + 0.5) / size) - 1.0;
  426. // warp fixup
  427. if (fixup && size > 1) {
  428. // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
  429. var a = Math.pow(size, 2.0) / Math.pow(size - 1, 3.0);
  430. nvcU = a * Math.pow(nvcU, 3) + nvcU;
  431. nvcV = a * Math.pow(nvcV, 3) + nvcV;
  432. }
  433. // Get current vector
  434. // generate x,y,z vector (xform 2d NVC coord to 3D vector)
  435. // U contribution
  436. var UVec = PMREMGenerator._sgFace2DMapping[faceIdx][PMREMGenerator.CP_UDIR];
  437. PMREMGenerator._vectorTemp.x = UVec[0] * nvcU;
  438. PMREMGenerator._vectorTemp.y = UVec[1] * nvcU;
  439. PMREMGenerator._vectorTemp.z = UVec[2] * nvcU;
  440. // V contribution and Sum
  441. var VVec = PMREMGenerator._sgFace2DMapping[faceIdx][PMREMGenerator.CP_VDIR];
  442. PMREMGenerator._vectorTemp.x += VVec[0] * nvcV;
  443. PMREMGenerator._vectorTemp.y += VVec[1] * nvcV;
  444. PMREMGenerator._vectorTemp.z += VVec[2] * nvcV;
  445. //add face axis
  446. var faceAxis = PMREMGenerator._sgFace2DMapping[faceIdx][PMREMGenerator.CP_FACEAXIS];
  447. PMREMGenerator._vectorTemp.x += faceAxis[0];
  448. PMREMGenerator._vectorTemp.y += faceAxis[1];
  449. PMREMGenerator._vectorTemp.z += faceAxis[2];
  450. //normalize vector
  451. PMREMGenerator._vectorTemp.normalize();
  452. return PMREMGenerator._vectorTemp;
  453. }
  454. //--------------------------------------------------------------------------------------
  455. // Convert 3D vector to cubemap face texel coordinates and face idx
  456. // note the U and V coords are integer coords and range from 0 to size-1
  457. // this routine can be used to generate a normalizer cube map
  458. //
  459. // returns face IDX and texel coords
  460. //--------------------------------------------------------------------------------------
  461. // SL BEGIN
  462. /*
  463. Mapping Texture Coordinates to Cube Map Faces
  464. Because there are multiple faces, the mapping of texture coordinates to positions on cube map faces
  465. is more complicated than the other texturing targets. The EXT_texture_cube_map extension is purposefully
  466. designed to be consistent with DirectX 7's cube map arrangement. This is also consistent with the cube
  467. map arrangement in Pixar's RenderMan package.
  468. For cube map texturing, the (s,t,r) texture coordinates are treated as a direction vector (rx,ry,rz)
  469. emanating from the center of a cube. (The q coordinate can be ignored since it merely scales the vector
  470. without affecting the direction.) At texture application time, the interpolated per-fragment (s,t,r)
  471. selects one of the cube map face's 2D mipmap sets based on the largest magnitude coordinate direction
  472. the major axis direction). The target column in the table below explains how the major axis direction
  473. maps to the 2D image of a particular cube map target.
  474. major axis
  475. direction target sc tc ma
  476. ---------- --------------------------------- --- --- ---
  477. +rx GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT -rz -ry rx
  478. -rx GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT +rz -ry rx
  479. +ry GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT +rx +rz ry
  480. -ry GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT +rx -rz ry
  481. +rz GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT +rx -ry rz
  482. -rz GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT -rx -ry rz
  483. Using the sc, tc, and ma determined by the major axis direction as specified in the table above,
  484. an updated (s,t) is calculated as follows
  485. s = ( sc/|ma| + 1 ) / 2
  486. t = ( tc/|ma| + 1 ) / 2
  487. If |ma| is zero or very nearly zero, the results of the above two equations need not be defined
  488. (though the result may not lead to GL interruption or termination). Once the cube map face's 2D mipmap
  489. set and (s,t) is determined, texture fetching and filtering proceeds like standard OpenGL 2D texturing.
  490. */
  491. // Note this method return U and V in range from 0 to size-1
  492. // SL END
  493. // Store the information in vector3 for convenience (faceindex, u, v)
  494. private vectToTexelCoord(x: number, y: number, z: number, size: number): Vector4 {
  495. var maxCoord: number;
  496. var faceIdx: number;
  497. //absolute value 3
  498. var absX = Math.abs(x);
  499. var absY = Math.abs(y);
  500. var absZ = Math.abs(z);
  501. if (absX >= absY && absX >= absZ) {
  502. maxCoord = absX;
  503. if (x >= 0) //face = XPOS
  504. {
  505. faceIdx = PMREMGenerator.CP_FACE_X_POS;
  506. }
  507. else {
  508. faceIdx = PMREMGenerator.CP_FACE_X_NEG;
  509. }
  510. }
  511. else if (absY >= absX && absY >= absZ) {
  512. maxCoord = absY;
  513. if (y >= 0) //face = XPOS
  514. {
  515. faceIdx = PMREMGenerator.CP_FACE_Y_POS;
  516. }
  517. else {
  518. faceIdx = PMREMGenerator.CP_FACE_Y_NEG;
  519. }
  520. }
  521. else {
  522. maxCoord = absZ;
  523. if (z >= 0) //face = XPOS
  524. {
  525. faceIdx = PMREMGenerator.CP_FACE_Z_POS;
  526. }
  527. else {
  528. faceIdx = PMREMGenerator.CP_FACE_Z_NEG;
  529. }
  530. }
  531. //divide through by max coord so face vector lies on cube face
  532. var scale = 1 / maxCoord;
  533. x *= scale;
  534. y *= scale;
  535. z *= scale;
  536. var temp = PMREMGenerator._sgFace2DMapping[faceIdx][PMREMGenerator.CP_UDIR];
  537. var nvcU = temp[0] * x + temp[1] * y + temp[2] * z;
  538. temp = PMREMGenerator._sgFace2DMapping[faceIdx][PMREMGenerator.CP_VDIR];
  539. var nvcV = temp[0] * x + temp[1] * y + temp[2] * z;
  540. // Modify original AMD code to return value from 0 to Size - 1
  541. var u = Math.floor((size - 1) * 0.5 * (nvcU + 1.0));
  542. var v = Math.floor((size - 1) * 0.5 * (nvcV + 1.0));
  543. PMREMGenerator._vectorTemp.x = faceIdx;
  544. PMREMGenerator._vectorTemp.y = u;
  545. PMREMGenerator._vectorTemp.z = v;
  546. return PMREMGenerator._vectorTemp;
  547. }
  548. //--------------------------------------------------------------------------------------
  549. //Original code from Ignacio CastaÒo
  550. // This formula is from Manne ÷hrstrˆm's thesis.
  551. // Take two coordiantes in the range [-1, 1] that define a portion of a
  552. // cube face and return the area of the projection of that portion on the
  553. // surface of the sphere.
  554. //--------------------------------------------------------------------------------------
  555. private areaElement(x: number, y: number): number {
  556. return Math.atan2(x * y, Math.sqrt(x * x + y * y + 1));
  557. }
  558. private texelCoordSolidAngle(faceIdx: number, u: number, v: number, size: number): number {
  559. // transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
  560. // (+ 0.5f is for texel center addressing)
  561. u = (2.0 * (u + 0.5) / size) - 1.0;
  562. v = (2.0 * (v + 0.5) / size) - 1.0;
  563. // Shift from a demi texel, mean 1.0f / a_Size with U and V in [-1..1]
  564. var invResolution = 1.0 / size;
  565. // U and V are the -1..1 texture coordinate on the current face.
  566. // Get projected area for this texel
  567. var x0 = u - invResolution;
  568. var y0 = v - invResolution;
  569. var x1 = u + invResolution;
  570. var y1 = v + invResolution;
  571. var solidAngle = this.areaElement(x0, y0) - this.areaElement(x0, y1) - this.areaElement(x1, y0) + this.areaElement(x1, y1);
  572. return solidAngle;
  573. }
  574. //--------------------------------------------------------------------------------------
  575. //The key to the speed of these filtering routines is to quickly define a per-face
  576. // bounding box of pixels which enclose all the taps in the filter kernel efficiently.
  577. // Later these pixels are selectively processed based on their dot products to see if
  578. // they reside within the filtering cone.
  579. //
  580. //This is done by computing the smallest per-texel angle to get a conservative estimate
  581. // of the number of texels needed to be covered in width and height order to filter the
  582. // region. the bounding box for the center taps face is defined first, and if the
  583. // filtereing region bleeds onto the other faces, bounding boxes for the other faces are
  584. // defined next
  585. //--------------------------------------------------------------------------------------
  586. private filterCubeSurfaces(srcCubeMap: ArrayBufferView[], srcSize: number, dstCubeMap: ArrayBufferView[], dstSize: number, filterConeAngle: number,
  587. specularPower: number): void {
  588. // note that pixels within these regions may be rejected
  589. // based on the anlge
  590. var iCubeFace: number;
  591. var u: number;
  592. var v: number;
  593. // bounding box per face to specify region to process
  594. var filterExtents: CMGBoundinBox[] = [];
  595. for (iCubeFace = 0; iCubeFace < 6; iCubeFace++) {
  596. filterExtents.push(new CMGBoundinBox());
  597. }
  598. // min angle a src texel can cover (in degrees)
  599. var srcTexelAngle = (180.0 / (Math.PI) * Math.atan2(1.0, srcSize));
  600. // angle about center tap to define filter cone
  601. // filter angle is 1/2 the cone angle
  602. var filterAngle = filterConeAngle / 2.0;
  603. //ensure filter angle is larger than a texel
  604. if (filterAngle < srcTexelAngle) {
  605. filterAngle = srcTexelAngle;
  606. }
  607. //ensure filter cone is always smaller than the hemisphere
  608. if (filterAngle > 90.0) {
  609. filterAngle = 90.0;
  610. }
  611. // the maximum number of texels in 1D the filter cone angle will cover
  612. // used to determine bounding box size for filter extents
  613. var filterSize = Math.ceil(filterAngle / srcTexelAngle);
  614. // ensure conservative region always covers at least one texel
  615. if (filterSize < 1) {
  616. filterSize = 1;
  617. }
  618. // dotProdThresh threshold based on cone angle to determine whether or not taps
  619. // reside within the cone angle
  620. var dotProdThresh = Math.cos((Math.PI / 180.0) * filterAngle);
  621. // process required faces
  622. for (iCubeFace = 0; iCubeFace < 6; iCubeFace++) {
  623. //iterate over dst cube map face texel
  624. for (v = 0; v < dstSize; v++) {
  625. for (u = 0; u < dstSize; u++) {
  626. //get center tap direction
  627. var centerTapDir = this.texelCoordToVect(iCubeFace, u, v, dstSize, this.fixup).clone();
  628. //clear old per-face filter extents
  629. this.clearFilterExtents(filterExtents);
  630. //define per-face filter extents
  631. this.determineFilterExtents(centerTapDir, srcSize, filterSize, filterExtents);
  632. //perform filtering of src faces using filter extents
  633. var vect = this.processFilterExtents(centerTapDir, dotProdThresh, filterExtents, srcCubeMap, srcSize, specularPower);
  634. dstCubeMap[iCubeFace][(v * dstSize + u) * this.numChannels + 0] = vect.x;
  635. dstCubeMap[iCubeFace][(v * dstSize + u) * this.numChannels + 1] = vect.y;
  636. dstCubeMap[iCubeFace][(v * dstSize + u) * this.numChannels + 2] = vect.z;
  637. }
  638. }
  639. }
  640. }
  641. //--------------------------------------------------------------------------------------
  642. //Clear filter extents for the 6 cube map faces
  643. //--------------------------------------------------------------------------------------
  644. private clearFilterExtents(filterExtents: CMGBoundinBox[]): void {
  645. for (var iCubeFaces = 0; iCubeFaces < 6; iCubeFaces++) {
  646. filterExtents[iCubeFaces].clear();
  647. }
  648. }
  649. //--------------------------------------------------------------------------------------
  650. //Define per-face bounding box filter extents
  651. //
  652. // These define conservative texel regions in each of the faces the filter can possibly
  653. // process. When the pixels in the regions are actually processed, the dot product
  654. // between the tap vector and the center tap vector is used to determine the weight of
  655. // the tap and whether or not the tap is within the cone.
  656. //
  657. //--------------------------------------------------------------------------------------
  658. private determineFilterExtents(centerTapDir: Vector4, srcSize: number, bboxSize: number,
  659. filterExtents: CMGBoundinBox[]): void {
  660. //neighboring face and bleed over amount, and width of BBOX for
  661. // left, right, top, and bottom edges of this face
  662. var bleedOverAmount: number[] = [0, 0, 0, 0];
  663. var bleedOverBBoxMin: number[] = [0, 0, 0, 0];
  664. var bleedOverBBoxMax: number[] = [0, 0, 0, 0];
  665. var neighborFace: number;
  666. var neighborEdge: number;
  667. var oppositeFaceIdx: number;
  668. //get face idx, and u, v info from center tap dir
  669. var result = this.vectToTexelCoord(centerTapDir.x, centerTapDir.y, centerTapDir.z, srcSize);
  670. var faceIdx = result.x;
  671. var u = result.y;
  672. var v = result.z;
  673. //define bbox size within face
  674. filterExtents[faceIdx].augment(u - bboxSize, v - bboxSize, 0);
  675. filterExtents[faceIdx].augment(u + bboxSize, v + bboxSize, 0);
  676. filterExtents[faceIdx].clampMin(0, 0, 0);
  677. filterExtents[faceIdx].clampMax(srcSize - 1, srcSize - 1, 0);
  678. //u and v extent in face corresponding to center tap
  679. var minU = filterExtents[faceIdx].min.x;
  680. var minV = filterExtents[faceIdx].min.y;
  681. var maxU = filterExtents[faceIdx].max.x;
  682. var maxV = filterExtents[faceIdx].max.y;
  683. //bleed over amounts for face across u=0 edge (left)
  684. bleedOverAmount[0] = (bboxSize - u);
  685. bleedOverBBoxMin[0] = minV;
  686. bleedOverBBoxMax[0] = maxV;
  687. //bleed over amounts for face across u=1 edge (right)
  688. bleedOverAmount[1] = (u + bboxSize) - (srcSize - 1);
  689. bleedOverBBoxMin[1] = minV;
  690. bleedOverBBoxMax[1] = maxV;
  691. //bleed over to face across v=0 edge (up)
  692. bleedOverAmount[2] = (bboxSize - v);
  693. bleedOverBBoxMin[2] = minU;
  694. bleedOverBBoxMax[2] = maxU;
  695. //bleed over to face across v=1 edge (down)
  696. bleedOverAmount[3] = (v + bboxSize) - (srcSize - 1);
  697. bleedOverBBoxMin[3] = minU;
  698. bleedOverBBoxMax[3] = maxU;
  699. //compute bleed over regions in neighboring faces
  700. for (var i = 0; i < 4; i++) {
  701. if (bleedOverAmount[i] > 0) {
  702. neighborFace = PMREMGenerator._sgCubeNgh[faceIdx][i][0];
  703. neighborEdge = PMREMGenerator._sgCubeNgh[faceIdx][i][1];
  704. //For certain types of edge abutments, the bleedOverBBoxMin, and bleedOverBBoxMax need to
  705. // be flipped: the cases are
  706. // if a left edge mates with a left or bottom edge on the neighbor
  707. // if a top edge mates with a top or right edge on the neighbor
  708. // if a right edge mates with a right or top edge on the neighbor
  709. // if a bottom edge mates with a bottom or left edge on the neighbor
  710. //Seeing as the edges are enumerated as follows
  711. // left =0
  712. // right =1
  713. // top =2
  714. // bottom =3
  715. //
  716. // so if the edge enums are the same, or the sum of the enums == 3,
  717. // the bbox needs to be flipped
  718. if ((i == neighborEdge) || ((i + neighborEdge) == 3)) {
  719. bleedOverBBoxMin[i] = (srcSize - 1) - bleedOverBBoxMin[i];
  720. bleedOverBBoxMax[i] = (srcSize - 1) - bleedOverBBoxMax[i];
  721. }
  722. //The way the bounding box is extended onto the neighboring face
  723. // depends on which edge of neighboring face abuts with this one
  724. switch (PMREMGenerator._sgCubeNgh[faceIdx][i][1]) {
  725. case PMREMGenerator.CP_EDGE_LEFT:
  726. filterExtents[neighborFace].augment(0, bleedOverBBoxMin[i], 0);
  727. filterExtents[neighborFace].augment(bleedOverAmount[i], bleedOverBBoxMax[i], 0);
  728. break;
  729. case PMREMGenerator.CP_EDGE_RIGHT:
  730. filterExtents[neighborFace].augment((srcSize - 1), bleedOverBBoxMin[i], 0);
  731. filterExtents[neighborFace].augment((srcSize - 1) - bleedOverAmount[i], bleedOverBBoxMax[i], 0);
  732. break;
  733. case PMREMGenerator.CP_EDGE_TOP:
  734. filterExtents[neighborFace].augment(bleedOverBBoxMin[i], 0, 0);
  735. filterExtents[neighborFace].augment(bleedOverBBoxMax[i], bleedOverAmount[i], 0);
  736. break;
  737. case PMREMGenerator.CP_EDGE_BOTTOM:
  738. filterExtents[neighborFace].augment(bleedOverBBoxMin[i], (srcSize - 1), 0);
  739. filterExtents[neighborFace].augment(bleedOverBBoxMax[i], (srcSize - 1) - bleedOverAmount[i], 0);
  740. break;
  741. }
  742. //clamp filter extents in non-center tap faces to remain within surface
  743. filterExtents[neighborFace].clampMin(0, 0, 0);
  744. filterExtents[neighborFace].clampMax(srcSize - 1, srcSize - 1, 0);
  745. }
  746. //If the bleed over amount bleeds past the adjacent face onto the opposite face
  747. // from the center tap face, then process the opposite face entirely for now.
  748. //Note that the cases in which this happens, what usually happens is that
  749. // more than one edge bleeds onto the opposite face, and the bounding box
  750. // encompasses the entire cube map face.
  751. if (bleedOverAmount[i] > srcSize) {
  752. //determine opposite face
  753. switch (faceIdx) {
  754. case PMREMGenerator.CP_FACE_X_POS:
  755. oppositeFaceIdx = PMREMGenerator.CP_FACE_X_NEG;
  756. break;
  757. case PMREMGenerator.CP_FACE_X_NEG:
  758. oppositeFaceIdx = PMREMGenerator.CP_FACE_X_POS;
  759. break;
  760. case PMREMGenerator.CP_FACE_Y_POS:
  761. oppositeFaceIdx = PMREMGenerator.CP_FACE_Y_NEG;
  762. break;
  763. case PMREMGenerator.CP_FACE_Y_NEG:
  764. oppositeFaceIdx = PMREMGenerator.CP_FACE_Y_POS;
  765. break;
  766. case PMREMGenerator.CP_FACE_Z_POS:
  767. oppositeFaceIdx = PMREMGenerator.CP_FACE_Z_NEG;
  768. break;
  769. case PMREMGenerator.CP_FACE_Z_NEG:
  770. oppositeFaceIdx = PMREMGenerator.CP_FACE_Z_POS;
  771. break;
  772. default:
  773. break;
  774. }
  775. //just encompass entire face for now
  776. filterExtents[oppositeFaceIdx].augment(0, 0, 0);
  777. filterExtents[oppositeFaceIdx].augment((srcSize - 1), (srcSize - 1), 0);
  778. }
  779. }
  780. }
  781. //--------------------------------------------------------------------------------------
  782. //ProcessFilterExtents
  783. // Process bounding box in each cube face
  784. //
  785. //--------------------------------------------------------------------------------------
  786. private processFilterExtents(centerTapDir: Vector4, dotProdThresh: number, filterExtents: CMGBoundinBox[],
  787. srcCubeMap: ArrayBufferView[], srcSize: number, specularPower: number): Vector4 {
  788. //accumulators are 64-bit floats in order to have the precision needed
  789. // over a summation of a large number of pixels
  790. var dstAccum = [0, 0, 0, 0];
  791. var weightAccum = 0;
  792. var k = 0;
  793. var nSrcChannels = this.numChannels;
  794. // norm cube map and srcCubeMap have same face width
  795. var faceWidth = srcSize;
  796. //amount to add to pointer to move to next scanline in images
  797. var normCubePitch = faceWidth * 4; // 4 channels in normCubeMap.
  798. var srcCubePitch = faceWidth * this.numChannels; // numChannels correponds to the cubemap number of channel
  799. var IsPhongBRDF = 1; // Only works in Phong BRDF yet.
  800. //(a_LightingModel == CP_LIGHTINGMODEL_PHONG_BRDF || a_LightingModel == CP_LIGHTINGMODEL_BLINN_BRDF) ? 1 : 0; // This value will be added to the specular power
  801. // iterate over cubefaces
  802. for (var iFaceIdx = 0; iFaceIdx < 6; iFaceIdx++) {
  803. //if bbox is non empty
  804. if (!filterExtents[iFaceIdx].empty()) {
  805. var uStart = filterExtents[iFaceIdx].min.x;
  806. var vStart = filterExtents[iFaceIdx].min.y;
  807. var uEnd = filterExtents[iFaceIdx].max.x;
  808. var vEnd = filterExtents[iFaceIdx].max.y;
  809. var startIndexNormCubeMap = (4 * ((vStart * faceWidth) + uStart));
  810. var startIndexSrcCubeMap = (this.numChannels * ((vStart * faceWidth) + uStart));
  811. //note that <= is used to ensure filter extents always encompass at least one pixel if bbox is non empty
  812. for (var v = vStart; v <= vEnd; v++) {
  813. var normCubeRowWalk = 0;
  814. var srcCubeRowWalk = 0;
  815. for (var u = uStart; u <= uEnd; u++) {
  816. //pointer to direction in cube map associated with texel
  817. var texelVectX = this._normCubeMap[iFaceIdx][startIndexNormCubeMap + normCubeRowWalk + 0];
  818. var texelVectY = this._normCubeMap[iFaceIdx][startIndexNormCubeMap + normCubeRowWalk + 1];
  819. var texelVectZ = this._normCubeMap[iFaceIdx][startIndexNormCubeMap + normCubeRowWalk + 2];
  820. //check dot product to see if texel is within cone
  821. var tapDotProd = texelVectX * centerTapDir.x +
  822. texelVectY * centerTapDir.y +
  823. texelVectZ * centerTapDir.z;
  824. if (tapDotProd >= dotProdThresh && tapDotProd > 0.0) {
  825. //solid angle stored in 4th channel of normalizer/solid angle cube map
  826. var weight = this._normCubeMap[iFaceIdx][startIndexNormCubeMap + normCubeRowWalk + 3];
  827. // Here we decide if we use a Phong/Blinn or a Phong/Blinn BRDF.
  828. // Phong/Blinn BRDF is just the Phong/Blinn model multiply by the cosine of the lambert law
  829. // so just adding one to specularpower do the trick.
  830. weight *= Math.pow(tapDotProd, (specularPower + IsPhongBRDF));
  831. //iterate over channels
  832. for (k = 0; k < nSrcChannels; k++) //(aSrcCubeMap[iFaceIdx].m_NumChannels) //up to 4 channels
  833. {
  834. dstAccum[k] += weight * srcCubeMap[iFaceIdx][startIndexSrcCubeMap + srcCubeRowWalk];
  835. srcCubeRowWalk++;
  836. }
  837. weightAccum += weight; //accumulate weight
  838. }
  839. else {
  840. //step across source pixel
  841. srcCubeRowWalk += nSrcChannels;
  842. }
  843. normCubeRowWalk += 4; // 4 channels per norm cube map.
  844. }
  845. startIndexNormCubeMap += normCubePitch;
  846. startIndexSrcCubeMap += srcCubePitch;
  847. }
  848. }
  849. }
  850. //divide through by weights if weight is non zero
  851. if (weightAccum != 0.0) {
  852. PMREMGenerator._vectorTemp.x = (dstAccum[0] / weightAccum);
  853. PMREMGenerator._vectorTemp.y = (dstAccum[1] / weightAccum);
  854. PMREMGenerator._vectorTemp.z = (dstAccum[2] / weightAccum);
  855. if (this.numChannels > 3) {
  856. PMREMGenerator._vectorTemp.w = (dstAccum[3] / weightAccum);
  857. }
  858. }
  859. else {
  860. // otherwise sample nearest
  861. // get face idx and u, v texel coordinate in face
  862. var coord = this.vectToTexelCoord(centerTapDir.x, centerTapDir.y, centerTapDir.z, srcSize).clone();
  863. PMREMGenerator._vectorTemp.x = srcCubeMap[coord.x][this.numChannels * (coord.z * srcSize + coord.y) + 0];
  864. PMREMGenerator._vectorTemp.y = srcCubeMap[coord.x][this.numChannels * (coord.z * srcSize + coord.y) + 1];
  865. PMREMGenerator._vectorTemp.z = srcCubeMap[coord.x][this.numChannels * (coord.z * srcSize + coord.y) + 2];
  866. if (this.numChannels > 3) {
  867. PMREMGenerator._vectorTemp.z = srcCubeMap[coord.x][this.numChannels * (coord.z * srcSize + coord.y) + 3];
  868. }
  869. }
  870. return PMREMGenerator._vectorTemp;
  871. }
  872. //--------------------------------------------------------------------------------------
  873. // Fixup cube edges
  874. //
  875. // average texels on cube map faces across the edges
  876. // WARP/BENT Method Only.
  877. //--------------------------------------------------------------------------------------
  878. private fixupCubeEdges(cubeMap: ArrayBufferView[], cubeMapSize: number): void {
  879. var k: number;
  880. var j: number;
  881. var i: number;
  882. var iFace: number;
  883. var iCorner = 0;
  884. var cornerNumPtrs = [0, 0, 0, 0, 0, 0, 0, 0]; //indexed by corner and face idx
  885. var faceCornerStartIndicies = [[], [], [], []]; //corner pointers for face keeping track of the face they belong to.
  886. // note that if functionality to filter across the three texels for each corner, then
  887. //indexed by corner and face idx. the array contains the face the start points belongs to.
  888. var cornerPtr = [
  889. [[], [], []],
  890. [[], [], []],
  891. [[], [], []],
  892. [[], [], []],
  893. [[], [], []],
  894. [[], [], []],
  895. [[], [], []],
  896. [[], [], []]
  897. ];
  898. //if there is no fixup, or fixup width = 0, do nothing
  899. if (cubeMapSize < 1) {
  900. return;
  901. }
  902. //special case 1x1 cubemap, average face colors
  903. if (cubeMapSize == 1) {
  904. //iterate over channels
  905. for (k = 0; k < this.numChannels; k++) {
  906. var accum = 0.0;
  907. //iterate over faces to accumulate face colors
  908. for (iFace = 0; iFace < 6; iFace++) {
  909. accum += cubeMap[iFace][k];
  910. }
  911. //compute average over 6 face colors
  912. accum /= 6.0;
  913. //iterate over faces to distribute face colors
  914. for (iFace = 0; iFace < 6; iFace++) {
  915. cubeMap[iFace][k] = accum;
  916. }
  917. }
  918. return;
  919. }
  920. //iterate over faces to collect list of corner texel pointers
  921. for (iFace = 0; iFace < 6; iFace++) {
  922. //the 4 corner pointers for this face
  923. faceCornerStartIndicies[0] = [iFace, 0];
  924. faceCornerStartIndicies[1] = [iFace, ((cubeMapSize - 1) * this.numChannels)];
  925. faceCornerStartIndicies[2] = [iFace, ((cubeMapSize) * (cubeMapSize - 1) * this.numChannels)];
  926. faceCornerStartIndicies[3] = [iFace, ((((cubeMapSize) * (cubeMapSize - 1)) + (cubeMapSize - 1)) * this.numChannels)];
  927. //iterate over face corners to collect cube corner pointers
  928. for (iCorner = 0; iCorner < 4; iCorner++) {
  929. var corner = PMREMGenerator._sgCubeCornerList[iFace][iCorner];
  930. cornerPtr[corner][cornerNumPtrs[corner]] = faceCornerStartIndicies[iCorner];
  931. cornerNumPtrs[corner]++;
  932. }
  933. }
  934. //iterate over corners to average across corner tap values
  935. for (iCorner = 0; iCorner < 8; iCorner++) {
  936. for (k = 0; k < this.numChannels; k++) {
  937. var cornerTapAccum = 0.0;
  938. //iterate over corner texels and average results
  939. for (i = 0; i < 3; i++) {
  940. cornerTapAccum += cubeMap[cornerPtr[iCorner][i][0]][cornerPtr[iCorner][i][1] + k]; // Get in the cube map face the start point + channel.
  941. }
  942. //divide by 3 to compute average of corner tap values
  943. cornerTapAccum *= (1.0 / 3.0);
  944. //iterate over corner texels and average results
  945. for (i = 0; i < 3; i++) {
  946. cubeMap[cornerPtr[iCorner][i][0]][cornerPtr[iCorner][i][1] + k] = cornerTapAccum;
  947. }
  948. }
  949. }
  950. //iterate over the twelve edges of the cube to average across edges
  951. for (i = 0; i < 12; i++) {
  952. var face = PMREMGenerator._sgCubeEdgeList[i][0];
  953. var edge = PMREMGenerator._sgCubeEdgeList[i][1];
  954. var neighborFace = PMREMGenerator._sgCubeNgh[face][edge][0];
  955. var neighborEdge = PMREMGenerator._sgCubeNgh[face][edge][1];
  956. var edgeStartIndex = 0; // a_CubeMap[face].m_ImgData;
  957. var neighborEdgeStartIndex = 0; // a_CubeMap[neighborFace].m_ImgData;
  958. var edgeWalk = 0;
  959. var neighborEdgeWalk = 0;
  960. //Determine walking pointers based on edge type
  961. // e.g. CP_EDGE_LEFT, CP_EDGE_RIGHT, CP_EDGE_TOP, CP_EDGE_BOTTOM
  962. switch (edge) {
  963. case PMREMGenerator.CP_EDGE_LEFT:
  964. // no change to faceEdgeStartPtr
  965. edgeWalk = this.numChannels * cubeMapSize;
  966. break;
  967. case PMREMGenerator.CP_EDGE_RIGHT:
  968. edgeStartIndex += (cubeMapSize - 1) * this.numChannels;
  969. edgeWalk = this.numChannels * cubeMapSize;
  970. break;
  971. case PMREMGenerator.CP_EDGE_TOP:
  972. // no change to faceEdgeStartPtr
  973. edgeWalk = this.numChannels;
  974. break;
  975. case PMREMGenerator.CP_EDGE_BOTTOM:
  976. edgeStartIndex += (cubeMapSize) * (cubeMapSize - 1) * this.numChannels;
  977. edgeWalk = this.numChannels;
  978. break;
  979. }
  980. //For certain types of edge abutments, the neighbor edge walk needs to
  981. // be flipped: the cases are
  982. // if a left edge mates with a left or bottom edge on the neighbor
  983. // if a top edge mates with a top or right edge on the neighbor
  984. // if a right edge mates with a right or top edge on the neighbor
  985. // if a bottom edge mates with a bottom or left edge on the neighbor
  986. //Seeing as the edges are enumerated as follows
  987. // left =0
  988. // right =1
  989. // top =2
  990. // bottom =3
  991. //
  992. //If the edge enums are the same, or the sum of the enums == 3,
  993. // the neighbor edge walk needs to be flipped
  994. if ((edge == neighborEdge) || ((edge + neighborEdge) == 3)) { //swapped direction neighbor edge walk
  995. switch (neighborEdge) {
  996. case PMREMGenerator.CP_EDGE_LEFT: //start at lower left and walk up
  997. neighborEdgeStartIndex += (cubeMapSize - 1) * (cubeMapSize) * this.numChannels;
  998. neighborEdgeWalk = -(this.numChannels * cubeMapSize);
  999. break;
  1000. case PMREMGenerator.CP_EDGE_RIGHT: //start at lower right and walk up
  1001. neighborEdgeStartIndex += ((cubeMapSize - 1) * (cubeMapSize) + (cubeMapSize - 1)) * this.numChannels;
  1002. neighborEdgeWalk = -(this.numChannels * cubeMapSize);
  1003. break;
  1004. case PMREMGenerator.CP_EDGE_TOP: //start at upper right and walk left
  1005. neighborEdgeStartIndex += (cubeMapSize - 1) * this.numChannels;
  1006. neighborEdgeWalk = -this.numChannels;
  1007. break;
  1008. case PMREMGenerator.CP_EDGE_BOTTOM: //start at lower right and walk left
  1009. neighborEdgeStartIndex += ((cubeMapSize - 1) * (cubeMapSize) + (cubeMapSize - 1)) * this.numChannels;
  1010. neighborEdgeWalk = -this.numChannels;
  1011. break;
  1012. }
  1013. }
  1014. else {
  1015. //swapped direction neighbor edge walk
  1016. switch (neighborEdge) {
  1017. case PMREMGenerator.CP_EDGE_LEFT: //start at upper left and walk down
  1018. //no change to neighborEdgeStartPtr for this case since it points
  1019. // to the upper left corner already
  1020. neighborEdgeWalk = this.numChannels * cubeMapSize;
  1021. break;
  1022. case PMREMGenerator.CP_EDGE_RIGHT: //start at upper right and walk down
  1023. neighborEdgeStartIndex += (cubeMapSize - 1) * this.numChannels;
  1024. neighborEdgeWalk = this.numChannels * cubeMapSize;
  1025. break;
  1026. case PMREMGenerator.CP_EDGE_TOP: //start at upper left and walk left
  1027. //no change to neighborEdgeStartPtr for this case since it points
  1028. // to the upper left corner already
  1029. neighborEdgeWalk = this.numChannels;
  1030. break;
  1031. case PMREMGenerator.CP_EDGE_BOTTOM: //start at lower left and walk left
  1032. neighborEdgeStartIndex += (cubeMapSize) * (cubeMapSize - 1) * this.numChannels;
  1033. neighborEdgeWalk = this.numChannels;
  1034. break;
  1035. }
  1036. }
  1037. //Perform edge walk, to average across the 12 edges and smoothly propagate change to
  1038. //nearby neighborhood
  1039. //step ahead one texel on edge
  1040. edgeStartIndex += edgeWalk;
  1041. neighborEdgeStartIndex += neighborEdgeWalk;
  1042. // note that this loop does not process the corner texels, since they have already been
  1043. // averaged across faces across earlier
  1044. for (j = 1; j < (cubeMapSize - 1); j++) {
  1045. //for each set of taps along edge, average them
  1046. // and rewrite the results into the edges
  1047. for (k = 0; k < this.numChannels; k++) {
  1048. var edgeTap = cubeMap[face][edgeStartIndex + k];
  1049. var neighborEdgeTap = cubeMap[neighborFace][neighborEdgeStartIndex + k];
  1050. //compute average of tap intensity values
  1051. var avgTap = 0.5 * (edgeTap + neighborEdgeTap);
  1052. //propagate average of taps to edge taps
  1053. cubeMap[face][edgeStartIndex + k] = avgTap;
  1054. cubeMap[neighborFace][neighborEdgeStartIndex + k] = avgTap;
  1055. }
  1056. edgeStartIndex += edgeWalk;
  1057. neighborEdgeStartIndex += neighborEdgeWalk;
  1058. }
  1059. }
  1060. }
  1061. }
  1062. }