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.

1,388 lines (1,386 loc) 56.9 kB
import { __decorate } from "../tslib.es6.js"; import { serialize } from "../Misc/decorators.js"; import { Tools } from "../Misc/tools.js"; import { Observable } from "../Misc/observable.js"; import { EngineStore } from "../Engines/engineStore.js"; import { SubMesh } from "../Meshes/subMesh.js"; import { UniformBuffer } from "./uniformBuffer.js"; import { Logger } from "../Misc/logger.js"; import { Plane } from "../Maths/math.plane.js"; import { DrawWrapper } from "./drawWrapper.js"; import { MaterialStencilState } from "./materialStencilState.js"; import { BindSceneUniformBuffer } from "./materialHelper.functions.js"; import { SerializationHelper } from "../Misc/decorators.serialization.js"; /** * Base class for the main features of a material in Babylon.js */ export class Material { /** @internal */ get _supportGlowLayer() { return false; } /** @internal */ set _glowModeEnabled(value) { // Do nothing here } /** * Gets the shader language used in this material. */ get shaderLanguage() { return this._shaderLanguage; } /** * If the material can be rendered to several textures with MRT extension */ get canRenderToMRT() { // By default, shaders are not compatible with MRTs // Base classes should override that if their shader supports MRT return false; } /** * Sets the alpha value of the material */ set alpha(value) { if (this._alpha === value) { return; } const oldValue = this._alpha; this._alpha = value; // Only call dirty when there is a state change (no alpha / alpha) if (oldValue === 1 || value === 1) { this.markAsDirty(Material.MiscDirtyFlag + Material.PrePassDirtyFlag); } } /** * Gets the alpha value of the material */ get alpha() { return this._alpha; } /** * Sets the culling state (true to enable culling, false to disable) */ set backFaceCulling(value) { if (this._backFaceCulling === value) { return; } this._backFaceCulling = value; this.markAsDirty(Material.TextureDirtyFlag); } /** * Gets the culling state */ get backFaceCulling() { return this._backFaceCulling; } /** * Sets the type of faces that should be culled (true for back faces, false for front faces) */ set cullBackFaces(value) { if (this._cullBackFaces === value) { return; } this._cullBackFaces = value; this.markAsDirty(Material.TextureDirtyFlag); } /** * Gets the type of faces that should be culled */ get cullBackFaces() { return this._cullBackFaces; } /** * Block the dirty-mechanism for this specific material * When set to false after being true the material will be marked as dirty. */ get blockDirtyMechanism() { return this._blockDirtyMechanism; } set blockDirtyMechanism(value) { if (this._blockDirtyMechanism === value) { return; } this._blockDirtyMechanism = value; if (!value) { this.markDirty(); } } /** * This allows you to modify the material without marking it as dirty after every change. * This function should be used if you need to make more than one dirty-enabling change to the material - adding a texture, setting a new fill mode and so on. * The callback will pass the material as an argument, so you can make your changes to it. * @param callback the callback to be executed that will update the material */ atomicMaterialsUpdate(callback) { this.blockDirtyMechanism = true; try { callback(this); } finally { this.blockDirtyMechanism = false; } } /** * Gets a boolean indicating that current material needs to register RTT */ get hasRenderTargetTextures() { this._eventInfo.hasRenderTargetTextures = false; this._callbackPluginEventHasRenderTargetTextures(this._eventInfo); return this._eventInfo.hasRenderTargetTextures; } /** * Called during a dispose event */ set onDispose(callback) { if (this._onDisposeObserver) { this.onDisposeObservable.remove(this._onDisposeObserver); } this._onDisposeObserver = this.onDisposeObservable.add(callback); } /** * An event triggered when the material is bound */ get onBindObservable() { if (!this._onBindObservable) { this._onBindObservable = new Observable(); } return this._onBindObservable; } /** * Called during a bind event */ set onBind(callback) { if (this._onBindObserver) { this.onBindObservable.remove(this._onBindObserver); } this._onBindObserver = this.onBindObservable.add(callback); } /** * An event triggered when the material is unbound */ get onUnBindObservable() { if (!this._onUnBindObservable) { this._onUnBindObservable = new Observable(); } return this._onUnBindObservable; } /** * An event triggered when the effect is (re)created */ get onEffectCreatedObservable() { if (!this._onEffectCreatedObservable) { this._onEffectCreatedObservable = new Observable(); } return this._onEffectCreatedObservable; } /** * Sets the value of the alpha mode. * * | Value | Type | Description | * | --- | --- | --- | * | 0 | ALPHA_DISABLE | | * | 1 | ALPHA_ADD | | * | 2 | ALPHA_COMBINE | | * | 3 | ALPHA_SUBTRACT | | * | 4 | ALPHA_MULTIPLY | | * | 5 | ALPHA_MAXIMIZED | | * | 6 | ALPHA_ONEONE | | * | 7 | ALPHA_PREMULTIPLIED | | * | 8 | ALPHA_PREMULTIPLIED_PORTERDUFF | | * | 9 | ALPHA_INTERPOLATE | | * | 10 | ALPHA_SCREENMODE | | * */ set alphaMode(value) { if (this._alphaMode === value) { return; } this._alphaMode = value; this.markAsDirty(Material.TextureDirtyFlag); } /** * Gets the value of the alpha mode */ get alphaMode() { return this._alphaMode; } /** * Sets the need depth pre-pass value */ set needDepthPrePass(value) { if (this._needDepthPrePass === value) { return; } this._needDepthPrePass = value; if (this._needDepthPrePass) { this.checkReadyOnEveryCall = true; } } /** * Gets the depth pre-pass value */ get needDepthPrePass() { return this._needDepthPrePass; } /** * Can this material render to prepass */ get isPrePassCapable() { return false; } /** * Sets the state for enabling fog */ set fogEnabled(value) { if (this._fogEnabled === value) { return; } this._fogEnabled = value; this.markAsDirty(Material.MiscDirtyFlag); } /** * Gets the value of the fog enabled state */ get fogEnabled() { return this._fogEnabled; } get wireframe() { switch (this._fillMode) { case Material.WireFrameFillMode: case Material.LineListDrawMode: case Material.LineLoopDrawMode: case Material.LineStripDrawMode: return true; } return this._scene.forceWireframe; } /** * Sets the state of wireframe mode */ set wireframe(value) { this.fillMode = value ? Material.WireFrameFillMode : Material.TriangleFillMode; } /** * Gets the value specifying if point clouds are enabled */ get pointsCloud() { switch (this._fillMode) { case Material.PointFillMode: case Material.PointListDrawMode: return true; } return this._scene.forcePointsCloud; } /** * Sets the state of point cloud mode */ set pointsCloud(value) { this.fillMode = value ? Material.PointFillMode : Material.TriangleFillMode; } /** * Gets the material fill mode */ get fillMode() { return this._fillMode; } /** * Sets the material fill mode */ set fillMode(value) { if (this._fillMode === value) { return; } this._fillMode = value; this.markAsDirty(Material.MiscDirtyFlag); } /** * In case the depth buffer does not allow enough depth precision for your scene (might be the case in large scenes) * You can try switching to logarithmic depth. * @see https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/logarithmicDepthBuffer */ get useLogarithmicDepth() { return this._useLogarithmicDepth; } set useLogarithmicDepth(value) { const fragmentDepthSupported = this.getScene().getEngine().getCaps().fragmentDepthSupported; if (value && !fragmentDepthSupported) { Logger.Warn("Logarithmic depth has been requested for a material on a device that doesn't support it."); } this._useLogarithmicDepth = value && fragmentDepthSupported; this._markAllSubMeshesAsMiscDirty(); } /** @internal */ _getDrawWrapper() { return this._drawWrapper; } /** * @internal */ _setDrawWrapper(drawWrapper) { this._drawWrapper = drawWrapper; } /** * Creates a material instance * @param name defines the name of the material * @param scene defines the scene to reference * @param doNotAdd specifies if the material should be added to the scene * @param forceGLSL Use the GLSL code generation for the shader (even on WebGPU). Default is false */ constructor(name, scene, doNotAdd, forceGLSL = false) { /** * Custom shadow depth material to use for shadow rendering instead of the in-built one */ this.shadowDepthWrapper = null; /** * Gets or sets a boolean indicating that the material is allowed (if supported) to do shader hot swapping. * This means that the material can keep using a previous shader while a new one is being compiled. * This is mostly used when shader parallel compilation is supported (true by default) */ this.allowShaderHotSwapping = true; /** Shader language used by the material */ this._shaderLanguage = 0 /* ShaderLanguage.GLSL */; this._forceGLSL = false; /** * Gets or sets user defined metadata */ this.metadata = null; /** * For internal use only. Please do not use. */ this.reservedDataStore = null; /** * Specifies if the ready state should be checked on each call */ this.checkReadyOnEveryCall = false; /** * Specifies if the ready state should be checked once */ this.checkReadyOnlyOnce = false; /** * The state of the material */ this.state = ""; /** * The alpha value of the material */ this._alpha = 1.0; /** * Specifies if back face culling is enabled */ this._backFaceCulling = true; /** * Specifies if back or front faces should be culled (when culling is enabled) */ this._cullBackFaces = true; this._blockDirtyMechanism = false; /** * Stores the value for side orientation */ this.sideOrientation = null; /** * Callback triggered when the material is compiled */ this.onCompiled = null; /** * Callback triggered when an error occurs */ this.onError = null; /** * Callback triggered to get the render target textures */ this.getRenderTargetTextures = null; /** * Specifies if the material should be serialized */ this.doNotSerialize = false; /** * @internal */ this._storeEffectOnSubMeshes = false; /** * Stores the animations for the material */ this.animations = null; /** * An event triggered when the material is disposed */ this.onDisposeObservable = new Observable(); /** * An observer which watches for dispose events */ this._onDisposeObserver = null; this._onUnBindObservable = null; /** * An observer which watches for bind events */ this._onBindObserver = null; /** * Stores the value of the alpha mode */ this._alphaMode = 2; /** * Stores the state of the need depth pre-pass value */ this._needDepthPrePass = false; /** * Specifies if depth writing should be disabled */ this.disableDepthWrite = false; /** * Specifies if color writing should be disabled */ this.disableColorWrite = false; /** * Specifies if depth writing should be forced */ this.forceDepthWrite = false; /** * Specifies the depth function that should be used. 0 means the default engine function */ this.depthFunction = 0; /** * Specifies if there should be a separate pass for culling */ this.separateCullingPass = false; /** * Stores the state specifying if fog should be enabled */ this._fogEnabled = true; /** * Stores the size of points */ this.pointSize = 1.0; /** * Stores the z offset Factor value */ this.zOffset = 0; /** * Stores the z offset Units value */ this.zOffsetUnits = 0; /** * Gives access to the stencil properties of the material */ this.stencil = new MaterialStencilState(); /** * Specifies if uniform buffers should be used */ this._useUBO = false; /** * Stores the fill mode state */ this._fillMode = Material.TriangleFillMode; /** * Specifies if the depth write state should be cached */ this._cachedDepthWriteState = false; /** * Specifies if the color write state should be cached */ this._cachedColorWriteState = false; /** * Specifies if the depth function state should be cached */ this._cachedDepthFunctionState = 0; /** @internal */ this._indexInSceneMaterialArray = -1; /** @internal */ this.meshMap = null; /** @internal */ this._parentContainer = null; /** @internal */ this._uniformBufferLayoutBuilt = false; this._eventInfo = {}; // will be initialized before each event notification /** @internal */ this._callbackPluginEventGeneric = () => void 0; /** @internal */ this._callbackPluginEventIsReadyForSubMesh = () => void 0; /** @internal */ this._callbackPluginEventPrepareDefines = () => void 0; /** @internal */ this._callbackPluginEventPrepareDefinesBeforeAttributes = () => void 0; /** @internal */ this._callbackPluginEventHardBindForSubMesh = () => void 0; /** @internal */ this._callbackPluginEventBindForSubMesh = () => void 0; /** @internal */ this._callbackPluginEventHasRenderTargetTextures = () => void 0; /** @internal */ this._callbackPluginEventFillRenderTargetTextures = () => void 0; /** * The transparency mode of the material. */ this._transparencyMode = null; this.name = name; const setScene = scene || EngineStore.LastCreatedScene; if (!setScene) { return; } this._scene = setScene; this._dirtyCallbacks = {}; this._forceGLSL = forceGLSL; this._dirtyCallbacks[1] = this._markAllSubMeshesAsTexturesDirty.bind(this); this._dirtyCallbacks[2] = this._markAllSubMeshesAsLightsDirty.bind(this); this._dirtyCallbacks[4] = this._markAllSubMeshesAsFresnelDirty.bind(this); this._dirtyCallbacks[8] = this._markAllSubMeshesAsAttributesDirty.bind(this); this._dirtyCallbacks[16] = this._markAllSubMeshesAsMiscDirty.bind(this); this._dirtyCallbacks[32] = this._markAllSubMeshesAsPrePassDirty.bind(this); this._dirtyCallbacks[127] = this._markAllSubMeshesAsAllDirty.bind(this); this.id = name || Tools.RandomId(); this.uniqueId = this._scene.getUniqueId(); this._materialContext = this._scene.getEngine().createMaterialContext(); this._drawWrapper = new DrawWrapper(this._scene.getEngine(), false); this._drawWrapper.materialContext = this._materialContext; this._uniformBuffer = new UniformBuffer(this._scene.getEngine(), undefined, undefined, name); this._useUBO = this.getScene().getEngine().supportsUniformBuffers; this._createUniformBuffer(); if (!doNotAdd) { this._scene.addMaterial(this); } if (this._scene.useMaterialMeshMap) { this.meshMap = {}; } Material.OnEventObservable.notifyObservers(this, 1 /* MaterialPluginEvent.Created */); } /** @internal */ _createUniformBuffer() { const engine = this.getScene().getEngine(); this._uniformBuffer?.dispose(); if (engine.isWebGPU && !this._forceGLSL) { // Switch main UBO to non UBO to connect to leftovers UBO in webgpu this._uniformBuffer = new UniformBuffer(engine, undefined, undefined, this.name, true); this._shaderLanguage = 1 /* ShaderLanguage.WGSL */; } else { this._uniformBuffer = new UniformBuffer(this._scene.getEngine(), undefined, undefined, this.name); } this._uniformBufferLayoutBuilt = false; } /** * Returns a string representation of the current material * @param fullDetails defines a boolean indicating which levels of logging is desired * @returns a string with material information */ // eslint-disable-next-line @typescript-eslint/no-unused-vars toString(fullDetails) { const ret = "Name: " + this.name; return ret; } /** * Gets the class name of the material * @returns a string with the class name of the material */ getClassName() { return "Material"; } /** @internal */ get _isMaterial() { return true; } /** * Specifies if updates for the material been locked */ get isFrozen() { return this.checkReadyOnlyOnce; } /** * Locks updates for the material */ freeze() { this.markDirty(); this.checkReadyOnlyOnce = true; } /** * Unlocks updates for the material */ unfreeze() { this.markDirty(); this.checkReadyOnlyOnce = false; } /** * Specifies if the material is ready to be used * @param mesh defines the mesh to check * @param useInstances specifies if instances should be used * @returns a boolean indicating if the material is ready to be used */ // eslint-disable-next-line @typescript-eslint/no-unused-vars isReady(mesh, useInstances) { return true; } /** * Specifies that the submesh is ready to be used * @param mesh defines the mesh to check * @param subMesh defines which submesh to check * @param useInstances specifies that instances should be used * @returns a boolean indicating that the submesh is ready or not */ // eslint-disable-next-line @typescript-eslint/no-unused-vars isReadyForSubMesh(mesh, subMesh, useInstances) { const defines = subMesh.materialDefines; if (!defines) { return false; } this._eventInfo.isReadyForSubMesh = true; this._eventInfo.defines = defines; this._callbackPluginEventIsReadyForSubMesh(this._eventInfo); return this._eventInfo.isReadyForSubMesh; } /** * Returns the material effect * @returns the effect associated with the material */ getEffect() { return this._drawWrapper.effect; } /** * Returns the current scene * @returns a Scene */ getScene() { return this._scene; } /** @internal */ _getEffectiveOrientation(mesh) { return this.sideOrientation !== null ? this.sideOrientation : mesh.sideOrientation; } /** * Gets the current transparency mode. */ get transparencyMode() { return this._transparencyMode; } /** * Sets the transparency mode of the material. * * | Value | Type | Description | * | ----- | ----------------------------------- | ----------- | * | 0 | OPAQUE | | * | 1 | ALPHATEST | | * | 2 | ALPHABLEND | | * | 3 | ALPHATESTANDBLEND | | * */ set transparencyMode(value) { if (this._transparencyMode === value) { return; } this._transparencyMode = value; this._markAllSubMeshesAsTexturesAndMiscDirty(); } get _hasTransparencyMode() { return this._transparencyMode != null; } get _transparencyModeIsBlend() { return this._transparencyMode === Material.MATERIAL_ALPHABLEND || this._transparencyMode === Material.MATERIAL_ALPHATESTANDBLEND; } get _transparencyModeIsTest() { return this._transparencyMode === Material.MATERIAL_ALPHATEST || this._transparencyMode === Material.MATERIAL_ALPHATESTANDBLEND; } /** * Returns true if alpha blending should be disabled. */ get _disableAlphaBlending() { return this._transparencyMode === Material.MATERIAL_OPAQUE || this._transparencyMode === Material.MATERIAL_ALPHATEST; } /** * Specifies whether or not this material should be rendered in alpha blend mode. * @returns a boolean specifying if alpha blending is needed * @deprecated Please use needAlphaBlendingForMesh instead */ needAlphaBlending() { if (this._hasTransparencyMode) { return this._transparencyModeIsBlend; } if (this._disableAlphaBlending) { return false; } return this.alpha < 1.0; } /** * Specifies if the mesh will require alpha blending * @param mesh defines the mesh to check * @returns a boolean specifying if alpha blending is needed for the mesh */ needAlphaBlendingForMesh(mesh) { if (this._hasTransparencyMode) { return this._transparencyModeIsBlend; } if (mesh.visibility < 1.0) { return true; } if (this._disableAlphaBlending) { return false; } return mesh.hasVertexAlpha || this.needAlphaBlending(); } /** * Specifies whether or not this material should be rendered in alpha test mode. * @returns a boolean specifying if an alpha test is needed. * @deprecated Please use needAlphaTestingForMesh instead */ needAlphaTesting() { if (this._hasTransparencyMode) { return this._transparencyModeIsTest; } return false; } /** * Specifies if material alpha testing should be turned on for the mesh * @param mesh defines the mesh to check * @returns a boolean specifying if alpha testing should be turned on for the mesh */ needAlphaTestingForMesh(mesh) { if (this._hasTransparencyMode) { return this._transparencyModeIsTest; } return !this.needAlphaBlendingForMesh(mesh) && this.needAlphaTesting(); } /** * Gets the texture used for the alpha test * @returns the texture to use for alpha testing */ getAlphaTestTexture() { return null; } /** * Marks the material to indicate that it needs to be re-calculated * @param forceMaterialDirty - Forces the material to be marked as dirty for all components (same as this.markAsDirty(Material.AllDirtyFlag)). You should use this flag if the material is frozen and you want to force a recompilation. */ markDirty(forceMaterialDirty = false) { const meshes = this.getScene().meshes; for (const mesh of meshes) { if (!mesh.subMeshes) { continue; } for (const subMesh of mesh.subMeshes) { if (subMesh.getMaterial() !== this) { continue; } for (const drawWrapper of subMesh._drawWrappers) { if (!drawWrapper) { continue; } if (this._materialContext === drawWrapper.materialContext) { drawWrapper._wasPreviouslyReady = false; drawWrapper._wasPreviouslyUsingInstances = null; drawWrapper._forceRebindOnNextCall = forceMaterialDirty; } } } } if (forceMaterialDirty) { this.markAsDirty(Material.AllDirtyFlag); } } /** * @internal */ _preBind(effect, overrideOrientation = null) { const engine = this._scene.getEngine(); const orientation = overrideOrientation == null ? this.sideOrientation : overrideOrientation; const reverse = orientation === Material.ClockWiseSideOrientation; engine.enableEffect(effect ? effect : this._getDrawWrapper()); engine.setState(this.backFaceCulling, this.zOffset, false, reverse, this._scene._mirroredCameraPosition ? !this.cullBackFaces : this.cullBackFaces, this.stencil, this.zOffsetUnits); return reverse; } /** * Binds the material to the mesh * @param world defines the world transformation matrix * @param mesh defines the mesh to bind the material to */ // eslint-disable-next-line @typescript-eslint/no-unused-vars bind(world, mesh) { } /** * Initializes the uniform buffer layout for the shader. */ buildUniformLayout() { const ubo = this._uniformBuffer; this._eventInfo.ubo = ubo; this._callbackPluginEventGeneric(8 /* MaterialPluginEvent.PrepareUniformBuffer */, this._eventInfo); ubo.create(); this._uniformBufferLayoutBuilt = true; } /** * Binds the submesh to the material * @param world defines the world transformation matrix * @param mesh defines the mesh containing the submesh * @param subMesh defines the submesh to bind the material to */ bindForSubMesh(world, mesh, subMesh) { const drawWrapper = subMesh._drawWrapper; this._eventInfo.subMesh = subMesh; this._callbackPluginEventBindForSubMesh(this._eventInfo); drawWrapper._forceRebindOnNextCall = false; } /** * Binds the world matrix to the material * @param world defines the world transformation matrix */ // eslint-disable-next-line @typescript-eslint/no-unused-vars bindOnlyWorldMatrix(world) { } /** * Binds the view matrix to the effect * @param effect defines the effect to bind the view matrix to */ bindView(effect) { if (!this._useUBO) { effect.setMatrix("view", this.getScene().getViewMatrix()); } else { this._needToBindSceneUbo = true; } } /** * Binds the view projection and projection matrices to the effect * @param effect defines the effect to bind the view projection and projection matrices to */ bindViewProjection(effect) { if (!this._useUBO) { effect.setMatrix("viewProjection", this.getScene().getTransformMatrix()); effect.setMatrix("projection", this.getScene().getProjectionMatrix()); } else { this._needToBindSceneUbo = true; } } /** * Binds the view matrix to the effect * @param effect defines the effect to bind the view matrix to * @param variableName name of the shader variable that will hold the eye position */ bindEyePosition(effect, variableName) { if (!this._useUBO) { this._scene.bindEyePosition(effect, variableName); } else { this._needToBindSceneUbo = true; } } /** * Processes to execute after binding the material to a mesh * @param mesh defines the rendered mesh * @param effect defines the effect used to bind the material * @param _subMesh defines the subMesh that the material has been bound for */ _afterBind(mesh, effect = null, _subMesh) { this._scene._cachedMaterial = this; if (this._needToBindSceneUbo) { if (effect) { this._needToBindSceneUbo = false; BindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer()); this._scene.finalizeSceneUbo(); } } if (mesh) { this._scene._cachedVisibility = mesh.visibility; } else { this._scene._cachedVisibility = 1; } if (this._onBindObservable && mesh) { this._onBindObservable.notifyObservers(mesh); } if (this.disableDepthWrite) { const engine = this._scene.getEngine(); this._cachedDepthWriteState = engine.getDepthWrite(); engine.setDepthWrite(false); } if (this.disableColorWrite) { const engine = this._scene.getEngine(); this._cachedColorWriteState = engine.getColorWrite(); engine.setColorWrite(false); } if (this.depthFunction !== 0) { const engine = this._scene.getEngine(); this._cachedDepthFunctionState = engine.getDepthFunction() || 0; engine.setDepthFunction(this.depthFunction); } } /** * Unbinds the material from the mesh */ unbind() { this._scene.getSceneUniformBuffer().unbindEffect(); if (this._onUnBindObservable) { this._onUnBindObservable.notifyObservers(this); } if (this.depthFunction !== 0) { const engine = this._scene.getEngine(); engine.setDepthFunction(this._cachedDepthFunctionState); } if (this.disableDepthWrite) { const engine = this._scene.getEngine(); engine.setDepthWrite(this._cachedDepthWriteState); } if (this.disableColorWrite) { const engine = this._scene.getEngine(); engine.setColorWrite(this._cachedColorWriteState); } } /** * Returns the animatable textures. * @returns - Array of animatable textures. */ getAnimatables() { this._eventInfo.animatables = []; this._callbackPluginEventGeneric(256 /* MaterialPluginEvent.GetAnimatables */, this._eventInfo); return this._eventInfo.animatables; } /** * Gets the active textures from the material * @returns an array of textures */ getActiveTextures() { this._eventInfo.activeTextures = []; this._callbackPluginEventGeneric(512 /* MaterialPluginEvent.GetActiveTextures */, this._eventInfo); return this._eventInfo.activeTextures; } /** * Specifies if the material uses a texture * @param texture defines the texture to check against the material * @returns a boolean specifying if the material uses the texture */ hasTexture(texture) { this._eventInfo.hasTexture = false; this._eventInfo.texture = texture; this._callbackPluginEventGeneric(1024 /* MaterialPluginEvent.HasTexture */, this._eventInfo); return this._eventInfo.hasTexture; } /** * Makes a duplicate of the material, and gives it a new name * @param name defines the new name for the duplicated material * @returns the cloned material */ // eslint-disable-next-line @typescript-eslint/no-unused-vars clone(name) { return null; } _clonePlugins(targetMaterial, rootUrl) { const serializationObject = {}; // Create plugins in targetMaterial in case they don't exist this._serializePlugins(serializationObject); Material._ParsePlugins(serializationObject, targetMaterial, this._scene, rootUrl); // Copy the properties of the current plugins to the cloned material's plugins if (this.pluginManager) { for (const plugin of this.pluginManager._plugins) { const targetPlugin = targetMaterial.pluginManager.getPlugin(plugin.name); if (targetPlugin) { plugin.copyTo(targetPlugin); } } } } /** * Gets the meshes bound to the material * @returns an array of meshes bound to the material */ getBindedMeshes() { if (this.meshMap) { const result = []; for (const meshId in this.meshMap) { const mesh = this.meshMap[meshId]; if (mesh) { result.push(mesh); } } return result; } else { const meshes = this._scene.meshes; return meshes.filter((mesh) => mesh.material === this); } } /** * Force shader compilation * @param mesh defines the mesh associated with this material * @param onCompiled defines a function to execute once the material is compiled * @param options defines the options to configure the compilation * @param onError defines a function to execute if the material fails compiling */ forceCompilation(mesh, onCompiled, options, onError) { const localOptions = { clipPlane: false, useInstances: false, ...options, }; const scene = this.getScene(); const currentHotSwapingState = this.allowShaderHotSwapping; this.allowShaderHotSwapping = false; // Turned off to let us evaluate the real compilation state const checkReady = () => { if (!this._scene || !this._scene.getEngine()) { return; } const clipPlaneState = scene.clipPlane; if (localOptions.clipPlane) { scene.clipPlane = new Plane(0, 0, 0, 1); } if (this._storeEffectOnSubMeshes) { let allDone = true, lastError = null; if (mesh.subMeshes) { const tempSubMesh = new SubMesh(0, 0, 0, 0, 0, mesh, undefined, false, false); if (tempSubMesh.materialDefines) { tempSubMesh.materialDefines._renderId = -1; } if (!this.isReadyForSubMesh(mesh, tempSubMesh, localOptions.useInstances)) { if (tempSubMesh.effect && tempSubMesh.effect.getCompilationError() && tempSubMesh.effect.allFallbacksProcessed()) { lastError = tempSubMesh.effect.getCompilationError(); } else { allDone = false; setTimeout(checkReady, 16); } } } if (allDone) { this.allowShaderHotSwapping = currentHotSwapingState; if (lastError) { if (onError) { onError(lastError); } } if (onCompiled) { onCompiled(this); } } } else { if (this.isReady()) { this.allowShaderHotSwapping = currentHotSwapingState; if (onCompiled) { onCompiled(this); } } else { setTimeout(checkReady, 16); } } if (localOptions.clipPlane) { scene.clipPlane = clipPlaneState; } }; checkReady(); } /** * Force shader compilation * @param mesh defines the mesh that will use this material * @param options defines additional options for compiling the shaders * @returns a promise that resolves when the compilation completes */ forceCompilationAsync(mesh, options) { return new Promise((resolve, reject) => { this.forceCompilation(mesh, () => { resolve(); }, options, (reason) => { reject(reason); }); }); } /** * Marks a define in the material to indicate that it needs to be re-computed * @param flag defines a flag used to determine which parts of the material have to be marked as dirty */ markAsDirty(flag) { if (this.getScene().blockMaterialDirtyMechanism || this._blockDirtyMechanism) { return; } Material._DirtyCallbackArray.length = 0; if (flag & Material.ImageProcessingDirtyFlag) { Material._DirtyCallbackArray.push(Material._ImageProcessingDirtyCallBack); } if (flag & Material.TextureDirtyFlag) { Material._DirtyCallbackArray.push(Material._TextureDirtyCallBack); } if (flag & Material.LightDirtyFlag) { Material._DirtyCallbackArray.push(Material._LightsDirtyCallBack); } if (flag & Material.FresnelDirtyFlag) { Material._DirtyCallbackArray.push(Material._FresnelDirtyCallBack); } if (flag & Material.AttributesDirtyFlag) { Material._DirtyCallbackArray.push(Material._AttributeDirtyCallBack); } if (flag & Material.MiscDirtyFlag) { Material._DirtyCallbackArray.push(Material._MiscDirtyCallBack); } if (flag & Material.PrePassDirtyFlag) { Material._DirtyCallbackArray.push(Material._PrePassDirtyCallBack); } if (Material._DirtyCallbackArray.length) { this._markAllSubMeshesAsDirty(Material._RunDirtyCallBacks); } this.getScene().resetCachedMaterial(); } /** * Resets the draw wrappers cache for all submeshes that are using this material */ resetDrawCache() { const meshes = this.getScene().meshes; for (const mesh of meshes) { if (!mesh.subMeshes) { continue; } for (const subMesh of mesh.subMeshes) { if (subMesh.getMaterial() !== this) { continue; } subMesh.resetDrawCache(); } } } /** * Marks all submeshes of a material to indicate that their material defines need to be re-calculated * @param func defines a function which checks material defines against the submeshes */ _markAllSubMeshesAsDirty(func) { const scene = this.getScene(); if (scene.blockMaterialDirtyMechanism || this._blockDirtyMechanism) { return; } const meshes = scene.meshes; for (const mesh of meshes) { if (!mesh.subMeshes) { continue; } for (const subMesh of mesh.subMeshes) { // We want to skip the submeshes which are not using this material or which have not yet rendered at least once const material = subMesh.getMaterial() || (scene._hasDefaultMaterial ? scene.defaultMaterial : null); if (material !== this) { continue; } for (const drawWrapper of subMesh._drawWrappers) { if (!drawWrapper || !drawWrapper.defines || !drawWrapper.defines.markAllAsDirty) { continue; } if (this._materialContext === drawWrapper.materialContext) { func(drawWrapper.defines); } } } } } /** * Indicates that the scene should check if the rendering now needs a prepass */ _markScenePrePassDirty() { if (this.getScene().blockMaterialDirtyMechanism || this._blockDirtyMechanism) { return; } const prePassRenderer = this.getScene().enablePrePassRenderer(); if (prePassRenderer) { prePassRenderer.markAsDirty(); } } /** * Indicates that we need to re-calculated for all submeshes */ _markAllSubMeshesAsAllDirty() { this._markAllSubMeshesAsDirty(Material._AllDirtyCallBack); } /** * Indicates that image processing needs to be re-calculated for all submeshes */ _markAllSubMeshesAsImageProcessingDirty() { this._markAllSubMeshesAsDirty(Material._ImageProcessingDirtyCallBack); } /** * Indicates that textures need to be re-calculated for all submeshes */ _markAllSubMeshesAsTexturesDirty() { this._markAllSubMeshesAsDirty(Material._TextureDirtyCallBack); } /** * Indicates that fresnel needs to be re-calculated for all submeshes */ _markAllSubMeshesAsFresnelDirty() { this._markAllSubMeshesAsDirty(Material._FresnelDirtyCallBack); } /** * Indicates that fresnel and misc need to be re-calculated for all submeshes */ _markAllSubMeshesAsFresnelAndMiscDirty() { this._markAllSubMeshesAsDirty(Material._FresnelAndMiscDirtyCallBack); } /** * Indicates that lights need to be re-calculated for all submeshes */ _markAllSubMeshesAsLightsDirty() { this._markAllSubMeshesAsDirty(Material._LightsDirtyCallBack); } /** * Indicates that attributes need to be re-calculated for all submeshes */ _markAllSubMeshesAsAttributesDirty() { this._markAllSubMeshesAsDirty(Material._AttributeDirtyCallBack); } /** * Indicates that misc needs to be re-calculated for all submeshes */ _markAllSubMeshesAsMiscDirty() { this._markAllSubMeshesAsDirty(Material._MiscDirtyCallBack); } /** * Indicates that prepass needs to be re-calculated for all submeshes */ _markAllSubMeshesAsPrePassDirty() { this._markAllSubMeshesAsDirty(Material._PrePassDirtyCallBack); } /** * Indicates that textures and misc need to be re-calculated for all submeshes */ _markAllSubMeshesAsTexturesAndMiscDirty() { this._markAllSubMeshesAsDirty(Material._TextureAndMiscDirtyCallBack); } _checkScenePerformancePriority() { if (this._scene.performancePriority !== 0 /* ScenePerformancePriority.BackwardCompatible */) { this.checkReadyOnlyOnce = true; // re-set the flag when the perf priority changes const observer = this._scene.onScenePerformancePriorityChangedObservable.addOnce(() => { this.checkReadyOnlyOnce = false; }); // if this material is disposed before the scene is disposed, cleanup the observer this.onDisposeObservable.add(() => { this._scene.onScenePerformancePriorityChangedObservable.remove(observer); }); } } /** * Sets the required values to the prepass renderer. * @param prePassRenderer defines the prepass renderer to setup. * @returns true if the pre pass is needed. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars setPrePassRenderer(prePassRenderer) { // Do Nothing by default return false; } /** * Disposes the material * @param _forceDisposeEffect kept for backward compat. We reference count the effect now. * @param forceDisposeTextures specifies if textures should be forcefully disposed * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh */ dispose(_forceDisposeEffect, forceDisposeTextures, notBoundToMesh) { const scene = this.getScene(); // Animations scene.stopAnimation(this); scene.freeProcessedMaterials(); // Remove from scene scene.removeMaterial(this); this._eventInfo.forceDisposeTextures = forceDisposeTextures; this._callbackPluginEventGeneric(2 /* MaterialPluginEvent.Disposed */, this._eventInfo); if (this._parentContainer) { const index = this._parentContainer.materials.indexOf(this); if (index > -1) { this._parentContainer.materials.splice(index, 1); } this._parentContainer = null; } if (notBoundToMesh !== true) { // Remove from meshes if (this.meshMap) { for (const meshId in this.meshMap) { const mesh = this.meshMap[meshId]; this._disposeMeshResources(mesh); } } else { const meshes = scene.meshes; for (const mesh of meshes) { this._disposeMeshResources(mesh); } } } this._uniformBuffer.dispose(); // Shader are kept in cache for further use but we can get rid of this by using forceDisposeEffect if (this._drawWrapper.effect) { if (!this._storeEffectOnSubMeshes) { this._drawWrapper.effect.dispose(); } this._drawWrapper.effect = null; } this.metadata = null; // Callback this.onDisposeObservable.notifyObservers(this); this.onDisposeObservable.clear(); if (this._onBindObservable) { this._onBindObservable.clear(); } if (this._onUnBindObservable) { this._onUnBindObservable.clear(); } if (this._onEffectCreatedObservable) { this._onEffectCreatedObservable.clear(); } if (this._eventInfo) { this._eventInfo = {}; } } _disposeMeshResources(mesh) { if (!mesh) { return; } const geometry = mesh.geometry; const materialForRenderPass = mesh._internalAbstractMeshDataInfo._materialForRenderPass; if (this._storeEffectOnSubMeshes) { if (mesh.subMeshes && materialForRenderPass) { for (const subMesh of mesh.subMeshes) { const drawWrappers = subMesh._drawWrappers; for (let renderPassIndex = 0; renderPassIndex < drawWrappers.length; renderPassIndex++) { const effect = drawWrappers[renderPassIndex]?.effect; if (!effect) { continue; } const material = materialForRenderPass[renderPassIndex]; if (material === this) { geometry?._releaseVertexArrayObject(effect); subMesh._removeDrawWrapper(renderPassIndex, true, true); } } } } } else { geometry?._releaseVertexArrayObject(this._drawWrapper.effect); } if (mesh.material === this && !mesh.sourceMesh) { mesh.material = null; } } /** * Serializes this material * @returns the serialized material object */ serialize() { const serializationObject = SerializationHelper.Serialize(this); serializationObject.stencil = this.stencil.serialize(); serializationObject.uniqueId = this.uniqueId; this._serializePlugins(serializationObject); return serializationObject; } _serializePlugins(serializationObject) { serializationObject.plugins = {}; if (this.pluginManager) { for (const plugin of this.pluginManager._plugins) { if (!plugin.doNotSerialize) { serializationObject.plugins[plugin.getClassName()] = plugin.serialize(); } } } } /** * Creates a material from parsed material data * @param parsedMaterial defines parsed material data * @param scene defines the hosting scene * @param rootUrl defines the root URL to use to load textures * @returns a new material */ static Parse(parsedMaterial, scene, rootUrl) { if (!parsedMaterial.customType) { parsedMaterial.customType = "BABYLON.StandardMaterial"; } else if (parsedMaterial.customType === "BABYLON.PBRMaterial" && parsedMaterial.overloadedAlbedo) { parsedMaterial.customType = "BABYLON.LegacyPBRMaterial"; if (!BABYLON.LegacyPBRMaterial) { Logger.Error("Your scene is trying to load a legacy version of the PBRMaterial, please, include it from the materials library."); return null; }