浏览代码

Merge pull request #8623 from MiiBond/transmission_helper

Add transmission prerender pass when using KHR_materials_transmission
David Catuhe 5 年之前
父节点
当前提交
50172d6f30
共有 3 个文件被更改,包括 232 次插入1 次删除
  1. 1 0
      dist/preview release/what's new.md
  2. 230 0
      loaders/src/glTF/2.0/Extensions/KHR_materials_transmission.ts
  3. 1 1
      package.json

+ 1 - 0
dist/preview release/what's new.md

@@ -128,6 +128,7 @@
 - Added support for .glb file loading through a base64 encoded filename ([Popov72](https://github.com/Popov72))
 - Fixed issue with loading screen hiding too early when loading multiple assets concurrently. ([bghgary](https://github.com/bghgary))
 - Added the `loadAllMaterials` property to the gLTF loader to load materials even if not used by any mesh ([Popov72](https://github.com/Popov72))
+- Added transmission prerender pass when using KHR_materials_transmission ([MiiBond](https://github.com/MiiBond/))
 
 ### Serializers
 

+ 230 - 0
loaders/src/glTF/2.0/Extensions/KHR_materials_transmission.ts

@@ -6,7 +6,234 @@ import { IMaterial } from "../glTFLoaderInterfaces";
 import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
 import { GLTFLoader } from "../glTFLoader";
 import { IKHRMaterialsTransmission } from 'babylonjs-gltf2interface';
+import { Scene } from "babylonjs/scene";
+import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
+import { Mesh } from "babylonjs/Meshes/mesh";
+import { Texture } from "babylonjs/Materials/Textures/texture";
+import { RenderTargetTexture } from "babylonjs/Materials/Textures/renderTargetTexture";
+import { Observable } from "babylonjs/Misc/observable";
 
+interface ITransmissionHelperOptions {
+    /**
+     * The size of the render buffers
+     */
+    renderSize: number;
+}
+
+/**
+ * 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.
+     */
+    private static _getDefaultOptions(): ITransmissionHelperOptions {
+        return {
+            renderSize: 512
+        };
+    }
+
+    /**
+     * Stores the creation options.
+     */
+    private readonly _scene: Scene;
+    private _options: ITransmissionHelperOptions;
+
+    private _opaqueRenderTarget: Nullable<RenderTargetTexture> = null;
+    private _opaqueMeshesCache: Mesh[] = [];
+    private _transparentMeshesCache: Mesh[] = [];
+
+    /**
+     * This observable will be notified with any error during the creation of the environment,
+     * mainly texture creation errors.
+     */
+    public onErrorObservable: Observable<{ message?: string, exception?: any }>;
+
+    /**
+     * constructor
+     * @param options Defines the options we want to customize the helper
+     * @param scene The scene to add the material to
+     */
+    constructor(options: Partial<ITransmissionHelperOptions>, scene: Scene) {
+        this._options = {
+            ...TransmissionHelper._getDefaultOptions(),
+            ...options
+        };
+        this._scene = scene;
+        this.onErrorObservable = new Observable();
+
+        this._parseScene();
+        this._setupRenderTargets();
+    }
+
+    /**
+     * Updates the background according to the new options
+     * @param options
+     */
+    public updateOptions(options: Partial<ITransmissionHelperOptions>) {
+        // First check if any options are actually being changed. If not, exit.
+        const newValues = Object.keys(options).filter((key: string) => (this._options as any)[key] !== (options as any)[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) {
+            this._setupRenderTargets();
+        }
+    }
+
+    public getOpaqueTarget(): Nullable<Texture> {
+        return this._opaqueRenderTarget;
+    }
+
+    private shouldRenderAsTransmission(material: Nullable<Material>): boolean {
+        if (!material) {
+            return false;
+        }
+        if (material instanceof PBRMaterial && (material.subSurface.isRefractionEnabled)) {
+            return true;
+        }
+        return false;
+    }
+
+    private _addMesh(mesh: AbstractMesh): void {
+        if (mesh instanceof Mesh) {
+            mesh.onMaterialChangedObservable.add(this.onMeshMaterialChanged.bind(this));
+            if (this.shouldRenderAsTransmission(mesh.material)) {
+                this._transparentMeshesCache.push(mesh);
+            } else {
+                this._opaqueMeshesCache.push(mesh);
+            }
+        }
+    }
+
+    private _removeMesh(mesh: AbstractMesh): void {
+        if (mesh instanceof Mesh) {
+            mesh.onMaterialChangedObservable.remove(this.onMeshMaterialChanged.bind(this));
+            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);
+            }
+        }
+    }
+
+    private _parseScene(): void {
+        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.
+    private onMeshMaterialChanged(mesh: AbstractMesh) {
+        if (mesh instanceof 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);
+                }
+            }
+        }
+    }
+
+    /**
+     * Setup the render targets according to the specified options.
+     */
+    private _setupRenderTargets(): void {
+
+        let opaqueRTIndex = -1;
+
+        // Remove any layers rendering to the opaque scene.
+        if (this._scene.layers && this._opaqueRenderTarget) {
+            for (let layer of this._scene.layers) {
+                const idx = layer.renderTargetTextures.indexOf(this._opaqueRenderTarget);
+                if (idx >= 0) {
+                    layer.renderTargetTextures.splice(idx, 1);
+                }
+            }
+        }
+
+        // Remove opaque render target
+        if (this._opaqueRenderTarget) {
+            opaqueRTIndex = this._scene.customRenderTargets.indexOf(this._opaqueRenderTarget);
+            this._opaqueRenderTarget.dispose();
+        }
+
+        this._opaqueRenderTarget = new RenderTargetTexture("opaqueSceneTexture", this._options.renderSize, this._scene, true);
+        this._opaqueRenderTarget.renderList = this._opaqueMeshesCache;
+        // this._opaqueRenderTarget.clearColor = new Color4(0.0, 0.0, 0.0, 0.0);
+        this._opaqueRenderTarget.gammaSpace = true;
+        this._opaqueRenderTarget.lodGenerationScale = 1;
+        this._opaqueRenderTarget.lodGenerationOffset = -4;
+
+        if (opaqueRTIndex >= 0) {
+            this._scene.customRenderTargets.splice(opaqueRTIndex, 0, this._opaqueRenderTarget);
+        } else {
+            opaqueRTIndex = this._scene.customRenderTargets.length;
+            this._scene.customRenderTargets.push(this._opaqueRenderTarget);
+        }
+
+        // If there are other layers, they should be included in the render of the opaque background.
+        if (this._scene.layers && this._opaqueRenderTarget) {
+            for (let layer of this._scene.layers) {
+                layer.renderTargetTextures.push(this._opaqueRenderTarget);
+            }
+        }
+
+        this._transparentMeshesCache.forEach((mesh: AbstractMesh) => {
+            if (this.shouldRenderAsTransmission(mesh.material) && mesh.material instanceof PBRMaterial) {
+                mesh.material.refractionTexture = this._opaqueRenderTarget;
+            }
+        });
+    }
+
+    /**
+     * Dispose all the elements created by the Helper.
+     */
+    public dispose(): void {
+        if (this._opaqueRenderTarget) {
+            this._opaqueRenderTarget.dispose();
+            this._opaqueRenderTarget = null;
+        }
+        this._transparentMeshesCache = [];
+        this._opaqueMeshesCache = [];
+    }
+}
+
+let _transmissionHelper: TransmissionHelper;
 const NAME = "KHR_materials_transmission";
 
 /**
@@ -73,6 +300,9 @@ export class KHR_materials_transmission implements IGLTFLoaderExtension {
 
         if (extension.transmissionFactor !== undefined) {
             pbrMaterial.subSurface.refractionIntensity = extension.transmissionFactor;
+            if (pbrMaterial.subSurface.refractionIntensity && _transmissionHelper == undefined) {
+                _transmissionHelper = new TransmissionHelper({}, pbrMaterial.getScene());
+            }
         } else {
             pbrMaterial.subSurface.refractionIntensity = 0.0;
             pbrMaterial.subSurface.isRefractionEnabled = false;

+ 1 - 1
package.json

@@ -113,4 +113,4 @@
         "xhr2": "^0.2.0",
         "xmlbuilder": "15.1.1"
     }
-}
+}