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.

378 lines (377 loc) 20.8 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 { RegisterClass } from "../../../../Misc/typeStore.js"; import { InputBlock } from "../Input/inputBlock.js"; import { editableInPropertyPage } from "../../../../Decorators/nodeDecorator.js"; import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject.js"; import { TBNBlock } from "./TBNBlock.js"; /** * Block used to perturb normals based on a normal map */ export class PerturbNormalBlock extends NodeMaterialBlock { /** * Create a new PerturbNormalBlock * @param name defines the block name */ constructor(name) { super(name, NodeMaterialBlockTargets.Fragment); this._tangentSpaceParameterName = ""; this._tangentCorrectionFactorName = ""; this._worldMatrixName = ""; /** Gets or sets a boolean indicating that normal should be inverted on X axis */ this.invertX = false; /** Gets or sets a boolean indicating that normal should be inverted on Y axis */ this.invertY = false; /** Gets or sets a boolean indicating that parallax occlusion should be enabled */ this.useParallaxOcclusion = false; /** Gets or sets a boolean indicating that sampling mode is in Object space */ this.useObjectSpaceNormalMap = false; this._isUnique = true; // Vertex this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector4, false); this.registerInput("worldNormal", NodeMaterialBlockConnectionPointTypes.Vector4, false); this.registerInput("worldTangent", NodeMaterialBlockConnectionPointTypes.Vector4, true); this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, false); this.registerInput("normalMapColor", NodeMaterialBlockConnectionPointTypes.Color3, false); this.registerInput("strength", NodeMaterialBlockConnectionPointTypes.Float, false); this.registerInput("viewDirection", NodeMaterialBlockConnectionPointTypes.Vector3, true); this.registerInput("parallaxScale", NodeMaterialBlockConnectionPointTypes.Float, true); this.registerInput("parallaxHeight", NodeMaterialBlockConnectionPointTypes.Float, true); this.registerInput("TBN", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.VertexAndFragment, new NodeMaterialConnectionPointCustomObject("TBN", this, 0 /* NodeMaterialConnectionPointDirection.Input */, TBNBlock, "TBNBlock")); this.registerInput("world", NodeMaterialBlockConnectionPointTypes.Matrix, true); // Fragment this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4); this.registerOutput("uvOffset", NodeMaterialBlockConnectionPointTypes.Vector2); } /** * Gets the current class name * @returns the class name */ getClassName() { return "PerturbNormalBlock"; } /** * 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 world tangent input component */ get worldTangent() { return this._inputs[2]; } /** * Gets the uv input component */ get uv() { return this._inputs[3]; } /** * Gets the normal map color input component */ get normalMapColor() { return this._inputs[4]; } /** * Gets the strength input component */ get strength() { return this._inputs[5]; } /** * Gets the view direction input component */ get viewDirection() { return this._inputs[6]; } /** * Gets the parallax scale input component */ get parallaxScale() { return this._inputs[7]; } /** * Gets the parallax height input component */ get parallaxHeight() { return this._inputs[8]; } /** * Gets the TBN input component */ // eslint-disable-next-line @typescript-eslint/naming-convention get TBN() { return this._inputs[9]; } /** * Gets the World input component */ get world() { return this._inputs[10]; } /** * Gets the output component */ get output() { return this._outputs[0]; } /** * Gets the uv offset output component */ get uvOffset() { return this._outputs[1]; } initialize(state) { // 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/ShadersInclude/bumpFragment.js"), import("../../../../ShadersWGSL/ShadersInclude/bumpFragmentMainFunctions.js"), import("../../../../ShadersWGSL/ShadersInclude/bumpFragmentFunctions.js"), ]); } else { await Promise.all([ import("../../../../Shaders/ShadersInclude/bumpFragment.js"), import("../../../../Shaders/ShadersInclude/bumpFragmentMainFunctions.js"), import("../../../../Shaders/ShadersInclude/bumpFragmentFunctions.js"), ]); } this._codeIsReady = true; this.onCodeIsReadyObservable.notifyObservers(this); } prepareDefines(defines, nodeMaterial) { const normalSamplerName = this.normalMapColor.connectedPoint._ownerBlock.samplerName; const useParallax = this.viewDirection.isConnected && ((this.useParallaxOcclusion && normalSamplerName) || (!this.useParallaxOcclusion && this.parallaxHeight.isConnected)); defines.setValue("BUMP", true); defines.setValue("PARALLAX", useParallax, true); defines.setValue("PARALLAX_RHS", nodeMaterial.getScene().useRightHandedSystem, true); defines.setValue("PARALLAXOCCLUSION", this.useParallaxOcclusion, true); defines.setValue("OBJECTSPACE_NORMALMAP", this.useObjectSpaceNormalMap, true); } bind(effect, nodeMaterial, mesh) { if (nodeMaterial.getScene()._mirroredCameraPosition) { effect.setFloat2(this._tangentSpaceParameterName, this.invertX ? 1.0 : -1.0, this.invertY ? 1.0 : -1.0); } else { effect.setFloat2(this._tangentSpaceParameterName, this.invertX ? -1.0 : 1.0, this.invertY ? -1.0 : 1.0); } if (mesh) { effect.setFloat(this._tangentCorrectionFactorName, mesh.getWorldMatrix().determinant() < 0 ? -1 : 1); if (this.useObjectSpaceNormalMap && !this.world.isConnected) { // World default to the mesh world matrix effect.setMatrix(this._worldMatrixName, mesh.getWorldMatrix()); } } } autoConfigure(material, additionalFilteringInfo = () => true) { if (!this.uv.isConnected) { let uvInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "uv" && additionalFilteringInfo(b)); if (!uvInput) { uvInput = new InputBlock("uv"); uvInput.setAsAttribute(); } uvInput.output.connectTo(this.uv); } if (!this.strength.isConnected) { const strengthInput = new InputBlock("strength"); strengthInput.value = 1.0; strengthInput.output.connectTo(this.strength); } } _buildBlock(state) { super._buildBlock(state); const comments = `//${this.name}`; const uv = this.uv; const worldPosition = this.worldPosition; const worldNormal = this.worldNormal; const worldTangent = this.worldTangent; const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; const mat3 = isWebGPU ? "mat3x3f" : "mat3"; const fSuffix = isWebGPU ? "f" : ""; const uniformPrefix = isWebGPU ? "uniforms." : ""; const fragmentInputsPrefix = isWebGPU ? "fragmentInputs." : ""; state.sharedData.blocksWithDefines.push(this); state.sharedData.bindableBlocks.push(this); this._tangentSpaceParameterName = state._getFreeDefineName("tangentSpaceParameter"); state._emitUniformFromString(this._tangentSpaceParameterName, NodeMaterialBlockConnectionPointTypes.Vector2); this._tangentCorrectionFactorName = state._getFreeDefineName("tangentCorrectionFactor"); state._emitUniformFromString(this._tangentCorrectionFactorName, NodeMaterialBlockConnectionPointTypes.Float); this._worldMatrixName = state._getFreeDefineName("perturbNormalWorldMatrix"); state._emitUniformFromString(this._worldMatrixName, NodeMaterialBlockConnectionPointTypes.Matrix); let normalSamplerName = null; if (this.normalMapColor.connectedPoint) { normalSamplerName = this.normalMapColor.connectedPoint._ownerBlock.samplerName; } const useParallax = this.viewDirection.isConnected && ((this.useParallaxOcclusion && normalSamplerName) || (!this.useParallaxOcclusion && this.parallaxHeight.isConnected)); const replaceForParallaxInfos = !this.parallaxScale.isConnectedToInputBlock ? "0.05" : this.parallaxScale.connectInputBlock.isConstant ? state._emitFloat(this.parallaxScale.connectInputBlock.value) : this.parallaxScale.associatedVariableName; const replaceForBumpInfos = this.strength.isConnectedToInputBlock && this.strength.connectInputBlock.isConstant ? `\n#if !defined(NORMALXYSCALE)\n1.0/\n#endif\n${state._emitFloat(this.strength.connectInputBlock.value)}` : `\n#if !defined(NORMALXYSCALE)\n1.0/\n#endif\n${this.strength.associatedVariableName}`; if (!isWebGPU) { state._emitExtension("derivatives", "#extension GL_OES_standard_derivatives : enable"); } const tangentReplaceString = { search: /defined\(TANGENT\)/g, replace: worldTangent.isConnected ? "defined(TANGENT)" : "defined(IGNORE)" }; const tbnVarying = { search: /varying mat3 vTBN;/g, replace: "" }; const normalMatrixReplaceString = { search: isWebGPU ? /uniform normalMatrix: mat4x4f;/g : /uniform mat4 normalMatrix;/g, replace: "" }; const tbn = this.TBN; if (tbn.isConnected) { state.compilationString += ` #ifdef TBNBLOCK ${isWebGPU ? "var" : "mat3"} vTBN = ${tbn.associatedVariableName}; #endif `; } else if (worldTangent.isConnected) { state.compilationString += `${state._declareLocalVar("tbnNormal", NodeMaterialBlockConnectionPointTypes.Vector3)} = normalize(${worldNormal.associatedVariableName}.xyz);\n`; state.compilationString += `${state._declareLocalVar("tbnTangent", NodeMaterialBlockConnectionPointTypes.Vector3)} = normalize(${worldTangent.associatedVariableName}.xyz);\n`; state.compilationString += `${state._declareLocalVar("tbnBitangent", NodeMaterialBlockConnectionPointTypes.Vector3)} = cross(tbnNormal, tbnTangent) * ${uniformPrefix}${this._tangentCorrectionFactorName};\n`; state.compilationString += `${isWebGPU ? "var" : "mat3"} vTBN = ${mat3}(tbnTangent, tbnBitangent, tbnNormal);\n`; } let replaceStrings = [tangentReplaceString, tbnVarying, normalMatrixReplaceString]; if (isWebGPU) { replaceStrings.push({ search: /varying vTBN0: vec3f;/g, replace: "" }); replaceStrings.push({ search: /varying vTBN1: vec3f;/g, replace: "" }); replaceStrings.push({ search: /varying vTBN2: vec3f;/g, replace: "" }); } state._emitFunctionFromInclude("bumpFragmentMainFunctions", comments, { replaceStrings: replaceStrings, }); const replaceString0 = isWebGPU ? "fn parallaxOcclusion(vViewDirCoT: vec3f, vNormalCoT: vec3f, texCoord: vec2f, parallaxScale:f32, bumpSampler: texture_2d<f32>, bumpSamplerSampler: sampler)" : "#define inline\nvec2 parallaxOcclusion(vec3 vViewDirCoT, vec3 vNormalCoT, vec2 texCoord, float parallaxScale, sampler2D bumpSampler)"; const searchExp0 = isWebGPU ? /fn parallaxOcclusion\(vViewDirCoT: vec3f,vNormalCoT: vec3f,texCoord: vec2f,parallaxScale: f32\)/g : /vec2 parallaxOcclusion\(vec3 vViewDirCoT,vec3 vNormalCoT,vec2 texCoord,float parallaxScale\)/g; const replaceString1 = isWebGPU ? "fn parallaxOffset(viewDir: vec3f, heightScale: f32, height_: f32)" : "vec2 parallaxOffset(vec3 viewDir, float heightScale, float height_)"; const searchExp1 = isWebGPU ? /fn parallaxOffset\(viewDir: vec3f,heightScale: f32\)/g : /vec2 parallaxOffset\(vec3 viewDir,float heightScale\)/g; state._emitFunctionFromInclude("bumpFragmentFunctions", comments, { replaceStrings: [ { search: /#include<samplerFragmentDeclaration>\(_DEFINENAME_,BUMP,_VARYINGNAME_,Bump,_SAMPLERNAME_,bump\)/g, replace: "" }, { search: /uniform sampler2D bumpSampler;/g, replace: "" }, { search: searchExp0, replace: replaceString0, }, { search: searchExp1, replace: replaceString1 }, { search: /texture.+?bumpSampler,.*?vBumpUV\)\.w/g, replace: "height_" }, ], }); const normalRead = isWebGPU ? `textureSample(${normalSamplerName}, ${normalSamplerName + `Sampler`}` : `texture2D(${normalSamplerName}`; const uvForPerturbNormal = !useParallax || !normalSamplerName ? this.normalMapColor.associatedVariableName : `${normalRead}, ${uv.associatedVariableName} + uvOffset).xyz`; const tempOutput = state._getFreeVariableName("tempOutput"); state.compilationString += state._declareLocalVar(tempOutput, NodeMaterialBlockConnectionPointTypes.Vector3) + ` = vec3${fSuffix}(0.);\n`; replaceStrings = [ { search: new RegExp(`texture.+?bumpSampler${isWebGPU ? "Sampler,fragmentInputs." : ","}vBumpUV\\)`, "g"), replace: `${uvForPerturbNormal}` }, { search: /#define CUSTOM_FRAGMENT_BUMP_FRAGMENT/g, replace: `${state._declareLocalVar("normalMatrix", NodeMaterialBlockConnectionPointTypes.Matrix)} = toNormalMatrix(${this.world.isConnected ? this.world.associatedVariableName : uniformPrefix + this._worldMatrixName});`, }, { search: new RegExp(`perturbNormal\\(TBN,texture.+?bumpSampler${isWebGPU ? "Sampler,fragmentInputs." : ","}vBumpUV\\+uvOffset\\).xyz,${uniformPrefix}vBumpInfos.y\\)`, "g"), replace: `perturbNormal(TBN, ${uvForPerturbNormal}, ${uniformPrefix}vBumpInfos.y)`, }, { search: /parallaxOcclusion\(invTBN\*-viewDirectionW,invTBN\*normalW,(fragmentInputs\.)?vBumpUV,(uniforms\.)?vBumpInfos.z\)/g, replace: `parallaxOcclusion((invTBN * -viewDirectionW), (invTBN * normalW), ${fragmentInputsPrefix}vBumpUV, ${uniformPrefix}vBumpInfos.z, ${isWebGPU ? useParallax && this.useParallaxOcclusion ? `${normalSamplerName}, ${normalSamplerName + `Sampler`}` : "bump, bumpSampler" : useParallax && this.useParallaxOcclusion ? normalSamplerName : "bumpSampler"})`, }, { search: /parallaxOffset\(invTBN\*viewDirectionW,vBumpInfos\.z\)/g, replace: `parallaxOffset(invTBN * viewDirectionW, ${uniformPrefix}vBumpInfos.z, ${useParallax ? this.parallaxHeight.associatedVariableName : "0."})`, }, { search: isWebGPU ? /uniforms.vBumpInfos.y/g : /vBumpInfos.y/g, replace: replaceForBumpInfos }, { search: isWebGPU ? /uniforms.vBumpInfos.z/g : /vBumpInfos.z/g, replace: replaceForParallaxInfos }, { search: /normalW=/g, replace: tempOutput + " = " }, isWebGPU ? { search: /mat3x3f\(uniforms\.normalMatrix\[0\].xyz,uniforms\.normalMatrix\[1\]\.xyz,uniforms\.normalMatrix\[2\].xyz\)\*normalW/g, replace: `${mat3}(normalMatrix[0].xyz, normalMatrix[1].xyz, normalMatrix[2].xyz) * ` + tempOutput, } : { search: /mat3\(normalMatrix\)\*normalW/g, replace: `${mat3}(normalMatrix) * ` + tempOutput, }, { search: /normalW/g, replace: worldNormal.associatedVariableName + ".xyz" }, { search: /viewDirectionW/g, replace: useParallax ? this.viewDirection.associatedVariableName : `vec3${fSuffix}(0.)` }, tangentReplaceString, ]; if (isWebGPU) { replaceStrings.push({ search: /fragmentInputs.vBumpUV/g, replace: uv.associatedVariableName }); replaceStrings.push({ search: /input.vPositionW/g, replace: worldPosition.associatedVariableName + ".xyz" }); replaceStrings.push({ search: /uniforms.vTangentSpaceParams/g, replace: uniformPrefix + this._tangentSpaceParameterName }); replaceStrings.push({ search: /var TBN: mat3x3f=mat3x3<f32>\(input.vTBN0,input.vTBN1,input.vTBN2\);/g, replace: `var TBN = vTBN;` }); } else { replaceStrings.push({ search: /vBumpUV/g, replace: uv.associatedVariableName }); replaceStrings.push({ search: /vPositionW/g, replace: worldPosition.associatedVariableName + ".xyz" }); replaceStrings.push({ search: /vTangentSpaceParams/g, replace: uniformPrefix + this._tangentSpaceParameterName }); } state.compilationString += state._emitCodeFromInclude("bumpFragment", comments, { replaceStrings: replaceStrings, }); state.compilationString += state._declareOutput(this.output) + ` = vec4${fSuffix}(${tempOutput}, 0.);\n`; return this; } _dumpPropertiesCode() { let codeString = super._dumpPropertiesCode() + `${this._codeVariableName}.invertX = ${this.invertX};\n`; codeString += `${this._codeVariableName}.invertY = ${this.invertY};\n`; codeString += `${this._codeVariableName}.useParallaxOcclusion = ${this.useParallaxOcclusion};\n`; codeString += `${this._codeVariableName}.useObjectSpaceNormalMap = ${this.useObjectSpaceNormalMap};\n`; return codeString; } serialize() { const serializationObject = super.serialize(); serializationObject.invertX = this.invertX; serializationObject.invertY = this.invertY; serializationObject.useParallaxOcclusion = this.useParallaxOcclusion; serializationObject.useObjectSpaceNormalMap = this.useObjectSpaceNormalMap; return serializationObject; } _deserialize(serializationObject, scene, rootUrl) { super._deserialize(serializationObject, scene, rootUrl); this.invertX = serializationObject.invertX; this.invertY = serializationObject.invertY; this.useParallaxOcclusion = !!serializationObject.useParallaxOcclusion; this.useObjectSpaceNormalMap = !!serializationObject.useObjectSpaceNormalMap; } } __decorate([ editableInPropertyPage("Invert X axis", 0 /* PropertyTypeForEdition.Boolean */, "PROPERTIES", { embedded: true, notifiers: { update: true } }) ], PerturbNormalBlock.prototype, "invertX", void 0); __decorate([ editableInPropertyPage("Invert Y axis", 0 /* PropertyTypeForEdition.Boolean */, "PROPERTIES", { embedded: true, notifiers: { update: true } }) ], PerturbNormalBlock.prototype, "invertY", void 0); __decorate([ editableInPropertyPage("Use parallax occlusion", 0 /* PropertyTypeForEdition.Boolean */, undefined, { embedded: true }) ], PerturbNormalBlock.prototype, "useParallaxOcclusion", void 0); __decorate([ editableInPropertyPage("Object Space Mode", 0 /* PropertyTypeForEdition.Boolean */, "PROPERTIES", { embedded: true, notifiers: { update: true } }) ], PerturbNormalBlock.prototype, "useObjectSpaceNormalMap", void 0); RegisterClass("BABYLON.PerturbNormalBlock", PerturbNormalBlock); //# sourceMappingURL=perturbNormalBlock.js.map