@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.
663 lines (662 loc) • 26 kB
JavaScript
import { __decorate } from "../../../tslib.es6.js";
import { serialize } from "../../../Misc/decorators.js";
import { Observable } from "../../../Misc/observable.js";
import { VertexBuffer } from "../../../Buffers/buffer.js";
import { SceneComponentConstants } from "../../../sceneComponent.js";
import { Material } from "../../../Materials/material.js";
import { Texture } from "../../../Materials/Textures/texture.js";
import { RenderTargetTexture } from "../../../Materials/Textures/renderTargetTexture.js";
import { ProceduralTextureSceneComponent } from "./proceduralTextureSceneComponent.js";
import { RegisterClass } from "../../../Misc/typeStore.js";
import { EngineStore } from "../../../Engines/engineStore.js";
import { DrawWrapper } from "../../drawWrapper.js";
/**
* Procedural texturing is a way to programmatically create a texture. There are 2 types of procedural textures: code-only, and code that references some classic 2D images, sometimes calmpler' images.
* This is the base class of any Procedural texture and contains most of the shareable code.
* @see https://doc.babylonjs.com/features/featuresDeepDive/materials/using/proceduralTextures
*/
export class ProceduralTexture extends Texture {
/**
* Gets the shader language type used to generate vertex and fragment source code.
*/
get shaderLanguage() {
return this._shaderLanguage;
}
/**
* Instantiates a new procedural texture.
* Procedural texturing is a way to programmatically create a texture. There are 2 types of procedural textures: code-only, and code that references some classic 2D images, sometimes called 'refMaps' or 'sampler' images.
* This is the base class of any Procedural texture and contains most of the shareable code.
* @see https://doc.babylonjs.com/features/featuresDeepDive/materials/using/proceduralTextures
* @param name Define the name of the texture
* @param size Define the size of the texture to create
* @param fragment Define the fragment shader to use to generate the texture or null if it is defined later:
* * object: \{ fragmentElement: "fragmentShaderCode" \}, used with shader code in script tags
* * object: \{ fragmentSource: "fragment shader code string" \}, the string contains the shader code
* * string: the string contains a name "XXX" to lookup in Effect.ShadersStore["XXXFragmentShader"]
* @param scene Define the scene the texture belongs to
* @param fallbackTexture Define a fallback texture in case there were issues to create the custom texture
* @param generateMipMaps Define if the texture should creates mip maps or not
* @param isCube Define if the texture is a cube texture or not (this will render each faces of the cube)
* @param textureType The FBO internal texture type
*/
constructor(name, size, fragment, scene, fallbackTexture = null, generateMipMaps = true, isCube = false, textureType = 0) {
super(null, scene, !generateMipMaps);
/**
* Define if the texture is enabled or not (disabled texture will not render)
*/
this.isEnabled = true;
/**
* Define if the texture must be cleared before rendering (default is true)
*/
this.autoClear = true;
/**
* Event raised when the texture is generated
*/
this.onGeneratedObservable = new Observable();
/**
* Event raised before the texture is generated
*/
this.onBeforeGenerationObservable = new Observable();
/**
* Gets or sets the node material used to create this texture (null if the texture was manually created)
*/
this.nodeMaterialSource = null;
/**
* Define the list of custom preprocessor defines used in the shader
*/
this.defines = "";
/** @internal */
this._textures = {};
this._currentRefreshId = -1;
this._frameId = -1;
this._refreshRate = 1;
this._vertexBuffers = {};
this._uniforms = new Array();
this._samplers = new Array();
this._floats = {};
this._ints = {};
this._floatsArrays = {};
this._colors3 = {};
this._colors4 = {};
this._vectors2 = {};
this._vectors3 = {};
this._vectors4 = {};
this._matrices = {};
this._fallbackTextureUsed = false;
this._cachedDefines = null;
this._contentUpdateId = -1;
this._rtWrapper = null;
if (fallbackTexture !== null && !(fallbackTexture instanceof Texture)) {
this._options = fallbackTexture;
this._fallbackTexture = fallbackTexture.fallbackTexture ?? null;
}
else {
this._options = {};
this._fallbackTexture = fallbackTexture;
}
this._shaderLanguage = this._options.shaderLanguage ?? 0 /* ShaderLanguage.GLSL */;
scene = this.getScene() || EngineStore.LastCreatedScene;
let component = scene._getComponent(SceneComponentConstants.NAME_PROCEDURALTEXTURE);
if (!component) {
component = new ProceduralTextureSceneComponent(scene);
scene._addComponent(component);
}
scene.proceduralTextures.push(this);
this._fullEngine = scene.getEngine();
this.name = name;
this.isRenderTarget = true;
this._size = size;
this._textureType = textureType;
this._generateMipMaps = generateMipMaps;
this._drawWrapper = new DrawWrapper(this._fullEngine);
this.setFragment(fragment);
const rtWrapper = this._createRtWrapper(isCube, size, generateMipMaps, textureType);
this._texture = rtWrapper.texture;
// VBO
const vertices = [];
vertices.push(1, 1);
vertices.push(-1, 1);
vertices.push(-1, -1);
vertices.push(1, -1);
this._vertexBuffers[VertexBuffer.PositionKind] = new VertexBuffer(this._fullEngine, vertices, VertexBuffer.PositionKind, false, false, 2);
this._createIndexBuffer();
}
_createRtWrapper(isCube, size, generateMipMaps, textureType) {
if (isCube) {
this._rtWrapper = this._fullEngine.createRenderTargetCubeTexture(size, {
generateMipMaps: generateMipMaps,
generateDepthBuffer: false,
generateStencilBuffer: false,
type: textureType,
...this._options,
});
this.setFloat("face", 0);
}
else {
this._rtWrapper = this._fullEngine.createRenderTargetTexture(size, {
generateMipMaps: generateMipMaps,
generateDepthBuffer: false,
generateStencilBuffer: false,
type: textureType,
...this._options,
});
if (this._rtWrapper.is3D) {
this.setFloat("layer", 0);
this.setInt("layerNum", 0);
}
}
return this._rtWrapper;
}
/**
* The effect that is created when initializing the post process.
* @returns The created effect corresponding the postprocess.
*/
getEffect() {
return this._drawWrapper.effect;
}
/**
* @internal
*/
_setEffect(effect) {
this._drawWrapper.effect = effect;
}
/**
* Gets texture content (Use this function wisely as reading from a texture can be slow)
* @returns an ArrayBufferView promise (Uint8Array or Float32Array)
*/
getContent() {
if (this._contentData && this._frameId === this._contentUpdateId) {
return this._contentData;
}
if (this._contentData) {
this._contentData.then((buffer) => {
this._contentData = this.readPixels(0, 0, buffer);
this._contentUpdateId = this._frameId;
});
}
else {
this._contentData = this.readPixels(0, 0);
this._contentUpdateId = this._frameId;
}
return this._contentData;
}
_createIndexBuffer() {
const engine = this._fullEngine;
// Indices
const indices = [];
indices.push(0);
indices.push(1);
indices.push(2);
indices.push(0);
indices.push(2);
indices.push(3);
this._indexBuffer = engine.createIndexBuffer(indices);
}
/** @internal */
_rebuild() {
const vb = this._vertexBuffers[VertexBuffer.PositionKind];
if (vb) {
vb._rebuild();
}
this._createIndexBuffer();
if (this.refreshRate === RenderTargetTexture.REFRESHRATE_RENDER_ONCE) {
this.refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
}
}
/**
* Resets the texture in order to recreate its associated resources.
* This can be called in case of context loss or if you change the shader code and need to regenerate the texture with the new code
*/
reset() {
this._drawWrapper.effect?.dispose();
this._drawWrapper.effect = null;
this._cachedDefines = null;
}
_getDefines() {
return this.defines;
}
/**
* Executes a function when the texture will be ready to be drawn.
* @param func The callback to be used.
*/
executeWhenReady(func) {
if (this.isReady()) {
func(this);
return;
}
const effect = this.getEffect();
if (effect) {
effect.executeWhenCompiled(() => {
func(this);
});
}
}
/**
* Is the texture ready to be used ? (rendered at least once)
* @returns true if ready, otherwise, false.
*/
isReady() {
const engine = this._fullEngine;
if (this.nodeMaterialSource) {
return this._drawWrapper.effect.isReady();
}
if (!this._fragment) {
return false;
}
if (this._fallbackTextureUsed) {
return true;
}
if (!this._texture) {
return false;
}
const defines = this._getDefines();
if (this._drawWrapper.effect && defines === this._cachedDefines && this._drawWrapper.effect.isReady()) {
return true;
}
const shaders = {
vertex: "procedural",
fragmentElement: this._fragment.fragmentElement,
fragmentSource: this._fragment.fragmentSource,
fragment: typeof this._fragment === "string" ? this._fragment : undefined,
};
if (this._cachedDefines !== defines) {
this._cachedDefines = defines;
this._drawWrapper.effect = engine.createEffect(shaders, [VertexBuffer.PositionKind], this._uniforms, this._samplers, defines, undefined, undefined, () => {
this._rtWrapper?.dispose();
this._rtWrapper = this._texture = null;
if (this._fallbackTexture) {
this._texture = this._fallbackTexture._texture;
if (this._texture) {
this._texture.incrementReferences();
}
}
this._fallbackTextureUsed = true;
}, undefined, this._shaderLanguage, async () => {
if (this._options.extraInitializationsAsync) {
if (this.shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
await Promise.all([import("../../../ShadersWGSL/procedural.vertex.js"), this._options.extraInitializationsAsync()]);
}
else {
await Promise.all([import("../../../Shaders/procedural.vertex.js"), this._options.extraInitializationsAsync()]);
}
}
else {
if (this.shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
await import("../../../ShadersWGSL/procedural.vertex.js");
}
else {
await import("../../../Shaders/procedural.vertex.js");
}
}
});
}
return this._drawWrapper.effect.isReady();
}
/**
* Resets the refresh counter of the texture and start bak from scratch.
* Could be useful to regenerate the texture if it is setup to render only once.
*/
resetRefreshCounter() {
this._currentRefreshId = -1;
}
/**
* Set the fragment shader to use in order to render the texture.
* @param fragment This can be set to a path (into the shader store) or to a json object containing a fragmentElement property.
*/
setFragment(fragment) {
this._fragment = fragment;
}
/**
* Define the refresh rate of the texture or the rendering frequency.
* Use 0 to render just once, 1 to render on every frame, 2 to render every two frames and so on...
*/
get refreshRate() {
return this._refreshRate;
}
set refreshRate(value) {
this._refreshRate = value;
this.resetRefreshCounter();
}
/** @internal */
_shouldRender() {
if (!this.isEnabled || !this.isReady() || !this._texture) {
if (this._texture) {
this._texture.isReady = false;
}
return false;
}
if (this._fallbackTextureUsed) {
return false;
}
if (this._currentRefreshId === -1) {
// At least render once
this._currentRefreshId = 1;
this._frameId++;
return true;
}
if (this.refreshRate === this._currentRefreshId) {
this._currentRefreshId = 1;
this._frameId++;
return true;
}
this._currentRefreshId++;
return false;
}
/**
* Get the size the texture is rendering at.
* @returns the size (on cube texture it is always squared)
*/
getRenderSize() {
return this._size;
}
/**
* Resize the texture to new value.
* @param size Define the new size the texture should have
* @param generateMipMaps Define whether the new texture should create mip maps
*/
resize(size, generateMipMaps) {
if (this._fallbackTextureUsed || !this._rtWrapper || !this._texture) {
return;
}
const isCube = this._texture.isCube;
this._rtWrapper.dispose();
const rtWrapper = this._createRtWrapper(isCube, size, generateMipMaps, this._textureType);
this._texture = rtWrapper.texture;
// Update properties
this._size = size;
this._generateMipMaps = generateMipMaps;
}
_checkUniform(uniformName) {
if (this._uniforms.indexOf(uniformName) === -1) {
this._uniforms.push(uniformName);
}
}
/**
* Set a texture in the shader program used to render.
* @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 texture itself allowing "fluent" like uniform updates
*/
setTexture(name, texture) {
if (this._samplers.indexOf(name) === -1) {
this._samplers.push(name);
}
this._textures[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 texture 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 texture itself allowing "fluent" like uniform updates
*/
setInt(name, value) {
this._checkUniform(name);
this._ints[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 texture 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 texture itself allowing "fluent" like uniform updates
*/
setColor3(name, value) {
this._checkUniform(name);
this._colors3[name] = value;
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 texture itself allowing "fluent" like uniform updates
*/
setColor4(name, value) {
this._checkUniform(name);
this._colors4[name] = value;
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 texture 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 texture 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 texture itself allowing "fluent" like uniform updates
*/
setVector4(name, value) {
this._checkUniform(name);
this._vectors4[name] = value;
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 texture itself allowing "fluent" like uniform updates
*/
setMatrix(name, value) {
this._checkUniform(name);
this._matrices[name] = value;
return this;
}
/**
* Render the texture to its associated render target.
* @param useCameraPostProcess Define if camera post process should be applied to the texture
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render(useCameraPostProcess) {
const scene = this.getScene();
if (!scene) {
return;
}
const engine = this._fullEngine;
// Render
engine.enableEffect(this._drawWrapper);
this.onBeforeGenerationObservable.notifyObservers(this);
engine.setState(false);
if (!this.nodeMaterialSource) {
// Texture
for (const name in this._textures) {
this._drawWrapper.effect.setTexture(name, this._textures[name]);
}
// Float
for (const name in this._ints) {
this._drawWrapper.effect.setInt(name, this._ints[name]);
}
// Float
for (const name in this._floats) {
this._drawWrapper.effect.setFloat(name, this._floats[name]);
}
// Floats
for (const name in this._floatsArrays) {
this._drawWrapper.effect.setArray(name, this._floatsArrays[name]);
}
// Color3
for (const name in this._colors3) {
this._drawWrapper.effect.setColor3(name, this._colors3[name]);
}
// Color4
for (const name in this._colors4) {
const color = this._colors4[name];
this._drawWrapper.effect.setFloat4(name, color.r, color.g, color.b, color.a);
}
// Vector2
for (const name in this._vectors2) {
this._drawWrapper.effect.setVector2(name, this._vectors2[name]);
}
// Vector3
for (const name in this._vectors3) {
this._drawWrapper.effect.setVector3(name, this._vectors3[name]);
}
// Vector4
for (const name in this._vectors4) {
this._drawWrapper.effect.setVector4(name, this._vectors4[name]);
}
// Matrix
for (const name in this._matrices) {
this._drawWrapper.effect.setMatrix(name, this._matrices[name]);
}
}
if (!this._texture || !this._rtWrapper) {
return;
}
engine._debugPushGroup?.(`procedural texture generation for ${this.name}`, 1);
const viewPort = engine.currentViewport;
if (this.isCube) {
for (let face = 0; face < 6; face++) {
engine.bindFramebuffer(this._rtWrapper, face, undefined, undefined, true);
// VBOs
engine.bindBuffers(this._vertexBuffers, this._indexBuffer, this._drawWrapper.effect);
this._drawWrapper.effect.setFloat("face", face);
// Clear
if (this.autoClear) {
engine.clear(scene.clearColor, true, false, false);
}
// Draw order
engine.drawElementsType(Material.TriangleFillMode, 0, 6);
// Unbind and restore viewport
engine.unBindFramebuffer(this._rtWrapper, true);
}
}
else {
let numLayers = 1;
if (this._rtWrapper.is3D) {
numLayers = this._rtWrapper.depth;
}
else if (this._rtWrapper.is2DArray) {
numLayers = this._rtWrapper.layers;
}
for (let layer = 0; layer < numLayers; layer++) {
engine.bindFramebuffer(this._rtWrapper, 0, undefined, undefined, true, 0, layer);
// VBOs
engine.bindBuffers(this._vertexBuffers, this._indexBuffer, this._drawWrapper.effect);
if (this._rtWrapper.is3D || this._rtWrapper.is2DArray) {
this._drawWrapper.effect?.setFloat("layer", numLayers !== 1 ? layer / (numLayers - 1) : 0);
this._drawWrapper.effect?.setInt("layerNum", layer);
for (const name in this._textures) {
this._drawWrapper.effect.setTexture(name, this._textures[name]);
}
}
// Clear
if (this.autoClear) {
engine.clear(scene.clearColor, true, false, false);
}
// Draw order
engine.drawElementsType(Material.TriangleFillMode, 0, 6);
// Unbind and restore viewport
engine.unBindFramebuffer(this._rtWrapper, !this._generateMipMaps);
}
}
if (viewPort) {
engine.setViewport(viewPort);
}
// Mipmaps
if (this.isCube) {
engine.generateMipMapsForCubemap(this._texture, true);
}
engine._debugPopGroup?.(1);
if (this.onGenerated) {
this.onGenerated();
}
this.onGeneratedObservable.notifyObservers(this);
}
/**
* Clone the texture.
* @returns the cloned texture
*/
clone() {
const textureSize = this.getSize();
const newTexture = new ProceduralTexture(this.name, textureSize.width, this._fragment, this.getScene(), this._fallbackTexture, this._generateMipMaps);
// Base texture
newTexture.hasAlpha = this.hasAlpha;
newTexture.level = this.level;
// RenderTarget Texture
newTexture.coordinatesMode = this.coordinatesMode;
return newTexture;
}
/**
* Dispose the texture and release its associated resources.
*/
dispose() {
const scene = this.getScene();
if (!scene) {
return;
}
const index = scene.proceduralTextures.indexOf(this);
if (index >= 0) {
scene.proceduralTextures.splice(index, 1);
}
const vertexBuffer = this._vertexBuffers[VertexBuffer.PositionKind];
if (vertexBuffer) {
vertexBuffer.dispose();
this._vertexBuffers[VertexBuffer.PositionKind] = null;
}
if (this._indexBuffer && this._fullEngine._releaseBuffer(this._indexBuffer)) {
this._indexBuffer = null;
}
this.onGeneratedObservable.clear();
this.onBeforeGenerationObservable.clear();
super.dispose();
}
}
__decorate([
serialize()
], ProceduralTexture.prototype, "isEnabled", void 0);
__decorate([
serialize()
], ProceduralTexture.prototype, "autoClear", void 0);
__decorate([
serialize()
], ProceduralTexture.prototype, "_generateMipMaps", void 0);
__decorate([
serialize()
], ProceduralTexture.prototype, "_size", void 0);
__decorate([
serialize()
], ProceduralTexture.prototype, "refreshRate", null);
RegisterClass("BABYLON.ProceduralTexture", ProceduralTexture);
//# sourceMappingURL=proceduralTexture.js.map