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,161 lines (1,160 loc) 91 kB
import { __decorate } from "../../tslib.es6.js"; import { PushMaterial } from "../pushMaterial.js"; import { Matrix, Vector2 } from "../../Maths/math.vector.js"; import { Color3, Color4 } from "../../Maths/math.color.js"; import { NodeMaterialBuildState } from "./nodeMaterialBuildState.js"; import { Effect } from "../effect.js"; import { Observable } from "../../Misc/observable.js"; import { NodeMaterialBlockTargets } from "./Enums/nodeMaterialBlockTargets.js"; import { NodeMaterialBuildStateSharedData } from "./nodeMaterialBuildStateSharedData.js"; import { MaterialDefines } from "../../Materials/materialDefines.js"; import { VertexBuffer } from "../../Buffers/buffer.js"; import { Tools } from "../../Misc/tools.js"; import { SfeModeDefine } from "./Blocks/Fragment/smartFilterFragmentOutputBlock.js"; import { TransformBlock } from "./Blocks/transformBlock.js"; import { VertexOutputBlock } from "./Blocks/Vertex/vertexOutputBlock.js"; import { FragmentOutputBlock } from "./Blocks/Fragment/fragmentOutputBlock.js"; import { InputBlock } from "./Blocks/Input/inputBlock.js"; import { GetClass, RegisterClass } from "../../Misc/typeStore.js"; import { serialize } from "../../Misc/decorators.js"; import { SerializationHelper } from "../../Misc/decorators.serialization.js"; import { CurrentScreenBlock } from "./Blocks/Dual/currentScreenBlock.js"; import { ParticleTextureBlock } from "./Blocks/Particle/particleTextureBlock.js"; import { ParticleRampGradientBlock } from "./Blocks/Particle/particleRampGradientBlock.js"; import { ParticleBlendMultiplyBlock } from "./Blocks/Particle/particleBlendMultiplyBlock.js"; import { EffectFallbacks } from "../effectFallbacks.js"; import { WebRequest } from "../../Misc/webRequest.js"; import { PostProcess } from "../../PostProcesses/postProcess.js"; import { VectorMergerBlock } from "./Blocks/vectorMergerBlock.js"; import { RemapBlock } from "./Blocks/remapBlock.js"; import { MultiplyBlock } from "./Blocks/multiplyBlock.js"; import { NodeMaterialModes } from "./Enums/nodeMaterialModes.js"; import { Texture } from "../Textures/texture.js"; import { BaseParticleSystem } from "../../Particles/baseParticleSystem.js"; import { ColorSplitterBlock } from "./Blocks/colorSplitterBlock.js"; import { TimingTools } from "../../Misc/timingTools.js"; import { ProceduralTexture } from "../Textures/Procedurals/proceduralTexture.js"; import { AnimatedInputBlockTypes } from "./Blocks/Input/animatedInputBlockTypes.js"; import { TrigonometryBlock, TrigonometryBlockOperations } from "./Blocks/trigonometryBlock.js"; import { NodeMaterialSystemValues } from "./Enums/nodeMaterialSystemValues.js"; import { EngineStore } from "../../Engines/engineStore.js"; import { Logger } from "../../Misc/logger.js"; import { PrepareDefinesForCamera, PrepareDefinesForPrePass } from "../materialHelper.functions.js"; import { AbstractEngine } from "../../Engines/abstractEngine.js"; import { MaterialHelperGeometryRendering } from "../materialHelper.geometryrendering.js"; const onCreatedEffectParameters = { effect: null, subMesh: null }; /** @internal */ export class NodeMaterialDefines extends MaterialDefines { /** * Creates a new NodeMaterialDefines */ constructor() { super(); /** Normal */ this.NORMAL = false; /** Tangent */ this.TANGENT = false; /** Vertex color */ this.VERTEXCOLOR_NME = false; /** Uv1 **/ this.UV1 = false; /** Uv2 **/ this.UV2 = false; /** Uv3 **/ this.UV3 = false; /** Uv4 **/ this.UV4 = false; /** Uv5 **/ this.UV5 = false; /** Uv6 **/ this.UV6 = false; /** Prepass **/ this.PREPASS = false; /** Prepass normal */ this.PREPASS_NORMAL = false; /** Prepass normal index */ this.PREPASS_NORMAL_INDEX = -1; /** Prepass world normal */ this.PREPASS_WORLD_NORMAL = false; /** Prepass world normal index */ this.PREPASS_WORLD_NORMAL_INDEX = -1; /** Prepass position */ this.PREPASS_POSITION = false; /** Prepass position index */ this.PREPASS_POSITION_INDEX = -1; /** Prepass local position */ this.PREPASS_LOCAL_POSITION = false; /** Prepass local position index */ this.PREPASS_LOCAL_POSITION_INDEX = -1; /** Prepass depth */ this.PREPASS_DEPTH = false; /** Prepass depth index */ this.PREPASS_DEPTH_INDEX = -1; /** Clip-space depth */ this.PREPASS_SCREENSPACE_DEPTH = false; /** Clip-space depth index */ this.PREPASS_SCREENSPACE_DEPTH_INDEX = -1; /** Scene MRT count */ this.SCENE_MRT_COUNT = 0; /** BONES */ this.NUM_BONE_INFLUENCERS = 0; /** Bones per mesh */ this.BonesPerMesh = 0; /** Using texture for bone storage */ this.BONETEXTURE = false; /** MORPH TARGETS */ this.MORPHTARGETS = false; /** Morph target position */ this.MORPHTARGETS_POSITION = false; /** Morph target normal */ this.MORPHTARGETS_NORMAL = false; /** Morph target tangent */ this.MORPHTARGETS_TANGENT = false; /** Morph target uv */ this.MORPHTARGETS_UV = false; /** Morph target uv2 */ this.MORPHTARGETS_UV2 = false; this.MORPHTARGETS_COLOR = false; /** Morph target support positions */ this.MORPHTARGETTEXTURE_HASPOSITIONS = false; /** Morph target support normals */ this.MORPHTARGETTEXTURE_HASNORMALS = false; /** Morph target support tangents */ this.MORPHTARGETTEXTURE_HASTANGENTS = false; /** Morph target support uvs */ this.MORPHTARGETTEXTURE_HASUVS = false; /** Morph target support uv2s */ this.MORPHTARGETTEXTURE_HASUV2S = false; this.MORPHTARGETTEXTURE_HASCOLORS = false; /** Number of morph influencers */ this.NUM_MORPH_INFLUENCERS = 0; /** Using a texture to store morph target data */ this.MORPHTARGETS_TEXTURE = false; /** IMAGE PROCESSING */ this.IMAGEPROCESSING = false; /** Vignette */ this.VIGNETTE = false; /** Multiply blend mode for vignette */ this.VIGNETTEBLENDMODEMULTIPLY = false; /** Opaque blend mode for vignette */ this.VIGNETTEBLENDMODEOPAQUE = false; /** Tone mapping */ this.TONEMAPPING = 0; /** Contrast */ this.CONTRAST = false; /** Exposure */ this.EXPOSURE = false; /** Color curves */ this.COLORCURVES = false; /** Color grading */ this.COLORGRADING = false; /** 3D color grading */ this.COLORGRADING3D = false; /** Sampler green depth */ this.SAMPLER3DGREENDEPTH = false; /** Sampler for BGR map */ this.SAMPLER3DBGRMAP = false; /** Dithering */ this.DITHER = false; /** Using post process for image processing */ this.IMAGEPROCESSINGPOSTPROCESS = false; /** Skip color clamp */ this.SKIPFINALCOLORCLAMP = false; /** MISC. */ this.BUMPDIRECTUV = 0; /** Camera is orthographic */ this.CAMERA_ORTHOGRAPHIC = false; /** Camera is perspective */ this.CAMERA_PERSPECTIVE = false; this.AREALIGHTSUPPORTED = true; this.AREALIGHTNOROUGHTNESS = true; this.rebuild(); } /** * Set the value of a specific key * @param name defines the name of the key to set * @param value defines the value to set * @param markAsUnprocessedIfDirty Flag to indicate to the cache that this value needs processing */ setValue(name, value, markAsUnprocessedIfDirty = false) { if (this[name] === undefined) { this._keys.push(name); } if (markAsUnprocessedIfDirty && this[name] !== value) { this.markAsUnprocessed(); } this[name] = value; } } /** * Class used to create a node based material built by assembling shader blocks */ export class NodeMaterial extends PushMaterial { /** * Checks if a block is a texture block * @param block The block to check * @returns True if the block is a texture block */ static _BlockIsTextureBlock(block) { return (block.getClassName() === "TextureBlock" || block.getClassName() === "ReflectionTextureBaseBlock" || block.getClassName() === "ReflectionTextureBlock" || block.getClassName() === "ReflectionBlock" || block.getClassName() === "RefractionBlock" || block.getClassName() === "CurrentScreenBlock" || block.getClassName() === "SmartFilterTextureBlock" || block.getClassName() === "ParticleTextureBlock" || block.getClassName() === "ImageSourceBlock" || block.getClassName() === "TriPlanarBlock" || block.getClassName() === "BiPlanarBlock" || block.getClassName() === "PrePassTextureBlock"); } set _glowModeEnabled(value) { this._useAdditionalColor = value; } /** Get the inspector from bundle or global * @returns the global NME */ _getGlobalNodeMaterialEditor() { // UMD Global name detection from Webpack Bundle UMD Name. if (typeof NODEEDITOR !== "undefined") { return NODEEDITOR; } // In case of module let's check the global emitted from the editor entry point. if (typeof BABYLON !== "undefined" && typeof BABYLON.NodeEditor !== "undefined") { return BABYLON; } return undefined; } /** Gets or sets the active shader language */ get shaderLanguage() { return this._options?.shaderLanguage || NodeMaterial.DefaultShaderLanguage; } set shaderLanguage(value) { this._options.shaderLanguage = value; } /** Gets or sets options to control the node material overall behavior */ get options() { return this._options; } set options(options) { this._options = options; } /** * Gets the image processing configuration used either in this material. */ get imageProcessingConfiguration() { return this._imageProcessingConfiguration; } /** * Sets the Default image processing configuration used either in the this material. * * If sets to null, the scene one is in use. */ set imageProcessingConfiguration(value) { this._attachImageProcessingConfiguration(value); // Ensure the effect will be rebuilt. this._markAllSubMeshesAsTexturesDirty(); } /** * Gets or sets the mode property */ get mode() { return this._mode; } set mode(value) { this._mode = value; } /** Gets or sets the unique identifier used to identified the effect associated with the material */ get buildId() { return this._buildId; } set buildId(value) { this._buildId = value; } /** * Create a new node based material * @param name defines the material name * @param scene defines the hosting scene * @param options defines creation option */ constructor(name, scene, options = {}) { super(name, scene || EngineStore.LastCreatedScene); this._buildId = NodeMaterial._BuildIdGenerator++; this._buildWasSuccessful = false; this._cachedWorldViewMatrix = new Matrix(); this._cachedWorldViewProjectionMatrix = new Matrix(); this._optimizers = new Array(); this._animationFrame = -1; this._buildIsInProgress = false; this.BJSNODEMATERIALEDITOR = this._getGlobalNodeMaterialEditor(); /** @internal */ this._useAdditionalColor = false; /** * Gets or sets data used by visual editor * @see https://nme.babylonjs.com */ this.editorData = null; /** * Gets or sets a boolean indicating that alpha value must be ignored (This will turn alpha blending off even if an alpha value is produced by the material) */ this.ignoreAlpha = false; /** * Defines the maximum number of lights that can be used in the material */ this.maxSimultaneousLights = 4; /** * Observable raised when the material is built */ this.onBuildObservable = new Observable(); /** * Observable raised when an error is detected */ this.onBuildErrorObservable = new Observable(); /** * Gets or sets the root nodes of the material vertex shader */ this._vertexOutputNodes = new Array(); /** * Gets or sets the root nodes of the material fragment (pixel) shader */ this._fragmentOutputNodes = new Array(); /** * Gets an array of blocks that needs to be serialized even if they are not yet connected */ this.attachedBlocks = []; /** * Specifies the mode of the node material * @internal */ this._mode = NodeMaterialModes.Material; /** * Gets or sets a boolean indicating that alpha blending must be enabled no matter what alpha value or alpha channel of the FragmentBlock are */ this.forceAlphaBlending = false; if (!NodeMaterial.UseNativeShaderLanguageOfEngine && options && options.shaderLanguage === 1 /* ShaderLanguage.WGSL */ && !this.getScene().getEngine().isWebGPU) { throw new Error("WebGPU shader language is only supported with WebGPU engine"); } this._options = { emitComments: false, shaderLanguage: NodeMaterial.DefaultShaderLanguage, ...options, }; if (NodeMaterial.UseNativeShaderLanguageOfEngine) { this._options.shaderLanguage = this.getScene().getEngine().isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */; } // Setup the default processing configuration to the scene. this._attachImageProcessingConfiguration(null); } /** * Gets the current class name of the material e.g. "NodeMaterial" * @returns the class name */ getClassName() { return "NodeMaterial"; } /** * Attaches a new image processing configuration to the Standard Material. * @param configuration */ _attachImageProcessingConfiguration(configuration) { if (configuration === this._imageProcessingConfiguration) { return; } // Detaches observer. if (this._imageProcessingConfiguration && this._imageProcessingObserver) { this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver); } // Pick the scene configuration if needed. if (!configuration) { this._imageProcessingConfiguration = this.getScene().imageProcessingConfiguration; } else { this._imageProcessingConfiguration = configuration; } // Attaches observer. if (this._imageProcessingConfiguration) { this._imageProcessingObserver = this._imageProcessingConfiguration.onUpdateParameters.add(() => { this._markAllSubMeshesAsImageProcessingDirty(); }); } } /** * Get a block by its name * @param name defines the name of the block to retrieve * @returns the required block or null if not found */ getBlockByName(name) { let result = null; for (const block of this.attachedBlocks) { if (block.name === name) { if (!result) { result = block; } else { Tools.Warn("More than one block was found with the name `" + name + "`"); return result; } } } return result; } /** * Get a block using a predicate * @param predicate defines the predicate used to find the good candidate * @returns the required block or null if not found */ getBlockByPredicate(predicate) { for (const block of this.attachedBlocks) { if (predicate(block)) { return block; } } return null; } /** * Get an input block using a predicate * @param predicate defines the predicate used to find the good candidate * @returns the required input block or null if not found */ getInputBlockByPredicate(predicate) { for (const block of this.attachedBlocks) { if (block.isInput && predicate(block)) { return block; } } return null; } /** * Gets the list of input blocks attached to this material * @returns an array of InputBlocks */ getInputBlocks() { const blocks = []; for (const block of this.attachedBlocks) { if (block.isInput) { blocks.push(block); } } return blocks; } /** * Adds a new optimizer to the list of optimizers * @param optimizer defines the optimizers to add * @returns the current material */ registerOptimizer(optimizer) { const index = this._optimizers.indexOf(optimizer); if (index > -1) { return; } this._optimizers.push(optimizer); return this; } /** * Remove an optimizer from the list of optimizers * @param optimizer defines the optimizers to remove * @returns the current material */ unregisterOptimizer(optimizer) { const index = this._optimizers.indexOf(optimizer); if (index === -1) { return; } this._optimizers.splice(index, 1); return this; } /** * Add a new block to the list of output nodes * @param node defines the node to add * @returns the current material */ addOutputNode(node) { if (node.target === null) { // eslint-disable-next-line no-throw-literal throw "This node is not meant to be an output node. You may want to explicitly set its target value."; } if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) { this._addVertexOutputNode(node); } if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) { this._addFragmentOutputNode(node); } return this; } /** * Remove a block from the list of root nodes * @param node defines the node to remove * @returns the current material */ removeOutputNode(node) { if (node.target === null) { return this; } if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) { this._removeVertexOutputNode(node); } if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) { this._removeFragmentOutputNode(node); } return this; } _addVertexOutputNode(node) { if (this._vertexOutputNodes.indexOf(node) !== -1) { return; } node.target = NodeMaterialBlockTargets.Vertex; this._vertexOutputNodes.push(node); return this; } _removeVertexOutputNode(node) { const index = this._vertexOutputNodes.indexOf(node); if (index === -1) { return; } this._vertexOutputNodes.splice(index, 1); return this; } _addFragmentOutputNode(node) { if (this._fragmentOutputNodes.indexOf(node) !== -1) { return; } node.target = NodeMaterialBlockTargets.Fragment; this._fragmentOutputNodes.push(node); return this; } _removeFragmentOutputNode(node) { const index = this._fragmentOutputNodes.indexOf(node); if (index === -1) { return; } this._fragmentOutputNodes.splice(index, 1); return this; } get _supportGlowLayer() { if (this._fragmentOutputNodes.length === 0) { return false; } if (this._fragmentOutputNodes.some((f) => f.additionalColor && f.additionalColor.isConnected)) { return true; } return false; } /** * Specifies if the material will require alpha blending * @returns a boolean specifying if alpha blending is needed */ needAlphaBlending() { if (this.ignoreAlpha) { return false; } return this.forceAlphaBlending || this.alpha < 1.0 || (this._sharedData && this._sharedData.hints.needAlphaBlending); } /** * Specifies if this material should be rendered in alpha test mode * @returns a boolean specifying if an alpha test is needed. */ needAlphaTesting() { return this._sharedData && this._sharedData.hints.needAlphaTesting; } _processInitializeOnLink(block, state, nodesToProcessForOtherBuildState, autoConfigure = true) { if (block.target === NodeMaterialBlockTargets.VertexAndFragment) { nodesToProcessForOtherBuildState.push(block); } else if (state.target === NodeMaterialBlockTargets.Fragment && block.target === NodeMaterialBlockTargets.Vertex && block._preparationId !== this._buildId) { nodesToProcessForOtherBuildState.push(block); } this._initializeBlock(block, state, nodesToProcessForOtherBuildState, autoConfigure); } _attachBlock(node) { if (this.attachedBlocks.indexOf(node) === -1) { if (node.isUnique) { const className = node.getClassName(); for (const other of this.attachedBlocks) { if (other.getClassName() === className) { this._sharedData.raiseBuildError(`Cannot have multiple blocks of type ${className} in the same NodeMaterial`); return; } } } this.attachedBlocks.push(node); } } _initializeBlock(node, state, nodesToProcessForOtherBuildState, autoConfigure = true) { node.initialize(state); if (autoConfigure) { node.autoConfigure(this); } node._preparationId = this._buildId; this._attachBlock(node); for (const input of node.inputs) { input.associatedVariableName = ""; const connectedPoint = input.connectedPoint; if (connectedPoint && !connectedPoint._preventBubbleUp) { const block = connectedPoint.ownerBlock; if (block !== node) { this._processInitializeOnLink(block, state, nodesToProcessForOtherBuildState, autoConfigure); } } } // Loop if (node.isLoop) { // We need to keep the storage write block in the active blocks const loopBlock = node; if (loopBlock.loopID.hasEndpoints) { for (const endpoint of loopBlock.loopID.endpoints) { const block = endpoint.ownerBlock; if (block.outputs.length !== 0) { continue; } state._terminalBlocks.add(block); // Attach the storage write only this._processInitializeOnLink(block, state, nodesToProcessForOtherBuildState, autoConfigure); } } } else if (node.isTeleportOut) { // Teleportation const teleport = node; if (teleport.entryPoint) { this._processInitializeOnLink(teleport.entryPoint, state, nodesToProcessForOtherBuildState, autoConfigure); } } for (const output of node.outputs) { output.associatedVariableName = ""; } } _resetDualBlocks(node, id) { if (node.target === NodeMaterialBlockTargets.VertexAndFragment) { node.buildId = id; } for (const input of node.inputs) { const connectedPoint = input.connectedPoint; if (connectedPoint && !connectedPoint._preventBubbleUp) { const block = connectedPoint.ownerBlock; if (block !== node) { this._resetDualBlocks(block, id); } } } // If this is a teleport out, we need to reset the connected block if (node.isTeleportOut) { const teleportOut = node; if (teleportOut.entryPoint) { this._resetDualBlocks(teleportOut.entryPoint, id); } } else if (node.isLoop) { // Loop const loopBlock = node; if (loopBlock.loopID.hasEndpoints) { for (const endpoint of loopBlock.loopID.endpoints) { const block = endpoint.ownerBlock; if (block.outputs.length !== 0) { continue; } this._resetDualBlocks(block, id); } } } } /** * Remove a block from the current node material * @param block defines the block to remove */ removeBlock(block) { const attachedBlockIndex = this.attachedBlocks.indexOf(block); if (attachedBlockIndex > -1) { this.attachedBlocks.splice(attachedBlockIndex, 1); } if (block.isFinalMerger) { this.removeOutputNode(block); } } /** * Build the material and generates the inner effect * @param verbose defines if the build should log activity * @param updateBuildId defines if the internal build Id should be updated (default is true) * @param autoConfigure defines if the autoConfigure method should be called when initializing blocks (default is false) */ build(verbose = false, updateBuildId = true, autoConfigure = false) { if (this._buildIsInProgress) { Logger.Warn("Build is already in progress, You can use NodeMaterial.onBuildObservable to determine when the build is completed."); return; } this._buildIsInProgress = true; // First time? if (!this._vertexCompilationState && !autoConfigure) { autoConfigure = true; } this._buildWasSuccessful = false; const engine = this.getScene().getEngine(); const allowEmptyVertexProgram = this._mode === NodeMaterialModes.Particle || this._mode === NodeMaterialModes.SFE; if (this._vertexOutputNodes.length === 0 && !allowEmptyVertexProgram) { this.onBuildErrorObservable.notifyObservers("You must define at least one vertexOutputNode"); this._buildIsInProgress = false; return; } if (this._fragmentOutputNodes.length === 0) { this.onBuildErrorObservable.notifyObservers("You must define at least one fragmentOutputNode"); this._buildIsInProgress = false; return; } // Compilation state this._vertexCompilationState = new NodeMaterialBuildState(); this._vertexCompilationState.supportUniformBuffers = engine.supportsUniformBuffers; this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex; this._fragmentCompilationState = new NodeMaterialBuildState(); this._fragmentCompilationState.supportUniformBuffers = engine.supportsUniformBuffers; this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment; // Shared data const needToPurgeList = this._fragmentOutputNodes.filter((n) => n._isFinalOutputAndActive).length > 1; let fragmentOutputNodes = this._fragmentOutputNodes; if (needToPurgeList) { // Get all but the final output nodes fragmentOutputNodes = this._fragmentOutputNodes.filter((n) => !n._isFinalOutputAndActive); // Get the first with precedence on fragmentOutputNodes.push(this._fragmentOutputNodes.filter((n) => n._isFinalOutputAndActive && n._hasPrecedence)[0]); } this._sharedData = new NodeMaterialBuildStateSharedData(); this._sharedData.nodeMaterial = this; this._sharedData.fragmentOutputNodes = fragmentOutputNodes; this._vertexCompilationState.sharedData = this._sharedData; this._fragmentCompilationState.sharedData = this._sharedData; this._sharedData.buildId = this._buildId; this._sharedData.emitComments = this._options.emitComments; this._sharedData.verbose = verbose; this._sharedData.scene = this.getScene(); this._sharedData.allowEmptyVertexProgram = allowEmptyVertexProgram; // Initialize blocks const vertexNodes = []; const fragmentNodes = []; for (const vertexOutputNode of this._vertexOutputNodes) { vertexNodes.push(vertexOutputNode); this._initializeBlock(vertexOutputNode, this._vertexCompilationState, fragmentNodes, autoConfigure); } for (const fragmentOutputNode of fragmentOutputNodes) { fragmentNodes.push(fragmentOutputNode); this._initializeBlock(fragmentOutputNode, this._fragmentCompilationState, vertexNodes, autoConfigure); } // Are blocks code ready? let waitingNodeCount = 0; for (const node of this.attachedBlocks) { if (!node.codeIsReady) { waitingNodeCount++; node.onCodeIsReadyObservable.addOnce(() => { waitingNodeCount--; if (waitingNodeCount === 0) { this._finishBuildProcess(verbose, updateBuildId, vertexNodes, fragmentNodes); } }); } } if (waitingNodeCount !== 0) { return; } this._finishBuildProcess(verbose, updateBuildId, vertexNodes, fragmentNodes); } _finishBuildProcess(verbose = false, updateBuildId = true, vertexNodes, fragmentNodes) { // Optimize this.optimize(); // Vertex for (const vertexOutputNode of vertexNodes) { vertexOutputNode.build(this._vertexCompilationState, vertexNodes); } // Fragment this._fragmentCompilationState.uniforms = this._vertexCompilationState.uniforms.slice(0); this._fragmentCompilationState._uniformDeclaration = this._vertexCompilationState._uniformDeclaration; this._fragmentCompilationState._constantDeclaration = this._vertexCompilationState._constantDeclaration; this._fragmentCompilationState._vertexState = this._vertexCompilationState; for (const fragmentOutputNode of fragmentNodes) { this._resetDualBlocks(fragmentOutputNode, this._buildId - 1); } for (const fragmentOutputNode of fragmentNodes) { fragmentOutputNode.build(this._fragmentCompilationState, fragmentNodes); } // Finalize this._vertexCompilationState.finalize(this._vertexCompilationState); this._fragmentCompilationState.finalize(this._fragmentCompilationState); if (updateBuildId) { this._buildId = NodeMaterial._BuildIdGenerator++; } if (verbose) { Logger.Log("Vertex shader:"); Logger.Log(this._vertexCompilationState.compilationString); Logger.Log("Fragment shader:"); Logger.Log(this._fragmentCompilationState.compilationString); } // Errors const noError = this._sharedData.emitErrors(); this._buildIsInProgress = false; if (noError) { this._buildWasSuccessful = true; this.onBuildObservable.notifyObservers(this); } // Wipe defines 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; } if (!subMesh.materialDefines) { continue; } const defines = subMesh.materialDefines; defines.markAllAsDirty(); defines.reset(); } } if (this.prePassTextureInputs.length) { this.getScene().enablePrePassRenderer(); } const prePassRenderer = this.getScene().prePassRenderer; if (prePassRenderer) { prePassRenderer.markAsDirty(); } } /** * Runs an optimization phase to try to improve the shader code */ optimize() { for (const optimizer of this._optimizers) { optimizer.optimize(this._vertexOutputNodes, this._fragmentOutputNodes); } } _prepareDefinesForAttributes(mesh, defines) { const oldNormal = defines["NORMAL"]; const oldTangent = defines["TANGENT"]; const oldColor = defines["VERTEXCOLOR_NME"]; defines["NORMAL"] = mesh.isVerticesDataPresent(VertexBuffer.NormalKind); defines["TANGENT"] = mesh.isVerticesDataPresent(VertexBuffer.TangentKind); const hasVertexColors = mesh.useVertexColors && mesh.isVerticesDataPresent(VertexBuffer.ColorKind); defines["VERTEXCOLOR_NME"] = hasVertexColors; let uvChanged = false; for (let i = 1; i <= 6; ++i) { const oldUV = defines["UV" + i]; defines["UV" + i] = mesh.isVerticesDataPresent(`uv${i === 1 ? "" : i}`); uvChanged = uvChanged || defines["UV" + i] !== oldUV; } // PrePass const oit = this.needAlphaBlendingForMesh(mesh) && this.getScene().useOrderIndependentTransparency; PrepareDefinesForPrePass(this.getScene(), defines, !oit); MaterialHelperGeometryRendering.PrepareDefines(this.getScene().getEngine().currentRenderPassId, mesh, defines); if (oldNormal !== defines["NORMAL"] || oldTangent !== defines["TANGENT"] || oldColor !== defines["VERTEXCOLOR_NME"] || uvChanged) { defines.markAsAttributesDirty(); } } /** * Can this material render to prepass */ get isPrePassCapable() { return true; } /** * Outputs written to the prepass */ get prePassTextureOutputs() { const prePassOutputBlock = this.getBlockByPredicate((block) => block.getClassName() === "PrePassOutputBlock"); const result = [4]; if (!prePassOutputBlock) { return result; } // Cannot write to prepass if we alread read from prepass if (this.prePassTextureInputs.length) { return result; } if (prePassOutputBlock.viewDepth.isConnected) { result.push(5); } if (prePassOutputBlock.screenDepth.isConnected) { result.push(10); } if (prePassOutputBlock.viewNormal.isConnected) { result.push(6); } if (prePassOutputBlock.worldNormal.isConnected) { result.push(8); } if (prePassOutputBlock.worldPosition.isConnected) { result.push(1); } if (prePassOutputBlock.localPosition.isConnected) { result.push(9); } if (prePassOutputBlock.reflectivity.isConnected) { result.push(3); } if (prePassOutputBlock.velocity.isConnected) { result.push(2); } if (prePassOutputBlock.velocityLinear.isConnected) { result.push(11); } return result; } /** * Gets the list of prepass texture required */ get prePassTextureInputs() { const prePassTextureBlocks = this.getAllTextureBlocks().filter((block) => block.getClassName() === "PrePassTextureBlock"); const result = []; for (const block of prePassTextureBlocks) { if (block.position.isConnected && !result.includes(1)) { result.push(1); } if (block.localPosition.isConnected && !result.includes(9)) { result.push(9); } if (block.depth.isConnected && !result.includes(5)) { result.push(5); } if (block.screenDepth.isConnected && !result.includes(10)) { result.push(10); } if (block.normal.isConnected && !result.includes(6)) { result.push(6); } if (block.worldNormal.isConnected && !result.includes(8)) { result.push(8); } } return result; } /** * Sets the required values to the prepass renderer. * @param prePassRenderer defines the prepass renderer to set * @returns true if the pre pass is needed */ setPrePassRenderer(prePassRenderer) { const prePassTexturesRequired = this.prePassTextureInputs.concat(this.prePassTextureOutputs); if (prePassRenderer && prePassTexturesRequired.length > 1) { let cfg = prePassRenderer.getEffectConfiguration("nodeMaterial"); if (!cfg) { cfg = prePassRenderer.addEffectConfiguration({ enabled: true, needsImageProcessing: false, name: "nodeMaterial", texturesRequired: [], }); } for (const prePassTexture of prePassTexturesRequired) { if (!cfg.texturesRequired.includes(prePassTexture)) { cfg.texturesRequired.push(prePassTexture); } } cfg.enabled = true; } // COLOR_TEXTURE is always required for prepass, length > 1 means // we actually need to write to special prepass textures return prePassTexturesRequired.length > 1; } /** * Create a post process from the material * @param camera The camera to apply the render pass to. * @param options The required width/height ratio to downsize to before computing the render pass. (Use 1.0 for full size) * @param samplingMode The sampling mode to be used when computing the pass. (default: 0) * @param engine The engine which the post process will be applied. (default: current engine) * @param reusable If the post process can be reused on the same frame. (default: false) * @param textureType Type of textures used when performing the post process. (default: 0) * @param textureFormat Format of textures used when performing the post process. (default: TEXTUREFORMAT_RGBA) * @returns the post process created */ createPostProcess(camera, options = 1, samplingMode = 1, engine, reusable, textureType = 0, textureFormat = 5) { if (this.mode !== NodeMaterialModes.PostProcess && this.mode !== NodeMaterialModes.SFE) { Logger.Log("Incompatible material mode"); return null; } return this._createEffectForPostProcess(null, camera, options, samplingMode, engine, reusable, textureType, textureFormat); } /** * Create the post process effect from the material * @param postProcess The post process to create the effect for */ createEffectForPostProcess(postProcess) { this._createEffectForPostProcess(postProcess); } _createEffectForPostProcess(postProcess, camera, options = 1, samplingMode = 1, engine, reusable, textureType = 0, textureFormat = 5) { let tempName = this.name + this._buildId; const defines = new NodeMaterialDefines(); let buildId = this._buildId; this._processDefines(defines); // If no vertex shader emitted, fallback to default postprocess vertex shader const vertexCode = this._sharedData.checks.emitVertex ? this._vertexCompilationState._builtCompilationString : undefined; Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, vertexCode, this.shaderLanguage); if (!postProcess) { postProcess = new PostProcess(this.name + "PostProcess", tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, options, camera, samplingMode, engine, reusable, defines.toString(), textureType, vertexCode ? tempName : "postprocess", { maxSimultaneousLights: this.maxSimultaneousLights }, false, textureFormat, this.shaderLanguage); } else { postProcess.updateEffect(defines.toString(), this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, { maxSimultaneousLights: this.maxSimultaneousLights }, undefined, undefined, tempName, tempName); } postProcess.nodeMaterialSource = this; postProcess.onApplyObservable.add((effect) => { if (buildId !== this._buildId) { delete Effect.ShadersStore[tempName + "VertexShader"]; delete Effect.ShadersStore[tempName + "PixelShader"]; tempName = this.name + this._buildId; defines.markAllAsDirty(); buildId = this._buildId; } const result = this._processDefines(defines); if (result) { Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString); TimingTools.SetImmediate(() => postProcess.updateEffect(defines.toString(), this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, { maxSimultaneousLights: this.maxSimultaneousLights }, undefined, undefined, tempName, tempName)); } this._checkInternals(effect); }); return postProcess; } /** * Create a new procedural texture based on this node material * @param size defines the size of the texture * @param scene defines the hosting scene * @returns the new procedural texture attached to this node material */ createProceduralTexture(size, scene) { if (this.mode !== NodeMaterialModes.ProceduralTexture) { Logger.Log("Incompatible material mode"); return null; } let tempName = this.name + this._buildId; const proceduralTexture = new ProceduralTexture(tempName, size, null, scene); const defines = new NodeMaterialDefines(); const result = this._processDefines(defines); Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString, this.shaderLanguage); let effect = this.getScene().getEngine().createEffect({ vertexElement: tempName, fragmentElement: tempName, }, [VertexBuffer.PositionKind], this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString(), result?.fallbacks, undefined, undefined, undefined, this.shaderLanguage); proceduralTexture.nodeMaterialSource = this; proceduralTexture._setEffect(effect); let buildId = this._buildId; const refreshEffect = () => { if (buildId !== this._buildId) { delete Effect.ShadersStore[tempName + "VertexShader"]; delete Effect.ShadersStore[tempName + "PixelShader"]; tempName = this.name + this._buildId; defines.markAllAsDirty(); buildId = this._buildId; } const result = this._processDefines(defines); if (result) { Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString, this.shaderLanguage); TimingTools.SetImmediate(() => { effect = this.getScene().getEngine().createEffect({ vertexElement: tempName, fragmentElement: tempName, }, [VertexBuffer.PositionKind], this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString(), result?.fallbacks, undefined); proceduralTexture._setEffect(effect); }); } this._checkInternals(effect); }; proceduralTexture.onBeforeGenerationObservable.add(() => { refreshEffect(); }); // This is needed if the procedural texture is not set to refresh automatically this.onBuildObservable.add(() => { refreshEffect(); }); return proceduralTexture; } _createEffectForParticles(particleSystem, blendMode, onCompiled, onError, effect, defines, particleSystemDefinesJoined = "") { let tempName = this.name + this._buildId + "_" + blendMode; if (!defines) { defines = new NodeMaterialDefines(); } let buildId = this._buildId; const particleSystemDefines = []; let join = particleSystemDefinesJoined; if (!effect) { const result = this._processDefines(defines); Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, undefined, this.shaderLanguage); particleSystem.fillDefines(particleSystemDefines, blendMode, false); join = particleSystemDefines.join("\n"); effect = this.getScene() .getEngine() .createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString() + "\n" + join, result?.fallbacks, onCompiled, onError, particleSystem, this.shaderLanguage); particleSystem.setCustomEffect(effect, blendMode); } effect.onBindObservable.add((effect) => { if (buildId !== this._buildId) { delete Effect.ShadersStore[tempName + "PixelShader"]; tempName = this.name + this._buildId + "_" + blendMode; defines.markAllAsDirty(); buildId = this._buildId; } particleSystemDefines.length = 0; particleSystem.fillDefines(particleSystemDefines, blendMode, false); const particleSystemDefinesJoinedCurrent = particleSystemDefines.join("\n"); if (particleSystemDefinesJoinedCurrent !== join) { defines.markAllAsDirty(); join = particleSystemDefinesJoinedCurrent; } const result = this._processDefines(defines); if (result) { Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, undefined, this.shaderLanguage); effect = this.getScene() .getEngine() .createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString() + "\n" + join, result?.fallbacks, onCompiled, onError, particleSystem); particleSystem.setCustomEffect(effect, blendMode); this._createEffectForParticles(particleSystem, blendMode, onCompiled, onError, effect, defines, particleSystemDefinesJoined); // add the effect.onBindObservable observer return; } this._checkInternals(effect); }); } _checkInternals(effect) { // Animated blocks if (this._sharedData.animatedInputs) { const scene = this.getScene(); const frameId = scene.getFrameId(); if (this._animationFrame !== frameId) { for (const input of this._sharedData.animatedInputs) { input.animate(scene); } this._animationFrame = frameId; } } // Bindable blocks for (const block of this._sharedData.bindableBlocks) { block.bind(effect, this); } // Connection points for (const inputBlock of this._sharedData.inputBlocks) { inputBlock._transmit(effect, this.getScene(), this); } } /** * Create the effect to be used as the custom effect for a particle system * @param particleSystem Particle system to create the effect for * @param onCompiled defines a function to call when the effect creation is successful * @param onError defines a function to call when the effect creation has failed */ createEffectForParticles(particleSystem, onCompiled, onError) { if (this.mode !== NodeMaterialModes.Particle) { Logger.Log("Incompatible material mode"); return; } this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_ONEONE, onCompiled, onError); this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_MULTIPLY, onCompiled, onError); } /** * Use this material as the shadow depth wrapper of a target material * @param targetMaterial defines the target material */ createAsShadowDepthWrapper(targetMaterial) { if (this.mode !== NodeMaterialModes.Material) { Logger.Log("Incompatible material mode"); return; } targetMaterial.shadowDepthWrapper = new BABYLON.ShadowDepthWrapper(this, this.getScene()); } _processDefines(defines, mesh, useInstances = false, subMesh) { let result = null; // Global defines const scene = this.getScene(); if (PrepareDefinesForCamera(scene, defines)) { defines.markAsMiscDirty()