@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
JavaScript
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