@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.
1,221 lines (1,220 loc) • 60.6 kB
JavaScript
import { SerializationHelper } from "../Misc/decorators.serialization.js";
import { Scene } from "../scene.js";
import { Matrix, Vector3, Vector2, Vector4, Quaternion } from "../Maths/math.vector.js";
import { VertexBuffer } from "../Buffers/buffer.js";
import { Texture } from "../Materials/Textures/texture.js";
import { RegisterClass } from "../Misc/typeStore.js";
import { Color3, Color4 } from "../Maths/math.color.js";
import { EffectFallbacks } from "./effectFallbacks.js";
import { WebRequest } from "../Misc/webRequest.js";
import { PushMaterial } from "./pushMaterial.js";
import { EngineStore } from "../Engines/engineStore.js";
import { addClipPlaneUniforms, bindClipPlane, prepareStringDefinesForClipPlanes } from "./clipPlaneMaterialHelper.js";
import { BindBonesParameters, BindFogParameters, BindLogDepth, BindMorphTargetParameters, BindSceneUniformBuffer, PrepareAttributesForBakedVertexAnimation, PrepareDefinesAndAttributesForMorphTargets, PushAttributesForInstances, } from "./materialHelper.functions.js";
const onCreatedEffectParameters = { effect: null, subMesh: null };
/**
* The ShaderMaterial object has the necessary methods to pass data from your scene to the Vertex and Fragment Shaders and returns a material that can be applied to any mesh.
*
* This returned material effects how the mesh will look based on the code in the shaders.
*
* @see https://doc.babylonjs.com/features/featuresDeepDive/materials/shaders/shaderMaterial
*/
export class ShaderMaterial extends PushMaterial {
/**
* Instantiate a new shader material.
* The ShaderMaterial object has the necessary methods to pass data from your scene to the Vertex and Fragment Shaders and returns a material that can be applied to any mesh.
* This returned material effects how the mesh will look based on the code in the shaders.
* @see https://doc.babylonjs.com/features/featuresDeepDive/materials/shaders/shaderMaterial
* @param name Define the name of the material in the scene
* @param scene Define the scene the material belongs to
* @param shaderPath Defines the route to the shader code.
* @param options Define the options used to create the shader
* @param storeEffectOnSubMeshes true to store effect on submeshes, false to store the effect directly in the material class.
*/
constructor(name, scene, shaderPath, options = {}, storeEffectOnSubMeshes = true) {
super(name, scene, storeEffectOnSubMeshes);
this._textures = {};
this._textureArrays = {};
this._externalTextures = {};
this._floats = {};
this._ints = {};
this._uints = {};
this._floatsArrays = {};
this._colors3 = {};
this._colors3Arrays = {};
this._colors4 = {};
this._colors4Arrays = {};
this._vectors2 = {};
this._vectors3 = {};
this._vectors4 = {};
this._quaternions = {};
this._quaternionsArrays = {};
this._matrices = {};
this._matrixArrays = {};
this._matrices3x3 = {};
this._matrices2x2 = {};
this._vectors2Arrays = {};
this._vectors3Arrays = {};
this._vectors4Arrays = {};
this._uniformBuffers = {};
this._textureSamplers = {};
this._storageBuffers = {};
this._cachedWorldViewMatrix = new Matrix();
this._cachedWorldViewProjectionMatrix = new Matrix();
this._multiview = false;
/**
* @internal
*/
this._materialHelperNeedsPreviousMatrices = false;
this._shaderPath = shaderPath;
this._options = {
needAlphaBlending: false,
needAlphaTesting: false,
attributes: ["position", "normal", "uv"],
uniforms: ["worldViewProjection"],
uniformBuffers: [],
samplers: [],
externalTextures: [],
samplerObjects: [],
storageBuffers: [],
defines: [],
useClipPlane: false,
...options,
};
}
/**
* Gets the shader path used to define the shader code
* It can be modified to trigger a new compilation
*/
get shaderPath() {
return this._shaderPath;
}
/**
* Sets the shader path used to define the shader code
* It can be modified to trigger a new compilation
*/
set shaderPath(shaderPath) {
this._shaderPath = shaderPath;
}
/**
* Gets the options used to compile the shader.
* They can be modified to trigger a new compilation
*/
get options() {
return this._options;
}
/**
* is multiview set to true?
*/
get isMultiview() {
return this._multiview;
}
/**
* Gets the current class name of the material e.g. "ShaderMaterial"
* Mainly use in serialization.
* @returns the class name
*/
getClassName() {
return "ShaderMaterial";
}
/**
* Specifies if the material will require alpha blending
* @returns a boolean specifying if alpha blending is needed
*/
needAlphaBlending() {
return this.alpha < 1.0 || this._options.needAlphaBlending;
}
/**
* Specifies if this material should be rendered in alpha test mode
* @returns a boolean specifying if an alpha test is needed.
*/
needAlphaTesting() {
return this._options.needAlphaTesting;
}
_checkUniform(uniformName) {
if (this._options.uniforms.indexOf(uniformName) === -1) {
this._options.uniforms.push(uniformName);
}
}
/**
* Set a texture in the shader.
* @param name Define the name of the uniform samplers as defined in the shader
* @param texture Define the texture to bind to this sampler
* @returns the material itself allowing "fluent" like uniform updates
*/
setTexture(name, texture) {
if (this._options.samplers.indexOf(name) === -1) {
this._options.samplers.push(name);
}
this._textures[name] = texture;
return this;
}
/**
* Remove a texture from the material.
* @param name Define the name of the texture to remove
*/
removeTexture(name) {
delete this._textures[name];
}
/**
* Set a texture array in the shader.
* @param name Define the name of the uniform sampler array as defined in the shader
* @param textures Define the list of textures to bind to this sampler
* @returns the material itself allowing "fluent" like uniform updates
*/
setTextureArray(name, textures) {
if (this._options.samplers.indexOf(name) === -1) {
this._options.samplers.push(name);
}
this._checkUniform(name);
this._textureArrays[name] = textures;
return this;
}
/**
* Set an internal texture in the shader.
* @param name Define the name of the uniform samplers as defined in the shader
* @param texture Define the texture to bind to this sampler
* @returns the material itself allowing "fluent" like uniform updates
*/
setExternalTexture(name, texture) {
if (this._options.externalTextures.indexOf(name) === -1) {
this._options.externalTextures.push(name);
}
this._externalTextures[name] = texture;
return this;
}
/**
* Set a float in the shader.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setFloat(name, value) {
this._checkUniform(name);
this._floats[name] = value;
return this;
}
/**
* Set a int in the shader.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setInt(name, value) {
this._checkUniform(name);
this._ints[name] = value;
return this;
}
/**
* Set a unsigned int in the shader.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setUInt(name, value) {
this._checkUniform(name);
this._uints[name] = value;
return this;
}
/**
* Set an array of floats in the shader.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setFloats(name, value) {
this._checkUniform(name);
this._floatsArrays[name] = value;
return this;
}
/**
* Set a vec3 in the shader from a Color3.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setColor3(name, value) {
this._checkUniform(name);
this._colors3[name] = value;
return this;
}
/**
* Set a vec3 array in the shader from a Color3 array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setColor3Array(name, value) {
this._checkUniform(name);
this._colors3Arrays[name] = value.reduce((arr, color) => {
color.toArray(arr, arr.length);
return arr;
}, []);
return this;
}
/**
* Set a vec4 in the shader from a Color4.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setColor4(name, value) {
this._checkUniform(name);
this._colors4[name] = value;
return this;
}
/**
* Set a vec4 array in the shader from a Color4 array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setColor4Array(name, value) {
this._checkUniform(name);
this._colors4Arrays[name] = value.reduce((arr, color) => {
color.toArray(arr, arr.length);
return arr;
}, []);
return this;
}
/**
* Set a vec2 in the shader from a Vector2.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setVector2(name, value) {
this._checkUniform(name);
this._vectors2[name] = value;
return this;
}
/**
* Set a vec3 in the shader from a Vector3.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setVector3(name, value) {
this._checkUniform(name);
this._vectors3[name] = value;
return this;
}
/**
* Set a vec4 in the shader from a Vector4.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setVector4(name, value) {
this._checkUniform(name);
this._vectors4[name] = value;
return this;
}
/**
* Set a vec4 in the shader from a Quaternion.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setQuaternion(name, value) {
this._checkUniform(name);
this._quaternions[name] = value;
return this;
}
/**
* Set a vec4 array in the shader from a Quaternion array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setQuaternionArray(name, value) {
this._checkUniform(name);
this._quaternionsArrays[name] = value.reduce((arr, quaternion) => {
quaternion.toArray(arr, arr.length);
return arr;
}, []);
return this;
}
/**
* Set a mat4 in the shader from a Matrix.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setMatrix(name, value) {
this._checkUniform(name);
this._matrices[name] = value;
return this;
}
/**
* Set a float32Array in the shader from a matrix array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setMatrices(name, value) {
this._checkUniform(name);
const float32Array = new Float32Array(value.length * 16);
for (let index = 0; index < value.length; index++) {
const matrix = value[index];
matrix.copyToArray(float32Array, index * 16);
}
this._matrixArrays[name] = float32Array;
return this;
}
/**
* Set a mat3 in the shader from a Float32Array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setMatrix3x3(name, value) {
this._checkUniform(name);
this._matrices3x3[name] = value;
return this;
}
/**
* Set a mat2 in the shader from a Float32Array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setMatrix2x2(name, value) {
this._checkUniform(name);
this._matrices2x2[name] = value;
return this;
}
/**
* Set a vec2 array in the shader from a number array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setArray2(name, value) {
this._checkUniform(name);
this._vectors2Arrays[name] = value;
return this;
}
/**
* Set a vec3 array in the shader from a number array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setArray3(name, value) {
this._checkUniform(name);
this._vectors3Arrays[name] = value;
return this;
}
/**
* Set a vec4 array in the shader from a number array.
* @param name Define the name of the uniform as defined in the shader
* @param value Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setArray4(name, value) {
this._checkUniform(name);
this._vectors4Arrays[name] = value;
return this;
}
/**
* Set a uniform buffer in the shader
* @param name Define the name of the uniform as defined in the shader
* @param buffer Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setUniformBuffer(name, buffer) {
if (this._options.uniformBuffers.indexOf(name) === -1) {
this._options.uniformBuffers.push(name);
}
this._uniformBuffers[name] = buffer;
return this;
}
/**
* Set a texture sampler in the shader
* @param name Define the name of the uniform as defined in the shader
* @param sampler Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setTextureSampler(name, sampler) {
if (this._options.samplerObjects.indexOf(name) === -1) {
this._options.samplerObjects.push(name);
}
this._textureSamplers[name] = sampler;
return this;
}
/**
* Set a storage buffer in the shader
* @param name Define the name of the storage buffer as defined in the shader
* @param buffer Define the value to give to the uniform
* @returns the material itself allowing "fluent" like uniform updates
*/
setStorageBuffer(name, buffer) {
if (this._options.storageBuffers.indexOf(name) === -1) {
this._options.storageBuffers.push(name);
}
this._storageBuffers[name] = buffer;
return this;
}
/**
* Adds, removes, or replaces the specified shader define and value.
* * setDefine("MY_DEFINE", true); // enables a boolean define
* * setDefine("MY_DEFINE", "0.5"); // adds "#define MY_DEFINE 0.5" to the shader (or sets and replaces the value of any existing define with that name)
* * setDefine("MY_DEFINE", false); // disables and removes the define
* Note if the active defines do change, the shader will be recompiled and this can be expensive.
* @param define the define name e.g., "OUTPUT_TO_SRGB" or "#define OUTPUT_TO_SRGB". If the define was passed into the constructor already, the version used should match that, and in either case, it should not include any appended value.
* @param value either the value of the define (e.g. a numerical value) or for booleans, true if the define should be enabled or false if it should be disabled
* @returns the material itself allowing "fluent" like uniform updates
*/
setDefine(define, value) {
// First remove any existing define with this name.
const defineName = define.trimEnd() + " ";
const existingDefineIdx = this.options.defines.findIndex((x) => x === define || x.startsWith(defineName));
if (existingDefineIdx >= 0) {
this.options.defines.splice(existingDefineIdx, 1);
}
// Then add the new define value. (If it's a boolean value and false, don't add it.)
if (typeof value !== "boolean" || value) {
this.options.defines.push(defineName + value);
}
return this;
}
/**
* Specifies that the submesh is ready to be used
* @param mesh defines the mesh to check
* @param subMesh defines which submesh to check
* @param useInstances specifies that instances should be used
* @returns a boolean indicating that the submesh is ready or not
*/
isReadyForSubMesh(mesh, subMesh, useInstances) {
return this.isReady(mesh, useInstances, subMesh);
}
/**
* Checks if the material is ready to render the requested mesh
* @param mesh Define the mesh to render
* @param useInstances Define whether or not the material is used with instances
* @param subMesh defines which submesh to render
* @returns true if ready, otherwise false
*/
isReady(mesh, useInstances, subMesh) {
const storeEffectOnSubMeshes = subMesh && this._storeEffectOnSubMeshes;
if (this.isFrozen) {
const drawWrapper = storeEffectOnSubMeshes ? subMesh._drawWrapper : this._drawWrapper;
if (drawWrapper.effect && drawWrapper._wasPreviouslyReady && drawWrapper._wasPreviouslyUsingInstances === useInstances) {
return true;
}
}
const scene = this.getScene();
const engine = scene.getEngine();
// Instances
const defines = [];
const attribs = [];
let fallbacks = null;
let shaderName = this._shaderPath, uniforms = this._options.uniforms, uniformBuffers = this._options.uniformBuffers, samplers = this._options.samplers;
// global multiview
if (engine.getCaps().multiview && scene.activeCamera && scene.activeCamera.outputRenderTarget && scene.activeCamera.outputRenderTarget.getViewCount() > 1) {
this._multiview = true;
defines.push("#define MULTIVIEW");
if (uniforms.indexOf("viewProjection") !== -1 && uniforms.indexOf("viewProjectionR") === -1) {
uniforms.push("viewProjectionR");
}
}
for (let index = 0; index < this._options.defines.length; index++) {
const defineToAdd = this._options.defines[index].indexOf("#define") === 0 ? this._options.defines[index] : `#define ${this._options.defines[index]}`;
defines.push(defineToAdd);
}
for (let index = 0; index < this._options.attributes.length; index++) {
attribs.push(this._options.attributes[index]);
}
if (mesh && mesh.isVerticesDataPresent(VertexBuffer.ColorKind)) {
if (attribs.indexOf(VertexBuffer.ColorKind) === -1) {
attribs.push(VertexBuffer.ColorKind);
}
defines.push("#define VERTEXCOLOR");
}
if (useInstances) {
defines.push("#define INSTANCES");
PushAttributesForInstances(attribs, this._materialHelperNeedsPreviousMatrices);
if (mesh?.hasThinInstances) {
defines.push("#define THIN_INSTANCES");
if (mesh && mesh.isVerticesDataPresent(VertexBuffer.ColorInstanceKind)) {
attribs.push(VertexBuffer.ColorInstanceKind);
defines.push("#define INSTANCESCOLOR");
}
}
}
// Bones
if (mesh && mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
attribs.push(VertexBuffer.MatricesIndicesKind);
attribs.push(VertexBuffer.MatricesWeightsKind);
if (mesh.numBoneInfluencers > 4) {
attribs.push(VertexBuffer.MatricesIndicesExtraKind);
attribs.push(VertexBuffer.MatricesWeightsExtraKind);
}
const skeleton = mesh.skeleton;
defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
fallbacks = new EffectFallbacks();
fallbacks.addCPUSkinningFallback(0, mesh);
if (skeleton.isUsingTextureForMatrices) {
defines.push("#define BONETEXTURE");
if (uniforms.indexOf("boneTextureWidth") === -1) {
uniforms.push("boneTextureWidth");
}
if (this._options.samplers.indexOf("boneSampler") === -1) {
this._options.samplers.push("boneSampler");
}
}
else {
defines.push("#define BonesPerMesh " + (skeleton.bones.length + 1));
if (uniforms.indexOf("mBones") === -1) {
uniforms.push("mBones");
}
}
}
else {
defines.push("#define NUM_BONE_INFLUENCERS 0");
}
// Morph
let numInfluencers = 0;
const manager = mesh ? mesh.morphTargetManager : null;
if (manager) {
const uv = defines.indexOf("#define UV1") !== -1;
const uv2 = defines.indexOf("#define UV2") !== -1;
const tangent = defines.indexOf("#define TANGENT") !== -1;
const normal = defines.indexOf("#define NORMAL") !== -1;
const color = defines.indexOf("#define VERTEXCOLOR") !== -1;
numInfluencers = PrepareDefinesAndAttributesForMorphTargets(manager, defines, attribs, mesh, true, // usePositionMorph
normal, // useNormalMorph
tangent, // useTangentMorph
uv, // useUVMorph
uv2, // useUV2Morph
color // useColorMorph
);
if (manager.isUsingTextureForTargets) {
if (uniforms.indexOf("morphTargetTextureIndices") === -1) {
uniforms.push("morphTargetTextureIndices");
}
if (this._options.samplers.indexOf("morphTargets") === -1) {
this._options.samplers.push("morphTargets");
}
}
if (numInfluencers > 0) {
uniforms = uniforms.slice();
uniforms.push("morphTargetInfluences");
uniforms.push("morphTargetCount");
uniforms.push("morphTargetTextureInfo");
uniforms.push("morphTargetTextureIndices");
}
}
else {
defines.push("#define NUM_MORPH_INFLUENCERS 0");
}
// Baked Vertex Animation
if (mesh) {
const bvaManager = mesh.bakedVertexAnimationManager;
if (bvaManager && bvaManager.isEnabled) {
defines.push("#define BAKED_VERTEX_ANIMATION_TEXTURE");
if (uniforms.indexOf("bakedVertexAnimationSettings") === -1) {
uniforms.push("bakedVertexAnimationSettings");
}
if (uniforms.indexOf("bakedVertexAnimationTextureSizeInverted") === -1) {
uniforms.push("bakedVertexAnimationTextureSizeInverted");
}
if (uniforms.indexOf("bakedVertexAnimationTime") === -1) {
uniforms.push("bakedVertexAnimationTime");
}
if (this._options.samplers.indexOf("bakedVertexAnimationTexture") === -1) {
this._options.samplers.push("bakedVertexAnimationTexture");
}
}
PrepareAttributesForBakedVertexAnimation(attribs, mesh, defines);
}
// Textures
for (const name in this._textures) {
if (!this._textures[name].isReady()) {
return false;
}
}
// Alpha test
if (mesh && this.needAlphaTestingForMesh(mesh)) {
defines.push("#define ALPHATEST");
}
// Clip planes
if (this._options.useClipPlane !== false) {
addClipPlaneUniforms(uniforms);
prepareStringDefinesForClipPlanes(this, scene, defines);
}
// Fog
if (scene.fogEnabled && mesh?.applyFog && scene.fogMode !== Scene.FOGMODE_NONE) {
defines.push("#define FOG");
if (uniforms.indexOf("view") === -1) {
uniforms.push("view");
}
if (uniforms.indexOf("vFogInfos") === -1) {
uniforms.push("vFogInfos");
}
if (uniforms.indexOf("vFogColor") === -1) {
uniforms.push("vFogColor");
}
}
// Misc
if (this._useLogarithmicDepth) {
defines.push("#define LOGARITHMICDEPTH");
if (uniforms.indexOf("logarithmicDepthConstant") === -1) {
uniforms.push("logarithmicDepthConstant");
}
}
if (this.customShaderNameResolve) {
uniforms = uniforms.slice();
uniformBuffers = uniformBuffers.slice();
samplers = samplers.slice();
shaderName = this.customShaderNameResolve(this.name, uniforms, uniformBuffers, samplers, defines, attribs);
}
const drawWrapper = storeEffectOnSubMeshes ? subMesh._getDrawWrapper(undefined, true) : this._drawWrapper;
const previousEffect = drawWrapper?.effect ?? null;
const previousDefines = drawWrapper?.defines ?? null;
const join = defines.join("\n");
let effect = previousEffect;
if (previousDefines !== join) {
effect = engine.createEffect(shaderName, {
attributes: attribs,
uniformsNames: uniforms,
uniformBuffersNames: uniformBuffers,
samplers: samplers,
defines: join,
fallbacks: fallbacks,
onCompiled: this.onCompiled,
onError: this.onError,
indexParameters: { maxSimultaneousMorphTargets: numInfluencers },
shaderLanguage: this._options.shaderLanguage,
extraInitializationsAsync: this._options.extraInitializationsAsync,
}, engine);
if (storeEffectOnSubMeshes) {
subMesh.setEffect(effect, join, this._materialContext);
}
else if (drawWrapper) {
drawWrapper.setEffect(effect, join);
}
if (this._onEffectCreatedObservable) {
onCreatedEffectParameters.effect = effect;
onCreatedEffectParameters.subMesh = subMesh ?? mesh?.subMeshes[0] ?? null;
this._onEffectCreatedObservable.notifyObservers(onCreatedEffectParameters);
}
}
drawWrapper._wasPreviouslyUsingInstances = !!useInstances;
if (!effect?.isReady()) {
return false;
}
if (previousEffect !== effect) {
scene.resetCachedMaterial();
}
drawWrapper._wasPreviouslyReady = true;
return true;
}
/**
* Binds the world matrix to the material
* @param world defines the world transformation matrix
* @param effectOverride - If provided, use this effect instead of internal effect
*/
bindOnlyWorldMatrix(world, effectOverride) {
const effect = effectOverride ?? this.getEffect();
if (!effect) {
return;
}
const uniforms = this._options.uniforms;
if (uniforms.indexOf("world") !== -1) {
effect.setMatrix("world", world);
}
const scene = this.getScene();
if (uniforms.indexOf("worldView") !== -1) {
world.multiplyToRef(scene.getViewMatrix(), this._cachedWorldViewMatrix);
effect.setMatrix("worldView", this._cachedWorldViewMatrix);
}
if (uniforms.indexOf("worldViewProjection") !== -1) {
world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
effect.setMatrix("worldViewProjection", this._cachedWorldViewProjectionMatrix);
}
if (uniforms.indexOf("view") !== -1) {
effect.setMatrix("view", scene.getViewMatrix());
}
}
/**
* Binds the submesh to this material by preparing the effect and shader to draw
* @param world defines the world transformation matrix
* @param mesh defines the mesh containing the submesh
* @param subMesh defines the submesh to bind the material to
*/
bindForSubMesh(world, mesh, subMesh) {
this.bind(world, mesh, subMesh._drawWrapperOverride?.effect, subMesh);
}
/**
* Binds the material to the mesh
* @param world defines the world transformation matrix
* @param mesh defines the mesh to bind the material to
* @param effectOverride - If provided, use this effect instead of internal effect
* @param subMesh defines the submesh to bind the material to
*/
bind(world, mesh, effectOverride, subMesh) {
// Std values
const storeEffectOnSubMeshes = subMesh && this._storeEffectOnSubMeshes;
const effect = effectOverride ?? (storeEffectOnSubMeshes ? subMesh.effect : this.getEffect());
if (!effect) {
return;
}
const scene = this.getScene();
this._activeEffect = effect;
this.bindOnlyWorldMatrix(world, effectOverride);
const uniformBuffers = this._options.uniformBuffers;
let useSceneUBO = false;
if (effect && uniformBuffers && uniformBuffers.length > 0 && scene.getEngine().supportsUniformBuffers) {
for (let i = 0; i < uniformBuffers.length; ++i) {
const bufferName = uniformBuffers[i];
switch (bufferName) {
case "Mesh":
if (mesh) {
mesh.getMeshUniformBuffer().bindToEffect(effect, "Mesh");
mesh.transferToEffect(world);
}
break;
case "Scene":
BindSceneUniformBuffer(effect, scene.getSceneUniformBuffer());
scene.finalizeSceneUbo();
useSceneUBO = true;
break;
}
}
}
const mustRebind = mesh && storeEffectOnSubMeshes ? this._mustRebind(scene, effect, subMesh, mesh.visibility) : scene.getCachedMaterial() !== this;
if (effect && mustRebind) {
if (!useSceneUBO && this._options.uniforms.indexOf("view") !== -1) {
effect.setMatrix("view", scene.getViewMatrix());
}
if (!useSceneUBO && this._options.uniforms.indexOf("projection") !== -1) {
effect.setMatrix("projection", scene.getProjectionMatrix());
}
if (!useSceneUBO && this._options.uniforms.indexOf("viewProjection") !== -1) {
effect.setMatrix("viewProjection", scene.getTransformMatrix());
if (this._multiview) {
effect.setMatrix("viewProjectionR", scene._transformMatrixR);
}
}
if (scene.activeCamera && this._options.uniforms.indexOf("cameraPosition") !== -1) {
effect.setVector3("cameraPosition", scene.activeCamera.globalPosition);
}
// Bones
BindBonesParameters(mesh, effect);
// Clip plane
bindClipPlane(effect, this, scene);
// Misc
if (this._useLogarithmicDepth) {
BindLogDepth(storeEffectOnSubMeshes ? subMesh.materialDefines : effect.defines, effect, scene);
}
// Fog
if (mesh) {
BindFogParameters(scene, mesh, effect);
}
let name;
// Texture
for (name in this._textures) {
effect.setTexture(name, this._textures[name]);
}
// Texture arrays
for (name in this._textureArrays) {
effect.setTextureArray(name, this._textureArrays[name]);
}
// Int
for (name in this._ints) {
effect.setInt(name, this._ints[name]);
}
// UInt
for (name in this._uints) {
effect.setUInt(name, this._uints[name]);
}
// Float
for (name in this._floats) {
effect.setFloat(name, this._floats[name]);
}
// Floats
for (name in this._floatsArrays) {
effect.setArray(name, this._floatsArrays[name]);
}
// Color3
for (name in this._colors3) {
effect.setColor3(name, this._colors3[name]);
}
// Color3Array
for (name in this._colors3Arrays) {
effect.setArray3(name, this._colors3Arrays[name]);
}
// Color4
for (name in this._colors4) {
const color = this._colors4[name];
effect.setFloat4(name, color.r, color.g, color.b, color.a);
}
// Color4Array
for (name in this._colors4Arrays) {
effect.setArray4(name, this._colors4Arrays[name]);
}
// Vector2
for (name in this._vectors2) {
effect.setVector2(name, this._vectors2[name]);
}
// Vector3
for (name in this._vectors3) {
effect.setVector3(name, this._vectors3[name]);
}
// Vector4
for (name in this._vectors4) {
effect.setVector4(name, this._vectors4[name]);
}
// Quaternion
for (name in this._quaternions) {
effect.setQuaternion(name, this._quaternions[name]);
}
// Matrix
for (name in this._matrices) {
effect.setMatrix(name, this._matrices[name]);
}
// MatrixArray
for (name in this._matrixArrays) {
effect.setMatrices(name, this._matrixArrays[name]);
}
// Matrix 3x3
for (name in this._matrices3x3) {
effect.setMatrix3x3(name, this._matrices3x3[name]);
}
// Matrix 2x2
for (name in this._matrices2x2) {
effect.setMatrix2x2(name, this._matrices2x2[name]);
}
// Vector2Array
for (name in this._vectors2Arrays) {
effect.setArray2(name, this._vectors2Arrays[name]);
}
// Vector3Array
for (name in this._vectors3Arrays) {
effect.setArray3(name, this._vectors3Arrays[name]);
}
// Vector4Array
for (name in this._vectors4Arrays) {
effect.setArray4(name, this._vectors4Arrays[name]);
}
// QuaternionArray
for (name in this._quaternionsArrays) {
effect.setArray4(name, this._quaternionsArrays[name]);
}
// Uniform buffers
for (name in this._uniformBuffers) {
const buffer = this._uniformBuffers[name].getBuffer();
if (buffer) {
effect.bindUniformBuffer(buffer, name);
}
}
const engineWebGPU = scene.getEngine();
// External texture
const setExternalTexture = engineWebGPU.setExternalTexture;
if (setExternalTexture) {
for (name in this._externalTextures) {
setExternalTexture.call(engineWebGPU, name, this._externalTextures[name]);
}
}
// Samplers
const setTextureSampler = engineWebGPU.setTextureSampler;
if (setTextureSampler) {
for (name in this._textureSamplers) {
setTextureSampler.call(engineWebGPU, name, this._textureSamplers[name]);
}
}
// Storage buffers
const setStorageBuffer = engineWebGPU.setStorageBuffer;
if (setStorageBuffer) {
for (name in this._storageBuffers) {
setStorageBuffer.call(engineWebGPU, name, this._storageBuffers[name]);
}
}
}
if (effect && mesh && (mustRebind || !this.isFrozen)) {
// Morph targets
BindMorphTargetParameters(mesh, effect);
if (mesh.morphTargetManager && mesh.morphTargetManager.isUsingTextureForTargets) {
mesh.morphTargetManager._bind(effect);
}
const bvaManager = mesh.bakedVertexAnimationManager;
if (bvaManager && bvaManager.isEnabled) {
const drawWrapper = storeEffectOnSubMeshes ? subMesh._drawWrapper : this._drawWrapper;
mesh.bakedVertexAnimationManager?.bind(effect, !!drawWrapper._wasPreviouslyUsingInstances);
}
}
this._afterBind(mesh, effect, subMesh);
}
/**
* Gets the active textures from the material
* @returns an array of textures
*/
getActiveTextures() {
const activeTextures = super.getActiveTextures();
for (const name in this._textures) {
activeTextures.push(this._textures[name]);
}
for (const name in this._textureArrays) {
const array = this._textureArrays[name];
for (let index = 0; index < array.length; index++) {
activeTextures.push(array[index]);
}
}
return activeTextures;
}
/**
* Specifies if the material uses a texture
* @param texture defines the texture to check against the material
* @returns a boolean specifying if the material uses the texture
*/
hasTexture(texture) {
if (super.hasTexture(texture)) {
return true;
}
for (const name in this._textures) {
if (this._textures[name] === texture) {
return true;
}
}
for (const name in this._textureArrays) {
const array = this._textureArrays[name];
for (let index = 0; index < array.length; index++) {
if (array[index] === texture) {
return true;
}
}
}
return false;
}
/**
* Makes a duplicate of the material, and gives it a new name
* @param name defines the new name for the duplicated material
* @returns the cloned material
*/
clone(name) {
const result = SerializationHelper.Clone(() => new ShaderMaterial(name, this.getScene(), this._shaderPath, this._options, this._storeEffectOnSubMeshes), this);
result.name = name;
result.id = name;
// Shader code path
if (typeof result._shaderPath === "object") {
result._shaderPath = { ...result._shaderPath };
}
// Options
this._options = { ...this._options };
const keys = Object.keys(this._options);
for (const propName of keys) {
const propValue = this._options[propName];
if (Array.isArray(propValue)) {
this._options[propName] = propValue.slice(0);
}
}
// Stencil
this.stencil.copyTo(result.stencil);
// Texture
for (const key in this._textures) {
result.setTexture(key, this._textures[key]);
}
// TextureArray
for (const key in this._textureArrays) {
result.setTextureArray(key, this._textureArrays[key]);
}
// External texture
for (const key in this._externalTextures) {
result.setExternalTexture(key, this._externalTextures[key]);
}
// Int
for (const key in this._ints) {
result.setInt(key, this._ints[key]);
}
// UInt
for (const key in this._uints) {
result.setUInt(key, this._uints[key]);
}
// Float
for (const key in this._floats) {
result.setFloat(key, this._floats[key]);
}
// Floats
for (const key in this._floatsArrays) {
result.setFloats(key, this._floatsArrays[key]);
}
// Color3
for (const key in this._colors3) {
result.setColor3(key, this._colors3[key]);
}
// Color3Array
for (const key in this._colors3Arrays) {
result._colors3Arrays[key] = this._colors3Arrays[key];
}
// Color4
for (const key in this._colors4) {
result.setColor4(key, this._colors4[key]);
}
// Color4Array
for (const key in this._colors4Arrays) {
result._colors4Arrays[key] = this._colors4Arrays[key];
}
// Vector2
for (const key in this._vectors2) {
result.setVector2(key, this._vectors2[key]);
}
// Vector3
for (const key in this._vectors3) {
result.setVector3(key, this._vectors3[key]);
}
// Vector4
for (const key in this._vectors4) {
result.setVector4(key, this._vectors4[key]);
}
// Quaternion
for (const key in this._quaternions) {
result.setQuaternion(key, this._quaternions[key]);
}
// QuaternionArray
for (const key in this._quaternionsArrays) {
result._quaternionsArrays[key] = this._quaternionsArrays[key];
}
// Matrix
for (const key in this._matrices) {
result.setMatrix(key, this._matrices[key]);
}
// MatrixArray
for (const key in this._matrixArrays) {
result._matrixArrays[key] = this._matrixArrays[key].slice();
}
// Matrix 3x3
for (const key in this._matrices3x3) {
result.setMatrix3x3(key, this._matrices3x3[key]);
}
// Matrix 2x2
for (const key in this._matrices2x2) {
result.setMatrix2x2(key, this._matrices2x2[key]);
}
// Vector2Array
for (const key in this._vectors2Arrays) {
result.setArray2(key, this._vectors2Arrays[key]);
}
// Vector3Array
for (const key in this._vectors3Arrays) {
result.setArray3(key, this._vectors3Arrays[key]);
}
// Vector4Array
for (const key in this._vectors4Arrays) {
result.setArray4(key, this._vectors4Arrays[key]);
}
// Uniform buffers
for (const key in this._uniformBuffers) {
result.setUniformBuffer(key, this._uniformBuffers[key]);
}
// Samplers
for (const key in this._textureSamplers) {
result.setTextureSampler(key, this._textureSamplers[key]);
}
// Storag buffers
for (const key in this._storageBuffers) {
result.setStorageBuffer(key, this._storageBuffers[key]);
}
return result;
}
/**
* Disposes the material
* @param forceDisposeEffect specifies if effects should be forcefully disposed
* @param forceDisposeTextures specifies if textures should be forcefully disposed
* @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
*/
dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh) {
if (forceDisposeTextures) {
let name;
for (name in this._textures) {
this._textures[name].dispose();
}
for (name in this._textureArrays) {
const array = this._textureArrays[name];
for (let index = 0; index < array.length; index++) {
array[index].dispose();
}
}
}
this._textures = {};
super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
}
/**
* Serializes this material in a JSON representation
* @returns the serialized material object
*/
serialize() {
const serializationObject = SerializationHelper.Serialize(this);
serializationObject.customType = "BABYLON.ShaderMaterial";
serializationObject.uniqueId = this.uniqueId;
serializationObject.options = this._options;
serializationObject.shaderPath = this._shaderPath;
serializationObject.storeEffectOnSubMeshes = this._storeEffectOnSubMeshes;
let name;
// Stencil
serializationObject.stencil = this.stencil.serialize();
// Texture
serializationObject.textures = {};
for (name in this._textures) {
serializationObject.textures[name] = this._textures[name].serialize();
}
// Texture arrays
serializationObject.textureArrays = {};
for (name in this._textureArrays) {
serializationObject.textureArrays[name] = [];
const array = this._textureArrays[name];
for (let index = 0; index < array.length; index++) {
serializationObject.textureArrays[name].push(array[index].serialize());
}
}
// Int
serializationObject.ints = {};
for (name in this._ints) {
serializationObject.ints[name] = this._ints[name];
}
// UInt
serializationObject.uints = {};
for (name in this._uints) {
serializationObject.uints[name] = this._uints[name];
}
// Float
serializationObject.floats = {};
for (name in this._floats) {
serializationObject.floats[name] = this._floats[name];
}
// Floats
serializationObject.floatsArrays = {};
for (name in this._floatsArrays) {
serializationObject.floatsArrays[name] = this._floatsArrays[name];
}
// Color3
serializationObject.colors3 = {};
for (name in this._colors3) {
serializationObject.colors3[name] = this._colors3[name].asArray();
}
// Color3 array
serializationObject.colors3Arrays = {};
for (name in this._colors3Arrays) {
serializationObject.colors3Arrays[name] = this._colors3Arrays[name];
}
// Color4
serializationObject.colors4 = {};
for (name in this._colors4) {
serializationObject.colors4[name] = this._colors4[name].asArray();
}
// Color4 array
serializationObject.colors4Arrays = {};
for (name in this._colors4Arrays) {
serializationObject.colors4Arrays[name] = this._colors4Arrays[name];
}
// Vector2
serializationObject.vectors2 = {};
for (name in this._vectors2) {
const v2 = this._vectors2[name];
serializationObject.vectors2[name] = [v2.x, v2.y];
}
// Vector3
serializationObject.vectors3 = {};
for (name in this._vectors3) {
cons