texture.ts 16 KB

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