UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

417 lines (416 loc) 17.3 kB
import { Vector2 } from "../Maths/math.vector.js"; import { VertexBuffer } from "../Buffers/buffer.js"; import { Material } from "../Materials/material.js"; import { ThinPassPostProcess } from "../PostProcesses/thinPassPostProcess.js"; import { ThinEffectLayer, ThinGlowBlurPostProcess } from "./thinEffectLayer.js"; import { Color4 } from "../Maths/math.color.js"; import { ThinBlurPostProcess } from "../PostProcesses/thinBlurPostProcess.js"; /** * @internal */ export class ThinHighlightLayer extends ThinEffectLayer { /** * Specifies the horizontal size of the blur. */ set blurHorizontalSize(value) { this._horizontalBlurPostprocess.kernel = value; this._options.blurHorizontalSize = value; } /** * Specifies the vertical size of the blur. */ set blurVerticalSize(value) { this._verticalBlurPostprocess.kernel = value; this._options.blurVerticalSize = value; } /** * Gets the horizontal size of the blur. */ get blurHorizontalSize() { return this._horizontalBlurPostprocess.kernel; } /** * Gets the vertical size of the blur. */ get blurVerticalSize() { return this._verticalBlurPostprocess.kernel; } /** * Instantiates a new highlight Layer and references it to the scene.. * @param name The name of the layer * @param scene The scene to use the layer in * @param options Sets of none mandatory options to use with the layer (see IHighlightLayerOptions for more information) * @param dontCheckIfReady Specifies if the layer should disable checking whether all the post processes are ready (default: false). To save performance, this should be set to true and you should call `isReady` manually before rendering to the layer. */ constructor(name, scene, options, dontCheckIfReady = false) { super(name, scene, options !== undefined ? !!options.forceGLSL : false); /** * Specifies whether or not the inner glow is ACTIVE in the layer. */ this.innerGlow = true; /** * Specifies whether or not the outer glow is ACTIVE in the layer. */ this.outerGlow = true; this._instanceGlowingMeshStencilReference = ThinHighlightLayer.GlowingMeshStencilReference++; /** @internal */ this._meshes = {}; /** @internal */ this._excludedMeshes = {}; /** @internal */ this._mainObjectRendererRenderPassId = -1; this.neutralColor = ThinHighlightLayer.NeutralColor; // Adapt options this._options = { mainTextureRatio: 0.5, blurTextureSizeRatio: 0.5, mainTextureFixedSize: 0, blurHorizontalSize: 1.0, blurVerticalSize: 1.0, alphaBlendingMode: 2, camera: null, renderingGroupId: -1, forceGLSL: false, mainTextureType: 0, isStroke: false, ...options, }; // Initialize the layer this._init(this._options); // Do not render as long as no meshes have been added this._shouldRender = false; if (dontCheckIfReady) { // When dontCheckIfReady is true, we are in the new ThinXXX layer mode, so we must call _createTextureAndPostProcesses ourselves (it is called by EffectLayer otherwise) this._createTextureAndPostProcesses(); } } /** * Gets the class name of the effect layer * @returns the string with the class name of the effect layer */ getClassName() { return "HighlightLayer"; } async _importShadersAsync() { if (this._shaderLanguage === 1 /* ShaderLanguage.WGSL */) { await Promise.all([ import("../ShadersWGSL/glowMapMerge.fragment.js"), import("../ShadersWGSL/glowMapMerge.vertex.js"), import("../ShadersWGSL/glowBlurPostProcess.fragment.js"), ]); } else { await Promise.all([import("../Shaders/glowMapMerge.fragment.js"), import("../Shaders/glowMapMerge.vertex.js"), import("../Shaders/glowBlurPostProcess.fragment.js")]); } await super._importShadersAsync(); } getEffectName() { return ThinHighlightLayer.EffectName; } _numInternalDraws() { return 2; // we need two rendering, one for the inner glow and the other for the outer glow } _createMergeEffect() { return this._engine.createEffect("glowMapMerge", [VertexBuffer.PositionKind], ["offset"], ["textureSampler"], this._options.isStroke ? "#define STROKE \n" : undefined, undefined, undefined, undefined, undefined, this._shaderLanguage, this._shadersLoaded ? undefined : async () => { await this._importShadersAsync(); this._shadersLoaded = true; }); } _createTextureAndPostProcesses() { if (this._options.alphaBlendingMode === 2) { this._downSamplePostprocess = new ThinPassPostProcess("HighlightLayerPPP", this._scene.getEngine()); this._horizontalBlurPostprocess = new ThinGlowBlurPostProcess("HighlightLayerHBP", this._scene.getEngine(), new Vector2(1.0, 0), this._options.blurHorizontalSize); this._verticalBlurPostprocess = new ThinGlowBlurPostProcess("HighlightLayerVBP", this._scene.getEngine(), new Vector2(0, 1.0), this._options.blurVerticalSize); this._postProcesses = [this._downSamplePostprocess, this._horizontalBlurPostprocess, this._verticalBlurPostprocess]; } else { this._horizontalBlurPostprocess = new ThinBlurPostProcess("HighlightLayerHBP", this._scene.getEngine(), new Vector2(1.0, 0), this._options.blurHorizontalSize / 2); this._verticalBlurPostprocess = new ThinBlurPostProcess("HighlightLayerVBP", this._scene.getEngine(), new Vector2(0, 1.0), this._options.blurVerticalSize / 2); this._postProcesses = [this._horizontalBlurPostprocess, this._verticalBlurPostprocess]; } } needStencil() { return true; } isReady(subMesh, useInstances) { const material = subMesh.getMaterial(); const mesh = subMesh.getRenderingMesh(); if (!material || !mesh || !this._meshes) { return false; } let emissiveTexture = null; const highlightLayerMesh = this._meshes[mesh.uniqueId]; if (highlightLayerMesh && highlightLayerMesh.glowEmissiveOnly && material) { emissiveTexture = material.emissiveTexture; } return super._isSubMeshReady(subMesh, useInstances, emissiveTexture); } _canRenderMesh(_mesh, _material) { // all meshes can be rendered in the highlight layer, even transparent ones return true; } _internalCompose(effect, renderIndex) { // Texture this.bindTexturesForCompose(effect); // Cache const engine = this._engine; engine.cacheStencilState(); // Stencil operations engine.setStencilOperationPass(7681); engine.setStencilOperationFail(7680); engine.setStencilOperationDepthFail(7680); // Draw order engine.setStencilMask(0x00); engine.setStencilBuffer(true); engine.setStencilFunctionReference(this._instanceGlowingMeshStencilReference); // 2 passes inner outer if (this.outerGlow && renderIndex === 0) { // the outer glow is rendered the first time _internalRender is called, so when renderIndex == 0 (and only if outerGlow is enabled) effect.setFloat("offset", 0); engine.setStencilFunction(517); engine.drawElementsType(Material.TriangleFillMode, 0, 6); } if (this.innerGlow && renderIndex === 1) { // the inner glow is rendered the second time _internalRender is called, so when renderIndex == 1 (and only if innerGlow is enabled) effect.setFloat("offset", 1); engine.setStencilFunction(514); engine.drawElementsType(Material.TriangleFillMode, 0, 6); } // Restore Cache engine.restoreStencilState(); } _setEmissiveTextureAndColor(mesh, _subMesh, material) { const highlightLayerMesh = this._meshes[mesh.uniqueId]; if (highlightLayerMesh) { this._emissiveTextureAndColor.color.set(highlightLayerMesh.color.r, highlightLayerMesh.color.g, highlightLayerMesh.color.b, 1.0); } else { this._emissiveTextureAndColor.color.set(this.neutralColor.r, this.neutralColor.g, this.neutralColor.b, this.neutralColor.a); } if (highlightLayerMesh && highlightLayerMesh.glowEmissiveOnly && material) { this._emissiveTextureAndColor.texture = material.emissiveTexture; this._emissiveTextureAndColor.color.set(1.0, 1.0, 1.0, 1.0); } else { this._emissiveTextureAndColor.texture = null; } } shouldRender() { return this._meshes && super.shouldRender() ? true : false; } _shouldRenderMesh(mesh) { if (this._excludedMeshes && this._excludedMeshes[mesh.uniqueId]) { return false; } return super.hasMesh(mesh); } _addCustomEffectDefines(defines) { defines.push("#define HIGHLIGHT"); } /** * Add a mesh in the exclusion list to prevent it to impact or being impacted by the highlight layer. * @param mesh The mesh to exclude from the highlight layer */ addExcludedMesh(mesh) { if (!this._excludedMeshes) { return; } const meshExcluded = this._excludedMeshes[mesh.uniqueId]; if (!meshExcluded) { const obj = { mesh: mesh, beforeBind: null, afterRender: null, stencilState: false, }; obj.beforeBind = mesh.onBeforeBindObservable.add((mesh) => { if (this._mainObjectRendererRenderPassId !== -1 && this._mainObjectRendererRenderPassId !== this._engine.currentRenderPassId) { return; } obj.stencilState = mesh.getEngine().getStencilBuffer(); mesh.getEngine().setStencilBuffer(false); }); obj.afterRender = mesh.onAfterRenderObservable.add((mesh) => { if (this._mainObjectRendererRenderPassId !== -1 && this._mainObjectRendererRenderPassId !== this._engine.currentRenderPassId) { return; } mesh.getEngine().setStencilBuffer(obj.stencilState); }); this._excludedMeshes[mesh.uniqueId] = obj; } } /** * Remove a mesh from the exclusion list to let it impact or being impacted by the highlight layer. * @param mesh The mesh to highlight */ removeExcludedMesh(mesh) { if (!this._excludedMeshes) { return; } const meshExcluded = this._excludedMeshes[mesh.uniqueId]; if (meshExcluded) { if (meshExcluded.beforeBind) { mesh.onBeforeBindObservable.remove(meshExcluded.beforeBind); } if (meshExcluded.afterRender) { mesh.onAfterRenderObservable.remove(meshExcluded.afterRender); } } this._excludedMeshes[mesh.uniqueId] = null; } hasMesh(mesh) { if (!this._meshes || !super.hasMesh(mesh)) { return false; } return !!this._meshes[mesh.uniqueId]; } /** * Add a mesh in the highlight layer in order to make it glow with the chosen color. * @param mesh The mesh to highlight * @param color The color of the highlight * @param glowEmissiveOnly Extract the glow from the emissive texture */ addMesh(mesh, color, glowEmissiveOnly = false) { if (!this._meshes) { return; } const meshHighlight = this._meshes[mesh.uniqueId]; if (meshHighlight) { meshHighlight.color = color; } else { this._meshes[mesh.uniqueId] = { mesh: mesh, color: color, // Lambda required for capture due to Observable this context observerHighlight: mesh.onBeforeBindObservable.add((mesh) => { if (this._mainObjectRendererRenderPassId !== -1 && this._mainObjectRendererRenderPassId !== this._engine.currentRenderPassId) { return; } if (this.isEnabled) { if (this._excludedMeshes && this._excludedMeshes[mesh.uniqueId]) { this._defaultStencilReference(mesh); } else { mesh.getScene().getEngine().setStencilFunctionReference(this._instanceGlowingMeshStencilReference); } } }), observerDefault: mesh.onAfterRenderObservable.add((mesh) => { if (this._mainObjectRendererRenderPassId !== -1 && this._mainObjectRendererRenderPassId !== this._engine.currentRenderPassId) { return; } if (this.isEnabled) { this._defaultStencilReference(mesh); } }), glowEmissiveOnly: glowEmissiveOnly, }; mesh.onDisposeObservable.add(() => { this._disposeMesh(mesh); }); } this._shouldRender = true; } /** * Remove a mesh from the highlight layer in order to make it stop glowing. * @param mesh The mesh to highlight */ removeMesh(mesh) { if (!this._meshes) { return; } const meshHighlight = this._meshes[mesh.uniqueId]; if (meshHighlight) { if (meshHighlight.observerHighlight) { mesh.onBeforeBindObservable.remove(meshHighlight.observerHighlight); } if (meshHighlight.observerDefault) { mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault); } delete this._meshes[mesh.uniqueId]; } this._shouldRender = false; for (const meshHighlightToCheck in this._meshes) { if (this._meshes[meshHighlightToCheck]) { this._shouldRender = true; break; } } } /** * Remove all the meshes currently referenced in the highlight layer */ removeAllMeshes() { if (!this._meshes) { return; } for (const uniqueId in this._meshes) { if (Object.prototype.hasOwnProperty.call(this._meshes, uniqueId)) { const mesh = this._meshes[uniqueId]; if (mesh) { this.removeMesh(mesh.mesh); } } } } _defaultStencilReference(mesh) { mesh.getScene().getEngine().setStencilFunctionReference(ThinHighlightLayer.NormalMeshStencilReference); } _disposeMesh(mesh) { this.removeMesh(mesh); this.removeExcludedMesh(mesh); } dispose() { if (this._meshes) { // Clean mesh references for (const id in this._meshes) { const meshHighlight = this._meshes[id]; if (meshHighlight && meshHighlight.mesh) { if (meshHighlight.observerHighlight) { meshHighlight.mesh.onBeforeBindObservable.remove(meshHighlight.observerHighlight); } if (meshHighlight.observerDefault) { meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault); } } } this._meshes = null; } if (this._excludedMeshes) { for (const id in this._excludedMeshes) { const meshHighlight = this._excludedMeshes[id]; if (meshHighlight) { if (meshHighlight.beforeBind) { meshHighlight.mesh.onBeforeBindObservable.remove(meshHighlight.beforeBind); } if (meshHighlight.afterRender) { meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.afterRender); } } } this._excludedMeshes = null; } super.dispose(); } } /** * Effect Name of the highlight layer. */ ThinHighlightLayer.EffectName = "HighlightLayer"; /** * The neutral color used during the preparation of the glow effect. * This is black by default as the blend operation is a blend operation. */ ThinHighlightLayer.NeutralColor = new Color4(0, 0, 0, 0); /** * Stencil value used for glowing meshes. */ ThinHighlightLayer.GlowingMeshStencilReference = 0x02; /** * Stencil value used for the other meshes in the scene. */ ThinHighlightLayer.NormalMeshStencilReference = 0x01; //# sourceMappingURL=thinHighlightLayer.js.map