texture.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. import { Scene, CubeTexture, InternalTexture, Scalar, BaseTexture, Texture } from "babylonjs";
  2. /**
  3. * WebGL Pixel Formats
  4. */
  5. export const enum PixelFormat {
  6. DEPTH_COMPONENT = 0x1902,
  7. ALPHA = 0x1906,
  8. RGB = 0x1907,
  9. RGBA = 0x1908,
  10. LUMINANCE = 0x1909,
  11. LUMINANCE_ALPHA = 0x190a,
  12. }
  13. /**
  14. * WebGL Pixel Types
  15. */
  16. export const enum PixelType {
  17. UNSIGNED_BYTE = 0x1401,
  18. UNSIGNED_SHORT_4_4_4_4 = 0x8033,
  19. UNSIGNED_SHORT_5_5_5_1 = 0x8034,
  20. UNSIGNED_SHORT_5_6_5 = 0x8363,
  21. }
  22. /**
  23. * WebGL Texture Magnification Filter
  24. */
  25. export const enum TextureMagFilter {
  26. NEAREST = 0x2600,
  27. LINEAR = 0x2601,
  28. }
  29. /**
  30. * WebGL Texture Minification Filter
  31. */
  32. export const enum TextureMinFilter {
  33. NEAREST = 0x2600,
  34. LINEAR = 0x2601,
  35. NEAREST_MIPMAP_NEAREST = 0x2700,
  36. LINEAR_MIPMAP_NEAREST = 0x2701,
  37. NEAREST_MIPMAP_LINEAR = 0x2702,
  38. LINEAR_MIPMAP_LINEAR = 0x2703,
  39. }
  40. /**
  41. * WebGL Texture Wrap Modes
  42. */
  43. export const enum TextureWrapMode {
  44. REPEAT = 0x2901,
  45. CLAMP_TO_EDGE = 0x812f,
  46. MIRRORED_REPEAT = 0x8370,
  47. }
  48. /**
  49. * Raw texture data and descriptor sufficient for WebGL texture upload
  50. */
  51. export interface TextureData {
  52. /**
  53. * Width of image
  54. */
  55. width: number;
  56. /**
  57. * Height of image
  58. */
  59. height: number;
  60. /**
  61. * Format of pixels in data
  62. */
  63. format: PixelFormat;
  64. /**
  65. * Row byte alignment of pixels in data
  66. */
  67. alignment: number;
  68. /**
  69. * Pixel data
  70. */
  71. data: ArrayBufferView;
  72. }
  73. /**
  74. * Wraps sampling parameters for a WebGL texture
  75. */
  76. export interface SamplingParameters {
  77. /**
  78. * Magnification mode when upsampling from a WebGL texture
  79. */
  80. magFilter?: TextureMagFilter;
  81. /**
  82. * Minification mode when upsampling from a WebGL texture
  83. */
  84. minFilter?: TextureMinFilter;
  85. /**
  86. * X axis wrapping mode when sampling out of a WebGL texture bounds
  87. */
  88. wrapS?: TextureWrapMode;
  89. /**
  90. * Y axis wrapping mode when sampling out of a WebGL texture bounds
  91. */
  92. wrapT?: TextureWrapMode;
  93. /**
  94. * Anisotropic filtering samples
  95. */
  96. maxAnisotropy?: number;
  97. }
  98. /**
  99. * Represents a valid WebGL texture source for use in texImage2D
  100. */
  101. export type TextureSource = TextureData | ImageData | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement;
  102. /**
  103. * A generic set of texture mipmaps (where index 0 has the largest dimension)
  104. */
  105. export type Mipmaps<T> = Array<T>;
  106. /**
  107. * A set of 6 cubemap arranged in the order [+x, -x, +y, -y, +z, -z]
  108. */
  109. export type Faces<T> = Array<T>;
  110. /**
  111. * A set of texture mipmaps specifically for 2D textures in WebGL (where index 0 has the largest dimension)
  112. */
  113. export type Mipmaps2D = Mipmaps<TextureSource>;
  114. /**
  115. * A set of texture mipmaps specifically for cubemap textures in WebGL (where index 0 has the largest dimension)
  116. */
  117. export type MipmapsCube = Mipmaps<Faces<TextureSource>>;
  118. /**
  119. * A minimal WebGL cubemap descriptor
  120. */
  121. export class TextureCube {
  122. /**
  123. * Returns the width of a face of the texture or 0 if not available
  124. */
  125. public get Width(): number {
  126. return (this.source && this.source[0] && this.source[0][0]) ? this.source[0][0].width : 0;
  127. }
  128. /**
  129. * Returns the height of a face of the texture or 0 if not available
  130. */
  131. public get Height(): number {
  132. return (this.source && this.source[0] && this.source[0][0]) ? this.source[0][0].height : 0;
  133. }
  134. /**
  135. * constructor
  136. * @param internalFormat WebGL pixel format for the texture on the GPU
  137. * @param type WebGL pixel type of the supplied data and texture on the GPU
  138. * @param source An array containing mipmap levels of faces, where each mipmap level is an array of faces and each face is a TextureSource object
  139. */
  140. constructor(public internalFormat: PixelFormat, public type: PixelType, public source: MipmapsCube = []) { }
  141. }
  142. /**
  143. * A static class providing methods to aid working with Bablyon textures.
  144. */
  145. export class TextureUtils {
  146. /**
  147. * A prefix used when storing a babylon texture object reference on a Spectre texture object
  148. */
  149. public static BabylonTextureKeyPrefix = '__babylonTexture_';
  150. /**
  151. * Controls anisotropic filtering for deserialized textures.
  152. */
  153. public static MaxAnisotropy = 4;
  154. /**
  155. * Returns a BabylonCubeTexture instance from a Spectre texture cube, subject to sampling parameters.
  156. * If such a texture has already been requested in the past, this texture will be returned, otherwise a new one will be created.
  157. * The advantage of this is to enable working with texture objects without the need to initialize on the GPU until desired.
  158. * @param scene A Babylon Scene instance
  159. * @param textureCube A Spectre TextureCube object
  160. * @param parameters WebGL texture sampling parameters
  161. * @param automaticMipmaps Pass true to enable automatic mipmap generation where possible (requires power of images)
  162. * @param environment Specifies that the texture will be used as an environment
  163. * @param singleLod Specifies that the texture will be a singleLod (for environment)
  164. * @return Babylon cube texture
  165. */
  166. public static GetBabylonCubeTexture(scene: Scene, textureCube: TextureCube, automaticMipmaps: boolean, environment = false, singleLod = false): CubeTexture {
  167. if (!textureCube) throw new Error("no texture cube provided");
  168. var parameters: SamplingParameters;
  169. if (environment) {
  170. parameters = singleLod ? TextureUtils._EnvironmentSingleMipSampling : TextureUtils._EnvironmentSampling;
  171. }
  172. else {
  173. parameters = {
  174. magFilter: TextureMagFilter.NEAREST,
  175. minFilter: TextureMinFilter.NEAREST,
  176. wrapS: TextureWrapMode.CLAMP_TO_EDGE,
  177. wrapT: TextureWrapMode.CLAMP_TO_EDGE
  178. };
  179. }
  180. let key = TextureUtils.BabylonTextureKeyPrefix + parameters.magFilter + '' + parameters.minFilter + '' + parameters.wrapS + '' + parameters.wrapT;
  181. let babylonTexture: CubeTexture = (<any>textureCube)[key];
  182. if (!babylonTexture) {
  183. //initialize babylon texture
  184. babylonTexture = new CubeTexture('', scene);
  185. if (environment) {
  186. babylonTexture.lodGenerationOffset = TextureUtils.EnvironmentLODOffset;
  187. babylonTexture.lodGenerationScale = TextureUtils.EnvironmentLODScale;
  188. }
  189. babylonTexture.gammaSpace = false;
  190. let internalTexture = new InternalTexture(scene.getEngine(), InternalTexture.DATASOURCE_CUBERAW);
  191. let glTexture = internalTexture._webGLTexture;
  192. //babylon properties
  193. internalTexture.isCube = true;
  194. internalTexture.generateMipMaps = false;
  195. babylonTexture._texture = internalTexture;
  196. TextureUtils.ApplySamplingParameters(babylonTexture, parameters);
  197. let maxMipLevel = automaticMipmaps ? 0 : textureCube.source.length - 1;
  198. let texturesUploaded = 0;
  199. var textureComplete = function () {
  200. return texturesUploaded === ((maxMipLevel + 1) * 6);
  201. };
  202. var uploadFace = function (i: number, level: number, face: TextureSource) {
  203. if (!glTexture) return;
  204. if (i === 0 && level === 0) {
  205. internalTexture.width = face.width;
  206. internalTexture.height = face.height;
  207. }
  208. let gl = (<any>(scene.getEngine()))._gl;
  209. gl.bindTexture(gl.TEXTURE_CUBE_MAP, glTexture);
  210. scene.getEngine()._unpackFlipY(false);
  211. if (face instanceof HTMLElement || face instanceof ImageData) {
  212. gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, textureCube.internalFormat, textureCube.internalFormat, textureCube.type, <any>face);
  213. } else {
  214. let textureData = <TextureData>face;
  215. gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, textureCube.internalFormat, textureData.width, textureData.height, 0, textureData.format, textureCube.type, textureData.data);
  216. }
  217. texturesUploaded++;
  218. if (textureComplete()) {
  219. //generate mipmaps
  220. if (automaticMipmaps) {
  221. let w = face.width;
  222. let h = face.height;
  223. let isPot = (((w !== 0) && (w & (w - 1))) === 0) && (((h !== 0) && (h & (h - 1))) === 0);
  224. if (isPot) {
  225. gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
  226. }
  227. }
  228. // Upload Separate lods in case there is no support for texture lod.
  229. if (environment && !scene.getEngine().getCaps().textureLOD && !singleLod) {
  230. const mipSlices = 3;
  231. for (let i = 0; i < mipSlices; i++) {
  232. let lodKey = TextureUtils.BabylonTextureKeyPrefix + 'lod' + i;
  233. let lod: CubeTexture = (<any>textureCube)[lodKey];
  234. //initialize lod texture if it doesn't already exist
  235. if (lod == null && textureCube.Width) {
  236. //compute LOD from even spacing in smoothness (matching shader calculation)
  237. let smoothness = i / (mipSlices - 1);
  238. let roughness = 1 - smoothness;
  239. const kMinimumVariance = 0.0005;
  240. let alphaG = roughness * roughness + kMinimumVariance;
  241. let microsurfaceAverageSlopeTexels = alphaG * textureCube.Width;
  242. let environmentSpecularLOD = TextureUtils.EnvironmentLODScale * (Scalar.Log2(microsurfaceAverageSlopeTexels)) + TextureUtils.EnvironmentLODOffset;
  243. let maxLODIndex = textureCube.source.length - 1;
  244. let mipmapIndex = Math.min(Math.max(Math.round(environmentSpecularLOD), 0), maxLODIndex);
  245. lod = TextureUtils.GetBabylonCubeTexture(scene, new TextureCube(PixelFormat.RGBA, PixelType.UNSIGNED_BYTE, [textureCube.source[mipmapIndex]]), false, true, true);
  246. if (i === 0) {
  247. internalTexture._lodTextureLow = lod;
  248. }
  249. else if (i === 1) {
  250. internalTexture._lodTextureMid = lod;
  251. }
  252. else {
  253. internalTexture._lodTextureHigh = lod;
  254. }
  255. (<any>textureCube)[lodKey] = lod;
  256. }
  257. }
  258. }
  259. internalTexture.isReady = true;
  260. }
  261. gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
  262. scene.getEngine().resetTextureCache();
  263. };
  264. for (let i = 0; i <= maxMipLevel; i++) {
  265. let faces = textureCube.source[i];
  266. for (let j = 0; j < faces.length; j++) {
  267. let face = faces[j];
  268. if (face instanceof HTMLImageElement && !face.complete) {
  269. face.addEventListener('load', () => {
  270. uploadFace(j, i, face);
  271. }, false);
  272. } else {
  273. uploadFace(j, i, face);
  274. }
  275. }
  276. }
  277. scene.getEngine().resetTextureCache();
  278. babylonTexture.isReady = () => {
  279. return textureComplete();
  280. };
  281. (<any>textureCube)[key] = babylonTexture;
  282. }
  283. return babylonTexture;
  284. }
  285. /**
  286. * Applies Spectre SamplingParameters to a Babylon texture by directly setting texture parameters on the internal WebGLTexture as well as setting Babylon fields
  287. * @param babylonTexture Babylon texture to apply texture to (requires the Babylon texture has an initialize _texture field)
  288. * @param parameters Spectre SamplingParameters to apply
  289. */
  290. public static ApplySamplingParameters(babylonTexture: BaseTexture, parameters: SamplingParameters) {
  291. let scene = babylonTexture.getScene();
  292. if (!scene) return;
  293. let gl = (<any>(scene.getEngine()))._gl;
  294. let target = babylonTexture.isCube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
  295. let internalTexture = babylonTexture._texture;
  296. if (!internalTexture) return;
  297. let glTexture = internalTexture._webGLTexture;
  298. gl.bindTexture(target, glTexture);
  299. if (parameters.magFilter != null) gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, parameters.magFilter);
  300. if (parameters.minFilter != null) gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, parameters.minFilter);
  301. if (parameters.wrapS != null) gl.texParameteri(target, gl.TEXTURE_WRAP_S, parameters.wrapS);
  302. if (parameters.wrapT != null) gl.texParameteri(target, gl.TEXTURE_WRAP_T, parameters.wrapT);
  303. //set babylon wrap modes from sampling parameter
  304. switch (parameters.wrapS) {
  305. case TextureWrapMode.REPEAT: babylonTexture.wrapU = Texture.WRAP_ADDRESSMODE; break;
  306. case TextureWrapMode.CLAMP_TO_EDGE: babylonTexture.wrapU = Texture.CLAMP_ADDRESSMODE; break;
  307. case TextureWrapMode.MIRRORED_REPEAT: babylonTexture.wrapU = Texture.MIRROR_ADDRESSMODE; break;
  308. default: babylonTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
  309. }
  310. switch (parameters.wrapT) {
  311. case TextureWrapMode.REPEAT: babylonTexture.wrapV = Texture.WRAP_ADDRESSMODE; break;
  312. case TextureWrapMode.CLAMP_TO_EDGE: babylonTexture.wrapV = Texture.CLAMP_ADDRESSMODE; break;
  313. case TextureWrapMode.MIRRORED_REPEAT: babylonTexture.wrapV = Texture.MIRROR_ADDRESSMODE; break;
  314. default: babylonTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
  315. }
  316. if (parameters.maxAnisotropy != null && parameters.maxAnisotropy > 1) {
  317. let anisotropicExt = gl.getExtension('EXT_texture_filter_anisotropic');
  318. if (anisotropicExt) {
  319. let maxAnisotropicSamples = gl.getParameter(anisotropicExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
  320. let maxAnisotropy = Math.min(parameters.maxAnisotropy, maxAnisotropicSamples);
  321. gl.texParameterf(target, anisotropicExt.TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);
  322. babylonTexture.anisotropicFilteringLevel = maxAnisotropy;
  323. }
  324. }
  325. gl.bindTexture(target, null);
  326. scene.getEngine().resetTextureCache();
  327. }
  328. private static _EnvironmentSampling: SamplingParameters = {
  329. magFilter: TextureMagFilter.LINEAR,
  330. minFilter: TextureMinFilter.LINEAR_MIPMAP_LINEAR,
  331. wrapS: TextureWrapMode.CLAMP_TO_EDGE,
  332. wrapT: TextureWrapMode.CLAMP_TO_EDGE,
  333. maxAnisotropy: 1
  334. };
  335. private static _EnvironmentSingleMipSampling: SamplingParameters = {
  336. magFilter: TextureMagFilter.LINEAR,
  337. minFilter: TextureMinFilter.LINEAR,
  338. wrapS: TextureWrapMode.CLAMP_TO_EDGE,
  339. wrapT: TextureWrapMode.CLAMP_TO_EDGE,
  340. maxAnisotropy: 1
  341. };
  342. //from "/Internal/Lighting.EnvironmentFilterScale" in Engine/*/Configuration.cpp
  343. /**
  344. * Environment preprocessing dedicated value (Internal Use or Advanced only).
  345. */
  346. public static EnvironmentLODScale = 0.8;
  347. /**
  348. * Environment preprocessing dedicated value (Internal Use or Advanced only)..
  349. */
  350. public static EnvironmentLODOffset = 1.0;
  351. }