OctahedralProjectedCubeMap.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import ComponentDatatype from "../Core/ComponentDatatype.js";
  3. import defined from "../Core/defined.js";
  4. import destroyObject from "../Core/destroyObject.js";
  5. import IndexDatatype from "../Core/IndexDatatype.js";
  6. import loadKTX from "../Core/loadKTX.js";
  7. import PixelFormat from "../Core/PixelFormat.js";
  8. import Buffer from "../Renderer/Buffer.js";
  9. import BufferUsage from "../Renderer/BufferUsage.js";
  10. import ComputeCommand from "../Renderer/ComputeCommand.js";
  11. import CubeMap from "../Renderer/CubeMap.js";
  12. import PixelDatatype from "../Renderer/PixelDatatype.js";
  13. import ShaderProgram from "../Renderer/ShaderProgram.js";
  14. import Texture from "../Renderer/Texture.js";
  15. import VertexArray from "../Renderer/VertexArray.js";
  16. import OctahedralProjectionAtlasFS from "../Shaders/OctahedralProjectionAtlasFS.js";
  17. import OctahedralProjectionFS from "../Shaders/OctahedralProjectionFS.js";
  18. import OctahedralProjectionVS from "../Shaders/OctahedralProjectionVS.js";
  19. import when from "../ThirdParty/when.js";
  20. /**
  21. * Packs all mip levels of a cube map into a 2D texture atlas.
  22. *
  23. * Octahedral projection is a way of putting the cube maps onto a 2D texture
  24. * with minimal distortion and easy look up.
  25. * See Chapter 16 of WebGL Insights "HDR Image-Based Lighting on the Web" by Jeff Russell
  26. * and "Octahedron Environment Maps" for reference.
  27. *
  28. * @private
  29. */
  30. function OctahedralProjectedCubeMap(url) {
  31. this._url = url;
  32. this._cubeMapBuffers = undefined;
  33. this._cubeMaps = undefined;
  34. this._texture = undefined;
  35. this._mipTextures = undefined;
  36. this._va = undefined;
  37. this._sp = undefined;
  38. this._maximumMipmapLevel = undefined;
  39. this._loading = false;
  40. this._ready = false;
  41. this._readyPromise = when.defer();
  42. }
  43. Object.defineProperties(OctahedralProjectedCubeMap.prototype, {
  44. /**
  45. * The url to the KTX file containing the specular environment map and convoluted mipmaps.
  46. * @memberof OctahedralProjectedCubeMap.prototype
  47. * @type {String}
  48. * @readonly
  49. */
  50. url: {
  51. get: function () {
  52. return this._url;
  53. },
  54. },
  55. /**
  56. * A texture containing all the packed convolutions.
  57. * @memberof OctahedralProjectedCubeMap.prototype
  58. * @type {Texture}
  59. * @readonly
  60. */
  61. texture: {
  62. get: function () {
  63. return this._texture;
  64. },
  65. },
  66. /**
  67. * The maximum number of mip levels.
  68. * @memberOf OctahedralProjectedCubeMap.prototype
  69. * @type {Number}
  70. * @readonly
  71. */
  72. maximumMipmapLevel: {
  73. get: function () {
  74. return this._maximumMipmapLevel;
  75. },
  76. },
  77. /**
  78. * Determines if the texture atlas is complete and ready to use.
  79. * @memberof OctahedralProjectedCubeMap.prototype
  80. * @type {Boolean}
  81. * @readonly
  82. */
  83. ready: {
  84. get: function () {
  85. return this._ready;
  86. },
  87. },
  88. /**
  89. * Gets a promise that resolves when the texture atlas is ready to use.
  90. * @memberof OctahedralProjectedCubeMap.prototype
  91. * @type {Promise<void>}
  92. * @readonly
  93. */
  94. readyPromise: {
  95. get: function () {
  96. return this._readyPromise.promise;
  97. },
  98. },
  99. });
  100. OctahedralProjectedCubeMap.isSupported = function (context) {
  101. return (
  102. (context.colorBufferHalfFloat && context.halfFloatingPointTexture) ||
  103. (context.floatingPointTexture && context.colorBufferFloat)
  104. );
  105. };
  106. // These vertices are based on figure 1 from "Octahedron Environment Maps".
  107. var v1 = new Cartesian3(1.0, 0.0, 0.0);
  108. var v2 = new Cartesian3(0.0, 0.0, 1.0);
  109. var v3 = new Cartesian3(-1.0, 0.0, 0.0);
  110. var v4 = new Cartesian3(0.0, 0.0, -1.0);
  111. var v5 = new Cartesian3(0.0, 1.0, 0.0);
  112. var v6 = new Cartesian3(0.0, -1.0, 0.0);
  113. // top left, left, top, center, right, top right, bottom, bottom left, bottom right
  114. var cubeMapCoordinates = [v5, v3, v2, v6, v1, v5, v4, v5, v5];
  115. var length = cubeMapCoordinates.length;
  116. var flatCubeMapCoordinates = new Float32Array(length * 3);
  117. var offset = 0;
  118. for (var i = 0; i < length; ++i, offset += 3) {
  119. Cartesian3.pack(cubeMapCoordinates[i], flatCubeMapCoordinates, offset);
  120. }
  121. var flatPositions = new Float32Array([
  122. -1.0,
  123. 1.0, // top left
  124. -1.0,
  125. 0.0, // left
  126. 0.0,
  127. 1.0, // top
  128. 0.0,
  129. 0.0, // center
  130. 1.0,
  131. 0.0, // right
  132. 1.0,
  133. 1.0, // top right
  134. 0.0,
  135. -1.0, // bottom
  136. -1.0,
  137. -1.0, // bottom left
  138. 1.0,
  139. -1.0, // bottom right
  140. ]);
  141. var indices = new Uint16Array([
  142. 0,
  143. 1,
  144. 2, // top left, left, top,
  145. 2,
  146. 3,
  147. 1, // top, center, left,
  148. 7,
  149. 6,
  150. 1, // bottom left, bottom, left,
  151. 3,
  152. 6,
  153. 1, // center, bottom, left,
  154. 2,
  155. 5,
  156. 4, // top, top right, right,
  157. 3,
  158. 4,
  159. 2, // center, right, top,
  160. 4,
  161. 8,
  162. 6, // right, bottom right, bottom,
  163. 3,
  164. 4,
  165. 6, //center, right, bottom
  166. ]);
  167. function createVertexArray(context) {
  168. var positionBuffer = Buffer.createVertexBuffer({
  169. context: context,
  170. typedArray: flatPositions,
  171. usage: BufferUsage.STATIC_DRAW,
  172. });
  173. var cubeMapCoordinatesBuffer = Buffer.createVertexBuffer({
  174. context: context,
  175. typedArray: flatCubeMapCoordinates,
  176. usage: BufferUsage.STATIC_DRAW,
  177. });
  178. var indexBuffer = Buffer.createIndexBuffer({
  179. context: context,
  180. typedArray: indices,
  181. usage: BufferUsage.STATIC_DRAW,
  182. indexDatatype: IndexDatatype.UNSIGNED_SHORT,
  183. });
  184. var attributes = [
  185. {
  186. index: 0,
  187. vertexBuffer: positionBuffer,
  188. componentsPerAttribute: 2,
  189. componentDatatype: ComponentDatatype.FLOAT,
  190. },
  191. {
  192. index: 1,
  193. vertexBuffer: cubeMapCoordinatesBuffer,
  194. componentsPerAttribute: 3,
  195. componentDatatype: ComponentDatatype.FLOAT,
  196. },
  197. ];
  198. return new VertexArray({
  199. context: context,
  200. attributes: attributes,
  201. indexBuffer: indexBuffer,
  202. });
  203. }
  204. function createUniformTexture(texture) {
  205. return function () {
  206. return texture;
  207. };
  208. }
  209. function cleanupResources(map) {
  210. map._va = map._va && map._va.destroy();
  211. map._sp = map._sp && map._sp.destroy();
  212. var i;
  213. var length;
  214. var cubeMaps = map._cubeMaps;
  215. if (defined(cubeMaps)) {
  216. length = cubeMaps.length;
  217. for (i = 0; i < length; ++i) {
  218. cubeMaps[i].destroy();
  219. }
  220. }
  221. var mipTextures = map._mipTextures;
  222. if (defined(mipTextures)) {
  223. length = mipTextures.length;
  224. for (i = 0; i < length; ++i) {
  225. mipTextures[i].destroy();
  226. }
  227. }
  228. map._va = undefined;
  229. map._sp = undefined;
  230. map._cubeMaps = undefined;
  231. map._cubeMapBuffers = undefined;
  232. map._mipTextures = undefined;
  233. }
  234. /**
  235. * Creates compute commands to generate octahedral projections of each cube map
  236. * and then renders them to an atlas.
  237. * <p>
  238. * Only needs to be called twice. The first call queues the compute commands to generate the atlas.
  239. * The second call cleans up unused resources. Every call afterwards is a no-op.
  240. * </p>
  241. *
  242. * @param {FrameState} frameState The frame state.
  243. *
  244. * @private
  245. */
  246. OctahedralProjectedCubeMap.prototype.update = function (frameState) {
  247. var context = frameState.context;
  248. if (!OctahedralProjectedCubeMap.isSupported(context)) {
  249. return;
  250. }
  251. if (defined(this._texture) && defined(this._va)) {
  252. cleanupResources(this);
  253. }
  254. if (defined(this._texture)) {
  255. return;
  256. }
  257. if (!defined(this._texture) && !this._loading) {
  258. var cachedTexture = context.textureCache.getTexture(this._url);
  259. if (defined(cachedTexture)) {
  260. cleanupResources(this);
  261. this._texture = cachedTexture;
  262. this._maximumMipmapLevel = this._texture.maximumMipmapLevel;
  263. this._ready = true;
  264. this._readyPromise.resolve();
  265. return;
  266. }
  267. }
  268. var cubeMapBuffers = this._cubeMapBuffers;
  269. if (!defined(cubeMapBuffers) && !this._loading) {
  270. var that = this;
  271. loadKTX(this._url)
  272. .then(function (buffers) {
  273. that._cubeMapBuffers = buffers;
  274. that._loading = false;
  275. })
  276. .otherwise(this._readyPromise.reject);
  277. this._loading = true;
  278. }
  279. if (!defined(this._cubeMapBuffers)) {
  280. return;
  281. }
  282. this._va = createVertexArray(context);
  283. this._sp = ShaderProgram.fromCache({
  284. context: context,
  285. vertexShaderSource: OctahedralProjectionVS,
  286. fragmentShaderSource: OctahedralProjectionFS,
  287. attributeLocations: {
  288. position: 0,
  289. cubeMapCoordinates: 1,
  290. },
  291. });
  292. // We only need up to 6 mip levels to avoid artifacts.
  293. var length = Math.min(cubeMapBuffers.length, 6);
  294. this._maximumMipmapLevel = length - 1;
  295. var cubeMaps = (this._cubeMaps = new Array(length));
  296. var mipTextures = (this._mipTextures = new Array(length));
  297. var originalSize = cubeMapBuffers[0].positiveX.width * 2.0;
  298. var uniformMap = {
  299. originalSize: function () {
  300. return originalSize;
  301. },
  302. };
  303. var pixelDatatype = context.halfFloatingPointTexture
  304. ? PixelDatatype.HALF_FLOAT
  305. : PixelDatatype.FLOAT;
  306. var pixelFormat = PixelFormat.RGBA;
  307. // First we project each cubemap onto a flat octahedron, and write that to a texture.
  308. for (var i = 0; i < length; ++i) {
  309. // Swap +Y/-Y faces since the octahedral projection expects this order.
  310. var positiveY = cubeMapBuffers[i].positiveY;
  311. cubeMapBuffers[i].positiveY = cubeMapBuffers[i].negativeY;
  312. cubeMapBuffers[i].negativeY = positiveY;
  313. var cubeMap = (cubeMaps[i] = new CubeMap({
  314. context: context,
  315. source: cubeMapBuffers[i],
  316. }));
  317. var size = cubeMaps[i].width * 2;
  318. var mipTexture = (mipTextures[i] = new Texture({
  319. context: context,
  320. width: size,
  321. height: size,
  322. pixelDatatype: pixelDatatype,
  323. pixelFormat: pixelFormat,
  324. }));
  325. var command = new ComputeCommand({
  326. vertexArray: this._va,
  327. shaderProgram: this._sp,
  328. uniformMap: {
  329. cubeMap: createUniformTexture(cubeMap),
  330. },
  331. outputTexture: mipTexture,
  332. persists: true,
  333. owner: this,
  334. });
  335. frameState.commandList.push(command);
  336. uniformMap["texture" + i] = createUniformTexture(mipTexture);
  337. }
  338. this._texture = new Texture({
  339. context: context,
  340. width: originalSize * 1.5 + 2.0, // We add a 1 pixel border to avoid linear sampling artifacts.
  341. height: originalSize,
  342. pixelDatatype: pixelDatatype,
  343. pixelFormat: pixelFormat,
  344. });
  345. this._texture.maximumMipmapLevel = this._maximumMipmapLevel;
  346. context.textureCache.addTexture(this._url, this._texture);
  347. var atlasCommand = new ComputeCommand({
  348. fragmentShaderSource: OctahedralProjectionAtlasFS,
  349. uniformMap: uniformMap,
  350. outputTexture: this._texture,
  351. persists: false,
  352. owner: this,
  353. });
  354. frameState.commandList.push(atlasCommand);
  355. this._ready = true;
  356. this._readyPromise.resolve();
  357. };
  358. /**
  359. * Returns true if this object was destroyed; otherwise, false.
  360. * <p>
  361. * If this object was destroyed, it should not be used; calling any function other than
  362. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  363. * </p>
  364. *
  365. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  366. *
  367. * @see OctahedralProjectedCubeMap#destroy
  368. */
  369. OctahedralProjectedCubeMap.prototype.isDestroyed = function () {
  370. return false;
  371. };
  372. /**
  373. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  374. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  375. * <p>
  376. * Once an object is destroyed, it should not be used; calling any function other than
  377. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  378. * assign the return value (<code>undefined</code>) to the object as done in the example.
  379. * </p>
  380. *
  381. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  382. *
  383. * @see OctahedralProjectedCubeMap#isDestroyed
  384. */
  385. OctahedralProjectedCubeMap.prototype.destroy = function () {
  386. cleanupResources(this);
  387. this._texture = this._texture && this._texture.destroy();
  388. return destroyObject(this);
  389. };
  390. export default OctahedralProjectedCubeMap;