OctahedralProjectedCubeMap.js 14 KB

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