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.

435 lines (426 loc) 23.7 kB
import { __decorate } from "../../../../tslib.es6.js"; import { NodeMaterialBlock } from "../../nodeMaterialBlock.js"; import { NodeMaterialBlockTargets } from "../../Enums/nodeMaterialBlockTargets.js"; import { NodeMaterial } from "../../nodeMaterial.js"; import { RegisterClass } from "../../../../Misc/typeStore.js"; import { InputBlock } from "../Input/inputBlock.js"; import { NodeMaterialSystemValues } from "../../Enums/nodeMaterialSystemValues.js"; import { CubeTexture } from "../../../Textures/cubeTexture.js"; import { Texture } from "../../../Textures/texture.js"; import { EngineStore } from "../../../../Engines/engineStore.js"; import { editableInPropertyPage } from "../../../../Decorators/nodeDecorator.js"; import { NodeMaterialBlockConnectionPointTypes } from "../../Enums/nodeMaterialBlockConnectionPointTypes.js"; /** * Base block used to read a reflection texture from a sampler */ export class ReflectionTextureBaseBlock extends NodeMaterialBlock { /** * Gets or sets the texture associated with the node */ get texture() { return this._texture; } set texture(texture) { if (this._texture === texture) { return; } const scene = texture?.getScene() ?? EngineStore.LastCreatedScene; if (!texture && scene) { scene.markAllMaterialsAsDirty(1, (mat) => { return mat.hasTexture(this._texture); }); } this._texture = texture; if (texture && scene) { scene.markAllMaterialsAsDirty(1, (mat) => { return mat.hasTexture(texture); }); } } static _OnGenerateOnlyFragmentCodeChanged(block, _propertyName) { const that = block; return that._onGenerateOnlyFragmentCodeChanged(); } _onGenerateOnlyFragmentCodeChanged() { this._setTarget(); return true; } _setTarget() { this._setInitialTarget(this.generateOnlyFragmentCode ? NodeMaterialBlockTargets.Fragment : NodeMaterialBlockTargets.VertexAndFragment); } /** * Create a new ReflectionTextureBaseBlock * @param name defines the block name */ constructor(name) { super(name, NodeMaterialBlockTargets.VertexAndFragment); /** 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; } /** * Gets the current class name * @returns the class name */ getClassName() { return "ReflectionTextureBaseBlock"; } _getTexture() { return this.texture; } 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 import("../../../../ShadersWGSL/ShadersInclude/reflectionFunction.js"); } else { await import("../../../../Shaders/ShadersInclude/reflectionFunction.js"); } this._codeIsReady = true; this.onCodeIsReadyObservable.notifyObservers(this); } /** * Auto configure the node based on the existing material * @param material defines the material to configure * @param additionalFilteringInfo defines additional info to be used when filtering inputs (we might want to skip some non relevant blocks) */ autoConfigure(material, additionalFilteringInfo = () => true) { if (!this.position.isConnected) { let positionInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "position" && additionalFilteringInfo(b)); if (!positionInput) { positionInput = new InputBlock("position"); positionInput.setAsAttribute(); } positionInput.output.connectTo(this.position); } if (!this.world.isConnected) { let worldInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.World && additionalFilteringInfo(b)); if (!worldInput) { worldInput = new InputBlock("world"); worldInput.setAsSystemValue(NodeMaterialSystemValues.World); } worldInput.output.connectTo(this.world); } if (this.view && !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) { if (!defines._areTexturesDirty) { return; } const texture = this._getTexture(); if (!texture || !texture.getTextureMatrix) { return; } defines.setValue(this._define3DName, texture.isCube, true); defines.setValue(this._defineLocalCubicName, texture.boundingBoxSize ? true : false, true); defines.setValue(this._defineExplicitName, texture.coordinatesMode === 0, true); defines.setValue(this._defineSkyboxName, texture.coordinatesMode === 5, true); defines.setValue(this._defineCubicName, texture.coordinatesMode === 3 || texture.coordinatesMode === 6, true); defines.setValue("INVERTCUBICMAP", texture.coordinatesMode === 6, true); defines.setValue(this._defineSphericalName, texture.coordinatesMode === 1, true); defines.setValue(this._definePlanarName, texture.coordinatesMode === 2, true); defines.setValue(this._defineProjectionName, texture.coordinatesMode === 4, true); defines.setValue(this._defineEquirectangularName, texture.coordinatesMode === 7, true); defines.setValue(this._defineEquirectangularFixedName, texture.coordinatesMode === 8, true); defines.setValue(this._defineMirroredEquirectangularFixedName, texture.coordinatesMode === 9, true); } isReady() { const texture = this._getTexture(); if (texture && !texture.isReadyOrNotBlocking()) { return false; } return true; } bind(effect, nodeMaterial, mesh, _subMesh) { const texture = this._getTexture(); if (!mesh || !texture) { return; } effect.setMatrix(this._reflectionMatrixName, texture.getReflectionTextureMatrix()); if (texture.isCube) { effect.setTexture(this._cubeSamplerName, texture); } else { effect.setTexture(this._2DSamplerName, texture); } if (texture.boundingBoxSize) { const cubeTexture = texture; effect.setVector3(this._reflectionPositionName, cubeTexture.boundingBoxPosition); effect.setVector3(this._reflectionSizeName, cubeTexture.boundingBoxSize); } } /** * Gets the code to inject in the vertex shader * @param state current state of the node material building * @returns the shader code */ handleVertexSide(state) { if (this.generateOnlyFragmentCode && state.target === NodeMaterialBlockTargets.Vertex) { return ""; } const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; this._define3DName = state._getFreeDefineName("REFLECTIONMAP_3D"); this._defineCubicName = state._getFreeDefineName("REFLECTIONMAP_CUBIC"); this._defineSphericalName = state._getFreeDefineName("REFLECTIONMAP_SPHERICAL"); this._definePlanarName = state._getFreeDefineName("REFLECTIONMAP_PLANAR"); this._defineProjectionName = state._getFreeDefineName("REFLECTIONMAP_PROJECTION"); this._defineExplicitName = state._getFreeDefineName("REFLECTIONMAP_EXPLICIT"); this._defineEquirectangularName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR"); this._defineLocalCubicName = state._getFreeDefineName("USE_LOCAL_REFLECTIONMAP_CUBIC"); this._defineMirroredEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_MIRROREDEQUIRECTANGULAR_FIXED"); this._defineEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR_FIXED"); this._defineSkyboxName = state._getFreeDefineName("REFLECTIONMAP_SKYBOX"); this._defineOppositeZ = state._getFreeDefineName("REFLECTIONMAP_OPPOSITEZ"); this._reflectionMatrixName = state._getFreeVariableName("reflectionMatrix"); state._emitUniformFromString(this._reflectionMatrixName, NodeMaterialBlockConnectionPointTypes.Matrix); let code = ""; this._worldPositionNameInFragmentOnlyMode = state._getFreeVariableName("worldPosition"); const worldPosVaryingName = this.generateOnlyFragmentCode ? this._worldPositionNameInFragmentOnlyMode : "v_" + this.worldPosition.associatedVariableName; if (this.generateOnlyFragmentCode || state._emitVaryingFromString(worldPosVaryingName, NodeMaterialBlockConnectionPointTypes.Vector4)) { if (this.generateOnlyFragmentCode) { code += `${state._declareLocalVar(worldPosVaryingName, NodeMaterialBlockConnectionPointTypes.Vector4)} = ${this.worldPosition.associatedVariableName};\n`; } else { code += `${isWebGPU ? "vertexOutputs." : ""}${worldPosVaryingName} = ${this.worldPosition.associatedVariableName};\n`; } } this._positionUVWName = state._getFreeVariableName("positionUVW"); this._directionWname = state._getFreeVariableName("directionW"); if (this.generateOnlyFragmentCode || state._emitVaryingFromString(this._positionUVWName, NodeMaterialBlockConnectionPointTypes.Vector3, this._defineSkyboxName)) { code += `#ifdef ${this._defineSkyboxName}\n`; if (this.generateOnlyFragmentCode) { code += `${state._declareLocalVar(this._positionUVWName, NodeMaterialBlockConnectionPointTypes.Vector3)} = ${this.position.associatedVariableName}.xyz;\n`; } else { code += `${isWebGPU ? "vertexOutputs." : ""}${this._positionUVWName} = ${this.position.associatedVariableName}.xyz;\n`; } code += `#endif\n`; } if (this.generateOnlyFragmentCode || state._emitVaryingFromString(this._directionWname, NodeMaterialBlockConnectionPointTypes.Vector3, `defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})`)) { code += `#if defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})\n`; if (this.generateOnlyFragmentCode) { code += `${state._declareLocalVar(this._directionWname, NodeMaterialBlockConnectionPointTypes.Vector3)} = normalize(vec3${state.fSuffix}(${this.world.associatedVariableName} * vec4${state.fSuffix}(${this.position.associatedVariableName}.xyz, 0.0)));\n`; } else { code += `${isWebGPU ? "vertexOutputs." : ""}${this._directionWname} = normalize(vec3${state.fSuffix}(${this.world.associatedVariableName} * vec4${state.fSuffix}(${this.position.associatedVariableName}.xyz, 0.0)));\n`; } code += `#endif\n`; } return code; } /** * Handles the inits for the fragment code path * @param state node material build state */ handleFragmentSideInits(state) { state.sharedData.blockingBlocks.push(this); state.sharedData.textureBlocks.push(this); // Samplers this._cubeSamplerName = state._getFreeVariableName(this.name + "CubeSampler"); state.samplers.push(this._cubeSamplerName); this._2DSamplerName = state._getFreeVariableName(this.name + "2DSampler"); state.samplers.push(this._2DSamplerName); state._samplerDeclaration += `#ifdef ${this._define3DName}\n`; state._emitCubeSampler(this._cubeSamplerName, "", true); state._samplerDeclaration += `#else\n`; state._emit2DSampler(this._2DSamplerName, "", true); state._samplerDeclaration += `#endif\n`; // Fragment state.sharedData.blocksWithDefines.push(this); state.sharedData.bindableBlocks.push(this); const comments = `//${this.name}`; state._emitFunctionFromInclude("helperFunctions", comments); state._emitFunctionFromInclude("reflectionFunction", comments, { replaceStrings: [ { search: /vec3 computeReflectionCoords/g, replace: "void DUMMYFUNC" }, { search: /fn computeReflectionCoords\(worldPos: vec4f,worldNormal: vec3f\)->vec3f/g, replace: "fn DUMMYFUNC()" }, ], }); this._reflectionColorName = state._getFreeVariableName("reflectionColor"); this._reflectionVectorName = state._getFreeVariableName("reflectionUVW"); this._reflectionCoordsName = state._getFreeVariableName("reflectionCoords"); this._reflectionPositionName = state._getFreeVariableName("vReflectionPosition"); state._emitUniformFromString(this._reflectionPositionName, NodeMaterialBlockConnectionPointTypes.Vector3); this._reflectionSizeName = state._getFreeVariableName("vReflectionPosition"); state._emitUniformFromString(this._reflectionSizeName, NodeMaterialBlockConnectionPointTypes.Vector3); } /** * Generates the reflection coords code for the fragment code path * @param state defines the build state * @param worldNormalVarName name of the world normal variable * @param worldPos name of the world position variable. If not provided, will use the world position connected to this block * @param onlyReflectionVector if true, generates code only for the reflection vector computation, not for the reflection coordinates * @param doNotEmitInvertZ if true, does not emit the invertZ code * @returns the shader code */ handleFragmentSideCodeReflectionCoords(state, worldNormalVarName, worldPos, onlyReflectionVector = false, doNotEmitInvertZ = false) { const isWebGPU = state.shaderLanguage === 1 /* ShaderLanguage.WGSL */; const reflectionMatrix = (isWebGPU ? "uniforms." : "") + this._reflectionMatrixName; const direction = `normalize(${this._directionWname})`; const positionUVW = `${this._positionUVWName}`; const vEyePosition = `${this.cameraPosition.associatedVariableName}`; const view = `${this.view.associatedVariableName}`; const fragmentInputsPrefix = isWebGPU ? "fragmentInputs." : ""; if (!worldPos) { worldPos = this.generateOnlyFragmentCode ? this._worldPositionNameInFragmentOnlyMode : `${fragmentInputsPrefix}v_${this.worldPosition.associatedVariableName}`; } worldNormalVarName += ".xyz"; let code = ` #ifdef ${this._defineMirroredEquirectangularFixedName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeMirroredFixedEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${direction}); #endif #ifdef ${this._defineEquirectangularFixedName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeFixedEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${direction}); #endif #ifdef ${this._defineEquirectangularName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix}); #endif #ifdef ${this._defineSphericalName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeSphericalCoords(${worldPos}, ${worldNormalVarName}, ${view}, ${reflectionMatrix}); #endif #ifdef ${this._definePlanarName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computePlanarCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix}); #endif #ifdef ${this._defineCubicName} #ifdef ${this._defineLocalCubicName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeCubicLocalCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix}, ${this._reflectionSizeName}, ${this._reflectionPositionName}); #else ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeCubicCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix}); #endif #endif #ifdef ${this._defineProjectionName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeProjectionCoords(${worldPos}, ${view}, ${reflectionMatrix}); #endif #ifdef ${this._defineSkyboxName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = computeSkyBoxCoords(${positionUVW}, ${reflectionMatrix}); #endif #ifdef ${this._defineExplicitName} ${state._declareLocalVar(this._reflectionVectorName, NodeMaterialBlockConnectionPointTypes.Vector3)} = vec3(0, 0, 0); #endif\n`; if (!doNotEmitInvertZ) { code += `#ifdef ${this._defineOppositeZ} ${this._reflectionVectorName}.z *= -1.0; #endif\n`; } if (!onlyReflectionVector) { code += ` #ifdef ${this._define3DName} ${state._declareLocalVar(this._reflectionCoordsName, NodeMaterialBlockConnectionPointTypes.Vector3)} = ${this._reflectionVectorName}; #else ${state._declareLocalVar(this._reflectionCoordsName, NodeMaterialBlockConnectionPointTypes.Vector2)} = ${this._reflectionVectorName}.xy; #ifdef ${this._defineProjectionName} ${this._reflectionCoordsName} /= ${this._reflectionVectorName}.z; #endif ${this._reflectionCoordsName}.y = 1.0 - ${this._reflectionCoordsName}.y; #endif\n`; } return code; } /** * Generates the reflection color code for the fragment code path * @param state defines the build state * @param lodVarName name of the lod variable * @param swizzleLookupTexture swizzle to use for the final color variable * @returns the shader code */ handleFragmentSideCodeReflectionColor(state, lodVarName, swizzleLookupTexture = ".rgb") { let colorType = NodeMaterialBlockConnectionPointTypes.Vector4; if (swizzleLookupTexture.length === 3) { colorType = NodeMaterialBlockConnectionPointTypes.Vector3; } let code = `${state._declareLocalVar(this._reflectionColorName, colorType)}; #ifdef ${this._define3DName}\n`; if (lodVarName) { code += `${this._reflectionColorName} = ${state._generateTextureSampleCubeLOD(this._reflectionVectorName, this._cubeSamplerName, lodVarName)}${swizzleLookupTexture};\n`; } else { code += `${this._reflectionColorName} = ${state._generateTextureSampleCube(this._reflectionVectorName, this._cubeSamplerName)}${swizzleLookupTexture};\n`; } code += ` #else\n`; if (lodVarName) { code += `${this._reflectionColorName} =${state._generateTextureSampleLOD(this._reflectionCoordsName, this._2DSamplerName, lodVarName)}${swizzleLookupTexture};\n`; } else { code += `${this._reflectionColorName} = ${state._generateTextureSample(this._reflectionCoordsName, this._2DSamplerName)}${swizzleLookupTexture};\n`; } code += `#endif\n`; return code; } /** * Generates the code corresponding to the connected output points * @param state node material build state * @param varName name of the variable to output * @returns the shader code */ writeOutputs(state, varName) { let code = ""; if (state.target === NodeMaterialBlockTargets.Fragment) { for (const output of this._outputs) { if (output.hasEndpoints) { code += `${state._declareOutput(output)} = ${varName}.${output.name};\n`; } } } return code; } _buildBlock(state) { super._buildBlock(state); return this; } _dumpPropertiesCode() { let codeString = super._dumpPropertiesCode(); if (!this.texture) { return codeString; } if (this.texture.isCube) { const forcedExtension = this.texture.forcedExtension; codeString += `${this._codeVariableName}.texture = new BABYLON.CubeTexture("${this.texture.name}", undefined, undefined, ${this.texture.noMipmap}, null, undefined, undefined, undefined, ${this.texture._prefiltered}, ${forcedExtension ? '"' + forcedExtension + '"' : "null"});\n`; } else { codeString += `${this._codeVariableName}.texture = new BABYLON.Texture("${this.texture.name}", null);\n`; } codeString += `${this._codeVariableName}.texture.coordinatesMode = ${this.texture.coordinatesMode};\n`; return codeString; } serialize() { const serializationObject = super.serialize(); if (this.texture && !this.texture.isRenderTarget) { serializationObject.texture = this.texture.serialize(); } serializationObject.generateOnlyFragmentCode = this.generateOnlyFragmentCode; return serializationObject; } _deserialize(serializationObject, scene, rootUrl) { super._deserialize(serializationObject, scene, rootUrl); if (serializationObject.texture && !NodeMaterial.IgnoreTexturesAtLoadTime) { rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl; if (serializationObject.texture.isCube) { this.texture = CubeTexture.Parse(serializationObject.texture, scene, rootUrl); } else { this.texture = Texture.Parse(serializationObject.texture, scene, rootUrl); } } this.generateOnlyFragmentCode = serializationObject.generateOnlyFragmentCode; this._setTarget(); } } __decorate([ editableInPropertyPage("Generate only fragment code", 0 /* PropertyTypeForEdition.Boolean */, "ADVANCED", { notifiers: { rebuild: true, update: true, onValidation: ReflectionTextureBaseBlock._OnGenerateOnlyFragmentCodeChanged }, }) ], ReflectionTextureBaseBlock.prototype, "generateOnlyFragmentCode", void 0); RegisterClass("BABYLON.ReflectionTextureBaseBlock", ReflectionTextureBaseBlock); //# sourceMappingURL=reflectionTextureBaseBlock.js.map