فهرست منبع

Merge pull request #8918 from BabylonJS/nme_procedural

Add support for procedural textures on nme
David Catuhe 5 سال پیش
والد
کامیت
81b3c128f1

+ 2 - 1
dist/preview release/what's new.md

@@ -8,7 +8,8 @@
 - Added HDR texture filtering tools to the sandbox ([Sebavan](https://github.com/sebavan/))
 - Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added SubSurfaceScattering on PBR materials ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
-- Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
+- Added edition of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
+- Added edition of procedural texture in the node material editor ([Deltakosh](https://github.com/deltakosh))
 - Added Curve editor to manage entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
 - Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 - Added support for **thin instances** for faster mesh instances. [Doc](https://doc.babylonjs.com/how_to/how_to_use_thininstances) ([Popov72](https://github.com/Popov72))

+ 3 - 0
nodeEditor/public/index.js

@@ -134,6 +134,9 @@ if (BABYLON.Engine.isSupported()) {
                 break;
             case BABYLON.NodeMaterialModes.Particle:
                 nodeMaterial.setToDefaultParticle();
+                break;                
+            case BABYLON.NodeMaterialModes.ProceduralTexture:
+                nodeMaterial.setToDefaultProceduralTexture();
                 break;
         }
         nodeMaterial.build(true);

+ 11 - 1
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -240,6 +240,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             Particle: ["ParticleBlendMultiplyBlock", "ParticleColorBlock", "ParticlePositionWorldBlock", "ParticleRampGradientBlock", "ParticleTextureBlock", "ParticleTextureMaskBlock", "ParticleUVBlock"],
             PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
             PostProcess: ["Position2DBlock", "CurrentScreenBlock"],
+            Procedural__Texture: ["Position2DBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],
             Round: ["RoundBlock", "CeilingBlock", "FloorBlock"],
             Scene: ["FogBlock", "CameraPositionBlock", "FogColorBlock", "ImageProcessingBlock", "LightBlock", "LightInformationBlock", "ViewDirectionBlock"],
@@ -249,16 +250,25 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             case NodeMaterialModes.Material:
                 delete allBlocks["PostProcess"];
                 delete allBlocks["Particle"];
+                delete allBlocks["Procedural__Texture"];
                 break;
             case NodeMaterialModes.PostProcess:
                 delete allBlocks["Animation"];
                 delete allBlocks["Mesh"];
                 delete allBlocks["Particle"];
+                delete allBlocks["Procedural__Texture"];
+                break;
+            case NodeMaterialModes.ProceduralTexture:
+                delete allBlocks["Animation"];
+                delete allBlocks["Mesh"];  
+                delete allBlocks["Particle"];              
+                delete allBlocks["PostProcess"];
                 break;
             case NodeMaterialModes.Particle:
                 delete allBlocks["Animation"];
                 delete allBlocks["Mesh"];
-                delete allBlocks["PostProcess"];
+                delete allBlocks["PostProcess"];            
+                delete allBlocks["Procedural__Texture"];
                 allBlocks.Output_Nodes.splice(allBlocks.Output_Nodes.indexOf("VertexOutputBlock"), 1);
                 break;
         }

+ 4 - 2
nodeEditor/src/components/preview/previewManager.ts

@@ -229,7 +229,8 @@ export class PreviewManager {
                 this._handleAnimations();
                 break;
             }
-            case NodeMaterialModes.PostProcess: {
+            case NodeMaterialModes.PostProcess: 
+            case NodeMaterialModes.ProceduralTexture: {
                 this._camera.radius = 4;
                 this._camera.upperRadiusLimit = 10;
                 break;
@@ -421,7 +422,8 @@ export class PreviewManager {
             }
 
             switch (this._globalState.mode) {
-                case NodeMaterialModes.PostProcess: {
+                case NodeMaterialModes.PostProcess: 
+                case NodeMaterialModes.ProceduralTexture: {
                     this._globalState.onIsLoadingChanged.notifyObservers(false);
 
                     this._postprocess = tempMaterial.createPostProcess(this._camera, 1.0, Constants.TEXTURE_NEAREST_SAMPLINGMODE, this._engine);

+ 18 - 1
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -287,6 +287,9 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                 case NodeMaterialModes.Particle:
                     this.props.globalState.nodeMaterial!.setToDefaultParticle();
                     break;
+                case NodeMaterialModes.ProceduralTexture:
+                    this.props.globalState.nodeMaterial!.setToDefaultProceduralTexture();
+                    break;
             }
         }
 
@@ -350,6 +353,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
             { label: "Material", value: NodeMaterialModes.Material },
             { label: "Post Process", value: NodeMaterialModes.PostProcess },
             { label: "Particle", value: NodeMaterialModes.Particle },
+            { label: "Procedural", value: NodeMaterialModes.ProceduralTexture },
         ];
 
         return (
@@ -366,7 +370,20 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                         <TextLineComponent label="Version" value={Engine.Version}/>
                         <TextLineComponent label="Help" value="doc.babylonjs.com" underline={true} onLink={() => window.open('https://doc.babylonjs.com/how_to/node_material', '_blank')}/>
                         <ButtonLineComponent label="Reset to default" onClick={() => {
-                            this.props.globalState.nodeMaterial!.setToDefault();
+                            switch (this.props.globalState.mode) {
+                                case NodeMaterialModes.Material:
+                                    this.props.globalState.nodeMaterial!.setToDefault();
+                                    break;
+                                case NodeMaterialModes.PostProcess:
+                                    this.props.globalState.nodeMaterial!.setToDefaultPostProcess();
+                                    break;
+                                case NodeMaterialModes.Particle:
+                                    this.props.globalState.nodeMaterial!.setToDefaultParticle();
+                                    break;
+                                case NodeMaterialModes.ProceduralTexture:
+                                    this.props.globalState.nodeMaterial!.setToDefaultProceduralTexture();
+                                    break;
+                            }
                             this.props.globalState.onResetRequiredObservable.notifyObservers();
                         }} />
                     </LineContainerComponent>

+ 1 - 1
nodeEditor/src/diagram/display/inputDisplayManager.ts

@@ -18,7 +18,7 @@ const inputNameToAttributeValue: { [name: string] : string } = {
 };
 
 const inputNameToAttributeName: { [name: string] : string } = {
-    "position2d" : "postprocess",
+    "position2d" : "screen",
     "particle_uv" : "particle",
     "particle_color" : "particle",
     "particle_texturemask": "particle",

+ 1 - 0
nodeEditor/src/nodeEditor.ts

@@ -60,6 +60,7 @@ export class NodeEditor {
         if (options.customLoadObservable) {
             options.customLoadObservable.add(data => {
                 SerializationTools.Deserialize(data, globalState);
+                globalState.mode = options.nodeMaterial.mode;
                 globalState.onResetRequiredObservable.notifyObservers();
                 globalState.onBuiltObservable.notifyObservers();
             })

+ 1 - 1
proceduralTexturesLibrary/src/normalMap/normalMapProceduralTexture.ts

@@ -16,7 +16,7 @@ export class NormalMapProceduralTexture extends ProceduralTexture {
 
     public updateShaderUniforms() {
         this.setTexture("baseSampler", this._baseTexture);
-        this.setFloat("size", this.getRenderSize());
+        this.setFloat("size", this.getRenderSize() as number);
     }
 
     public render(useCameraPostProcess?: boolean) {

+ 1 - 1
proceduralTexturesLibrary/src/perlinNoise/perlinNoiseProceduralTexture.ts

@@ -24,7 +24,7 @@ export class PerlinNoiseProceduralTexture extends ProceduralTexture {
     }
 
     public updateShaderUniforms() {
-        this.setFloat("size", this.getRenderSize());
+        this.setFloat("size", this.getRenderSize() as number);
 
         let scene = this.getScene();
 

+ 12 - 6
src/Engines/Extensions/engine.renderTarget.ts

@@ -5,7 +5,13 @@ import { Constants } from '../constants';
 import { ThinEngine } from '../thinEngine';
 import { DepthTextureCreationOptions } from '../depthTextureCreationOptions';
 
+/**
+ * Type used to define a render target texture size (either with a number or with a rect width and height)
+ */
+export type RenderTargetTextureSize = number | { width: number, height: number, layers?: number };
+
 declare module "../../Engines/thinEngine" {
+
     export interface ThinEngine {
         /**
          * Creates a new render target texture
@@ -13,7 +19,7 @@ declare module "../../Engines/thinEngine" {
          * @param options defines the options used to create the texture
          * @returns a new render target texture stored in an InternalTexture
          */
-        createRenderTargetTexture(size: number | { width: number, height: number, layers?: number }, options: boolean | RenderTargetCreationOptions): InternalTexture;
+        createRenderTargetTexture(size: RenderTargetTextureSize, options: boolean | RenderTargetCreationOptions): InternalTexture;
 
         /**
          * Creates a depth stencil texture.
@@ -22,14 +28,14 @@ declare module "../../Engines/thinEngine" {
          * @param options The options defining the texture.
          * @returns The texture
          */
-        createDepthStencilTexture(size: number | { width: number, height: number, layers?: number }, options: DepthTextureCreationOptions): InternalTexture;
+        createDepthStencilTexture(size: RenderTargetTextureSize, options: DepthTextureCreationOptions): InternalTexture;
 
         /** @hidden */
-        _createDepthStencilTexture(size: number | { width: number, height: number, layers?: number }, options: DepthTextureCreationOptions): InternalTexture;
+        _createDepthStencilTexture(size: RenderTargetTextureSize, options: DepthTextureCreationOptions): InternalTexture;
     }
 }
 
-ThinEngine.prototype.createRenderTargetTexture = function(this: ThinEngine, size: number | { width: number, height: number, layers?: number }, options: boolean | RenderTargetCreationOptions): InternalTexture {
+ThinEngine.prototype.createRenderTargetTexture = function(this: ThinEngine, size: RenderTargetTextureSize, options: boolean | RenderTargetCreationOptions): InternalTexture {
     const fullOptions = new RenderTargetCreationOptions();
     if (options !== undefined && typeof options === "object") {
         fullOptions.generateMipMaps = options.generateMipMaps;
@@ -126,7 +132,7 @@ ThinEngine.prototype.createRenderTargetTexture = function(this: ThinEngine, size
     return texture;
 };
 
-ThinEngine.prototype.createDepthStencilTexture = function(size: number | { width: number, height: number, layers?: number }, options: DepthTextureCreationOptions): InternalTexture {
+ThinEngine.prototype.createDepthStencilTexture = function(size: RenderTargetTextureSize, options: DepthTextureCreationOptions): InternalTexture {
     if (options.isCube) {
         let width = (<{ width: number, height: number }>size).width || <number>size;
         return this._createDepthStencilCubeTexture(width, options);
@@ -136,7 +142,7 @@ ThinEngine.prototype.createDepthStencilTexture = function(size: number | { width
     }
 };
 
-ThinEngine.prototype._createDepthStencilTexture = function(size: number | { width: number, height: number, layers?: number }, options: DepthTextureCreationOptions): InternalTexture {
+ThinEngine.prototype._createDepthStencilTexture = function(size: RenderTargetTextureSize, options: DepthTextureCreationOptions): InternalTexture {
     const gl = this._gl;
     const layers = (<{ width: number, height: number, layers?: number }>size).layers || 0;
     const target = layers !== 0 ? gl.TEXTURE_2D_ARRAY : gl.TEXTURE_2D;

+ 2 - 0
src/Materials/Node/Enums/nodeMaterialModes.ts

@@ -8,4 +8,6 @@ export enum NodeMaterialModes {
     PostProcess = 1,
     /** For particle system */
     Particle = 2,
+    /** For procedural texture */
+    ProceduralTexture = 3,
 }

+ 154 - 40
src/Materials/Node/nodeMaterial.ts

@@ -3,7 +3,7 @@ import { PushMaterial } from '../pushMaterial';
 import { Scene } from '../../scene';
 import { AbstractMesh } from '../../Meshes/abstractMesh';
 import { Matrix, Vector2 } from '../../Maths/math.vector';
-import { Color4 } from '../../Maths/math.color';
+import { Color3, Color4 } from '../../Maths/math.color';
 import { Mesh } from '../../Meshes/mesh';
 import { Engine } from '../../Engines/engine';
 import { NodeMaterialBuildState } from './nodeMaterialBuildState';
@@ -47,6 +47,9 @@ import { IParticleSystem } from '../../Particles/IParticleSystem';
 import { BaseParticleSystem } from '../../Particles/baseParticleSystem';
 import { ColorSplitterBlock } from './Blocks/colorSplitterBlock';
 import { TimingTools } from '../../Misc/timingTools';
+import { ProceduralTexture } from '../Textures/Procedurals/proceduralTexture';
+import { AnimatedInputBlockTypes } from './Blocks/Input/animatedInputBlockTypes';
+import { TrigonometryBlock, TrigonometryBlockOperations } from './Blocks/trigonometryBlock';
 
 const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
 
@@ -748,7 +751,7 @@ export class NodeMaterial extends PushMaterial {
     public createPostProcess(
         camera: Nullable<Camera>, options: number | PostProcessOptions = 1, samplingMode: number = Constants.TEXTURE_NEAREST_SAMPLINGMODE, engine?: Engine, reusable?: boolean,
         textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, textureFormat = Constants.TEXTUREFORMAT_RGBA): PostProcess {
-            return this._createEffectOrPostProcess(null, camera, options, samplingMode, engine, reusable, textureType, textureFormat);
+            return this._createEffectForPostProcess(null, camera, options, samplingMode, engine, reusable, textureType, textureFormat);
     }
 
     /**
@@ -756,10 +759,10 @@ export class NodeMaterial extends PushMaterial {
      * @param postProcess The post process to create the effect for
      */
     public createEffectForPostProcess(postProcess: PostProcess) {
-        this._createEffectOrPostProcess(postProcess);
+        this._createEffectForPostProcess(postProcess);
     }
 
-    private _createEffectOrPostProcess(postProcess: Nullable<PostProcess>,
+    private _createEffectForPostProcess(postProcess: Nullable<PostProcess>,
         camera?: Nullable<Camera>, options: number | PostProcessOptions = 1, samplingMode: number = Constants.TEXTURE_NEAREST_SAMPLINGMODE, engine?: Engine, reusable?: boolean,
         textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, textureFormat = Constants.TEXTUREFORMAT_RGBA): PostProcess {
         let tempName = this.name + this._buildId;
@@ -807,33 +810,80 @@ export class NodeMaterial extends PushMaterial {
                 );
             }
 
-            // Animated blocks
-            if (this._sharedData.animatedInputs) {
-                const scene = this.getScene();
+            this._checkInternals(effect);
+        });
 
-                let frameId = scene.getFrameId();
+        return postProcess;
+    }
 
-                if (this._animationFrame !== frameId) {
-                    for (var input of this._sharedData.animatedInputs) {
-                        input.animate(scene);
-                    }
+    /**
+     * Create a new procedural texture based on this node material
+     * @param size defines the size of the texture
+     * @param scene defines the hosting scene
+     * @returns the new procedural texture attached to this node material
+     */
+    public createProceduralTexture(size: number | { width: number, height: number, layers?: number }, scene: Scene): ProceduralTexture {
+        let tempName = this.name + this._buildId;
 
-                    this._animationFrame = frameId;
-                }
-            }
+        let proceduralTexture = new ProceduralTexture(tempName, size, null, scene);
+
+        const dummyMesh = new AbstractMesh(tempName + "Procedural", this.getScene());
+        dummyMesh.reservedDataStore = {
+            hidden: true
+        };
+
+        const defines = new NodeMaterialDefines();
+        let result = this._processDefines(dummyMesh, defines);
+        Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
+
+        let effect = this.getScene().getEngine().createEffect({
+                vertexElement: tempName,
+                fragmentElement: tempName
+            },
+            [VertexBuffer.PositionKind],
+            this._fragmentCompilationState.uniforms,
+            this._fragmentCompilationState.samplers,
+            defines.toString(), result?.fallbacks, undefined);
+
+        proceduralTexture.nodeMaterialSource = this;
+        proceduralTexture._effect = effect;
+
+        let buildId = this._buildId;
+        proceduralTexture.onBeforeGenerationObservable.add(() => {
+            if (buildId !== this._buildId) {
+                delete Effect.ShadersStore[tempName + "VertexShader"];
+                delete Effect.ShadersStore[tempName + "PixelShader"];
 
-            // Bindable blocks
-            for (var block of this._sharedData.bindableBlocks) {
-                block.bind(effect, this);
+                tempName = this.name + this._buildId;
+
+                defines.markAsUnprocessed();
+
+                buildId = this._buildId;
             }
 
-            // Connection points
-            for (var inputBlock of this._sharedData.inputBlocks) {
-                inputBlock._transmit(effect, this.getScene());
+            const result = this._processDefines(dummyMesh, defines);
+
+            if (result) {
+                Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
+
+                TimingTools.SetImmediate(() => {
+                    effect = this.getScene().getEngine().createEffect({
+                            vertexElement: tempName,
+                            fragmentElement: tempName
+                        },
+                        [VertexBuffer.PositionKind],
+                        this._fragmentCompilationState.uniforms,
+                        this._fragmentCompilationState.samplers,
+                        defines.toString(), result?.fallbacks, undefined);
+
+                    proceduralTexture._effect = effect;
+                });
             }
+
+            this._checkInternals(effect);
         });
 
-        return postProcess;
+        return proceduralTexture;
     }
 
     private _createEffectForParticles(particleSystem: IParticleSystem, blendMode: number, onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, effect?: Effect, defines?: NodeMaterialDefines, dummyMesh?: Nullable<AbstractMesh>, particleSystemDefinesJoined_ = "") {
@@ -847,6 +897,9 @@ export class NodeMaterial extends PushMaterial {
             dummyMesh = this.getScene().getMeshByName(this.name + "Particle");
             if (!dummyMesh) {
                 dummyMesh = new AbstractMesh(this.name + "Particle", this.getScene());
+                dummyMesh.reservedDataStore = {
+                    hidden: true
+                };
             }
         }
 
@@ -902,31 +955,35 @@ export class NodeMaterial extends PushMaterial {
                 return;
             }
 
-            // Animated blocks
-            if (this._sharedData.animatedInputs) {
-                const scene = this.getScene();
+            this._checkInternals(effect);
+        });
+    }
 
-                let frameId = scene.getFrameId();
+    private _checkInternals(effect: Effect) {
+         // Animated blocks
+         if (this._sharedData.animatedInputs) {
+            const scene = this.getScene();
 
-                if (this._animationFrame !== frameId) {
-                    for (var input of this._sharedData.animatedInputs) {
-                        input.animate(scene);
-                    }
+            let frameId = scene.getFrameId();
 
-                    this._animationFrame = frameId;
+            if (this._animationFrame !== frameId) {
+                for (var input of this._sharedData.animatedInputs) {
+                    input.animate(scene);
                 }
-            }
 
-            // Bindable blocks
-            for (var block of this._sharedData.bindableBlocks) {
-                block.bind(effect, this);
+                this._animationFrame = frameId;
             }
+        }
 
-            // Connection points
-            for (var inputBlock of this._sharedData.inputBlocks) {
-                inputBlock._transmit(effect, this.getScene());
-            }
-        });
+        // Bindable blocks
+        for (var block of this._sharedData.bindableBlocks) {
+            block.bind(effect, this);
+        }
+
+        // Connection points
+        for (var inputBlock of this._sharedData.inputBlocks) {
+            inputBlock._transmit(effect, this.getScene());
+        }
     }
 
     /**
@@ -1401,6 +1458,62 @@ export class NodeMaterial extends PushMaterial {
     }
 
     /**
+     * Clear the current material and set it to a default state for procedural texture
+     */
+    public setToDefaultProceduralTexture() {
+        this.clear();
+
+        this.editorData = null;
+
+        const position = new InputBlock("Position");
+        position.setAsAttribute("position2d");
+
+        const const1 = new InputBlock("Constant1");
+        const1.isConstant = true;
+        const1.value = 1;
+
+        const vmerger = new VectorMergerBlock("Position3D");
+
+        position.connectTo(vmerger);
+        const1.connectTo(vmerger, { input: "w" });
+
+        const vertexOutput = new VertexOutputBlock("VertexOutput");
+        vmerger.connectTo(vertexOutput);
+
+        // Pixel
+        var time = new InputBlock("Time");
+        time.value = 0;
+        time.min = 0;
+        time.max = 0;
+        time.isBoolean = false;
+        time.matrixMode = 0;
+        time.animationType = AnimatedInputBlockTypes.Time;
+        time.isConstant = false;
+
+        const color = new InputBlock("Color3");
+        color.value = new Color3(1, 1, 1);
+        color.isConstant = false;
+        var fragmentOutput = new FragmentOutputBlock("FragmentOutput");
+
+        var vectorMerger = new VectorMergerBlock("VectorMerger");
+        vectorMerger.visibleInInspector = false;
+
+        var cos = new TrigonometryBlock("Cos");
+        cos.operation = TrigonometryBlockOperations.Cos;
+
+        position.connectTo(vectorMerger);
+        time.output.connectTo(cos.input);
+        cos.output.connectTo(vectorMerger.z);
+        vectorMerger.xyzOut.connectTo(fragmentOutput.rgb);
+
+        // Add to nodes
+        this.addOutputNode(vertexOutput);
+        this.addOutputNode(fragmentOutput);
+
+        this._mode = NodeMaterialModes.ProceduralTexture;
+    }
+
+    /**
      * Clear the current material and set it to a default state for particle
      */
     public setToDefaultParticle() {
@@ -1784,6 +1897,7 @@ export class NodeMaterial extends PushMaterial {
      */
     public static CreateDefault(name: string, scene?: Scene) {
         let newMaterial = new NodeMaterial(name, scene);
+
         newMaterial.setToDefault();
         newMaterial.build();
 

+ 63 - 41
src/Materials/Textures/Procedurals/proceduralTexture.ts

@@ -19,6 +19,8 @@ import "../../../Engines/Extensions/engine.renderTargetCube";
 import "../../../Shaders/procedural.vertex";
 import { DataBuffer } from '../../../Meshes/dataBuffer';
 import { _TypeStore } from '../../../Misc/typeStore';
+import { NodeMaterial } from '../../Node/nodeMaterial';
+import { RenderTargetTextureSize } from '../../../Engines/Extensions/engine.renderTarget';
 
 /**
  * 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.
@@ -48,6 +50,16 @@ export class ProceduralTexture extends Texture {
      */
     public onGeneratedObservable = new Observable<ProceduralTexture>();
 
+    /**
+     * Event raised before the texture is generated
+     */
+    public onBeforeGenerationObservable = new Observable<ProceduralTexture>();
+
+    /**
+     * Gets or sets the node material used to create this texture (null if the texture was manually created)
+     */
+    public nodeMaterialSource: Nullable<NodeMaterial> = null;
+
     /** @hidden */
     @serialize()
     public _generateMipMaps: boolean;
@@ -62,7 +74,7 @@ export class ProceduralTexture extends Texture {
     protected _fallbackTexture: Nullable<Texture>;
 
     @serialize()
-    private _size: number;
+    private _size: RenderTargetTextureSize;
     private _currentRefreshId = -1;
     private _frameId = -1;
     private _refreshRate = 1;
@@ -102,7 +114,7 @@ export class ProceduralTexture extends 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)
      */
-    constructor(name: string, size: any, fragment: any, scene: Nullable<Scene>, fallbackTexture: Nullable<Texture> = null, generateMipMaps = true, isCube = false) {
+    constructor(name: string, size: RenderTargetTextureSize, fragment: any, scene: Nullable<Scene>, fallbackTexture: Nullable<Texture> = null, generateMipMaps = true, isCube = false) {
         super(null, scene, !generateMipMaps);
 
         scene = this.getScene()!;
@@ -125,7 +137,7 @@ export class ProceduralTexture extends Texture {
         this._fallbackTexture = fallbackTexture;
 
         if (isCube) {
-            this._texture = this._fullEngine.createRenderTargetCubeTexture(size, { generateMipMaps: generateMipMaps, generateDepthBuffer: false, generateStencilBuffer: false });
+            this._texture = this._fullEngine.createRenderTargetCubeTexture(size as number, { generateMipMaps: generateMipMaps, generateDepthBuffer: false, generateStencilBuffer: false });
             this.setFloat("face", 0);
         }
         else {
@@ -221,6 +233,10 @@ export class ProceduralTexture extends Texture {
         var engine = this._fullEngine;
         var shaders;
 
+        if (this.nodeMaterialSource) {
+            return this._effect.isReady();
+        }
+
         if (!this._fragment) {
             return false;
         }
@@ -325,9 +341,9 @@ export class ProceduralTexture extends Texture {
 
     /**
      * Get the size the texture is rendering at.
-     * @returns the size (texture is always squared)
+     * @returns the size (on cube texture it is always squared)
      */
-    public getRenderSize(): number {
+    public getRenderSize(): RenderTargetTextureSize {
         return this._size;
     }
 
@@ -489,52 +505,55 @@ export class ProceduralTexture extends Texture {
 
         // Render
         engine.enableEffect(this._effect);
+        this.onBeforeGenerationObservable.notifyObservers(this);
         engine.setState(false);
 
-        // Texture
-        for (var name in this._textures) {
-            this._effect.setTexture(name, this._textures[name]);
-        }
+        if (!this.nodeMaterialSource) {
+            // Texture
+            for (var name in this._textures) {
+                this._effect.setTexture(name, this._textures[name]);
+            }
 
-        // Float
-        for (name in this._ints) {
-            this._effect.setInt(name, this._ints[name]);
-        }
+            // Float
+            for (name in this._ints) {
+                this._effect.setInt(name, this._ints[name]);
+            }
 
-        // Float
-        for (name in this._floats) {
-            this._effect.setFloat(name, this._floats[name]);
-        }
+            // Float
+            for (name in this._floats) {
+                this._effect.setFloat(name, this._floats[name]);
+            }
 
-        // Floats
-        for (name in this._floatsArrays) {
-            this._effect.setArray(name, this._floatsArrays[name]);
-        }
+            // Floats
+            for (name in this._floatsArrays) {
+                this._effect.setArray(name, this._floatsArrays[name]);
+            }
 
-        // Color3
-        for (name in this._colors3) {
-            this._effect.setColor3(name, this._colors3[name]);
-        }
+            // Color3
+            for (name in this._colors3) {
+                this._effect.setColor3(name, this._colors3[name]);
+            }
 
-        // Color4
-        for (name in this._colors4) {
-            var color = this._colors4[name];
-            this._effect.setFloat4(name, color.r, color.g, color.b, color.a);
-        }
+            // Color4
+            for (name in this._colors4) {
+                var color = this._colors4[name];
+                this._effect.setFloat4(name, color.r, color.g, color.b, color.a);
+            }
 
-        // Vector2
-        for (name in this._vectors2) {
-            this._effect.setVector2(name, this._vectors2[name]);
-        }
+            // Vector2
+            for (name in this._vectors2) {
+                this._effect.setVector2(name, this._vectors2[name]);
+            }
 
-        // Vector3
-        for (name in this._vectors3) {
-            this._effect.setVector3(name, this._vectors3[name]);
-        }
+            // Vector3
+            for (name in this._vectors3) {
+                this._effect.setVector3(name, this._vectors3[name]);
+            }
 
-        // Matrix
-        for (name in this._matrices) {
-            this._effect.setMatrix(name, this._matrices[name]);
+            // Matrix
+            for (name in this._matrices) {
+                this._effect.setMatrix(name, this._matrices[name]);
+            }
         }
 
         if (!this._texture) {
@@ -632,6 +651,9 @@ export class ProceduralTexture extends Texture {
             this._indexBuffer = null;
         }
 
+        this.onGeneratedObservable.clear();
+        this.onBeforeGenerationObservable.clear();
+
         super.dispose();
     }
 }

+ 1 - 1
src/PostProcesses/postProcess.ts

@@ -337,7 +337,7 @@ export class PostProcess {
      * @param textureType Type of textures used when performing the post process. (default: 0)
      * @param vertexUrl The url of the vertex shader to be used. (default: "postprocess")
      * @param indexParameters The index parameters to be used for babylons include syntax "#include<kernelBlurVaryingDeclaration>[0..varyingCount]". (default: undefined) See usage in babylon.blurPostProcess.ts and kernelBlur.vertex.fx
-     * @param blockCompilation If the shader should not be compiled imediatly. (default: false)
+     * @param blockCompilation If the shader should not be compiled immediatly. (default: false)
      * @param textureFormat Format of textures used when performing the post process. (default: TEXTUREFORMAT_RGBA)
      */
     constructor(

BIN
tests/validation/ReferenceImages/procedural_nme.png


+ 6 - 1
tests/validation/config.json

@@ -1,6 +1,11 @@
 {
     "root": "https://cdn.babylonjs.com",
-    "tests": [    
+    "tests": [   
+        {
+            "title": "Procedural texture with NME",
+            "playgroundId": "#8S19ZC#1",            
+            "referenceImage": "procedural_nme.png"
+        },   
         {
             "title": "EXT_texture_webp",
             "playgroundId": "#LSAUH2#2",