|
@@ -152,4 +152,259 @@ export class TextureCube {
|
|
|
* @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
|
|
|
*/
|
|
|
constructor(public internalFormat: PixelFormat, public type: PixelType, public source: MipmapsCube = []) { }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * A static class providing methods to aid working with Bablyon textures.
|
|
|
+ */
|
|
|
+export class TextureUtils {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A prefix used when storing a babylon texture object reference on a Spectre texture object
|
|
|
+ */
|
|
|
+ public static BabylonTextureKeyPrefix = '__babylonTexture_';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Controls anisotropic filtering for deserialized textures.
|
|
|
+ */
|
|
|
+ public static MaxAnisotropy = 4;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns a BabylonCubeTexture instance from a Spectre texture cube, subject to sampling parameters.
|
|
|
+ * If such a texture has already been requested in the past, this texture will be returned, otherwise a new one will be created.
|
|
|
+ * The advantage of this is to enable working with texture objects without the need to initialize on the GPU until desired.
|
|
|
+ * @param scene A Babylon Scene instance
|
|
|
+ * @param textureCube A Spectre TextureCube object
|
|
|
+ * @param parameters WebGL texture sampling parameters
|
|
|
+ * @param automaticMipmaps Pass true to enable automatic mipmap generation where possible (requires power of images)
|
|
|
+ * @param environment Specifies that the texture will be used as an environment
|
|
|
+ * @param singleLod Specifies that the texture will be a singleLod (for environment)
|
|
|
+ * @return Babylon cube texture
|
|
|
+ */
|
|
|
+ public static GetBabylonCubeTexture(scene: BABYLON.Scene, textureCube: TextureCube, automaticMipmaps: boolean, environment = false, singleLod = false): BABYLON.CubeTexture {
|
|
|
+ if (!textureCube) throw new Error("no texture cube provided");
|
|
|
+
|
|
|
+ var parameters: SamplingParameters;
|
|
|
+ if (environment) {
|
|
|
+ parameters = singleLod ? TextureUtils._EnvironmentSingleMipSampling : TextureUtils._EnvironmentSampling;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ parameters = {
|
|
|
+ magFilter: TextureMagFilter.NEAREST,
|
|
|
+ minFilter: TextureMinFilter.NEAREST,
|
|
|
+ wrapS: TextureWrapMode.CLAMP_TO_EDGE,
|
|
|
+ wrapT: TextureWrapMode.CLAMP_TO_EDGE
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ let key = TextureUtils.BabylonTextureKeyPrefix + parameters.magFilter + '' + parameters.minFilter + '' + parameters.wrapS + '' + parameters.wrapT;
|
|
|
+
|
|
|
+ let babylonTexture: BABYLON.CubeTexture = (<any>textureCube)[key];
|
|
|
+
|
|
|
+ if (!babylonTexture) {
|
|
|
+
|
|
|
+ //initialize babylon texture
|
|
|
+ babylonTexture = new BABYLON.CubeTexture('', scene);
|
|
|
+ if (environment) {
|
|
|
+ babylonTexture.lodGenerationOffset = TextureUtils.EnvironmentLODOffset;
|
|
|
+ babylonTexture.lodGenerationScale = TextureUtils.EnvironmentLODScale;
|
|
|
+ }
|
|
|
+
|
|
|
+ babylonTexture.gammaSpace = false;
|
|
|
+
|
|
|
+ let internalTexture = new BABYLON.InternalTexture(scene.getEngine(), BABYLON.InternalTexture.DATASOURCE_CUBERAW);
|
|
|
+ let glTexture = internalTexture._webGLTexture;
|
|
|
+ //babylon properties
|
|
|
+ internalTexture.isCube = true;
|
|
|
+ internalTexture.generateMipMaps = false;
|
|
|
+
|
|
|
+ babylonTexture._texture = internalTexture;
|
|
|
+
|
|
|
+ TextureUtils.ApplySamplingParameters(babylonTexture, parameters);
|
|
|
+
|
|
|
+ let maxMipLevel = automaticMipmaps ? 0 : textureCube.source.length - 1;
|
|
|
+ let texturesUploaded = 0;
|
|
|
+
|
|
|
+ var textureComplete = function () {
|
|
|
+ return texturesUploaded === ((maxMipLevel + 1) * 6);
|
|
|
+ };
|
|
|
+
|
|
|
+ var uploadFace = function (i: number, level: number, face: TextureSource) {
|
|
|
+ if (!glTexture) return;
|
|
|
+
|
|
|
+ if (i === 0 && level === 0) {
|
|
|
+ internalTexture.width = face.width;
|
|
|
+ internalTexture.height = face.height;
|
|
|
+ }
|
|
|
+
|
|
|
+ let gl = (<any>(scene.getEngine()))._gl;
|
|
|
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, glTexture);
|
|
|
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
|
|
|
+ if (face instanceof HTMLElement || face instanceof ImageData) {
|
|
|
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, textureCube.internalFormat, textureCube.internalFormat, textureCube.type, <any>face);
|
|
|
+ } else {
|
|
|
+ let textureData = <TextureData>face;
|
|
|
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, textureCube.internalFormat, textureData.width, textureData.height, 0, textureData.format, textureCube.type, textureData.data);
|
|
|
+ }
|
|
|
+
|
|
|
+ texturesUploaded++;
|
|
|
+
|
|
|
+ if (textureComplete()) {
|
|
|
+ //generate mipmaps
|
|
|
+ if (automaticMipmaps) {
|
|
|
+ let w = face.width;
|
|
|
+ let h = face.height;
|
|
|
+ let isPot = (((w !== 0) && (w & (w - 1))) === 0) && (((h !== 0) && (h & (h - 1))) === 0);
|
|
|
+ if (isPot) {
|
|
|
+ gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Upload Separate lods in case there is no support for texture lod.
|
|
|
+ if (environment && !scene.getEngine().getCaps().textureLOD && !singleLod) {
|
|
|
+ const mipSlices = 3;
|
|
|
+ for (let i = 0; i < mipSlices; i++) {
|
|
|
+ let lodKey = TextureUtils.BabylonTextureKeyPrefix + 'lod' + i;
|
|
|
+ let lod: BABYLON.CubeTexture = (<any>textureCube)[lodKey];
|
|
|
+
|
|
|
+ //initialize lod texture if it doesn't already exist
|
|
|
+ if (lod == null && textureCube.Width) {
|
|
|
+ //compute LOD from even spacing in smoothness (matching shader calculation)
|
|
|
+ let smoothness = i / (mipSlices - 1);
|
|
|
+ let roughness = 1 - smoothness;
|
|
|
+ const kMinimumVariance = 0.0005;
|
|
|
+ let alphaG = roughness * roughness + kMinimumVariance;
|
|
|
+ let microsurfaceAverageSlopeTexels = alphaG * textureCube.Width;
|
|
|
+
|
|
|
+ let environmentSpecularLOD = TextureUtils.EnvironmentLODScale * (BABYLON.Scalar.Log2(microsurfaceAverageSlopeTexels)) + TextureUtils.EnvironmentLODOffset;
|
|
|
+
|
|
|
+ let maxLODIndex = textureCube.source.length - 1;
|
|
|
+ let mipmapIndex = Math.min(Math.max(Math.round(environmentSpecularLOD), 0), maxLODIndex);
|
|
|
+
|
|
|
+ lod = TextureUtils.GetBabylonCubeTexture(scene, new TextureCube(PixelFormat.RGBA, PixelType.UNSIGNED_BYTE, [textureCube.source[mipmapIndex]]), false, true, true);
|
|
|
+
|
|
|
+ if (i === 0) {
|
|
|
+ internalTexture._lodTextureLow = lod;
|
|
|
+ }
|
|
|
+ else if (i === 1) {
|
|
|
+ internalTexture._lodTextureMid = lod;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ internalTexture._lodTextureHigh = lod;
|
|
|
+ }
|
|
|
+
|
|
|
+ (<any>textureCube)[lodKey] = lod;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ internalTexture.isReady = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
|
|
|
+ scene.getEngine().resetTextureCache();
|
|
|
+ };
|
|
|
+
|
|
|
+ for (let i = 0; i <= maxMipLevel; i++) {
|
|
|
+ let faces = textureCube.source[i];
|
|
|
+ for (let j = 0; j < faces.length; j++) {
|
|
|
+ let face = faces[j];
|
|
|
+ if (face instanceof HTMLImageElement && !face.complete) {
|
|
|
+ face.addEventListener('load', () => {
|
|
|
+ uploadFace(j, i, face);
|
|
|
+ }, false);
|
|
|
+ } else {
|
|
|
+ uploadFace(j, i, face);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ scene.getEngine().resetTextureCache();
|
|
|
+
|
|
|
+ babylonTexture.isReady = () => {
|
|
|
+ return textureComplete();
|
|
|
+ };
|
|
|
+
|
|
|
+ (<any>textureCube)[key] = babylonTexture;
|
|
|
+ }
|
|
|
+
|
|
|
+ return babylonTexture;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Applies Spectre SamplingParameters to a Babylon texture by directly setting texture parameters on the internal WebGLTexture as well as setting Babylon fields
|
|
|
+ * @param babylonTexture Babylon texture to apply texture to (requires the Babylon texture has an initialize _texture field)
|
|
|
+ * @param parameters Spectre SamplingParameters to apply
|
|
|
+ */
|
|
|
+ public static ApplySamplingParameters(babylonTexture: BABYLON.BaseTexture, parameters: SamplingParameters) {
|
|
|
+ let scene = babylonTexture.getScene();
|
|
|
+ if (!scene) return;
|
|
|
+ let gl = (<any>(scene.getEngine()))._gl;
|
|
|
+
|
|
|
+ let target = babylonTexture.isCube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
|
|
|
+
|
|
|
+ let internalTexture = babylonTexture._texture;
|
|
|
+ if (!internalTexture) return;
|
|
|
+ let glTexture = internalTexture._webGLTexture;
|
|
|
+ gl.bindTexture(target, glTexture);
|
|
|
+
|
|
|
+ if (parameters.magFilter != null) gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, parameters.magFilter);
|
|
|
+ if (parameters.minFilter != null) gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, parameters.minFilter);
|
|
|
+ if (parameters.wrapS != null) gl.texParameteri(target, gl.TEXTURE_WRAP_S, parameters.wrapS);
|
|
|
+ if (parameters.wrapT != null) gl.texParameteri(target, gl.TEXTURE_WRAP_T, parameters.wrapT);
|
|
|
+
|
|
|
+ //set babylon wrap modes from sampling parameter
|
|
|
+ switch (parameters.wrapS) {
|
|
|
+ case TextureWrapMode.REPEAT: babylonTexture.wrapU = BABYLON.Texture.WRAP_ADDRESSMODE; break;
|
|
|
+ case TextureWrapMode.CLAMP_TO_EDGE: babylonTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; break;
|
|
|
+ case TextureWrapMode.MIRRORED_REPEAT: babylonTexture.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE; break;
|
|
|
+ default: babylonTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (parameters.wrapT) {
|
|
|
+ case TextureWrapMode.REPEAT: babylonTexture.wrapV = BABYLON.Texture.WRAP_ADDRESSMODE; break;
|
|
|
+ case TextureWrapMode.CLAMP_TO_EDGE: babylonTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; break;
|
|
|
+ case TextureWrapMode.MIRRORED_REPEAT: babylonTexture.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE; break;
|
|
|
+ default: babylonTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (parameters.maxAnisotropy != null && parameters.maxAnisotropy > 1) {
|
|
|
+ let anisotropicExt = gl.getExtension('EXT_texture_filter_anisotropic');
|
|
|
+ if (anisotropicExt) {
|
|
|
+ let maxAnisotropicSamples = gl.getParameter(anisotropicExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
|
|
|
+ let maxAnisotropy = Math.min(parameters.maxAnisotropy, maxAnisotropicSamples);
|
|
|
+ gl.texParameterf(target, anisotropicExt.TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);
|
|
|
+ babylonTexture.anisotropicFilteringLevel = maxAnisotropy;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ gl.bindTexture(target, null);
|
|
|
+ scene.getEngine().resetTextureCache();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static _EnvironmentSampling: SamplingParameters = {
|
|
|
+ magFilter: TextureMagFilter.LINEAR,
|
|
|
+ minFilter: TextureMinFilter.LINEAR_MIPMAP_LINEAR,
|
|
|
+ wrapS: TextureWrapMode.CLAMP_TO_EDGE,
|
|
|
+ wrapT: TextureWrapMode.CLAMP_TO_EDGE,
|
|
|
+ maxAnisotropy: 1
|
|
|
+ };
|
|
|
+
|
|
|
+ private static _EnvironmentSingleMipSampling: SamplingParameters = {
|
|
|
+ magFilter: TextureMagFilter.LINEAR,
|
|
|
+ minFilter: TextureMinFilter.LINEAR,
|
|
|
+ wrapS: TextureWrapMode.CLAMP_TO_EDGE,
|
|
|
+ wrapT: TextureWrapMode.CLAMP_TO_EDGE,
|
|
|
+ maxAnisotropy: 1
|
|
|
+ };
|
|
|
+
|
|
|
+ //from "/Internal/Lighting.EnvironmentFilterScale" in Engine/*/Configuration.cpp
|
|
|
+ /**
|
|
|
+ * Environment preprocessing dedicated value (Internal Use or Advanced only).
|
|
|
+ */
|
|
|
+ public static EnvironmentLODScale = 0.8;
|
|
|
+ /**
|
|
|
+ * Environment preprocessing dedicated value (Internal Use or Advanced only)..
|
|
|
+ */
|
|
|
+ public static EnvironmentLODOffset = 1.0;
|
|
|
}
|