123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- import { PBRMaterial } from "@babylonjs/core/Materials/PBR/pbrMaterial.js";
- import { GLTFLoader } from "../glTFLoader.js";
- import { RenderTargetTexture } from "@babylonjs/core/Materials/Textures/renderTargetTexture.js";
- import { Observable } from "@babylonjs/core/Misc/observable.js";
- import { Constants } from "@babylonjs/core/Engines/constants.js";
- import { Tools } from "@babylonjs/core/Misc/tools.js";
- /**
- * A class to handle setting up the rendering of opaque objects to be shown through transmissive objects.
- */
- class TransmissionHelper {
- /**
- * Creates the default options for the helper.
- * @returns the default options
- */
- static _GetDefaultOptions() {
- return {
- renderSize: 1024,
- samples: 4,
- lodGenerationScale: 1,
- lodGenerationOffset: -4,
- renderTargetTextureType: Constants.TEXTURETYPE_HALF_FLOAT,
- generateMipmaps: true,
- };
- }
- /**
- * constructor
- * @param options Defines the options we want to customize the helper
- * @param scene The scene to add the material to
- */
- constructor(options, scene) {
- this._opaqueRenderTarget = null;
- this._opaqueMeshesCache = [];
- this._transparentMeshesCache = [];
- this._materialObservers = {};
- this._options = {
- ...TransmissionHelper._GetDefaultOptions(),
- ...options,
- };
- this._scene = scene;
- this._scene._transmissionHelper = this;
- this.onErrorObservable = new Observable();
- this._scene.onDisposeObservable.addOnce(() => {
- this.dispose();
- });
- this._parseScene();
- this._setupRenderTargets();
- }
- /**
- * Updates the background according to the new options
- * @param options
- */
- updateOptions(options) {
- // First check if any options are actually being changed. If not, exit.
- const newValues = Object.keys(options).filter((key) => this._options[key] !== options[key]);
- if (!newValues.length) {
- return;
- }
- const newOptions = {
- ...this._options,
- ...options,
- };
- const oldOptions = this._options;
- this._options = newOptions;
- // If size changes, recreate everything
- if (newOptions.renderSize !== oldOptions.renderSize ||
- newOptions.renderTargetTextureType !== oldOptions.renderTargetTextureType ||
- newOptions.generateMipmaps !== oldOptions.generateMipmaps ||
- !this._opaqueRenderTarget) {
- this._setupRenderTargets();
- }
- else {
- this._opaqueRenderTarget.samples = newOptions.samples;
- this._opaqueRenderTarget.lodGenerationScale = newOptions.lodGenerationScale;
- this._opaqueRenderTarget.lodGenerationOffset = newOptions.lodGenerationOffset;
- }
- }
- /**
- * @returns the opaque render target texture or null if not available.
- */
- getOpaqueTarget() {
- return this._opaqueRenderTarget;
- }
- _shouldRenderAsTransmission(material) {
- if (!material) {
- return false;
- }
- if (material instanceof PBRMaterial && material.subSurface.isRefractionEnabled) {
- return true;
- }
- return false;
- }
- _addMesh(mesh) {
- this._materialObservers[mesh.uniqueId] = mesh.onMaterialChangedObservable.add(this._onMeshMaterialChanged.bind(this));
- // we need to defer the processing because _addMesh may be called as part as an instance mesh creation, in which case some
- // internal properties are not setup yet, like _sourceMesh (needed when doing mesh.material below)
- Tools.SetImmediate(() => {
- if (this._shouldRenderAsTransmission(mesh.material)) {
- mesh.material.refractionTexture = this._opaqueRenderTarget;
- if (this._transparentMeshesCache.indexOf(mesh) === -1) {
- this._transparentMeshesCache.push(mesh);
- }
- }
- else {
- if (this._opaqueMeshesCache.indexOf(mesh) === -1) {
- this._opaqueMeshesCache.push(mesh);
- }
- }
- });
- }
- _removeMesh(mesh) {
- mesh.onMaterialChangedObservable.remove(this._materialObservers[mesh.uniqueId]);
- delete this._materialObservers[mesh.uniqueId];
- let idx = this._transparentMeshesCache.indexOf(mesh);
- if (idx !== -1) {
- this._transparentMeshesCache.splice(idx, 1);
- }
- idx = this._opaqueMeshesCache.indexOf(mesh);
- if (idx !== -1) {
- this._opaqueMeshesCache.splice(idx, 1);
- }
- }
- _parseScene() {
- this._scene.meshes.forEach(this._addMesh.bind(this));
- // Listen for when a mesh is added to the scene and add it to our cache lists.
- this._scene.onNewMeshAddedObservable.add(this._addMesh.bind(this));
- // Listen for when a mesh is removed from to the scene and remove it from our cache lists.
- this._scene.onMeshRemovedObservable.add(this._removeMesh.bind(this));
- }
- // When one of the meshes in the scene has its material changed, make sure that it's in the correct cache list.
- _onMeshMaterialChanged(mesh) {
- const transparentIdx = this._transparentMeshesCache.indexOf(mesh);
- const opaqueIdx = this._opaqueMeshesCache.indexOf(mesh);
- // If the material is transparent, make sure that it's added to the transparent list and removed from the opaque list
- const useTransmission = this._shouldRenderAsTransmission(mesh.material);
- if (useTransmission) {
- if (mesh.material instanceof PBRMaterial) {
- mesh.material.subSurface.refractionTexture = this._opaqueRenderTarget;
- }
- if (opaqueIdx !== -1) {
- this._opaqueMeshesCache.splice(opaqueIdx, 1);
- this._transparentMeshesCache.push(mesh);
- }
- else if (transparentIdx === -1) {
- this._transparentMeshesCache.push(mesh);
- }
- // If the material is opaque, make sure that it's added to the opaque list and removed from the transparent list
- }
- else {
- if (transparentIdx !== -1) {
- this._transparentMeshesCache.splice(transparentIdx, 1);
- this._opaqueMeshesCache.push(mesh);
- }
- else if (opaqueIdx === -1) {
- this._opaqueMeshesCache.push(mesh);
- }
- }
- }
- /**
- * @internal
- * Check if the opaque render target has not been disposed and can still be used.
- * @returns
- */
- _isRenderTargetValid() {
- return this._opaqueRenderTarget?.getInternalTexture() !== null;
- }
- /**
- * @internal
- * Setup the render targets according to the specified options.
- */
- _setupRenderTargets() {
- if (this._opaqueRenderTarget) {
- this._opaqueRenderTarget.dispose();
- }
- this._opaqueRenderTarget = new RenderTargetTexture("opaqueSceneTexture", this._options.renderSize, this._scene, this._options.generateMipmaps, undefined, this._options.renderTargetTextureType);
- this._opaqueRenderTarget.ignoreCameraViewport = true;
- this._opaqueRenderTarget.renderList = this._opaqueMeshesCache;
- this._opaqueRenderTarget.clearColor = this._options.clearColor?.clone() ?? this._scene.clearColor.clone();
- this._opaqueRenderTarget.gammaSpace = false;
- this._opaqueRenderTarget.lodGenerationScale = this._options.lodGenerationScale;
- this._opaqueRenderTarget.lodGenerationOffset = this._options.lodGenerationOffset;
- this._opaqueRenderTarget.samples = this._options.samples;
- this._opaqueRenderTarget.renderSprites = true;
- this._opaqueRenderTarget.renderParticles = true;
- let sceneImageProcessingapplyByPostProcess;
- let saveSceneEnvIntensity;
- this._opaqueRenderTarget.onBeforeBindObservable.add((opaqueRenderTarget) => {
- saveSceneEnvIntensity = this._scene.environmentIntensity;
- this._scene.environmentIntensity = 1.0;
- sceneImageProcessingapplyByPostProcess = this._scene.imageProcessingConfiguration.applyByPostProcess;
- if (!this._options.clearColor) {
- this._scene.clearColor.toLinearSpaceToRef(opaqueRenderTarget.clearColor, this._scene.getEngine().useExactSrgbConversions);
- }
- else {
- opaqueRenderTarget.clearColor.copyFrom(this._options.clearColor);
- }
- // we do not use the applyByPostProcess setter to avoid flagging all the materials as "image processing dirty"!
- this._scene.imageProcessingConfiguration._applyByPostProcess = true;
- });
- this._opaqueRenderTarget.onAfterUnbindObservable.add(() => {
- this._scene.environmentIntensity = saveSceneEnvIntensity;
- this._scene.imageProcessingConfiguration._applyByPostProcess = sceneImageProcessingapplyByPostProcess;
- });
- this._transparentMeshesCache.forEach((mesh) => {
- if (this._shouldRenderAsTransmission(mesh.material)) {
- mesh.material.refractionTexture = this._opaqueRenderTarget;
- }
- });
- }
- /**
- * Dispose all the elements created by the Helper.
- */
- dispose() {
- this._scene._transmissionHelper = undefined;
- if (this._opaqueRenderTarget) {
- this._opaqueRenderTarget.dispose();
- this._opaqueRenderTarget = null;
- }
- this._transparentMeshesCache = [];
- this._opaqueMeshesCache = [];
- }
- }
- const NAME = "KHR_materials_transmission";
- /**
- * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_transmission/README.md)
- */
- // eslint-disable-next-line @typescript-eslint/naming-convention
- export class KHR_materials_transmission {
- /**
- * @internal
- */
- constructor(loader) {
- /**
- * The name of this extension.
- */
- this.name = NAME;
- /**
- * Defines a number that determines the order the extensions are applied.
- */
- this.order = 175;
- this._loader = loader;
- this.enabled = this._loader.isExtensionUsed(NAME);
- if (this.enabled) {
- loader.parent.transparencyAsCoverage = true;
- }
- }
- /** @internal */
- dispose() {
- this._loader = null;
- }
- /**
- * @internal
- */
- loadMaterialPropertiesAsync(context, material, babylonMaterial) {
- return GLTFLoader.LoadExtensionAsync(context, material, this.name, (extensionContext, extension) => {
- const promises = new Array();
- promises.push(this._loader.loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
- promises.push(this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial));
- promises.push(this._loadTransparentPropertiesAsync(extensionContext, material, babylonMaterial, extension));
- return Promise.all(promises).then(() => { });
- });
- }
- _loadTransparentPropertiesAsync(context, material, babylonMaterial, extension) {
- if (!(babylonMaterial instanceof PBRMaterial)) {
- throw new Error(`${context}: Material type not supported`);
- }
- const pbrMaterial = babylonMaterial;
- // Enables "refraction" texture which represents transmitted light.
- pbrMaterial.subSurface.isRefractionEnabled = true;
- // Since this extension models thin-surface transmission only, we must make IOR = 1.0
- pbrMaterial.subSurface.volumeIndexOfRefraction = 1.0;
- // Albedo colour will tint transmission.
- pbrMaterial.subSurface.useAlbedoToTintRefraction = true;
- if (extension.transmissionFactor !== undefined) {
- pbrMaterial.subSurface.refractionIntensity = extension.transmissionFactor;
- const scene = pbrMaterial.getScene();
- if (pbrMaterial.subSurface.refractionIntensity && !scene._transmissionHelper) {
- new TransmissionHelper({}, pbrMaterial.getScene());
- }
- else if (pbrMaterial.subSurface.refractionIntensity && !scene._transmissionHelper?._isRenderTargetValid()) {
- // If the render target is not valid, recreate it.
- scene._transmissionHelper?._setupRenderTargets();
- }
- }
- else {
- pbrMaterial.subSurface.refractionIntensity = 0.0;
- pbrMaterial.subSurface.isRefractionEnabled = false;
- return Promise.resolve();
- }
- pbrMaterial.subSurface.minimumThickness = 0.0;
- pbrMaterial.subSurface.maximumThickness = 0.0;
- if (extension.transmissionTexture) {
- extension.transmissionTexture.nonColorData = true;
- return this._loader.loadTextureInfoAsync(`${context}/transmissionTexture`, extension.transmissionTexture, undefined).then((texture) => {
- pbrMaterial.subSurface.refractionIntensityTexture = texture;
- pbrMaterial.subSurface.useGltfStyleTextures = true;
- });
- }
- else {
- return Promise.resolve();
- }
- }
- }
- GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_transmission(loader));
- //# sourceMappingURL=KHR_materials_transmission.js.map
|