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.

944 lines (940 loc) 73.2 kB
import { __decorate } from "../../../../tslib.es6.js"; import { NodeMaterialBlock } from "../../nodeMaterialBlock.js"; import { NodeMaterialBlockConnectionPointTypes } from "../../Enums/nodeMaterialBlockConnectionPointTypes.js"; import { NodeMaterialBlockTargets } from "../../Enums/nodeMaterialBlockTargets.js"; import { NodeMaterialSystemValues } from "../../Enums/nodeMaterialSystemValues.js"; import { InputBlock } from "../Input/inputBlock.js"; import { RegisterClass } from "../../../../Misc/typeStore.js"; import { PBRBaseMaterial } from "../../../PBR/pbrBaseMaterial.js"; import { editableInPropertyPage } from "../../../../Decorators/nodeDecorator.js"; import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject.js"; import { SheenBlock } from "./sheenBlock.js"; import { GetEnvironmentBRDFTexture } from "../../../../Misc/brdfTextureTools.js"; import { MaterialFlags } from "../../../materialFlags.js"; import { AnisotropyBlock } from "./anisotropyBlock.js"; import { ReflectionBlock } from "./reflectionBlock.js"; import { ClearCoatBlock } from "./clearCoatBlock.js"; import { IridescenceBlock } from "./iridescenceBlock.js"; import { SubSurfaceBlock } from "./subSurfaceBlock.js"; import { Color3 } from "../../../../Maths/math.color.js"; import { Logger } from "../../../../Misc/logger.js"; import { BindLight, BindLights, PrepareDefinesForLight, PrepareDefinesForLights, PrepareDefinesForMultiview, PrepareUniformsAndSamplersForLight, } from "../../../materialHelper.functions.js"; const MapOutputToVariable = { ambientClr: ["finalAmbient", ""], diffuseDir: ["finalDiffuse", ""], specularDir: ["finalSpecularScaled", "!defined(UNLIT) && defined(SPECULARTERM)"], clearcoatDir: ["finalClearCoatScaled", "!defined(UNLIT) && defined(CLEARCOAT)"], sheenDir: ["finalSheenScaled", "!defined(UNLIT) && defined(SHEEN)"], diffuseInd: ["finalIrradiance", "!defined(UNLIT) && defined(REFLECTION)"], specularInd: ["finalRadianceScaled", "!defined(UNLIT) && defined(REFLECTION)"], clearcoatInd: ["clearcoatOut.finalClearCoatRadianceScaled", "!defined(UNLIT) && defined(REFLECTION) && defined(CLEARCOAT)"], sheenInd: ["sheenOut.finalSheenRadianceScaled", "!defined(UNLIT) && defined(REFLECTION) && defined(SHEEN) && defined(ENVIRONMENTBRDF)"], refraction: ["subSurfaceOut.finalRefraction", "!defined(UNLIT) && defined(SS_REFRACTION)"], lighting: ["finalColor.rgb", ""], shadow: ["aggShadow", ""], alpha: ["alpha", ""], }; /** * Block used to implement the PBR metallic/roughness model * @see https://playground.babylonjs.com/#D8AK3Z#80 */ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock { static _OnGenerateOnlyFragmentCodeChanged(block, _propertyName) { const that = block; if (that.worldPosition.isConnected || that.worldNormal.isConnected) { that.generateOnlyFragmentCode = !that.generateOnlyFragmentCode; Logger.Error("The worldPosition and worldNormal inputs must not be connected to be able to switch!"); return false; } that._setTarget(); return true; } _setTarget() { this._setInitialTarget(this.generateOnlyFragmentCode ? NodeMaterialBlockTargets.Fragment : NodeMaterialBlockTargets.VertexAndFragment); this.getInputByName("worldPosition").target = this.generateOnlyFragmentCode ? NodeMaterialBlockTargets.Fragment : NodeMaterialBlockTargets.Vertex; this.getInputByName("worldNormal").target = this.generateOnlyFragmentCode ? NodeMaterialBlockTargets.Fragment : NodeMaterialBlockTargets.Vertex; } /** * Create a new ReflectionBlock * @param name defines the block name */ constructor(name) { super(name, NodeMaterialBlockTargets.VertexAndFragment); this._environmentBRDFTexture = null; this._metallicReflectanceColor = Color3.White(); this._metallicF0Factor = 1; /** * Intensity of the direct lights e.g. the four lights available in your scene. * This impacts both the direct diffuse and specular highlights. */ this.directIntensity = 1.0; /** * Intensity of the environment e.g. how much the environment will light the object * either through harmonics for rough material or through the reflection for shiny ones. */ this.environmentIntensity = 1.0; /** * This is a special control allowing the reduction of the specular highlights coming from the * four lights of the scene. Those highlights may not be needed in full environment lighting. */ this.specularIntensity = 1.0; /** * Defines the falloff type used in this material. * It by default is Physical. */ this.lightFalloff = 0; /** * Specifies that alpha test should be used */ this.useAlphaTest = false; /** * Defines the alpha limits in alpha test mode. */ this.alphaTestCutoff = 0.5; /** * Specifies that alpha blending should be used */ this.useAlphaBlending = false; /** * Specifies that the material will keeps the reflection highlights over a transparent surface (only the most luminous ones). * A car glass is a good example of that. When the street lights reflects on it you can not see what is behind. */ this.useRadianceOverAlpha = true; /** * Specifies that the material will keeps the specular highlights over a transparent surface (only the most luminous ones). * A car glass is a good example of that. When sun reflects on it you can not see what is behind. */ this.useSpecularOverAlpha = true; /** * Enables specular anti aliasing in the PBR shader. * It will both interacts on the Geometry for analytical and IBL lighting. * It also prefilter the roughness map based on the bump values. */ this.enableSpecularAntiAliasing = false; /** * Enables realtime filtering on the texture. */ this.realTimeFiltering = false; /** * Quality switch for realtime filtering */ this.realTimeFilteringQuality = 8; /** * Base Diffuse Model */ this.baseDiffuseModel = 0; /** * Defines if the material uses energy conservation. */ this.useEnergyConservation = true; /** * This parameters will enable/disable radiance occlusion by preventing the radiance to lit * too much the area relying on ambient texture to define their ambient occlusion. */ this.useRadianceOcclusion = true; /** * This parameters will enable/disable Horizon occlusion to prevent normal maps to look shiny when the normal * makes the reflect vector face the model (under horizon). */ this.useHorizonOcclusion = true; /** * If set to true, no lighting calculations will be applied. */ this.unlit = false; /** * Force normal to face away from face. */ this.forceNormalForward = false; /** Indicates that no code should be generated in the vertex shader. Can be useful in some specific circumstances (like when doing ray marching for eg) */ this.generateOnlyFragmentCode = false; /** * Defines the material debug mode. * It helps seeing only some components of the material while troubleshooting. */ this.debugMode = 0; /** * Specify from where on screen the debug mode should start. * The value goes from -1 (full screen) to 1 (not visible) * It helps with side by side comparison against the final render * This defaults to 0 */ this.debugLimit = 0; /** * As the default viewing range might not be enough (if the ambient is really small for instance) * You can use the factor to better multiply the final value. */ this.debugFactor = 1; this._isUnique = true; this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex); this.registerInput("worldNormal", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex); this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false); this.registerInput("cameraPosition", NodeMaterialBlockConnectionPointTypes.Vector3, false, NodeMaterialBlockTargets.Fragment); this.registerInput("perturbedNormal", NodeMaterialBlockConnectionPointTypes.Vector4, true, NodeMaterialBlockTargets.Fragment); this.registerInput("baseColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment); this.registerInput("metallic", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment); this.registerInput("roughness", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment); this.registerInput("ambientOcc", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment); this.registerInput("opacity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment); this.registerInput("indexOfRefraction", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment); this.registerInput("ambientColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment); this.registerInput("reflection", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment, new NodeMaterialConnectionPointCustomObject("reflection", this, 0 /* NodeMaterialConnectionPointDirection.Input */, ReflectionBlock, "ReflectionBlock")); this.registerInput("clearcoat", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment, new NodeMaterialConnectionPointCustomObject("clearcoat", this, 0 /* NodeMaterialConnectionPointDirection.Input */, ClearCoatBlock, "ClearCoatBlock")); this.registerInput("sheen", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment, new NodeMaterialConnectionPointCustomObject("sheen", this, 0 /* NodeMaterialConnectionPointDirection.Input */, SheenBlock, "SheenBlock")); this.registerInput("subsurface", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment, new NodeMaterialConnectionPointCustomObject("subsurface", this, 0 /* NodeMaterialConnectionPointDirection.Input */, SubSurfaceBlock, "SubSurfaceBlock")); this.registerInput("anisotropy", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment, new NodeMaterialConnectionPointCustomObject("anisotropy", this, 0 /* NodeMaterialConnectionPointDirection.Input */, AnisotropyBlock, "AnisotropyBlock")); this.registerInput("iridescence", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment, new NodeMaterialConnectionPointCustomObject("iridescence", this, 0 /* NodeMaterialConnectionPointDirection.Input */, IridescenceBlock, "IridescenceBlock")); this.registerOutput("ambientClr", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("diffuseDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("specularDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("clearcoatDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("sheenDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("diffuseInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("specularInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("clearcoatInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("sheenInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("refraction", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("lighting", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment); this.registerOutput("shadow", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment); this.registerOutput("alpha", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment); } /** * Initialize the block and prepare the context for build * @param state defines the state that will be used for the build */ initialize(state) { state._excludeVariableName("vLightingIntensity"); state._excludeVariableName("geometricNormalW"); state._excludeVariableName("normalW"); state._excludeVariableName("faceNormal"); state._excludeVariableName("albedoOpacityOut"); state._excludeVariableName("surfaceAlbedo"); state._excludeVariableName("alpha"); state._excludeVariableName("aoOut"); state._excludeVariableName("baseColor"); state._excludeVariableName("reflectivityOut"); state._excludeVariableName("microSurface"); state._excludeVariableName("roughness"); state._excludeVariableName("vReflectivityColor"); state._excludeVariableName("NdotVUnclamped"); state._excludeVariableName("NdotV"); state._excludeVariableName("alphaG"); state._excludeVariableName("AARoughnessFactors"); state._excludeVariableName("environmentBrdf"); state._excludeVariableName("ambientMonochrome"); state._excludeVariableName("seo"); state._excludeVariableName("eho"); state._excludeVariableName("environmentRadiance"); state._excludeVariableName("irradianceVector"); state._excludeVariableName("environmentIrradiance"); state._excludeVariableName("diffuseBase"); state._excludeVariableName("specularBase"); state._excludeVariableName("preInfo"); state._excludeVariableName("info"); state._excludeVariableName("shadow"); state._excludeVariableName("finalDiffuse"); state._excludeVariableName("finalAmbient"); state._excludeVariableName("ambientOcclusionForDirectDiffuse"); state._excludeVariableName("finalColor"); state._excludeVariableName("vClipSpacePosition"); state._excludeVariableName("vDebugMode"); // eslint-disable-next-line @typescript-eslint/no-floating-promises this._initShaderSourceAsync(state.shaderLanguage); } async _initShaderSourceAsync(shaderLanguage) { this._codeIsReady = false; if (shaderLanguage === 1 /* ShaderLanguage.WGSL */) { await Promise.all([import("../../../../ShadersWGSL/pbr.vertex.js"), import("../../../../ShadersWGSL/pbr.fragment.js")]); } else { await Promise.all([import("../../../../Shaders/pbr.vertex.js"), import("../../../../Shaders/pbr.fragment.js")]); } this._codeIsReady = true; this.onCodeIsReadyObservable.notifyObservers(this); } /** * Gets the current class name * @returns the class name */ getClassName() { return "PBRMetallicRoughnessBlock"; } /** * Gets the world position input component */ get worldPosition() { return this._inputs[0]; } /** * Gets the world normal input component */ get worldNormal() { return this._inputs[1]; } /** * Gets the view matrix parameter */ get view() { return this._inputs[2]; } /** * Gets the camera position input component */ get cameraPosition() { return this._inputs[3]; } /** * Gets the perturbed normal input component */ get perturbedNormal() { return this._inputs[4]; } /** * Gets the base color input component */ get baseColor() { return this._inputs[5]; } /** * Gets the metallic input component */ get metallic() { return this._inputs[6]; } /** * Gets the roughness input component */ get roughness() { return this._inputs[7]; } /** * Gets the ambient occlusion input component */ get ambientOcc() { return this._inputs[8]; } /** * Gets the opacity input component */ get opacity() { return this._inputs[9]; } /** * Gets the index of refraction input component */ get indexOfRefraction() { return this._inputs[10]; } /** * Gets the ambient color input component */ get ambientColor() { return this._inputs[11]; } /** * Gets the reflection object parameters */ get reflection() { return this._inputs[12]; } /** * Gets the clear coat object parameters */ get clearcoat() { return this._inputs[13]; } /** * Gets the sheen object parameters */ get sheen() { return this._inputs[14]; } /** * Gets the sub surface object parameters */ get subsurface() { return this._inputs[15]; } /** * Gets the anisotropy object parameters */ get anisotropy() { return this._inputs[16]; } /** * Gets the iridescence object parameters */ get iridescence() { return this._inputs[17]; } /** * Gets the ambient output component */ get ambientClr() { return this._outputs[0]; } /** * Gets the diffuse output component */ get diffuseDir() { return this._outputs[1]; } /** * Gets the specular output component */ get specularDir() { return this._outputs[2]; } /** * Gets the clear coat output component */ get clearcoatDir() { return this._outputs[3]; } /** * Gets the sheen output component */ get sheenDir() { return this._outputs[4]; } /** * Gets the indirect diffuse output component */ get diffuseInd() { return this._outputs[5]; } /** * Gets the indirect specular output component */ get specularInd() { return this._outputs[6]; } /** * Gets the indirect clear coat output component */ get clearcoatInd() { return this._outputs[7]; } /** * Gets the indirect sheen output component */ get sheenInd() { return this._outputs[8]; } /** * Gets the refraction output component */ get refraction() { return this._outputs[9]; } /** * Gets the global lighting output component */ get lighting() { return this._outputs[10]; } /** * Gets the shadow output component */ get shadow() { return this._outputs[11]; } /** * Gets the alpha output component */ get alpha() { return this._outputs[12]; } autoConfigure(material, additionalFilteringInfo = () => true) { if (!this.cameraPosition.isConnected) { let cameraPositionInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.CameraPosition && additionalFilteringInfo(b)); if (!cameraPositionInput) { cameraPositionInput = new InputBlock("cameraPosition"); cameraPositionInput.setAsSystemValue(NodeMaterialSystemValues.CameraPosition); } cameraPositionInput.output.connectTo(this.cameraPosition); } if (!this.view.isConnected) { let viewInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.View && additionalFilteringInfo(b)); if (!viewInput) { viewInput = new InputBlock("view"); viewInput.setAsSystemValue(NodeMaterialSystemValues.View); } viewInput.output.connectTo(this.view); } } prepareDefines(defines, nodeMaterial, mesh) { if (!mesh) { return; } // General defines.setValue("PBR", true); defines.setValue("METALLICWORKFLOW", true); defines.setValue("DEBUGMODE", this.debugMode, true); defines.setValue("DEBUGMODE_FORCERETURN", true); defines.setValue("NORMALXYSCALE", true); defines.setValue("BUMP", this.perturbedNormal.isConnected, true); defines.setValue("LODBASEDMICROSFURACE", this._scene.getEngine().getCaps().textureLOD); // Albedo & Opacity defines.setValue("ALBEDO", false, true); defines.setValue("OPACITY", this.opacity.isConnected, true); // Ambient occlusion defines.setValue("AMBIENT", true, true); defines.setValue("AMBIENTINGRAYSCALE", false, true); // Reflectivity defines.setValue("REFLECTIVITY", false, true); defines.setValue("AOSTOREINMETALMAPRED", false, true); defines.setValue("METALLNESSSTOREINMETALMAPBLUE", false, true); defines.setValue("ROUGHNESSSTOREINMETALMAPALPHA", false, true); defines.setValue("ROUGHNESSSTOREINMETALMAPGREEN", false, true); // Lighting & colors if (this.lightFalloff === PBRBaseMaterial.LIGHTFALLOFF_STANDARD) { defines.setValue("USEPHYSICALLIGHTFALLOFF", false); defines.setValue("USEGLTFLIGHTFALLOFF", false); } else if (this.lightFalloff === PBRBaseMaterial.LIGHTFALLOFF_GLTF) { defines.setValue("USEPHYSICALLIGHTFALLOFF", false); defines.setValue("USEGLTFLIGHTFALLOFF", true); } else { defines.setValue("USEPHYSICALLIGHTFALLOFF", true); defines.setValue("USEGLTFLIGHTFALLOFF", false); } // Transparency const alphaTestCutOffString = this.alphaTestCutoff.toString(); defines.setValue("ALPHABLEND", this.useAlphaBlending, true); defines.setValue("ALPHAFROMALBEDO", false, true); defines.setValue("ALPHATEST", this.useAlphaTest, true); defines.setValue("ALPHATESTVALUE", alphaTestCutOffString.indexOf(".") < 0 ? alphaTestCutOffString + "." : alphaTestCutOffString, true); defines.setValue("OPACITYRGB", false, true); // Rendering defines.setValue("RADIANCEOVERALPHA", this.useRadianceOverAlpha, true); defines.setValue("SPECULAROVERALPHA", this.useSpecularOverAlpha, true); defines.setValue("SPECULARAA", this._scene.getEngine().getCaps().standardDerivatives && this.enableSpecularAntiAliasing, true); defines.setValue("REALTIME_FILTERING", this.realTimeFiltering, true); const scene = mesh.getScene(); const engine = scene.getEngine(); if (engine._features.needTypeSuffixInShaderConstants) { defines.setValue("NUM_SAMPLES", this.realTimeFilteringQuality + "u", true); } else { defines.setValue("NUM_SAMPLES", "" + this.realTimeFilteringQuality, true); } defines.setValue("BASE_DIFFUSE_MODEL", this.baseDiffuseModel, true); // Advanced defines.setValue("BRDF_V_HEIGHT_CORRELATED", true); defines.setValue("LEGACY_SPECULAR_ENERGY_CONSERVATION", true); defines.setValue("MS_BRDF_ENERGY_CONSERVATION", this.useEnergyConservation, true); defines.setValue("RADIANCEOCCLUSION", this.useRadianceOcclusion, true); defines.setValue("HORIZONOCCLUSION", this.useHorizonOcclusion, true); defines.setValue("UNLIT", this.unlit, true); defines.setValue("FORCENORMALFORWARD", this.forceNormalForward, true); if (this._environmentBRDFTexture && MaterialFlags.ReflectionTextureEnabled) { defines.setValue("ENVIRONMENTBRDF", true); defines.setValue("ENVIRONMENTBRDF_RGBD", this._environmentBRDFTexture.isRGBD, true); } else { defines.setValue("ENVIRONMENTBRDF", false); defines.setValue("ENVIRONMENTBRDF_RGBD", false); } if (defines._areImageProcessingDirty && nodeMaterial.imageProcessingConfiguration) { nodeMaterial.imageProcessingConfiguration.prepareDefines(defines); } if (!defines._areLightsDirty) { return; } if (!this.light) { // Lights PrepareDefinesForLights(scene, mesh, defines, true, nodeMaterial.maxSimultaneousLights); defines._needNormals = true; // Multiview PrepareDefinesForMultiview(scene, defines); } else { const state = { needNormals: false, needRebuild: false, lightmapMode: false, shadowEnabled: false, specularEnabled: false, }; PrepareDefinesForLight(scene, mesh, this.light, this._lightId, defines, true, state); if (state.needRebuild) { defines.rebuild(); } } } updateUniformsAndSamples(state, nodeMaterial, defines, uniformBuffers) { for (let lightIndex = 0; lightIndex < nodeMaterial.maxSimultaneousLights; lightIndex++) { if (!defines["LIGHT" + lightIndex]) { break; } const onlyUpdateBuffersList = state.uniforms.indexOf("vLightData" + lightIndex) >= 0; PrepareUniformsAndSamplersForLight(lightIndex, state.uniforms, state.samplers, defines["PROJECTEDLIGHTTEXTURE" + lightIndex], uniformBuffers, onlyUpdateBuffersList, defines["IESLIGHTTEXTURE" + lightIndex]); } } isReady(mesh, nodeMaterial, defines) { if (this._environmentBRDFTexture && !this._environmentBRDFTexture.isReady()) { return false; } if (defines._areImageProcessingDirty && nodeMaterial.imageProcessingConfiguration) { if (!nodeMaterial.imageProcessingConfiguration.isReady()) { return false; } } return true; } bind(effect, nodeMaterial, mesh) { if (!mesh) { return; } const scene = mesh.getScene(); if (!this.light) { BindLights(scene, mesh, effect, true, nodeMaterial.maxSimultaneousLights); } else { BindLight(this.light, this._lightId, scene, effect, true); } effect.setTexture(this._environmentBrdfSamplerName, this._environmentBRDFTexture); effect.setFloat2("vDebugMode", this.debugLimit, this.debugFactor); const ambientScene = this._scene.ambientColor; if (ambientScene) { effect.setColor3("ambientFromScene", ambientScene); } const invertNormal = scene.useRightHandedSystem === (scene._mirroredCameraPosition != null); effect.setFloat(this._invertNormalName, invertNormal ? -1 : 1); effect.setFloat4("vLightingIntensity", this.directIntensity, 1, this.environmentIntensity * this._scene.environmentIntensity, this.specularIntensity); // reflectivity bindings const metallicF90 = this._metallicF0Factor; effect.setColor4(this._vMetallicReflectanceFactorsName, this._metallicReflectanceColor, metallicF90); if (nodeMaterial.imageProcessingConfiguration) { nodeMaterial.imageProcessingConfiguration.bind(effect); } } _injectVertexCode(state) { const worldPos = this.worldPosition; const worldNormal = this.worldNormal; const comments = `//${this.name}`; const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; // Declaration if (!this.light) { // Emit for all lights state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightVxUboDeclaration" : "lightVxFragmentDeclaration", comments, { repeatKey: "maxSimultaneousLights", }); this._lightId = 0; state.sharedData.dynamicUniformBlocks.push(this); } else { this._lightId = (state.counters["lightCounter"] !== undefined ? state.counters["lightCounter"] : -1) + 1; state.counters["lightCounter"] = this._lightId; state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightVxUboDeclaration" : "lightVxFragmentDeclaration", comments, { replaceStrings: [{ search: /{X}/g, replace: this._lightId.toString() }], }, this._lightId.toString()); } // Inject code in vertex const worldPosVaryingName = "v_" + worldPos.associatedVariableName; if (state._emitVaryingFromString(worldPosVaryingName, NodeMaterialBlockConnectionPointTypes.Vector4)) { state.compilationString += (isWebGPU ? "vertexOutputs." : "") + `${worldPosVaryingName} = ${worldPos.associatedVariableName};\n`; } const worldNormalVaryingName = "v_" + worldNormal.associatedVariableName; if (state._emitVaryingFromString(worldNormalVaryingName, NodeMaterialBlockConnectionPointTypes.Vector4)) { state.compilationString += (isWebGPU ? "vertexOutputs." : "") + `${worldNormalVaryingName} = ${worldNormal.associatedVariableName};\n`; } const reflectionBlock = this.reflection.isConnected ? this.reflection.connectedPoint?.ownerBlock : null; if (reflectionBlock) { reflectionBlock.viewConnectionPoint = this.view; } state.compilationString += reflectionBlock?.handleVertexSide(state) ?? ""; if (state._emitVaryingFromString("vClipSpacePosition", NodeMaterialBlockConnectionPointTypes.Vector4, "defined(IGNORE) || DEBUGMODE > 0")) { state._injectAtEnd += `#if DEBUGMODE > 0\n`; state._injectAtEnd += (isWebGPU ? "vertexOutputs." : "") + `vClipSpacePosition = ${isWebGPU ? "vertexOutputs.position" : "gl_Position"};\n`; state._injectAtEnd += `#endif\n`; } if (this.light) { state.compilationString += state._emitCodeFromInclude("shadowsVertex", comments, { replaceStrings: [ { search: /{X}/g, replace: this._lightId.toString() }, { search: /worldPos/g, replace: worldPos.associatedVariableName }, ], }); } else { state.compilationString += `${state._declareLocalVar("worldPos", NodeMaterialBlockConnectionPointTypes.Vector4)} = ${worldPos.associatedVariableName};\n`; if (this.view.isConnected) { state.compilationString += `${state._declareLocalVar("view", NodeMaterialBlockConnectionPointTypes.Matrix)} = ${this.view.associatedVariableName};\n`; } state.compilationString += state._emitCodeFromInclude("shadowsVertex", comments, { repeatKey: "maxSimultaneousLights", }); } } _getAlbedoOpacityCode(state) { const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; let code = isWebGPU ? "var albedoOpacityOut: albedoOpacityOutParams;\n" : `albedoOpacityOutParams albedoOpacityOut;\n`; const albedoColor = this.baseColor.isConnected ? this.baseColor.associatedVariableName : "vec3(1.)"; const opacity = this.opacity.isConnected ? this.opacity.associatedVariableName : "1."; code += `albedoOpacityOut = albedoOpacityBlock( vec4${state.fSuffix}(${albedoColor}, 1.) #ifdef ALBEDO ,vec4${state.fSuffix}(1.) ,vec2${state.fSuffix}(1., 1.) #endif ,1. /* Base Weight */ #ifdef OPACITY ,vec4${state.fSuffix}(${opacity}) ,vec2${state.fSuffix}(1., 1.) #endif ); ${state._declareLocalVar("surfaceAlbedo", NodeMaterialBlockConnectionPointTypes.Vector3)} = albedoOpacityOut.surfaceAlbedo; ${state._declareLocalVar("alpha", NodeMaterialBlockConnectionPointTypes.Float)} = albedoOpacityOut.alpha;\n`; return code; } _getAmbientOcclusionCode(state) { const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; let code = isWebGPU ? "var aoOut: ambientOcclusionOutParams;\n" : `ambientOcclusionOutParams aoOut;\n`; const ao = this.ambientOcc.isConnected ? this.ambientOcc.associatedVariableName : "1."; code += `aoOut = ambientOcclusionBlock( #ifdef AMBIENT vec3${state.fSuffix}(${ao}), vec4${state.fSuffix}(0., 1.0, 1.0, 0.) #endif );\n`; return code; } _getReflectivityCode(state) { const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; let code = isWebGPU ? "var reflectivityOut: reflectivityOutParams;\n" : `reflectivityOutParams reflectivityOut;\n`; const aoIntensity = "1."; this._vMetallicReflectanceFactorsName = state._getFreeVariableName("vMetallicReflectanceFactors"); state._emitUniformFromString(this._vMetallicReflectanceFactorsName, NodeMaterialBlockConnectionPointTypes.Vector4); this._baseDiffuseRoughnessName = state._getFreeVariableName("baseDiffuseRoughness"); state._emitUniformFromString(this._baseDiffuseRoughnessName, NodeMaterialBlockConnectionPointTypes.Float); const outsideIOR = 1; // consider air as clear coat and other layers would remap in the shader. const ior = this.indexOfRefraction.connectInputBlock?.value ?? 1.5; // Based of the schlick fresnel approximation model // for dielectrics. const f0 = Math.pow((ior - outsideIOR) / (ior + outsideIOR), 2); code += `${state._declareLocalVar("baseColor", NodeMaterialBlockConnectionPointTypes.Vector3)} = surfaceAlbedo; ${isWebGPU ? "let" : `vec4${state.fSuffix}`} vReflectivityColor = vec4${state.fSuffix}(${this.metallic.associatedVariableName}, ${this.roughness.associatedVariableName}, ${this.indexOfRefraction.associatedVariableName || "1.5"}, ${f0}); reflectivityOut = reflectivityBlock( vReflectivityColor #ifdef METALLICWORKFLOW , surfaceAlbedo , ${(isWebGPU ? "uniforms." : "") + this._vMetallicReflectanceFactorsName} #endif , ${(isWebGPU ? "uniforms." : "") + this._baseDiffuseRoughnessName} #ifdef BASE_DIFFUSE_ROUGHNESS , 0. , vec2${state.fSuffix}(0., 0.) #endif #ifdef REFLECTIVITY , vec3${state.fSuffix}(0., 0., ${aoIntensity}) , vec4${state.fSuffix}(1.) #endif #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY) && defined(AOSTOREINMETALMAPRED) , aoOut.ambientOcclusionColor #endif #ifdef MICROSURFACEMAP , microSurfaceTexel <== not handled! #endif ); ${state._declareLocalVar("microSurface", NodeMaterialBlockConnectionPointTypes.Float)} = reflectivityOut.microSurface; ${state._declareLocalVar("roughness", NodeMaterialBlockConnectionPointTypes.Float)} = reflectivityOut.roughness; ${state._declareLocalVar("diffuseRoughness", NodeMaterialBlockConnectionPointTypes.Float)} = reflectivityOut.diffuseRoughness; #ifdef METALLICWORKFLOW surfaceAlbedo = reflectivityOut.surfaceAlbedo; #endif #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY) && defined(AOSTOREINMETALMAPRED) aoOut.ambientOcclusionColor = reflectivityOut.ambientOcclusionColor; #endif\n`; return code; } _buildBlock(state) { super._buildBlock(state); this._scene = state.sharedData.scene; const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; if (!this._environmentBRDFTexture) { this._environmentBRDFTexture = GetEnvironmentBRDFTexture(this._scene); } const reflectionBlock = this.reflection.isConnected ? this.reflection.connectedPoint?.ownerBlock : null; if (reflectionBlock) { // Need those variables to be setup when calling _injectVertexCode reflectionBlock.worldPositionConnectionPoint = this.worldPosition; reflectionBlock.cameraPositionConnectionPoint = this.cameraPosition; reflectionBlock.worldNormalConnectionPoint = this.worldNormal; reflectionBlock.viewConnectionPoint = this.view; } if (state.target !== NodeMaterialBlockTargets.Fragment) { // Vertex this._injectVertexCode(state); return this; } // Fragment state.sharedData.forcedBindableBlocks.push(this); state.sharedData.blocksWithDefines.push(this); state.sharedData.blockingBlocks.push(this); if (this.generateOnlyFragmentCode) { state.sharedData.dynamicUniformBlocks.push(this); } const comments = `//${this.name}`; const normalShading = this.perturbedNormal; let worldPosVarName = this.worldPosition.associatedVariableName; let worldNormalVarName = this.worldNormal.associatedVariableName; if (this.generateOnlyFragmentCode) { worldPosVarName = state._getFreeVariableName("globalWorldPos"); state._emitFunction("pbr_globalworldpos", isWebGPU ? `var<private> ${worldPosVarName}:vec3${state.fSuffix};\n` : `vec3${state.fSuffix} ${worldPosVarName};\n`, comments); state.compilationString += `${worldPosVarName} = ${this.worldPosition.associatedVariableName}.xyz;\n`; worldNormalVarName = state._getFreeVariableName("globalWorldNormal"); state._emitFunction("pbr_globalworldnorm", isWebGPU ? `var<private> ${worldNormalVarName}:vec4${state.fSuffix};\n` : `vec4${state.fSuffix} ${worldNormalVarName};\n`, comments); state.compilationString += `${worldNormalVarName} = ${this.worldNormal.associatedVariableName};\n`; state.compilationString += state._emitCodeFromInclude("shadowsVertex", comments, { repeatKey: "maxSimultaneousLights", substitutionVars: this.generateOnlyFragmentCode ? `worldPos,${this.worldPosition.associatedVariableName}` : undefined, }); state.compilationString += `#if DEBUGMODE > 0\n`; state.compilationString += `${state._declareLocalVar("vClipSpacePosition", NodeMaterialBlockConnectionPointTypes.Vector4)} = vec4${state.fSuffix}((vec2${state.fSuffix}(${isWebGPU ? "fragmentInputs.position" : "gl_FragCoord.xy"}) / vec2${state.fSuffix}(1.0)) * 2.0 - 1.0, 0.0, 1.0);\n`; state.compilationString += `#endif\n`; } else { worldPosVarName = (isWebGPU ? "input." : "") + "v_" + worldPosVarName; worldNormalVarName = (isWebGPU ? "input." : "") + "v_" + worldNormalVarName; } this._environmentBrdfSamplerName = state._getFreeVariableName("environmentBrdfSampler"); state._emit2DSampler(this._environmentBrdfSamplerName); state.sharedData.hints.needAlphaBlending = state.sharedData.hints.needAlphaBlending || this.useAlphaBlending; state.sharedData.hints.needAlphaTesting = state.sharedData.hints.needAlphaTesting || this.useAlphaTest; state._emitExtension("lod", "#extension GL_EXT_shader_texture_lod : enable", "defined(LODBASEDMICROSFURACE)"); state._emitExtension("derivatives", "#extension GL_OES_standard_derivatives : enable"); state._emitUniformFromString("vDebugMode", NodeMaterialBlockConnectionPointTypes.Vector2, "defined(IGNORE) || DEBUGMODE > 0"); state._emitUniformFromString("ambientFromScene", NodeMaterialBlockConnectionPointTypes.Vector3); // Image processing uniforms state.uniforms.push("exposureLinear"); state.uniforms.push("contrast"); state.uniforms.push("vInverseScreenSize"); state.uniforms.push("vignetteSettings1"); state.uniforms.push("vignetteSettings2"); state.uniforms.push("vCameraColorCurveNegative"); state.uniforms.push("vCameraColorCurveNeutral"); state.uniforms.push("vCameraColorCurvePositive"); state.uniforms.push("txColorTransform"); state.uniforms.push("colorTransformSettings"); state.uniforms.push("ditherIntensity"); // // Includes // if (!this.light) { // Emit for all lights state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightUboDeclaration" : "lightFragmentDeclaration", comments, { repeatKey: "maxSimultaneousLights", substitutionVars: this.generateOnlyFragmentCode ? "varying," : undefined, }); } else { state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightUboDeclaration" : "lightFragmentDeclaration", comments, { replaceStrings: [{ search: /{X}/g, replace: this._lightId.toString() }], }, this._lightId.toString()); } state._emitFunctionFromInclude("helperFunctions", comments); state._emitFunctionFromInclude("importanceSampling", comments); state._emitFunctionFromInclude("pbrHelperFunctions", comments); state._emitFunctionFromInclude("imageProcessingDeclaration", comments); state._emitFunctionFromInclude("imageProcessingFunctions", comments); state._emitFunctionFromInclude("shadowsFragmentFunctions", comments); state._emitFunctionFromInclude("pbrDirectLightingSetupFunctions", comments); state._emitFunctionFromInclude("pbrDirectLightingFalloffFunctions", comments); state._emitFunctionFromInclude("pbrBRDFFunctions", comments, { replaceStrings: [{ search: /REFLECTIONMAP_SKYBOX/g, replace: reflectionBlock?._defineSkyboxName ?? "REFLECTIONMAP_SKYBOX" }], }); state._emitFunctionFromInclude("hdrFilteringFunctions", comments); state._emitFunctionFromInclude("pbrDirectLightingFunctions", comments); state._emitFunctionFromInclude("pbrIBLFunctions", comments); state._emitFunctionFromInclude("pbrBlockAlbedoOpacity", comments); state._emitFunctionFromInclude("pbrBlockReflectivity", comments); state._emitFunctionFromInclude("pbrBlockAmbientOcclusion", comments); state._emitFunctionFromInclude("pbrBlockAlphaFresnel", comments); state._emitFunctionFromInclude("pbrBlockAnisotropic", comments); // // code // state._emitUniformFromString("vLightingIntensity", NodeMaterialBlockConnectionPointTypes.Vector4); if (reflectionBlock?.generateOnlyFragmentCode) { state.compilationString += reflectionBlock.handleVertexSide(state); } // _____________________________ Geometry Information ____________________________ this._vNormalWName = state._getFreeVariableName("vNormalW"); state.compilationString += `${state._declareLocalVar(this._vNormalWName, NodeMaterialBlockConnectionPointTypes.Vector4)} = normalize(${worldNormalVarName});\n`; if (state._registerTempVariable("viewDirectionW")) { state.compilationString += `${state._declareLocalVar("viewDirectionW", NodeMaterialBlockConnectionPointTypes.Vector3)} = normalize(${this.cameraPosition.associatedVariableName} - ${worldPosVarName}.xyz);\n`; } state.compilationString += `${state._declareLocalVar("geometricNormalW", NodeMaterialBlockConnectionPointTypes.Vector3)} = ${this._vNormalWName}.xyz;\n`; state.compilationString += `${state._declareLocalVar("normalW", NodeMaterialBlockConnectionPointTypes.Vector3)} = ${normalShading.isConnected ? "normalize(" + normalShading.associatedVariableName + ".xyz)" : "geometricNormalW"};\n`; this._invertNormalName = state._getFreeVariableName("invertNormal"); state._emitUniformFromString(this._invertNormalName, NodeMaterialBlockConnectionPointTypes.Float); state.compilationString += state._emitCodeFromInclude("pbrBlockNormalFinal", comments, { replaceStrings: [ { search: /vPositionW/g, replace: worldPosVarName + ".xyz" }, { search: /vEyePosition.w/g, replace: this._invertNormalName }, ], }); // _____________________________ Albedo & Opacity ______________________________ state.compilationString += this._getAlbedoOpacityCode(state); state.compilationString += state._emitCodeFromInclude("depthPrePass", comments); // _____________________________ AO _______________________________ state.compilationString += this._getAmbientOcclusionCode(state); state.compilationString += state._emitCodeFromInclude("pbrBlockLightmapInit", comments); // _____________________________ UNLIT _______________________________ state.compilationString += `#ifdef UNLIT ${state._declareLocalVar("diffuseBase", NodeMaterialBlockConnectionPointTypes.Vector3)} = vec3${state.fSuffix}(1., 1., 1.); #else\n`; // _____________________________ Reflectivity _______________________________ state.compilationString += this._getReflectivityCode(state); // _____________________________ Geometry info _________________________________ state.compilationString += state._emitCodeFromInclude("pbrBlockGeometryInfo", comments, { replaceStrings: [ { search: /REFLECTIONMAP_SKYBOX/g, replace: reflectionBlock?._defineSkyboxName ?? "REFLECTIONMAP_SKYBOX" }, { search: /REFLECTIONMAP_3D/g, replace: reflectionBlock?._define3DName ?? "REFLECTIONMAP_3D" }, ], }); // _____________________________ Anisotropy _______________________________________ const anisotropyBlock = this.anisotropy.isConnected ? this.anisotropy.connectedPoint?.ownerBlock : null; if (anisotropyBlock) { anisotropyBlock.worldPositionConnectionPoint = this.worldPosition; anisotropyBlock.worldNormalConnectionPoint = this.worldNormal; state.compilationString += anisotropyBlock.getCode(state, !this.perturbedNormal.isConnected); } // _____________________________ Reflection _______________________________________ if (reflectionBlock && reflectionBlock.hasTexture) { state.compilationString += reflectionBlock.getCode(state, anisotropyBlock ? "anisotropicOut.anisotropicNormal" : "normalW"); } state._emitFunctionFromInclude("pbrBlockReflection", comments, { replaceStrings: [ { search: /computeReflectionCoords/g, replace: "computeReflectionCoordsPBR" }, { search: /REFLECTIONMAP_3D/g, replace: reflectionBlock?._define3DName ?? "REFLECTIONMAP_3D" }, { search: /REFLECTIONMAP_OPPOSITEZ/g, replace: reflectionBlock?._defineOppositeZ ?? "REFLECTIONMAP_OPPOSITEZ" }, { search: /REFLECTIONMAP_PROJECTION/g, replace: reflectionBlock?._defineProjectionName ?? "REFLECTIONMAP_PROJECTION" }, { search: /REFLECTIONMAP_SKYBOX/g, replace: reflectionBlock?._defineSkyboxName ?? "REFLECTIONMAP_SKYBOX" }, { search: /LODINREFLECTIONALPHA/g, replace: reflectionBlock?._defineLODReflectionAlpha ?? "LODINREFLECTIONALPHA" }, { search: /LINEARSPECULARREFLECTION/g, replace: reflectionBlock?._defineLinearSpecularReflection ?? "LINEARSPECULARREFLECTION" }, { search: /vReflectionFilteringInfo/g, replace: reflectionBlock?._vReflectionFilteringInfoName ?? "vReflectionFilteringInfo" }, ], }); // ___________________ Compute Reflectance aka R0 F0 info _________________________ state.compilationString += state._emitCodeFromInclude("pbrBlockReflectance0", comments, { replaceStrings: [{ search: /metallicReflectanceFactors/g, replace: (isWebGPU ? "uniforms." : "") + this._vMetallicReflectanceFactorsName }], }); // ________________________________ Sheen ______________________________ const sheenBlock = this.sheen.isConnected ? this.sheen.connectedPoint?.ownerBlock : null; if (sheenBlock) { state.compilationString += sheenBlock.getCode(reflectionBlock, state); } state._emitFunctionFromInclude("pbrBlockSheen", comments, { replaceStrings: [ { search: /REFLECTIONMAP_3D/g, replace: reflectionBlock?._define3DName ?? "REFLECTIONMAP_3D" }, { search: /REFLECTIONMAP_SKYBOX/g, replace: reflectionBlock?._defineSkyboxName ?? "REFLECTIONMAP_SKYBOX" }, { search: /LODINREFLECTIONALPHA/g, replace: reflectionBlock?._defineLODReflectionAlpha ?? "LODINREFLECTIONALPHA" }, { search: /LINEARSPECULARREFLECTION/g, replace: reflectionBlock?._defineLinearSpecularReflection ?? "LINEARSPECULARREFLECTION" }, ], }); // ____________________ Clear Coat Initialization Code _____________________ const clearcoatBlock = this.clearcoat.isConnected ? this.clearcoat.connectedPoint?.ownerBlock : null; state.compilationString += Cl