@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.
577 lines (576 loc) • 25.8 kB
JavaScript
import { NodeMaterialBlock } from "../../nodeMaterialBlock.js";
import { NodeMaterialBlockConnectionPointTypes } from "../../Enums/nodeMaterialBlockConnectionPointTypes.js";
import { NodeMaterialBlockTargets } from "../../Enums/nodeMaterialBlockTargets.js";
import { NodeMaterial } from "../../nodeMaterial.js";
import { InputBlock } from "../Input/inputBlock.js";
import { RegisterClass } from "../../../../Misc/typeStore.js";
import { Texture } from "../../../Textures/texture.js";
import { NodeMaterialModes } from "../../Enums/nodeMaterialModes.js";
import "../../../../Shaders/ShadersInclude/helperFunctions.js";
import { ImageSourceBlock } from "./imageSourceBlock.js";
import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject.js";
import { EngineStore } from "../../../../Engines/engineStore.js";
/**
* Block used to read a texture from a sampler
*/
export class TextureBlock extends NodeMaterialBlock {
/**
* Gets or sets the texture associated with the node
*/
get texture() {
if (this.source.isConnected) {
return (this.source.connectedPoint?.ownerBlock).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 _IsPrePassTextureBlock(block) {
return block?.getClassName() === "PrePassTextureBlock";
}
get _isSourcePrePass() {
return TextureBlock._IsPrePassTextureBlock(this._imageSource);
}
/**
* Gets the sampler name associated with this texture
*/
get samplerName() {
if (this._imageSource) {
if (!TextureBlock._IsPrePassTextureBlock(this._imageSource)) {
return this._imageSource.samplerName;
}
if (this.source.connectedPoint) {
return this._imageSource.getSamplerName(this.source.connectedPoint);
}
}
return this._samplerName;
}
/**
* Gets a boolean indicating that this block is linked to an ImageSourceBlock
*/
get hasImageSource() {
return this.source.isConnected;
}
/**
* Gets or sets a boolean indicating if content needs to be converted to gamma space
*/
set convertToGammaSpace(value) {
if (value === this._convertToGammaSpace) {
return;
}
this._convertToGammaSpace = value;
if (this.texture) {
const scene = this.texture.getScene() ?? EngineStore.LastCreatedScene;
scene?.markAllMaterialsAsDirty(1, (mat) => {
return mat.hasTexture(this.texture);
});
}
}
get convertToGammaSpace() {
return this._convertToGammaSpace;
}
/**
* Gets or sets a boolean indicating if content needs to be converted to linear space
*/
set convertToLinearSpace(value) {
if (value === this._convertToLinearSpace) {
return;
}
this._convertToLinearSpace = value;
if (this.texture) {
const scene = this.texture.getScene() ?? EngineStore.LastCreatedScene;
scene?.markAllMaterialsAsDirty(1, (mat) => {
return mat.hasTexture(this.texture);
});
}
}
get convertToLinearSpace() {
return this._convertToLinearSpace;
}
/**
* Create a new TextureBlock
* @param name defines the block name
* @param fragmentOnly
*/
constructor(name, fragmentOnly = false) {
super(name, fragmentOnly ? NodeMaterialBlockTargets.Fragment : NodeMaterialBlockTargets.VertexAndFragment);
this._convertToGammaSpace = false;
this._convertToLinearSpace = false;
/**
* Gets or sets a boolean indicating if multiplication of texture with level should be disabled
*/
this.disableLevelMultiplication = false;
this._fragmentOnly = fragmentOnly;
this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.AutoDetect, false, NodeMaterialBlockTargets.VertexAndFragment);
this.registerInput("source", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.VertexAndFragment, new NodeMaterialConnectionPointCustomObject("source", this, 0 /* NodeMaterialConnectionPointDirection.Input */, ImageSourceBlock, "ImageSourceBlock"));
this.registerInput("layer", NodeMaterialBlockConnectionPointTypes.Float, true);
this.registerInput("lod", NodeMaterialBlockConnectionPointTypes.Float, true);
this.registerOutput("rgba", NodeMaterialBlockConnectionPointTypes.Color4, NodeMaterialBlockTargets.Neutral);
this.registerOutput("rgb", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Neutral);
this.registerOutput("r", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
this.registerOutput("g", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
this.registerOutput("b", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
this.registerOutput("a", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
this.registerOutput("level", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
this._inputs[0].addExcludedConnectionPointFromAllowedTypes(NodeMaterialBlockConnectionPointTypes.Vector2 | NodeMaterialBlockConnectionPointTypes.Vector3 | NodeMaterialBlockConnectionPointTypes.Vector4);
this._inputs[0]._prioritizeVertex = !fragmentOnly;
}
/**
* Gets the current class name
* @returns the class name
*/
getClassName() {
return "TextureBlock";
}
/**
* Gets the uv input component
*/
get uv() {
return this._inputs[0];
}
/**
* Gets the source input component
*/
get source() {
return this._inputs[1];
}
/**
* Gets the layer input component
*/
get layer() {
return this._inputs[2];
}
/**
* Gets the LOD input component
*/
get lod() {
return this._inputs[3];
}
/**
* Gets the rgba output component
*/
get rgba() {
return this._outputs[0];
}
/**
* Gets the rgb output component
*/
get rgb() {
return this._outputs[1];
}
/**
* Gets the r output component
*/
get r() {
return this._outputs[2];
}
/**
* Gets the g output component
*/
get g() {
return this._outputs[3];
}
/**
* Gets the b output component
*/
get b() {
return this._outputs[4];
}
/**
* Gets the a output component
*/
get a() {
return this._outputs[5];
}
/**
* Gets the level output component
*/
get level() {
return this._outputs[6];
}
_isTiedToFragment(input) {
if (input.target === NodeMaterialBlockTargets.Fragment) {
return true;
}
if (input.target === NodeMaterialBlockTargets.Vertex) {
return false;
}
if (input.target === NodeMaterialBlockTargets.Neutral || input.target === NodeMaterialBlockTargets.VertexAndFragment) {
const parentBlock = input.ownerBlock;
if (parentBlock.target === NodeMaterialBlockTargets.Fragment) {
return true;
}
for (const input of parentBlock.inputs) {
if (!input.isConnected) {
continue;
}
if (this._isTiedToFragment(input.connectedPoint)) {
return true;
}
}
}
return false;
}
_getEffectiveTarget() {
if (this._fragmentOnly) {
return NodeMaterialBlockTargets.Fragment;
}
// TextureBlock has a special optimizations for uvs that come from the vertex shaders as they can be packed into a single varyings.
// But we need to detect uvs coming from fragment then
if (!this.uv.isConnected) {
return NodeMaterialBlockTargets.VertexAndFragment;
}
if (this.uv.sourceBlock.isInput) {
return NodeMaterialBlockTargets.VertexAndFragment;
}
if (this._isTiedToFragment(this.uv.connectedPoint)) {
return NodeMaterialBlockTargets.Fragment;
}
return NodeMaterialBlockTargets.VertexAndFragment;
}
get target() {
return this._getEffectiveTarget();
}
set target(value) { }
autoConfigure(material, additionalFilteringInfo = () => true) {
if (!this.uv.isConnected) {
if (material.mode === NodeMaterialModes.PostProcess) {
const uvInput = material.getBlockByPredicate((b) => b.name === "uv" && additionalFilteringInfo(b));
if (uvInput) {
uvInput.connectTo(this);
}
}
else if (material.mode !== NodeMaterialModes.ProceduralTexture) {
const attributeName = material.mode === NodeMaterialModes.Particle ? "particle_uv" : "uv";
let uvInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === attributeName && additionalFilteringInfo(b));
if (!uvInput) {
uvInput = new InputBlock("uv");
uvInput.setAsAttribute(attributeName);
}
uvInput.output.connectTo(this.uv);
}
}
}
initializeDefines(defines) {
if (!defines._areTexturesDirty) {
return;
}
if (this._mainUVDefineName !== undefined) {
defines.setValue(this._mainUVDefineName, false, true);
}
}
prepareDefines(defines) {
if (!defines._areTexturesDirty) {
return;
}
if (!this.texture || !this.texture.getTextureMatrix) {
if (this._isMixed) {
defines.setValue(this._defineName, false, true);
defines.setValue(this._mainUVDefineName, true, true);
}
return;
}
const toGamma = this.convertToGammaSpace && this.texture && !this.texture.gammaSpace;
const toLinear = this.convertToLinearSpace && this.texture && this.texture.gammaSpace;
// Not a bug... Name defines the texture space not the required conversion
defines.setValue(this._linearDefineName, toGamma, true);
defines.setValue(this._gammaDefineName, toLinear, true);
if (this._isMixed) {
if (!this.texture.getTextureMatrix().isIdentityAs3x2()) {
defines.setValue(this._defineName, true);
if (defines[this._mainUVDefineName] == undefined) {
defines.setValue(this._mainUVDefineName, false, true);
}
}
else {
defines.setValue(this._defineName, false, true);
defines.setValue(this._mainUVDefineName, true, true);
}
}
}
isReady() {
if (this._isSourcePrePass) {
return true;
}
if (this.texture && !this.texture.isReadyOrNotBlocking()) {
return false;
}
return true;
}
bind(effect) {
if (this._isSourcePrePass) {
effect.setFloat(this._textureInfoName, 1);
}
if (!this.texture) {
return;
}
if (this._isMixed) {
effect.setFloat(this._textureInfoName, this.texture.level);
effect.setMatrix(this._textureTransformName, this.texture.getTextureMatrix());
}
if (!this._imageSource) {
effect.setTexture(this._samplerName, this.texture);
}
}
get _isMixed() {
return this.target !== NodeMaterialBlockTargets.Fragment;
}
_injectVertexCode(state) {
const uvInput = this.uv;
// Inject code in vertex
this._defineName = state._getFreeDefineName("UVTRANSFORM");
this._mainUVDefineName = "VMAIN" + uvInput.declarationVariableName.toUpperCase();
this._mainUVName = "vMain" + uvInput.declarationVariableName;
this._transformedUVName = state._getFreeVariableName("transformedUV");
this._textureTransformName = state._getFreeVariableName("textureTransform");
this._textureInfoName = state._getFreeVariableName("textureInfoName");
this.level.associatedVariableName = this._textureInfoName;
state._emitVaryingFromString(this._transformedUVName, NodeMaterialBlockConnectionPointTypes.Vector2, this._defineName);
state._emitVaryingFromString(this._mainUVName, NodeMaterialBlockConnectionPointTypes.Vector2, this._mainUVDefineName);
state._emitUniformFromString(this._textureTransformName, NodeMaterialBlockConnectionPointTypes.Matrix, this._defineName);
const vec4 = state._getShaderType(NodeMaterialBlockConnectionPointTypes.Vector4);
const vec2 = state._getShaderType(NodeMaterialBlockConnectionPointTypes.Vector2);
state.compilationString += `#ifdef ${this._defineName}\n`;
state.compilationString += `${state._getVaryingName(this._transformedUVName)} = ${vec2}(${this._textureTransformName} * ${vec4}(${uvInput.associatedVariableName}.xy, 1.0, 0.0));\n`;
state.compilationString += `#elif defined(${this._mainUVDefineName})\n`;
let automaticPrefix = "";
if (state.shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
if (uvInput.isConnectedToInputBlock && uvInput.associatedVariableName.indexOf("vertexInputs.") === -1) {
automaticPrefix = "vertexInputs."; // Force the prefix
}
}
state.compilationString += `${state._getVaryingName(this._mainUVName)} = ${automaticPrefix}${uvInput.associatedVariableName}.xy;\n`;
state.compilationString += `#endif\n`;
if (!this._outputs.some((o) => o.isConnectedInVertexShader)) {
return;
}
this._writeTextureRead(state, true);
for (const output of this._outputs) {
if (output.hasEndpoints && output.name !== "level") {
this._writeOutput(state, output, output.name, true);
}
}
}
_getUVW(uvName) {
let coords = uvName;
const is2DArrayTexture = this._texture?._texture?.is2DArray ?? false;
const is3D = this._texture?._texture?.is3D ?? false;
if (is2DArrayTexture) {
const layerValue = this.layer.isConnected ? this.layer.associatedVariableName : "0";
coords = `vec3(${uvName}, ${layerValue})`;
}
else if (is3D) {
const layerValue = this.layer.isConnected ? this.layer.associatedVariableName : "0";
coords = `vec3(${uvName}, ${layerValue})`;
}
return coords;
}
_samplerFunc(state) {
if (state.shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
return state.target === NodeMaterialBlockTargets.Vertex ? "textureSampleLevel" : "textureSample";
}
return this.lod.isConnected ? "texture2DLodEXT" : "texture2D";
}
get _samplerLodSuffix() {
return this.lod.isConnected ? `, ${this.lod.associatedVariableName}` : "";
}
_generateTextureSample(uv, state) {
if (state.shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
const isVertex = state.target === NodeMaterialBlockTargets.Vertex;
return `${this._samplerFunc(state)}(${this.samplerName},${this.samplerName + `Sampler`}, ${this._getUVW(uv)}${this._samplerLodSuffix}${isVertex ? ", 0" : ""})`;
}
return `${this._samplerFunc(state)}(${this.samplerName}, ${this._getUVW(uv)}${this._samplerLodSuffix})`;
}
_generateTextureLookup(state) {
state.compilationString += `#ifdef ${this._defineName}\n`;
state.compilationString += `${state._declareLocalVar(this._tempTextureRead, NodeMaterialBlockConnectionPointTypes.Vector4)} = ${this._generateTextureSample(state._getVaryingName(this._transformedUVName), state)};\n`;
state.compilationString += `#elif defined(${this._mainUVDefineName})\n`;
state.compilationString += `${state._declareLocalVar(this._tempTextureRead, NodeMaterialBlockConnectionPointTypes.Vector4)} = ${this._generateTextureSample(this._mainUVName ? state._getVaryingName(this._mainUVName) : this.uv.associatedVariableName, state)}${this._samplerLodSuffix};\n`;
state.compilationString += `#endif\n`;
}
_writeTextureRead(state, vertexMode = false) {
const uvInput = this.uv;
if (vertexMode) {
if (state.target === NodeMaterialBlockTargets.Fragment) {
return;
}
this._generateTextureLookup(state);
return;
}
if (this.uv.ownerBlock.target === NodeMaterialBlockTargets.Fragment) {
state.compilationString += `${state._declareLocalVar(this._tempTextureRead, NodeMaterialBlockConnectionPointTypes.Vector4)} = ${this._generateTextureSample(uvInput.associatedVariableName, state)}${this._samplerLodSuffix};\n`;
return;
}
this._generateTextureLookup(state);
}
_generateConversionCode(state, output, swizzle) {
if (swizzle !== "a") {
// no conversion if the output is "a" (alpha)
if (!this.texture || !this.texture.gammaSpace) {
state.compilationString += `#ifdef ${this._linearDefineName}
${output.associatedVariableName} = toGammaSpace(${output.associatedVariableName});
#endif
`;
}
state.compilationString += `#ifdef ${this._gammaDefineName}
${output.associatedVariableName} = ${state._toLinearSpace(output)};
#endif
`;
}
}
_writeOutput(state, output, swizzle, vertexMode = false) {
if (vertexMode) {
if (state.target === NodeMaterialBlockTargets.Fragment) {
return;
}
state.compilationString += `${state._declareOutput(output)} = ${this._tempTextureRead}.${swizzle};\n`;
this._generateConversionCode(state, output, swizzle);
return;
}
if (this.uv.ownerBlock.target === NodeMaterialBlockTargets.Fragment) {
state.compilationString += `${state._declareOutput(output)} = ${this._tempTextureRead}.${swizzle};\n`;
this._generateConversionCode(state, output, swizzle);
return;
}
let complement = "";
if (!this.disableLevelMultiplication) {
complement = ` * ${(state.shaderLanguage === 1 /* ShaderLanguage.WGSL */ ? "uniforms." : "") + this._textureInfoName}`;
}
state.compilationString += `${state._declareOutput(output)} = ${this._tempTextureRead}.${swizzle}${complement};\n`;
this._generateConversionCode(state, output, swizzle);
}
_buildBlock(state) {
super._buildBlock(state);
if (this.source.isConnected) {
this._imageSource = this.source.connectedPoint.ownerBlock;
}
else {
this._imageSource = null;
}
if (state.target === NodeMaterialBlockTargets.Vertex || this._fragmentOnly || state.target === NodeMaterialBlockTargets.Fragment) {
this._tempTextureRead = state._getFreeVariableName("tempTextureRead");
this._linearDefineName = state._getFreeDefineName("ISLINEAR");
this._gammaDefineName = state._getFreeDefineName("ISGAMMA");
}
if ((!this._isMixed && state.target === NodeMaterialBlockTargets.Fragment) || (this._isMixed && state.target === NodeMaterialBlockTargets.Vertex)) {
if (!this._imageSource) {
const varName = state._getFreeVariableName(this.name);
this._samplerName = varName + "Texture";
if (this._texture?._texture?.is2DArray) {
state._emit2DArraySampler(this._samplerName);
}
else {
state._emit2DSampler(this._samplerName);
}
}
// Declarations
state.sharedData.blockingBlocks.push(this);
state.sharedData.textureBlocks.push(this);
state.sharedData.blocksWithDefines.push(this);
state.sharedData.bindableBlocks.push(this);
}
if (state.target !== NodeMaterialBlockTargets.Fragment) {
// Vertex
this._injectVertexCode(state);
return;
}
// Fragment
if (!this._outputs.some((o) => o.isConnectedInFragmentShader)) {
return;
}
if (this._isMixed && !this._imageSource) {
// Reexport the sampler
if (this._texture?._texture?.is2DArray) {
state._emit2DArraySampler(this._samplerName);
}
else {
state._emit2DSampler(this._samplerName);
}
}
const comments = `//${this.name}`;
state._emitFunctionFromInclude("helperFunctions", comments);
if (this._isMixed) {
state._emitUniformFromString(this._textureInfoName, NodeMaterialBlockConnectionPointTypes.Float);
}
this._writeTextureRead(state);
for (const output of this._outputs) {
if (output.hasEndpoints && output.name !== "level") {
this._writeOutput(state, output, output.name);
}
}
return this;
}
_dumpPropertiesCode() {
let codeString = super._dumpPropertiesCode();
codeString += `${this._codeVariableName}.convertToGammaSpace = ${this.convertToGammaSpace};\n`;
codeString += `${this._codeVariableName}.convertToLinearSpace = ${this.convertToLinearSpace};\n`;
codeString += `${this._codeVariableName}.disableLevelMultiplication = ${this.disableLevelMultiplication};\n`;
if (!this.texture) {
return codeString;
}
codeString += `${this._codeVariableName}.texture = new BABYLON.Texture("${this.texture.name}", null, ${this.texture.noMipmap}, ${this.texture.invertY}, ${this.texture.samplingMode});\n`;
codeString += `${this._codeVariableName}.texture.wrapU = ${this.texture.wrapU};\n`;
codeString += `${this._codeVariableName}.texture.wrapV = ${this.texture.wrapV};\n`;
codeString += `${this._codeVariableName}.texture.uAng = ${this.texture.uAng};\n`;
codeString += `${this._codeVariableName}.texture.vAng = ${this.texture.vAng};\n`;
codeString += `${this._codeVariableName}.texture.wAng = ${this.texture.wAng};\n`;
codeString += `${this._codeVariableName}.texture.uOffset = ${this.texture.uOffset};\n`;
codeString += `${this._codeVariableName}.texture.vOffset = ${this.texture.vOffset};\n`;
codeString += `${this._codeVariableName}.texture.uScale = ${this.texture.uScale};\n`;
codeString += `${this._codeVariableName}.texture.vScale = ${this.texture.vScale};\n`;
codeString += `${this._codeVariableName}.texture.coordinatesMode = ${this.texture.coordinatesMode};\n`;
return codeString;
}
serialize() {
const serializationObject = super.serialize();
serializationObject.convertToGammaSpace = this.convertToGammaSpace;
serializationObject.convertToLinearSpace = this.convertToLinearSpace;
serializationObject.fragmentOnly = this._fragmentOnly;
serializationObject.disableLevelMultiplication = this.disableLevelMultiplication;
if (!this.hasImageSource &&
this.texture &&
(NodeMaterial.AllowSerializationOfRenderTargetTextures || !this.texture.isRenderTarget) &&
this.texture.getClassName() !== "VideoTexture") {
serializationObject.texture = this.texture.serialize();
}
return serializationObject;
}
_deserialize(serializationObject, scene, rootUrl, urlRewriter) {
super._deserialize(serializationObject, scene, rootUrl);
this.convertToGammaSpace = serializationObject.convertToGammaSpace;
this.convertToLinearSpace = !!serializationObject.convertToLinearSpace;
this._fragmentOnly = !!serializationObject.fragmentOnly;
this.disableLevelMultiplication = !!serializationObject.disableLevelMultiplication;
if (serializationObject.texture && !NodeMaterial.IgnoreTexturesAtLoadTime) {
if (serializationObject.texture.url !== undefined) {
if (serializationObject.texture.url.indexOf("data:") === 0) {
rootUrl = "";
}
else if (urlRewriter) {
serializationObject.texture.url = urlRewriter(serializationObject.texture.url);
serializationObject.texture.name = serializationObject.texture.url;
}
}
if (serializationObject.texture.base64String || serializationObject.texture.url !== undefined) {
this.texture = Texture.Parse(serializationObject.texture, scene, rootUrl);
}
}
}
}
RegisterClass("BABYLON.TextureBlock", TextureBlock);
//# sourceMappingURL=textureBlock.js.map