import { Vector3, Tools } from "babylonjs"; import { TextureCube, PixelFormat, PixelType } from './texture'; /** * Spherical polynomial coefficients (counter part to spherical harmonic coefficients used in shader irradiance calculation) * @ignoreChildren */ export interface SphericalPolynomalCoefficients { x: Vector3; y: Vector3; z: Vector3; xx: Vector3; yy: Vector3; zz: Vector3; yz: Vector3; zx: Vector3; xy: Vector3; } /** * Wraps data and maps required for environments with physically based rendering */ export interface PBREnvironment { /** * Spherical Polynomial Coefficients representing an irradiance map */ irradiancePolynomialCoefficients: SphericalPolynomalCoefficients; /** * Specular cubemap */ specularTexture?: TextureCube; /** * A scale factor applied to RGB values after reading from environment maps */ textureIntensityScale: number; } /** * Environment map representations: layouts, projections and approximations */ export type MapType = 'irradiance_sh_coefficients_9' | 'cubemap_faces'; /** * Image type used for environment map */ export type ImageType = 'png'; //Payload Descriptor /** * A generic field in JSON that report's its type */ export interface TypedObject { type: T; } /** * Describes a range of bytes starting at byte pos (inclusive) and finishing at byte pos + length - 1 */ export interface ByteRange { pos: number; length: number; } /** * Complete Spectre Environment JSON Descriptor */ export interface EnvJsonDescriptor { radiance: TypedObject; irradiance: TypedObject; specular: TypedObject; } /** * Spherical harmonic coefficients to provide an irradiance map */ export interface IrradianceSHCoefficients9 extends TypedObject { l00: Array; l1_1: Array; l10: Array; l11: Array; l2_2: Array; l2_1: Array; l20: Array; l21: Array; l22: Array; } /** * A generic set of images, where the image content is specified by byte ranges in the mipmaps field */ export interface ImageSet extends TypedObject { imageType: ImageType; width: number; height: number; mipmaps: Array; multiplier: number; } /** * A set of cubemap faces */ export type CubemapFaces = ImageSet>; /** * A single image containing an atlas of equirectangular-projection maps across all mip levels */ export type EquirectangularMipmapAtlas = ImageSet; /** * A static class proving methods to aid parsing Spectre environment files */ export class EnvironmentDeserializer { /** * Parses an arraybuffer into a new PBREnvironment object * @param arrayBuffer The arraybuffer of the Spectre environment file * @return a PBREnvironment object */ public static Parse(arrayBuffer: ArrayBuffer): PBREnvironment { var environment: PBREnvironment = { //irradiance irradiancePolynomialCoefficients: { x: new Vector3(0, 0, 0), y: new Vector3(0, 0, 0), z: new Vector3(0, 0, 0), xx: new Vector3(0, 0, 0), yy: new Vector3(0, 0, 0), zz: new Vector3(0, 0, 0), yz: new Vector3(0, 0, 0), zx: new Vector3(0, 0, 0), xy: new Vector3(0, 0, 0) }, //specular textureIntensityScale: 1.0, }; //read .env let littleEndian = false; let magicBytes = [0x86, 0x16, 0x87, 0x96, 0xf6, 0xd6, 0x96, 0x36]; let dataView = new DataView(arrayBuffer); let pos = 0; for (let i = 0; i < magicBytes.length; i++) { if (dataView.getUint8(pos++) !== magicBytes[i]) { Tools.Error('Not a Spectre environment map'); } } let version = dataView.getUint16(pos, littleEndian); pos += 2; if (version !== 1) { Tools.Warn('Unsupported Spectre environment map version "' + version + '"'); } //read json descriptor - collect characters up to null terminator let descriptorString = ''; let charCode = 0x00; while ((charCode = dataView.getUint8(pos++))) { descriptorString += String.fromCharCode(charCode); } let descriptor: EnvJsonDescriptor = JSON.parse(descriptorString); let payloadPos = pos; //irradiance switch (descriptor.irradiance.type) { case 'irradiance_sh_coefficients_9': //irradiance let harmonics = descriptor.irradiance; EnvironmentDeserializer._ConvertSHIrradianceToLambertianRadiance(harmonics); //harmonics now represent radiance EnvironmentDeserializer._ConvertSHToSP(harmonics, environment.irradiancePolynomialCoefficients); break; default: Tools.Error('Unhandled MapType descriptor.irradiance.type (' + descriptor.irradiance.type + ')'); } //specular switch (descriptor.specular.type) { case 'cubemap_faces': var specularDescriptor = descriptor.specular; let specularTexture = environment.specularTexture = new TextureCube(PixelFormat.RGBA, PixelType.UNSIGNED_BYTE); environment.textureIntensityScale = specularDescriptor.multiplier != null ? specularDescriptor.multiplier : 1.0; let mipmaps = specularDescriptor.mipmaps; let imageType = specularDescriptor.imageType; for (let l = 0; l < mipmaps.length; l++) { let faceRanges = mipmaps[l]; specularTexture.source[l] = []; for (let i = 0; i < 6; i++) { let range = faceRanges[i]; let bytes = new Uint8Array(arrayBuffer, payloadPos + range.pos, range.length); switch (imageType) { case 'png': //construct image element from bytes let image = new Image(); let src = URL.createObjectURL(new Blob([bytes], { type: 'image/png' })); image.src = src; specularTexture.source[l][i] = image; break; default: Tools.Error('Unhandled ImageType descriptor.specular.imageType (' + imageType + ')'); } } } break; default: Tools.Error('Unhandled MapType descriptor.specular.type (' + descriptor.specular.type + ')'); } return environment; } /** * Convert from irradiance to outgoing radiance for Lambertian BDRF, suitable for efficient shader evaluation. * L = (1/pi) * E * rho * * This is done by an additional scale by 1/pi, so is a fairly trivial operation but important conceptually. * @param harmonics Spherical harmonic coefficients (9) */ private static _ConvertSHIrradianceToLambertianRadiance(harmonics: any): void { EnvironmentDeserializer._ScaleSH(harmonics, 1 / Math.PI); // The resultant SH now represents outgoing radiance, so includes the Lambert 1/pi normalisation factor but without albedo (rho) applied // (The pixel shader must apply albedo after texture fetches, etc). } /** * Convert spherical harmonics to spherical polynomial coefficients * @param harmonics Spherical harmonic coefficients (9) * @param outPolynomialCoefficents Polynomial coefficients (9) object to store result */ private static _ConvertSHToSP(harmonics: any, outPolynomialCoefficents: SphericalPolynomalCoefficients) { const rPi = 1 / Math.PI; //x outPolynomialCoefficents.x.x = 1.02333 * harmonics.l11[0] * rPi; outPolynomialCoefficents.x.y = 1.02333 * harmonics.l11[1] * rPi; outPolynomialCoefficents.x.z = 1.02333 * harmonics.l11[2] * rPi; outPolynomialCoefficents.y.x = 1.02333 * harmonics.l1_1[0] * rPi; outPolynomialCoefficents.y.y = 1.02333 * harmonics.l1_1[1] * rPi; outPolynomialCoefficents.y.z = 1.02333 * harmonics.l1_1[2] * rPi; outPolynomialCoefficents.z.x = 1.02333 * harmonics.l10[0] * rPi; outPolynomialCoefficents.z.y = 1.02333 * harmonics.l10[1] * rPi; outPolynomialCoefficents.z.z = 1.02333 * harmonics.l10[2] * rPi; //xx outPolynomialCoefficents.xx.x = (0.886277 * harmonics.l00[0] - 0.247708 * harmonics.l20[0] + 0.429043 * harmonics.l22[0]) * rPi; outPolynomialCoefficents.xx.y = (0.886277 * harmonics.l00[1] - 0.247708 * harmonics.l20[1] + 0.429043 * harmonics.l22[1]) * rPi; outPolynomialCoefficents.xx.z = (0.886277 * harmonics.l00[2] - 0.247708 * harmonics.l20[2] + 0.429043 * harmonics.l22[2]) * rPi; outPolynomialCoefficents.yy.x = (0.886277 * harmonics.l00[0] - 0.247708 * harmonics.l20[0] - 0.429043 * harmonics.l22[0]) * rPi; outPolynomialCoefficents.yy.y = (0.886277 * harmonics.l00[1] - 0.247708 * harmonics.l20[1] - 0.429043 * harmonics.l22[1]) * rPi; outPolynomialCoefficents.yy.z = (0.886277 * harmonics.l00[2] - 0.247708 * harmonics.l20[2] - 0.429043 * harmonics.l22[2]) * rPi; outPolynomialCoefficents.zz.x = (0.886277 * harmonics.l00[0] + 0.495417 * harmonics.l20[0]) * rPi; outPolynomialCoefficents.zz.y = (0.886277 * harmonics.l00[1] + 0.495417 * harmonics.l20[1]) * rPi; outPolynomialCoefficents.zz.z = (0.886277 * harmonics.l00[2] + 0.495417 * harmonics.l20[2]) * rPi; //yz outPolynomialCoefficents.yz.x = 0.858086 * harmonics.l2_1[0] * rPi; outPolynomialCoefficents.yz.y = 0.858086 * harmonics.l2_1[1] * rPi; outPolynomialCoefficents.yz.z = 0.858086 * harmonics.l2_1[2] * rPi; outPolynomialCoefficents.zx.x = 0.858086 * harmonics.l21[0] * rPi; outPolynomialCoefficents.zx.y = 0.858086 * harmonics.l21[1] * rPi; outPolynomialCoefficents.zx.z = 0.858086 * harmonics.l21[2] * rPi; outPolynomialCoefficents.xy.x = 0.858086 * harmonics.l2_2[0] * rPi; outPolynomialCoefficents.xy.y = 0.858086 * harmonics.l2_2[1] * rPi; outPolynomialCoefficents.xy.z = 0.858086 * harmonics.l2_2[2] * rPi; } /** * Multiplies harmonic coefficients in place * @param harmonics Spherical harmonic coefficients (9) * @param scaleFactor Value to multiply by */ private static _ScaleSH(harmonics: any, scaleFactor: number) { harmonics.l00[0] *= scaleFactor; harmonics.l00[1] *= scaleFactor; harmonics.l00[2] *= scaleFactor; harmonics.l1_1[0] *= scaleFactor; harmonics.l1_1[1] *= scaleFactor; harmonics.l1_1[2] *= scaleFactor; harmonics.l10[0] *= scaleFactor; harmonics.l10[1] *= scaleFactor; harmonics.l10[2] *= scaleFactor; harmonics.l11[0] *= scaleFactor; harmonics.l11[1] *= scaleFactor; harmonics.l11[2] *= scaleFactor; harmonics.l2_2[0] *= scaleFactor; harmonics.l2_2[1] *= scaleFactor; harmonics.l2_2[2] *= scaleFactor; harmonics.l2_1[0] *= scaleFactor; harmonics.l2_1[1] *= scaleFactor; harmonics.l2_1[2] *= scaleFactor; harmonics.l20[0] *= scaleFactor; harmonics.l20[1] *= scaleFactor; harmonics.l20[2] *= scaleFactor; harmonics.l21[0] *= scaleFactor; harmonics.l21[1] *= scaleFactor; harmonics.l21[2] *= scaleFactor; harmonics.l22[0] *= scaleFactor; harmonics.l22[1] *= scaleFactor; harmonics.l22[2] *= scaleFactor; } }