Bläddra i källkod

Merge pull request #8097 from Popov72/nme-postprocess

NME: Create Post Processes
David Catuhe 5 år sedan
förälder
incheckning
8a293d9db1

BIN
assets/nme/currentScreenPostProcess.png


+ 8 - 0
nodeEditor/src/blockTools.ts

@@ -71,6 +71,7 @@ import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionB
 import { ClearCoatBlock } from 'babylonjs/Materials/Node/Blocks/PBR/clearCoatBlock';
 import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
 import { SubSurfaceBlock } from 'babylonjs/Materials/Node/Blocks/PBR/subSurfaceBlock';
+import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
 
 export class BlockTools {
     public static GetBlockFromString(data: string, scene: Scene, nodeMaterial: NodeMaterial) {
@@ -341,6 +342,11 @@ export class BlockTools {
                 meshPosition.setAsAttribute("position");
                 return meshPosition;
             }
+            case "Position2DBlock": {
+                let meshPosition = new InputBlock("position");
+                meshPosition.setAsAttribute("position2d");
+                return meshPosition;
+            }
             case "UVBlock": {
                 let meshUV = new InputBlock("uv");
                 meshUV.setAsAttribute("uv");
@@ -459,6 +465,8 @@ export class BlockTools {
                 return new RefractionBlock("Refraction");
             case "SubSurfaceBlock":
                 return new SubSurfaceBlock("SubSurface");
+            case "CurrentScreenBlock":
+                return new CurrentScreenBlock("CurrentScreen");
         }
 
         return null;

+ 26 - 0
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -3,6 +3,9 @@ import * as React from "react";
 import { GlobalState } from '../../globalState';
 import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
 import { DraggableLineComponent } from '../../sharedComponents/draggableLineComponent';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
+import { Observer } from 'babylonjs/Misc/observable';
+import { Nullable } from 'babylonjs/types';
 
 require("./nodeList.scss");
 
@@ -12,6 +15,8 @@ interface INodeListComponentProps {
 
 export class NodeListComponent extends React.Component<INodeListComponentProps, {filter: string}> {
 
+    private _onResetRequiredObserver: Nullable<Observer<void>>;
+
     private static _Tooltips: {[key: string]: string} = {
         "BonesBlock": "Provides a world matrix for each vertex, based on skeletal (bone/joint) animation",
         "MorphTargetsBlock": "Provides the final positions, normals, tangents, and uvs based on morph targets in a mesh",
@@ -126,12 +131,22 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "ClearCoatBlock": "PBR ClearCoat block",
         "RefractionBlock": "PBR Refraction block",
         "SubSurfaceBlock": "PBR SubSurface block",
+        "Position2DBlock": "A Vector2 representing the position of each vertex of the screen quad",
+        "CurrentScreenBlock": "The screen buffer used as input for the post process",
     };
 
     constructor(props: INodeListComponentProps) {
         super(props);
 
         this.state = { filter: "" };
+
+        this._onResetRequiredObserver = this.props.globalState.onResetRequiredObservable.add(() => {
+            this.forceUpdate();
+        });
+    }
+
+    componentWillUnmount() {
+        this.props.globalState.onResetRequiredObservable.remove(this._onResetRequiredObserver);
     }
 
     filterContent(filter: string) {
@@ -155,11 +170,22 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             Noises: ["RandomNumberBlock", "SimplexPerlin3DBlock", "WorleyNoise3DBlock"],
             Output_Nodes: ["VertexOutputBlock", "FragmentOutputBlock", "DiscardBlock"],
             PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
+            PostProcess: ["Position2DBlock", "CurrentScreenBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],
             Round: ["RoundBlock", "CeilingBlock", "FloorBlock"],
             Scene: ["FogBlock", "CameraPositionBlock", "FogColorBlock", "ImageProcessingBlock", "LightBlock", "LightInformationBlock", "ViewDirectionBlock"],
         };
 
+        switch (this.props.globalState.mode) {
+            case NodeMaterialModes.Material:
+                delete allBlocks["PostProcess"];
+                break;
+            case NodeMaterialModes.PostProcess:
+                delete allBlocks["Animation"];
+                delete allBlocks["Mesh"];
+                break;
+        }
+
         // Create node menu
         var blockMenu = [];
         for (var key in allBlocks) {

+ 55 - 46
nodeEditor/src/components/preview/previewAreaComponent.tsx

@@ -4,6 +4,7 @@ import { GlobalState } from '../../globalState';
 import { DataStorage } from 'babylonjs/Misc/dataStorage';
 import { Observer } from 'babylonjs/Misc/observable';
 import { Nullable } from 'babylonjs/types';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 
 const doubleSided: string = require("./svgs/doubleSided.svg");
 const depthPass: string = require("./svgs/depthPass.svg");
@@ -18,86 +19,94 @@ interface IPreviewAreaComponentProps {
 
 export class PreviewAreaComponent extends React.Component<IPreviewAreaComponentProps, {isLoading: boolean}> {
     private _onIsLoadingChangedObserver: Nullable<Observer<boolean>>;
+    private _onResetRequiredObserver: Nullable<Observer<void>>;
 
     constructor(props: IPreviewAreaComponentProps) {
         super(props);
         this.state = {isLoading: true};
 
         this._onIsLoadingChangedObserver = this.props.globalState.onIsLoadingChanged.add((state) => this.setState({isLoading: state}));
+
+        this._onResetRequiredObserver = this.props.globalState.onResetRequiredObservable.add(() => {
+            this.forceUpdate();
+        });
     }
 
     componentWillUnmount() {
         this.props.globalState.onIsLoadingChanged.remove(this._onIsLoadingChangedObserver);
+        this.props.globalState.onResetRequiredObservable.remove(this._onResetRequiredObserver);
     }
 
-    changeBackFaceCulling(value: boolean) {        
+    changeBackFaceCulling(value: boolean) {
         this.props.globalState.backFaceCulling = value;
         DataStorage.WriteBoolean("BackFaceCulling", value);
         this.props.globalState.onBackFaceCullingChanged.notifyObservers();
         this.forceUpdate();
     }
 
-    changeDepthPrePass(value: boolean) {        
+    changeDepthPrePass(value: boolean) {
         this.props.globalState.depthPrePass = value;
         DataStorage.WriteBoolean("DepthPrePass", value);
         this.props.globalState.onDepthPrePassChanged.notifyObservers();
         this.forceUpdate();
-    }    
+    }
 
     render() {
         return (
             <>
                 <div id="preview" style={{height: this.props.width + "px"}}>
                     <canvas id="preview-canvas"/>
-                    {                        
+                    {
                         <div className={"waitPanel" + (this.state.isLoading ? "" : " hidden")}>
                             Please wait, loading...
                         </div>
                     }
-                </div>                
-                <div id="preview-config-bar">              
-                    <div
-                        title="Render without back face culling"
-                        onClick={() => this.changeBackFaceCulling(!this.props.globalState.backFaceCulling)} className={"button back-face" + (!this.props.globalState.backFaceCulling ? " selected" : "")}>
-                        <img src={doubleSided} alt=""/>
-                    </div>
-                    <div
-                        title="Render with depth pre-pass"
-                        onClick={() => this.changeDepthPrePass(!this.props.globalState.depthPrePass)} className={"button depth-pass" + (this.props.globalState.depthPrePass ? " selected" : "")}>
-                            <img src={depthPass} alt=""/>
-                    </div>
-                    <div
-                        title="Turn on/off hemispheric light"  
-                        onClick={() => {
-                            this.props.globalState.hemisphericLight = !this.props.globalState.hemisphericLight;                            
-                            DataStorage.WriteBoolean("HemisphericLight", this.props.globalState.hemisphericLight);
-                            this.props.globalState.onLightUpdated.notifyObservers();
-                            this.forceUpdate();
-                        }} className={"button hemispheric-light" + (this.props.globalState.hemisphericLight ? " selected" : "")}>
-                        <img src={omni} alt=""/>
-                    </div>
-                    <div
-                        title="Turn on/off direction light #1"  
-                        onClick={() => {
-                            this.props.globalState.directionalLight1 = !this.props.globalState.directionalLight1;                       
-                            DataStorage.WriteBoolean("DirectionalLight1", this.props.globalState.directionalLight1);
-                            this.props.globalState.onLightUpdated.notifyObservers();
-                            this.forceUpdate();
-                        }} className={"button direction-light-1" + (this.props.globalState.directionalLight1 ? " selected" : "")}>
-                        <img src={directionalRight} alt=""/>
+                </div>
+                { this.props.globalState.mode !== NodeMaterialModes.PostProcess && <>
+                    <div id="preview-config-bar">
+                        <div
+                            title="Render without back face culling"
+                            onClick={() => this.changeBackFaceCulling(!this.props.globalState.backFaceCulling)} className={"button back-face" + (!this.props.globalState.backFaceCulling ? " selected" : "")}>
+                            <img src={doubleSided} alt=""/>
+                        </div>
+                        <div
+                            title="Render with depth pre-pass"
+                            onClick={() => this.changeDepthPrePass(!this.props.globalState.depthPrePass)} className={"button depth-pass" + (this.props.globalState.depthPrePass ? " selected" : "")}>
+                                <img src={depthPass} alt=""/>
+                        </div>
+                        <div
+                            title="Turn on/off hemispheric light"
+                            onClick={() => {
+                                this.props.globalState.hemisphericLight = !this.props.globalState.hemisphericLight;
+                                DataStorage.WriteBoolean("HemisphericLight", this.props.globalState.hemisphericLight);
+                                this.props.globalState.onLightUpdated.notifyObservers();
+                                this.forceUpdate();
+                            }} className={"button hemispheric-light" + (this.props.globalState.hemisphericLight ? " selected" : "")}>
+                            <img src={omni} alt=""/>
+                        </div>
+                        <div
+                            title="Turn on/off direction light #1"
+                            onClick={() => {
+                                this.props.globalState.directionalLight1 = !this.props.globalState.directionalLight1;
+                                DataStorage.WriteBoolean("DirectionalLight1", this.props.globalState.directionalLight1);
+                                this.props.globalState.onLightUpdated.notifyObservers();
+                                this.forceUpdate();
+                            }} className={"button direction-light-1" + (this.props.globalState.directionalLight1 ? " selected" : "")}>
+                            <img src={directionalRight} alt=""/>
 
+                        </div>
+                        <div
+                            title="Turn on/off direction light #0"
+                            onClick={() => {
+                                this.props.globalState.directionalLight0 = !this.props.globalState.directionalLight0;
+                                DataStorage.WriteBoolean("DirectionalLight0", this.props.globalState.directionalLight0);
+                                this.props.globalState.onLightUpdated.notifyObservers();
+                                this.forceUpdate();
+                            }} className={"button direction-light-0" + (this.props.globalState.directionalLight0 ? " selected" : "")}>
+                            <img src={directionalLeft} alt=""/>
+                        </div>
                     </div>
-                    <div
-                        title="Turn on/off direction light #0"  
-                        onClick={() => {
-                            this.props.globalState.directionalLight0 = !this.props.globalState.directionalLight0;                       
-                            DataStorage.WriteBoolean("DirectionalLight0", this.props.globalState.directionalLight0);
-                            this.props.globalState.onLightUpdated.notifyObservers();
-                            this.forceUpdate();
-                        }} className={"button direction-light-0" + (this.props.globalState.directionalLight0 ? " selected" : "")}>
-                        <img src={directionalLeft} alt=""/>
-                    </div>
-                </div>
+                </> }
             </>
         );
 

+ 127 - 94
nodeEditor/src/components/preview/previewManager.ts

@@ -18,11 +18,15 @@ import { DirectionalLight } from 'babylonjs/Lights/directionalLight';
 import { LogEntry } from '../log/logComponent';
 import { PointerEventTypes } from 'babylonjs/Events/pointerEvents';
 import { Color3 } from 'babylonjs/Maths/math.color';
+import { PostProcess } from 'babylonjs/PostProcesses/postProcess';
+import { Constants } from 'babylonjs/Engines/constants';
+import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 
 export class PreviewManager {
     private _nodeMaterial: NodeMaterial;
-    private _onBuildObserver: Nullable<Observer<NodeMaterial>>;    
-    private _onPreviewCommandActivatedObserver: Nullable<Observer<void>>;
+    private _onBuildObserver: Nullable<Observer<NodeMaterial>>;
+    private _onPreviewCommandActivatedObserver: Nullable<Observer<boolean>>;
     private _onAnimationCommandActivatedObserver: Nullable<Observer<void>>;
     private _onUpdateRequiredObserver: Nullable<Observer<void>>;
     private _onPreviewBackgroundChangedObserver: Nullable<Observer<void>>;
@@ -34,9 +38,10 @@ export class PreviewManager {
     private _meshes: AbstractMesh[];
     private _camera: ArcRotateCamera;
     private _material: NodeMaterial;
-    private _globalState: GlobalState;   
-    private _currentType: number; 
+    private _globalState: GlobalState;
+    private _currentType: number;
     private _lightParent: TransformNode;
+    private _postprocess: Nullable<PostProcess>;
 
     public constructor(targetCanvas: HTMLCanvasElement, globalState: GlobalState) {
         this._nodeMaterial = globalState.nodeMaterial;
@@ -47,13 +52,16 @@ export class PreviewManager {
             this._updatePreview(serializationObject);
         });
 
-        this._onPreviewCommandActivatedObserver = globalState.onPreviewCommandActivated.add(() => {
+        this._onPreviewCommandActivatedObserver = globalState.onPreviewCommandActivated.add((forceRefresh: boolean) => {
+            if (forceRefresh) {
+                this._currentType = -1;
+            }
             this._refreshPreviewMesh();
         });
 
         this._onLightUpdatedObserver = globalState.onLightUpdated.add(() => {
             this._prepareLights();
-        });        
+        });
 
         this._onUpdateRequiredObserver = globalState.onUpdateRequiredObservable.add(() => {
             let serializationObject = this._nodeMaterial.serialize();
@@ -61,7 +69,7 @@ export class PreviewManager {
         });
 
         this._onPreviewBackgroundChangedObserver = globalState.onPreviewBackgroundChanged.add(() => {
-            this._scene.clearColor = this._globalState.backgroundColor;    
+            this._scene.clearColor = this._globalState.backgroundColor;
         });
 
         this._onAnimationCommandActivatedObserver = globalState.onAnimationCommandActivated.add(() => {
@@ -74,7 +82,7 @@ export class PreviewManager {
 
         this._onDepthPrePassChangedObserver = globalState.onDepthPrePassChanged.add(() => {
             this._material.needDepthPrePass = this._globalState.depthPrePass;
-        });        
+        });
 
         this._engine = new Engine(targetCanvas, true);
         this._scene = new Scene(this._engine);
@@ -97,11 +105,11 @@ export class PreviewManager {
         });
 
    //     let cameraLastRotation = 0;
-        let lastOffsetX:number | undefined = undefined; 
-     //   const lightRotationParallaxSpeed = 0.5;        
-        const lightRotationSpeed = 0.01;        
+        let lastOffsetX: number | undefined = undefined;
+     //   const lightRotationParallaxSpeed = 0.5;
+        const lightRotationSpeed = 0.01;
 
-        this._scene.onPointerObservable.add(evt => {
+        this._scene.onPointerObservable.add((evt) => {
             if (this._globalState.controlCamera) {
                 return;
             }
@@ -119,7 +127,7 @@ export class PreviewManager {
                 lastOffsetX = evt.event.offsetX;
             }
 
-            var rotateLighting = (lastOffsetX - evt.event.offsetX) * lightRotationSpeed; 
+            var rotateLighting = (lastOffsetX - evt.event.offsetX) * lightRotationSpeed;
             this._lightParent.rotation.y += rotateLighting;
             lastOffsetX = evt.event.offsetX;
         });
@@ -131,7 +139,7 @@ export class PreviewManager {
         //     if (!this._globalState.controlCamera) {
         //         return;
         //     }
-        //     var rotateLighting = (this._camera.alpha - cameraLastRotation) * lightRotationParallaxSpeed; 
+        //     var rotateLighting = (this._camera.alpha - cameraLastRotation) * lightRotationParallaxSpeed;
         //     this._lightParent.rotate(Vector3.Up(), rotateLighting);
         //     cameraLastRotation = this._camera.alpha;
         // });
@@ -139,7 +147,7 @@ export class PreviewManager {
 
     private _handleAnimations() {
         this._scene.stopAllAnimations();
-                        
+
         if (this._globalState.rotatePreview) {
             for (var root of this._scene.rootNodes) {
                 let transformNode = root as TransformNode;
@@ -165,14 +173,14 @@ export class PreviewManager {
 
         // Create new lights based on settings
         if (this._globalState.hemisphericLight) {
-            new HemisphericLight("Hemispheric light", new Vector3(0, 1, 0), this._scene);            
+            new HemisphericLight("Hemispheric light", new Vector3(0, 1, 0), this._scene);
         }
 
         if (this._globalState.directionalLight0) {
             let dir0 = new DirectionalLight("Directional light #0", new Vector3(0.841626576496605, -0.2193391004130599, -0.49351298337996535), this._scene);
             dir0.intensity = 0.9;
             dir0.diffuse = new Color3(0.9294117647058824, 0.9725490196078431, 0.996078431372549);
-            dir0.specular = new Color3(0.9294117647058824, 0.9725490196078431, 0.996078431372549);      
+            dir0.specular = new Color3(0.9294117647058824, 0.9725490196078431, 0.996078431372549);
             dir0.parent = this._lightParent;
         }
 
@@ -180,49 +188,50 @@ export class PreviewManager {
             let dir1 = new DirectionalLight("Directional light #1", new Vector3(-0.9519937437504213, -0.24389315636999764, -0.1849974057546125), this._scene);
             dir1.intensity = 1.2;
             dir1.specular = new Color3(0.9803921568627451, 0.9529411764705882, 0.7725490196078432);
-            dir1.diffuse = new Color3(0.9803921568627451, 0.9529411764705882, 0.7725490196078432);        
-            dir1.parent = this._lightParent;                 
+            dir1.diffuse = new Color3(0.9803921568627451, 0.9529411764705882, 0.7725490196078432);
+            dir1.parent = this._lightParent;
         }
     }
 
     private _prepareMeshes() {
-        this._prepareLights();
-
-        // Framing
-        this._camera.useFramingBehavior = true;
-
-        var framingBehavior = this._camera.getBehaviorByName("Framing") as FramingBehavior;
-
-        setTimeout(() => { // Let the behavior activate first
-            framingBehavior.framingTime = 0;
-            framingBehavior.elevationReturnTime = -1;
-    
-            if (this._scene.meshes.length) {
-                var worldExtends = this._scene.getWorldExtends();
-                this._camera.lowerRadiusLimit = null;
-                this._camera.upperRadiusLimit = null;
-                framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
-            }
-    
-            this._camera.pinchPrecision = 200 / this._camera.radius;
-            this._camera.upperRadiusLimit = 5 * this._camera.radius;    
-        });
+        if (this._globalState.mode !== NodeMaterialModes.PostProcess) {
+            this._prepareLights();
+
+            // Framing
+            this._camera.useFramingBehavior = true;
 
-        this._camera.wheelDeltaPercentage = 0.01;
-        this._camera.pinchDeltaPercentage = 0.01;
+            var framingBehavior = this._camera.getBehaviorByName("Framing") as FramingBehavior;
 
-        // Animations
-        this._handleAnimations();
+            setTimeout(() => { // Let the behavior activate first
+                framingBehavior.framingTime = 0;
+                framingBehavior.elevationReturnTime = -1;
 
-        // Material        
+                if (this._scene.meshes.length) {
+                    var worldExtends = this._scene.getWorldExtends();
+                    this._camera.lowerRadiusLimit = null;
+                    this._camera.upperRadiusLimit = null;
+                    framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
+                }
+
+                this._camera.pinchPrecision = 200 / this._camera.radius;
+                this._camera.upperRadiusLimit = 5 * this._camera.radius;
+            });
+
+            this._camera.wheelDeltaPercentage = 0.01;
+            this._camera.pinchDeltaPercentage = 0.01;
+
+            // Animations
+            this._handleAnimations();
+        }
+
+        // Material
         let serializationObject = this._nodeMaterial.serialize();
         this._updatePreview(serializationObject);
     }
 
-    private _refreshPreviewMesh() {    
+    private _refreshPreviewMesh() {
 
         if (this._currentType !== this._globalState.previewMeshType || this._currentType === PreviewMeshType.Custom) {
-
             this._currentType = this._globalState.previewMeshType;
             if (this._meshes && this._meshes.length) {
                 for (var mesh of this._meshes) {
@@ -241,46 +250,48 @@ export class PreviewManager {
             SceneLoader.ShowLoadingScreen = false;
 
             this._globalState.onIsLoadingChanged.notifyObservers(true);
-        
-            switch (this._globalState.previewMeshType) {
-                case PreviewMeshType.Box:
-                    SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCube.glb", this._scene).then(() => {     
-                        this._meshes.push(...this._scene.meshes);
-                        this._prepareMeshes();
-                    });     
-                    break;
-                case PreviewMeshType.Sphere:
-                    this._meshes.push(Mesh.CreateSphere("dummy-sphere", 32, 2, this._scene));
-                    break;
-                case PreviewMeshType.Torus:
-                    this._meshes.push(Mesh.CreateTorus("dummy-torus", 2, 0.5, 32, this._scene));
-                    break;
-                case PreviewMeshType.Cylinder:
-                    SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCylinder.glb", this._scene).then(() => {     
-                        this._meshes.push(...this._scene.meshes);
-                        this._prepareMeshes();
-                    });                    
-                    return;                   
-                case PreviewMeshType.Plane:
-                    let plane = Mesh.CreateGround("dummy-plane", 2, 2, 128, this._scene);
-                    plane.scaling.y = -1;
-                    plane.rotation.x = Math.PI;
-                    this._meshes.push(plane);
-                    break;    
-                case PreviewMeshType.ShaderBall:
-                    SceneLoader.AppendAsync("https://models.babylonjs.com/", "shaderBall.glb", this._scene).then(() => {     
-                        this._meshes.push(...this._scene.meshes);
-                        this._prepareMeshes();
-                    });
-                    return;                             
-                case PreviewMeshType.Custom:
-                    SceneLoader.AppendAsync("file:", this._globalState.previewMeshFile, this._scene).then(() => {     
-                        this._meshes.push(...this._scene.meshes);
-                        this._prepareMeshes();
-                    });
-                    return;     
+
+            if (this._globalState.mode !== NodeMaterialModes.PostProcess) {
+                switch (this._globalState.previewMeshType) {
+                    case PreviewMeshType.Box:
+                        SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCube.glb", this._scene).then(() => {
+                            this._meshes.push(...this._scene.meshes);
+                            this._prepareMeshes();
+                        });
+                        return;
+                    case PreviewMeshType.Sphere:
+                        this._meshes.push(Mesh.CreateSphere("dummy-sphere", 32, 2, this._scene));
+                        break;
+                    case PreviewMeshType.Torus:
+                        this._meshes.push(Mesh.CreateTorus("dummy-torus", 2, 0.5, 32, this._scene));
+                        break;
+                    case PreviewMeshType.Cylinder:
+                        SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCylinder.glb", this._scene).then(() => {
+                            this._meshes.push(...this._scene.meshes);
+                            this._prepareMeshes();
+                        });
+                        return;
+                    case PreviewMeshType.Plane:
+                        let plane = Mesh.CreateGround("dummy-plane", 2, 2, 128, this._scene);
+                        plane.scaling.y = -1;
+                        plane.rotation.x = Math.PI;
+                        this._meshes.push(plane);
+                        break;
+                    case PreviewMeshType.ShaderBall:
+                        SceneLoader.AppendAsync("https://models.babylonjs.com/", "shaderBall.glb", this._scene).then(() => {
+                            this._meshes.push(...this._scene.meshes);
+                            this._prepareMeshes();
+                        });
+                        return;
+                    case PreviewMeshType.Custom:
+                        SceneLoader.AppendAsync("file:", this._globalState.previewMeshFile, this._scene).then(() => {
+                            this._meshes.push(...this._scene.meshes);
+                            this._prepareMeshes();
+                        });
+                        return;
+                }
             }
-            
+
             this._prepareMeshes();
         }
     }
@@ -296,8 +307,29 @@ export class PreviewManager {
             tempMaterial.backFaceCulling = this._globalState.backFaceCulling;
             tempMaterial.needDepthPrePass = this._globalState.depthPrePass;
 
-            if (this._meshes.length) {
-                let tasks = this._meshes.map(m => this._forceCompilationAsync(tempMaterial, m));
+            if (this._postprocess) {
+                this._postprocess.dispose(this._camera);
+                this._postprocess = null;
+            }
+
+            if (this._globalState.mode === NodeMaterialModes.PostProcess) {
+                this._globalState.onIsLoadingChanged.notifyObservers(false);
+
+                this._postprocess = tempMaterial.createPostProcess(this._camera, 1.0, Constants.TEXTURE_NEAREST_SAMPLINGMODE, this._engine);
+
+                const currentScreen = tempMaterial.getBlockByPredicate((block) => block instanceof CurrentScreenBlock);
+                if (currentScreen) {
+                    this._postprocess!.onApplyObservable.add((effect) => {
+                        effect.setTexture("textureSampler", (currentScreen as CurrentScreenBlock).texture);
+                    });
+                }
+
+                if (this._material) {
+                    this._material.dispose();
+                }
+                this._material = tempMaterial;
+            } else if (this._meshes.length) {
+                let tasks = this._meshes.map((m) => this._forceCompilationAsync(tempMaterial, m));
 
                 Promise.all(tasks).then(() => {
                     for (var mesh of this._meshes) {
@@ -306,19 +338,20 @@ export class PreviewManager {
 
                     if (this._material) {
                         this._material.dispose();
-                    }      
-        
-                    this._material = tempMaterial;  
+                    }
+
+                    this._material = tempMaterial;
                     this._globalState.onIsLoadingChanged.notifyObservers(false);
-                }).catch(reason => {
+                }).catch((reason) => {
                     this._globalState.onLogRequiredObservable.notifyObservers(new LogEntry("Shader compilation error:\r\n" + reason, true));
                     this._globalState.onIsLoadingChanged.notifyObservers(false);
                 });
             } else {
-                this._material = tempMaterial;    
+                this._material = tempMaterial;
             }
-        } catch(err) {
+        } catch (err) {
             // Ignore the error
+            this._globalState.onIsLoadingChanged.notifyObservers(false);
         }
     }
 

+ 50 - 36
nodeEditor/src/components/preview/previewMeshControlComponent.tsx

@@ -5,6 +5,9 @@ import { Color3, Color4 } from 'babylonjs/Maths/math.color';
 import { PreviewMeshType } from './previewMeshType';
 import { DataStorage } from 'babylonjs/Misc/dataStorage';
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
+import { Observer } from 'babylonjs/Misc/observable';
+import { Nullable } from 'babylonjs/types';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 
 const popUpIcon: string = require("./svgs/popOut.svg");
 const colorPicker: string = require("./svgs/colorPicker.svg");
@@ -19,11 +22,20 @@ interface IPreviewMeshControlComponent {
 export class PreviewMeshControlComponent extends React.Component<IPreviewMeshControlComponent> {
     private colorInputRef: React.RefObject<HTMLInputElement>;
     private filePickerRef: React.RefObject<HTMLInputElement>;
+    private _onResetRequiredObserver: Nullable<Observer<void>>;
 
     constructor(props: IPreviewMeshControlComponent) {
         super(props);
         this.colorInputRef = React.createRef();
         this.filePickerRef = React.createRef();
+
+        this._onResetRequiredObserver = this.props.globalState.onResetRequiredObservable.add(() => {
+            this.forceUpdate();
+        });
+    }
+
+    componentWillUnmount() {
+        this.props.globalState.onResetRequiredObservable.remove(this._onResetRequiredObserver);
     }
 
     changeMeshType(newOne: PreviewMeshType) {
@@ -32,7 +44,7 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
         }
 
         this.props.globalState.previewMeshType = newOne;
-        this.props.globalState.onPreviewCommandActivated.notifyObservers();
+        this.props.globalState.onPreviewCommandActivated.notifyObservers(false);
 
         DataStorage.WriteNumber("PreviewMeshType", newOne);
 
@@ -46,12 +58,12 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
 
             this.props.globalState.previewMeshFile = file;
             this.props.globalState.previewMeshType = PreviewMeshType.Custom;
-            this.props.globalState.onPreviewCommandActivated.notifyObservers();
-            this.props.globalState.listOfCustomPreviewMeshFiles = [file];       
+            this.props.globalState.onPreviewCommandActivated.notifyObservers(false);
+            this.props.globalState.listOfCustomPreviewMeshFiles = [file];
             this.forceUpdate();
         }
-        if(this.filePickerRef.current){
-            this.filePickerRef.current.value = ""
+        if (this.filePickerRef.current) {
+            this.filePickerRef.current.value = "";
         }
     }
 
@@ -100,37 +112,39 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
 
         return (
             <div id="preview-mesh-bar">
-                <OptionsLineComponent label="" options={meshTypeOptions} target={this.props.globalState} 
-                            propertyName="previewMeshType"
-                            noDirectUpdate={true}
-                            onSelect={(value: any) => {
-                                if (value !== PreviewMeshType.Custom + 1) {
-                                    this.changeMeshType(value);
-                                } else {
-                                    this.filePickerRef.current?.click();
-                                }
-                            }} />
-                <div style={{
-                    display: "none"
-                }} title="Preview with a custom mesh" >
-                    <input ref={this.filePickerRef} id="file-picker" type="file" onChange={evt => this.useCustomMesh(evt)} accept=".gltf, .glb, .babylon, .obj"/>
-                </div>
-                <div
-                    title="Turn-table animation"
-                    onClick={() => this.changeAnimation()} className="button" id="play-button">
-                    {this.props.globalState.rotatePreview ? <img src={pauseIcon} alt=""/> : <img src={playIcon} alt=""/>}
-                </div>
-                <div 
-                id="color-picker-button"
-                    title="Background color"
-                    className={"button align"}
-                    onClick={_ => this.changeBackgroundClick()}
-                    >
-                    <img src={colorPicker} alt=""/>
-                    <label htmlFor="color-picker" id="color-picker-label">
-                    </label>
-                    <input ref={this.colorInputRef} id="color-picker" type="color" onChange={evt => this.changeBackground(evt.target.value)} />
-                </div>
+                { this.props.globalState.mode !== NodeMaterialModes.PostProcess && <>
+                    <OptionsLineComponent label="" options={meshTypeOptions} target={this.props.globalState}
+                                propertyName="previewMeshType"
+                                noDirectUpdate={true}
+                                onSelect={(value: any) => {
+                                    if (value !== PreviewMeshType.Custom + 1) {
+                                        this.changeMeshType(value);
+                                    } else {
+                                        this.filePickerRef.current?.click();
+                                    }
+                                }} />
+                    <div style={{
+                        display: "none"
+                    }} title="Preview with a custom mesh" >
+                        <input ref={this.filePickerRef} id="file-picker" type="file" onChange={(evt) => this.useCustomMesh(evt)} accept=".gltf, .glb, .babylon, .obj"/>
+                    </div>
+                    <div
+                        title="Turn-table animation"
+                        onClick={() => this.changeAnimation()} className="button" id="play-button">
+                        {this.props.globalState.rotatePreview ? <img src={pauseIcon} alt=""/> : <img src={playIcon} alt=""/>}
+                    </div>
+                    <div
+                    id="color-picker-button"
+                        title="Background color"
+                        className={"button align"}
+                        onClick={(_) => this.changeBackgroundClick()}
+                        >
+                        <img src={colorPicker} alt=""/>
+                        <label htmlFor="color-picker" id="color-picker-label">
+                        </label>
+                        <input ref={this.colorInputRef} id="color-picker" type="color" onChange={(evt) => this.changeBackground(evt.target.value)} />
+                    </div>
+                </> }
                 <div
                     title="Open preview in new window" id="preview-new-window"
                     onClick={() => this.onPopUp()} className="button">

+ 93 - 55
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -31,38 +31,43 @@ import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial';
 import { FrameNodePort } from '../../diagram/frameNodePort';
 import { NodePort } from '../../diagram/nodePort';
 import { isFramePortData } from '../../diagram/graphCanvas';
+import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 require("./propertyTab.scss");
 
 interface IPropertyTabComponentProps {
     globalState: GlobalState;
 }
 
-interface IPropertyTabComponentState { 
-    currentNode: Nullable<GraphNode>, 
-    currentFrame: Nullable<GraphFrame>, 
-    currentFrameNodePort: Nullable<FrameNodePort>,
-    currentNodePort: Nullable<NodePort>
+interface IPropertyTabComponentState {
+    currentNode: Nullable<GraphNode>;
+    currentFrame: Nullable<GraphFrame>;
+    currentFrameNodePort: Nullable<FrameNodePort>;
+    currentNodePort: Nullable<NodePort>;
  }
 
 export class PropertyTabComponent extends React.Component<IPropertyTabComponentProps, IPropertyTabComponentState> {
     private _onBuiltObserver: Nullable<Observer<void>>;
+    private _modeSelect: React.RefObject<OptionsLineComponent>;
 
     constructor(props: IPropertyTabComponentProps) {
-        super(props)
+        super(props);
 
         this.state = { currentNode: null, currentFrame: null, currentFrameNodePort: null, currentNodePort: null };
+
+        this._modeSelect = React.createRef();
     }
 
     componentDidMount() {
-        this.props.globalState.onSelectionChangedObservable.add(selection => {
+        this.props.globalState.onSelectionChangedObservable.add((selection) => {
             if (selection instanceof GraphNode) {
                 this.setState({ currentNode: selection, currentFrame: null, currentFrameNodePort: null, currentNodePort: null });
             } else if (selection instanceof GraphFrame) {
                 this.setState({ currentNode: null, currentFrame: selection, currentFrameNodePort: null, currentNodePort: null });
-            } else if(isFramePortData(selection)) {
+            } else if (isFramePortData(selection)) {
                 this.setState({ currentNode: null, currentFrame: selection.frame, currentFrameNodePort: selection.port, currentNodePort: null });
             } else if (selection instanceof NodePort && selection.hasLabel()) {
-                this.setState({ currentNode: null, currentFrame: null, currentFrameNodePort: null, currentNodePort: selection})
+                this.setState({ currentNode: null, currentFrame: null, currentFrameNodePort: null, currentNodePort: selection});
             } else {
                 this.setState({ currentNode: null, currentFrame: null, currentFrameNodePort: null, currentNodePort: null });
             }
@@ -90,57 +95,57 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
             case NodeMaterialBlockConnectionPointTypes.Float:
                     let cantDisplaySlider = (isNaN(block.min) || isNaN(block.max) || block.min === block.max);
                     return (
-                        <div key={block.uniqueId} >                            
+                        <div key={block.uniqueId} >
                             {
                                 block.isBoolean &&
-                                <CheckBoxLineComponent key={block.uniqueId} label={block.name} target={block} propertyName="value"                                 
+                                <CheckBoxLineComponent key={block.uniqueId} label={block.name} target={block} propertyName="value"
                                 onValueChanged={() => {
                                     this.processInputBlockUpdate(block);
                                 }}/>
                             }
                             {
                                 !block.isBoolean && cantDisplaySlider &&
-                                <FloatLineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} propertyName="value" 
+                                <FloatLineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} propertyName="value"
                                 onChange={() => this.processInputBlockUpdate(block)}/>
-                            }        
+                            }
                             {
                                 !block.isBoolean && !cantDisplaySlider &&
-                                <SliderLineComponent key={block.uniqueId} label={block.name} target={block} propertyName="value" 
+                                <SliderLineComponent key={block.uniqueId} label={block.name} target={block} propertyName="value"
                                 step={(block.max - block.min) / 100.0} minimum={block.min} maximum={block.max}
                                 onChange={() => this.processInputBlockUpdate(block)}/>
                             }
                         </div>
-                    );  
+                    );
             case NodeMaterialBlockConnectionPointTypes.Color3:
                 return (
-                    <Color3LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} 
-                        propertyName="value" 
+                    <Color3LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
+                        propertyName="value"
                         onChange={() => this.processInputBlockUpdate(block)}
                     />
-                )     
+                );
             case NodeMaterialBlockConnectionPointTypes.Color4:
                 return (
-                    <Color4LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} propertyName="value" 
+                    <Color4LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} propertyName="value"
                     onChange={() => this.processInputBlockUpdate(block)}/>
-                )                         
+                );
             case NodeMaterialBlockConnectionPointTypes.Vector2:
                 return (
-                        <Vector2LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} 
-                        propertyName="value" 
+                        <Vector2LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
+                        propertyName="value"
                         onChange={() => this.processInputBlockUpdate(block)}/>
-                    )                                
+                );
             case NodeMaterialBlockConnectionPointTypes.Vector3:
                 return (
-                    <Vector3LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} 
-                    propertyName="value" 
+                    <Vector3LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
+                    propertyName="value"
                     onChange={() => this.processInputBlockUpdate(block)}/>
-                )
+                );
             case NodeMaterialBlockConnectionPointTypes.Vector4:
                 return (
-                    <Vector4LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} 
-                    propertyName="value" 
+                    <Vector4LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
+                    propertyName="value"
                     onChange={() => this.processInputBlockUpdate(block)}/>
-                )
+                );
             }
         return null;
     }
@@ -150,6 +155,8 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
             let decoder = new TextDecoder("utf-8");
             SerializationTools.Deserialize(JSON.parse(decoder.decode(data)), this.props.globalState);
 
+            this.changeMode(this.props.globalState.nodeMaterial!.mode, true, false);
+            this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
         }, undefined, true);
     }
 
@@ -162,7 +169,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
         this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Saving your material to Babylon.js snippet server...", isError: false});
         this.props.globalState.customSave!.action(SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState)).then(() => {
             this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Material saved successfully", isError: false});
-        }).catch(err => {
+        }).catch((err) => {
             this.props.globalState.onLogRequiredObservable.notifyObservers({message: err, isError: true});
         });
     }
@@ -170,7 +177,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
     saveToSnippetServer() {
         const material = this.props.globalState.nodeMaterial;
         const xmlHttp = new XMLHttpRequest();
-        
+
         let json = SerializationTools.Serialize(material, this.props.globalState);
 
         xmlHttp.onreadystatechange = () => {
@@ -198,13 +205,13 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                     }
 
                     alert("NodeMaterial saved with ID: " + material.snippetId + " (please note that the id was also saved to your clipboard)");
-                               
+
                 }
                 else {
                     alert(`Unable to save your node material. It may be too large (${(dataToSend.payload.length / 1024).toFixed(2)} KB) because of embedded textures. Please reduce texture sizes or point to a specific url instead of embedding them and try again.`);
                 }
             }
-        }
+        };
 
         xmlHttp.open("POST", NodeMaterial.SnippetUrl + (material.snippetId ? "/" + material.snippetId : ""), true);
         xmlHttp.setRequestHeader("Content-Type", "application/json");
@@ -230,16 +237,46 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
         if (!snippedID) {
             return;
         }
-        
+
         this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
 
         NodeMaterial.ParseFromSnippetAsync(snippedID, scene, "", material).then(() => {
             material.build();
             this.props.globalState.onResetRequiredObservable.notifyObservers();
-        }).catch(err => {
+        }).catch((err) => {
             alert("Unable to load your node material: " + err);
         });
-    }    
+    }
+
+    changeMode(value: any, force = false, loadDefault = true) {
+        if (this.props.globalState.mode === value) {
+            return;
+        }
+
+        if (!force && !confirm('Are your sure? You will loose your current changes (if any) if they are not saved!')) {
+            this._modeSelect.current?.setValue(this.props.globalState.mode);
+            return;
+        }
+
+        if (force) {
+            this._modeSelect.current?.setValue(value);
+        }
+
+        this.props.globalState.mode = value as NodeMaterialModes;
+
+        if (loadDefault) {
+            switch (value) {
+                case NodeMaterialModes.Material:
+                    this.props.globalState.nodeMaterial!.setToDefault();
+                    break;
+                case NodeMaterialModes.PostProcess:
+                    this.props.globalState.nodeMaterial!.setToDefaultPostProcess();
+                    break;
+            }
+        }
+
+        this.props.globalState.onResetRequiredObservable.notifyObservers();
+    }
 
     render() {
         if (this.state.currentNode) {
@@ -255,7 +292,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                 </div>
             );
         }
-        
+
         if (this.state.currentFrameNodePort && this.state.currentFrame) {
             return (
                 <FrameNodePortPropertyTabComponent globalState={this.props.globalState} frame={this.state.currentFrame} frameNodePort={this.state.currentFrameNodePort}/>
@@ -286,6 +323,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                 </div>
                 <div>
                     <LineContainerComponent title="GENERAL">
+                        <OptionsLineComponent ref={this._modeSelect} label="Mode" target={this} getSelection={(target) => this.props.globalState.mode} options={[{ label: "Material", value: NodeMaterialModes.Material }, { label: "Post Process", value: NodeMaterialModes.PostProcess }]} onSelect={(value) => this.changeMode(value)} />
                         <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={() => {
@@ -302,30 +340,30 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                         }} />
                     </LineContainerComponent>
                     <LineContainerComponent title="OPTIONS">
-                        <CheckBoxLineComponent label="Embed textures when saving" 
+                        <CheckBoxLineComponent label="Embed textures when saving"
                             isSelected={() => DataStorage.ReadBoolean("EmbedTextures", true)}
                             onSelect={(value: boolean) => {
                                 DataStorage.WriteBoolean("EmbedTextures", value);
                             }}
                         />
-                        <SliderLineComponent label="Grid size" minimum={0} maximum={100} step={5} 
-                            decimalCount={0} 
+                        <SliderLineComponent label="Grid size" minimum={0} maximum={100} step={5}
+                            decimalCount={0}
                             directValue={gridSize}
-                            onChange={value => {
-                                DataStorage.WriteNumber("GridSize", value);                                
+                            onChange={(value) => {
+                                DataStorage.WriteNumber("GridSize", value);
                                 this.props.globalState.onGridSizeChanged.notifyObservers();
                                 this.forceUpdate();
                             }}
                         />
-                        <CheckBoxLineComponent label="Show grid" 
+                        <CheckBoxLineComponent label="Show grid"
                             isSelected={() => DataStorage.ReadBoolean("ShowGrid", true)}
                             onSelect={(value: boolean) => {
-                                DataStorage.WriteBoolean("ShowGrid", value);                
+                                DataStorage.WriteBoolean("ShowGrid", value);
                                 this.props.globalState.onGridSizeChanged.notifyObservers();
                             }}
                         />
                     </LineContainerComponent>
-                    <LineContainerComponent title="FILE">                        
+                    <LineContainerComponent title="FILE">
                         <FileButtonLineComponent label="Load" onClick={(file) => this.load(file)} accept=".json" />
                         <ButtonLineComponent label="Save" onClick={() => {
                             this.save();
@@ -337,29 +375,29 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                             StringTools.DownloadAsFile(this.props.globalState.hostDocument, this.props.globalState.nodeMaterial!.compiledShaders, "shaders.txt");
                         }} />
                         {
-                            this.props.globalState.customSave && 
+                            this.props.globalState.customSave &&
                             <ButtonLineComponent label={this.props.globalState.customSave!.label} onClick={() => {
                                 this.customSave();
                             }} />
-                        }                        
+                        }
 
-                    </LineContainerComponent>                            
+                    </LineContainerComponent>
                     {
-                        !this.props.globalState.customSave &&                           
-                        <LineContainerComponent title="SNIPPET"> 
+                        !this.props.globalState.customSave &&
+                        <LineContainerComponent title="SNIPPET">
                             {
                                 this.props.globalState.nodeMaterial!.snippetId &&
                                 <TextLineComponent label="Snippet ID" value={this.props.globalState.nodeMaterial!.snippetId} />
-                            }                                
+                            }
                             <ButtonLineComponent label="Load from snippet server" onClick={() => this.loadFromSnippet()} />
                             <ButtonLineComponent label="Save to snippet server" onClick={() => {
                                 this.saveToSnippetServer();
                             }} />
-                        </LineContainerComponent>  
-                    }                
-                    <LineContainerComponent title="INPUTS">   
+                        </LineContainerComponent>
+                    }
+                    <LineContainerComponent title="INPUTS">
                     {
-                        this.props.globalState.nodeMaterial.getInputBlocks().map(ib => {
+                        this.props.globalState.nodeMaterial.getInputBlocks().map((ib) => {
                             if (!ib.isUniform || ib.isSystemValue || !ib.name) {
                                 return null;
                             }

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

@@ -64,7 +64,9 @@ export class InputDisplayManager implements IDisplayManager {
         let inputBlock = block as InputBlock;
 
         if (inputBlock.isAttribute) {
-            value = "mesh." + inputBlock.name;
+            const attrVal = inputBlock.name === 'position2d' ? 'position' : inputBlock.name;
+            const attrName = inputBlock.name === 'position2d' ? 'postprocess' : 'mesh';
+            value = attrName + "." + attrVal;
         } else if (inputBlock.isSystemValue) {
             switch (inputBlock.systemValue) {
                 case NodeMaterialSystemValues.World:

+ 2 - 1
nodeEditor/src/diagram/display/textureDisplayManager.ts

@@ -4,6 +4,7 @@ import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock'
 import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
 import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
 import { TextureLineComponent } from '../../sharedComponents/textureLineComponent';
+import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
 
 export class TextureDisplayManager implements IDisplayManager {
     private _previewCanvas: HTMLCanvasElement;
@@ -30,7 +31,7 @@ export class TextureDisplayManager implements IDisplayManager {
 
         if (!this._previewCanvas) {
             contentArea.classList.add("texture-block");
-            if (block instanceof TextureBlock) {
+            if (block instanceof TextureBlock || block instanceof CurrentScreenBlock) {
                 contentArea.classList.add("regular-texture-block");
             }
 

+ 1 - 0
nodeEditor/src/diagram/displayLedger.ts

@@ -22,4 +22,5 @@ DisplayLedger.RegisteredControls["TextureBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["ReflectionTextureBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["ReflectionBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["RefractionBlock"] = TextureDisplayManager;
+DisplayLedger.RegisteredControls["CurrentScreenBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["DiscardBlock"] = DiscardDisplayManager;

+ 19 - 13
nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx

@@ -17,14 +17,18 @@ import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/ref
 import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionBlock';
 import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
 import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
+import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
 import { GeneralPropertyTabComponent, GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 
 type ReflectionTexture = ReflectionTextureBlock | ReflectionBlock | RefractionBlock;
 
+type AnyTexture = TextureBlock | ReflectionTexture | CurrentScreenBlock;
+
 export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
 
-    get textureBlock(): TextureBlock | ReflectionTexture {
-        return this.props.block as TextureBlock | ReflectionTexture;
+    get textureBlock(): AnyTexture {
+        return this.props.block as AnyTexture;
     }
 
     constructor(props: IPropertyComponentProps) {
@@ -37,7 +41,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
 
     UNSAFE_componentWillUpdate(nextProps: IPropertyComponentProps, nextState: {isEmbedded: boolean, loadAsCubeTexture: boolean}) {
         if (nextProps.block !== this.props.block) {
-            let texture = (nextProps.block as TextureBlock | ReflectionTexture).texture as BaseTexture;
+            let texture = (nextProps.block as AnyTexture).texture as BaseTexture;
 
             nextState.isEmbedded = !texture || texture.name.substring(0, 4) === "data";
             nextState.loadAsCubeTexture = texture && texture.isCube;
@@ -80,7 +84,8 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
 
         if (!texture) {
             if (!this.state.loadAsCubeTexture) {
-                this.textureBlock.texture = new Texture(null, this.props.globalState.nodeMaterial.getScene(), false, this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock);
+                this.textureBlock.texture = new Texture(null, this.props.globalState.nodeMaterial.getScene(), false, 
+                    this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock || this.props.globalState.mode === NodeMaterialModes.PostProcess);
                 texture = this.textureBlock.texture;
                 texture.coordinatesMode = Texture.EQUIRECTANGULAR_MODE;
             } else {
@@ -148,6 +153,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
         url = url.replace(/\?nocache=\d+/, "");
 
         let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock;
+        let isFrozenTexture = this.textureBlock instanceof CurrentScreenBlock;
 
         var reflectionModeOptions: {label: string, value: number}[] = [
             {
@@ -207,21 +213,21 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         }} />
                     }                    
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <CheckBoxLineComponent label="Clamp U" isSelected={() => texture.wrapU === Texture.CLAMP_ADDRESSMODE} onSelect={(value) => {
                             texture.wrapU = value ? Texture.CLAMP_ADDRESSMODE : Texture.WRAP_ADDRESSMODE;
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                         }} />
                     }
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <CheckBoxLineComponent label="Clamp V" isSelected={() => texture.wrapV === Texture.CLAMP_ADDRESSMODE} onSelect={(value) => {
                             texture.wrapV = value ? Texture.CLAMP_ADDRESSMODE : Texture.WRAP_ADDRESSMODE;
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                         }} />
                     }        
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <FloatLineComponent globalState={this.props.globalState} label="Offset U" target={texture} propertyName="uOffset" 
                         onChange={() => {
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
@@ -229,7 +235,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         />
                     }
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <FloatLineComponent globalState={this.props.globalState} label="Offset V" target={texture} propertyName="vOffset"
                         onChange={() => {
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
@@ -237,21 +243,21 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         />
                     }
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <FloatLineComponent globalState={this.props.globalState} label="Scale U" target={texture} propertyName="uScale"
                         onChange={() => {
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                         }} />
                     }
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <FloatLineComponent globalState={this.props.globalState} label="Scale V" target={texture} propertyName="vScale"
                         onChange={() => {
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                         }} />
                     }
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <SliderLineComponent label="Rotation U" target={texture} propertyName="uAng" minimum={0} maximum={Math.PI * 2} useEuler={true} step={0.1}
                         onChange={() => {
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
@@ -259,7 +265,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         />
                     }
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <SliderLineComponent label="Rotation V" target={texture} propertyName="vAng" minimum={0} maximum={Math.PI * 2} useEuler={true} step={0.1}
                         onChange={() => {
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
@@ -267,7 +273,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         />
                     }                    
                     {
-                        texture && !isInReflectionMode &&
+                        texture && !isInReflectionMode && !isFrozenTexture &&
                         <SliderLineComponent label="Rotation W" target={texture} propertyName="wAng" minimum={0} maximum={Math.PI * 2} useEuler={true} step={0.1}
                         onChange={() => {
                             this.props.globalState.onUpdateRequiredObservable.notifyObservers();

+ 1 - 0
nodeEditor/src/diagram/propertyLedger.ts

@@ -21,4 +21,5 @@ PropertyLedger.RegisteredControls["TextureBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["ReflectionTextureBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["ReflectionBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["RefractionBlock"] = TexturePropertyTabComponent;
+PropertyLedger.RegisteredControls["CurrentScreenBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["TrigonometryBlock"] = TrigonometryPropertyTabComponent;

+ 18 - 4
nodeEditor/src/globalState.ts

@@ -1,5 +1,5 @@
-import { NodeMaterial } from "babylonjs/Materials/Node/nodeMaterial"
-import { Nullable } from "babylonjs/types"
+import { NodeMaterial } from "babylonjs/Materials/Node/nodeMaterial";
+import { Nullable } from "babylonjs/types";
 import { Observable } from 'babylonjs/Misc/observable';
 import { LogEntry } from './components/log/logComponent';
 import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
@@ -13,6 +13,7 @@ import { NodeLink } from './diagram/nodeLink';
 import { GraphFrame } from './diagram/graphFrame';
 import { FrameNodePort } from './diagram/frameNodePort';
 import { FramePortData } from './diagram/graphCanvas';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 
 export class GlobalState {
     nodeMaterial: NodeMaterial;
@@ -29,7 +30,7 @@ export class GlobalState {
     onLogRequiredObservable = new Observable<LogEntry>();
     onErrorMessageDialogRequiredObservable = new Observable<string>();
     onIsLoadingChanged = new Observable<boolean>();
-    onPreviewCommandActivated = new Observable<void>();
+    onPreviewCommandActivated = new Observable<boolean>();
     onLightUpdated = new Observable<void>();
     onPreviewBackgroundChanged = new Observable<void>();
     onBackFaceCullingChanged = new Observable<void>();
@@ -55,7 +56,19 @@ export class GlobalState {
     directionalLight0: boolean;
     directionalLight1: boolean;
     controlCamera: boolean;
-    storeEditorData:(serializationObject: any) => void;
+    storeEditorData: (serializationObject: any) => void;
+    _mode: NodeMaterialModes;
+
+    /** Gets the mode */
+    public get mode(): NodeMaterialModes {
+        return this._mode;
+    }
+
+    /** Sets the mode */
+    public set mode(m: NodeMaterialModes) {
+        this._mode = m;
+        this.onPreviewCommandActivated.notifyObservers(true);
+    }
 
     customSave?: {label: string, action: (data: string) => Promise<void>};
 
@@ -67,6 +80,7 @@ export class GlobalState {
         this.directionalLight0 = DataStorage.ReadBoolean("DirectionalLight0", false);
         this.directionalLight1 = DataStorage.ReadBoolean("DirectionalLight1", false);
         this.controlCamera = DataStorage.ReadBoolean("ControlCamera", true);
+        this._mode = NodeMaterialModes.Material;
 
         let r = DataStorage.ReadNumber("BackgroundColorR", 0.12549019607843137);
         let g = DataStorage.ReadNumber("BackgroundColorG", 0.09803921568627451);

+ 4 - 3
nodeEditor/src/serializationTools.ts

@@ -38,10 +38,11 @@ export class SerializationTools {
         return JSON.stringify(serializationObject, undefined, 2);
     }
 
-    public static Deserialize(serializationObject:any, globalState: GlobalState) {       
-        globalState.onIsLoadingChanged.notifyObservers(true); 
+    public static Deserialize(serializationObject: any, globalState: GlobalState) {
+        globalState.onIsLoadingChanged.notifyObservers(true);
         globalState.nodeMaterial!.loadFromSerialization(serializationObject, "");
-        
+        globalState.mode = globalState.nodeMaterial!.mode;
+
         globalState.onResetRequiredObservable.notifyObservers();
     }
 }

+ 4 - 0
nodeEditor/src/sharedComponents/optionsLineComponent.tsx

@@ -38,6 +38,10 @@ export class OptionsLineComponent extends React.Component<IOptionsLineComponentP
         this.state = { value: this._getValue(props) };
     }
 
+    setValue(value: string | number) {
+        this.setState({ value: value });
+    }
+
     shouldComponentUpdate(nextProps: IOptionsLineComponentProps, nextState: { value: number }) {
         if (this._localChange) {
             this._localChange = false;

+ 329 - 0
src/Materials/Node/Blocks/Dual/currentScreenBlock.ts

@@ -0,0 +1,329 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterialDefines } from '../../nodeMaterial';
+import { BaseTexture } from '../../../Textures/baseTexture';
+import { Nullable } from '../../../../types';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { Texture } from '../../../Textures/texture';
+import { Scene } from '../../../../scene';
+import { InputBlock } from '../Input/inputBlock';
+
+declare type NodeMaterial = import("../../nodeMaterial").NodeMaterial;
+
+/**
+ * Base block used as input for post process
+ */
+export class CurrentScreenBlock extends NodeMaterialBlock {
+
+    private _samplerName = "textureSampler";
+    private _linearDefineName: string;
+    private _gammaDefineName: string;
+    private _mainUVName: string;
+    private _tempTextureRead: string;
+
+    /**
+     * Gets or sets the texture associated with the node
+     */
+    public texture: Nullable<BaseTexture>;
+
+    /**
+     * Gets or sets a boolean indicating if content needs to be converted to gamma space
+     */
+    public convertToGammaSpace = false;
+
+    /**
+     * Gets or sets a boolean indicating if content needs to be converted to linear space
+     */
+    public convertToLinearSpace = false;
+
+    /**
+     * Create a new CurrentScreenBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.VertexAndFragment);
+
+        this._isUnique = true;
+
+        this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, false, NodeMaterialBlockTargets.VertexAndFragment);
+
+        this.registerOutput("rgba", NodeMaterialBlockConnectionPointTypes.Color4, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("rgb", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("r", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("g", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("b", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("a", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+
+        this._inputs[0].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Vector3);
+        this._inputs[0].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Vector4);
+
+        this._inputs[0]._prioritizeVertex = true;
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "CurrentScreenBlock";
+    }
+
+    /**
+     * Gets the uv input component
+     */
+    public get uv(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the rgba output component
+     */
+    public get rgba(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Gets the rgb output component
+     */
+    public get rgb(): NodeMaterialConnectionPoint {
+        return this._outputs[1];
+    }
+
+    /**
+     * Gets the r output component
+     */
+    public get r(): NodeMaterialConnectionPoint {
+        return this._outputs[2];
+    }
+
+    /**
+     * Gets the g output component
+     */
+    public get g(): NodeMaterialConnectionPoint {
+        return this._outputs[3];
+    }
+
+    /**
+     * Gets the b output component
+     */
+    public get b(): NodeMaterialConnectionPoint {
+        return this._outputs[4];
+    }
+
+    /**
+     * Gets the a output component
+     */
+    public get a(): NodeMaterialConnectionPoint {
+        return this._outputs[5];
+    }
+
+    /**
+     * Initialize the block and prepare the context for build
+     * @param state defines the state that will be used for the build
+     */
+    public initialize(state: NodeMaterialBuildState) {
+        state._excludeVariableName("textureSampler");
+    }
+
+    public get target() {
+        // TextureBlock has a special optimizations for uvs that come from the vertex shaders as they can be packed into a single varyings.
+        // But we need to detect uvs coming from fragment then
+        if (!this.uv.isConnected) {
+            return NodeMaterialBlockTargets.VertexAndFragment;
+        }
+
+        if (this.uv.sourceBlock!.isInput) {
+            return NodeMaterialBlockTargets.VertexAndFragment;
+        }
+
+        let parent = this.uv.connectedPoint;
+
+        while (parent) {
+            if (parent.target === NodeMaterialBlockTargets.Fragment) {
+                return NodeMaterialBlockTargets.Fragment;
+            }
+
+            if (parent.target === NodeMaterialBlockTargets.Vertex) {
+                return NodeMaterialBlockTargets.VertexAndFragment;
+            }
+
+            if (parent.target === NodeMaterialBlockTargets.Neutral || parent.target === NodeMaterialBlockTargets.VertexAndFragment) {
+                let parentBlock = parent.ownerBlock;
+
+                parent = null;
+                for (var input of parentBlock.inputs) {
+                    if (input.connectedPoint) {
+                        parent = input.connectedPoint;
+                        break;
+                    }
+                }
+            }
+
+        }
+
+        return NodeMaterialBlockTargets.VertexAndFragment;
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        defines.setValue(this._linearDefineName, this.convertToGammaSpace, true);
+        defines.setValue(this._gammaDefineName, this.convertToLinearSpace, true);
+    }
+
+    public isReady() {
+        if (this.texture && !this.texture.isReadyOrNotBlocking()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private _injectVertexCode(state: NodeMaterialBuildState) {
+        let uvInput = this.uv;
+
+        if (uvInput.connectedPoint!.ownerBlock.isInput) {
+            let uvInputOwnerBlock = uvInput.connectedPoint!.ownerBlock as InputBlock;
+
+            if (!uvInputOwnerBlock.isAttribute) {
+                state._emitUniformFromString(uvInput.associatedVariableName, "vec2");
+            }
+        }
+
+        this._mainUVName = "vMain" + uvInput.associatedVariableName;
+
+        state._emitVaryingFromString(this._mainUVName, "vec2");
+
+        state.compilationString += `${this._mainUVName} = ${uvInput.associatedVariableName}.xy;\r\n`;
+
+        if (!this._outputs.some((o) => o.isConnectedInVertexShader)) {
+            return;
+        }
+
+        this._writeTextureRead(state, true);
+
+        for (var output of this._outputs) {
+            if (output.hasEndpoints) {
+                this._writeOutput(state, output, output.name, true);
+            }
+        }
+    }
+
+    private _writeTextureRead(state: NodeMaterialBuildState, vertexMode = false) {
+        let uvInput = this.uv;
+
+        if (vertexMode) {
+            if (state.target === NodeMaterialBlockTargets.Fragment) {
+                return;
+            }
+
+            state.compilationString += `vec4 ${this._tempTextureRead} = texture2D(${this._samplerName}, ${uvInput.associatedVariableName});\r\n`;
+            return;
+        }
+
+        if (this.uv.ownerBlock.target === NodeMaterialBlockTargets.Fragment) {
+            state.compilationString += `vec4 ${this._tempTextureRead} = texture2D(${this._samplerName}, ${uvInput.associatedVariableName});\r\n`;
+            return;
+        }
+
+        state.compilationString += `vec4 ${this._tempTextureRead} = texture2D(${this._samplerName}, ${this._mainUVName});\r\n`;
+    }
+
+    private _writeOutput(state: NodeMaterialBuildState, output: NodeMaterialConnectionPoint, swizzle: string, vertexMode = false) {
+        if (vertexMode) {
+            if (state.target === NodeMaterialBlockTargets.Fragment) {
+                return;
+            }
+
+            state.compilationString += `${this._declareOutput(output, state)} = ${this._tempTextureRead}.${swizzle};\r\n`;
+
+            return;
+        }
+
+        if (this.uv.ownerBlock.target === NodeMaterialBlockTargets.Fragment) {
+            state.compilationString += `${this._declareOutput(output, state)} = ${this._tempTextureRead}.${swizzle};\r\n`;
+            return;
+        }
+
+        state.compilationString += `${this._declareOutput(output, state)} = ${this._tempTextureRead}.${swizzle};\r\n`;
+
+        state.compilationString += `#ifdef ${this._linearDefineName}\r\n`;
+        state.compilationString += `${output.associatedVariableName} = toGammaSpace(${output.associatedVariableName});\r\n`;
+        state.compilationString += `#endif\r\n`;
+
+        state.compilationString += `#ifdef ${this._gammaDefineName}\r\n`;
+        state.compilationString += `${output.associatedVariableName} = toLinearSpace(${output.associatedVariableName});\r\n`;
+        state.compilationString += `#endif\r\n`;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        if (state.target === NodeMaterialBlockTargets.Vertex) {
+            this._tempTextureRead = state._getFreeVariableName("tempTextureRead");
+
+            state._emit2DSampler(this._samplerName);
+
+            state.sharedData.blockingBlocks.push(this);
+            state.sharedData.textureBlocks.push(this);
+            state.sharedData.blocksWithDefines.push(this);
+        }
+
+        if (state.target !== NodeMaterialBlockTargets.Fragment) {
+            // Vertex
+            this._injectVertexCode(state);
+            return;
+        }
+
+        // Fragment
+        if (!this._outputs.some((o) => o.isConnectedInFragmentShader)) {
+            return;
+        }
+
+        state._emit2DSampler(this._samplerName);
+
+        this._linearDefineName = state._getFreeDefineName("ISLINEAR");
+        this._gammaDefineName = state._getFreeDefineName("ISGAMMA");
+
+        let comments = `//${this.name}`;
+        state._emitFunctionFromInclude("helperFunctions", comments);
+
+        this._writeTextureRead(state);
+
+        for (var output of this._outputs) {
+            if (output.hasEndpoints) {
+                this._writeOutput(state, output, output.name);
+            }
+        }
+
+        return this;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        serializationObject.convertToGammaSpace = this.convertToGammaSpace;
+        serializationObject.convertToLinearSpace = this.convertToLinearSpace;
+        if (this.texture) {
+            serializationObject.texture = this.texture.serialize();
+        }
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        this.convertToGammaSpace = serializationObject.convertToGammaSpace;
+        this.convertToLinearSpace = !!serializationObject.convertToLinearSpace;
+
+        if (serializationObject.texture) {
+            rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl;
+            this.texture = Texture.Parse(serializationObject.texture, scene, rootUrl) as Texture;
+        }
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.CurrentScreenBlock"] = CurrentScreenBlock;

+ 2 - 1
src/Materials/Node/Blocks/Dual/index.ts

@@ -2,4 +2,5 @@
 export * from "./fogBlock";
 export * from "./lightBlock";
 export * from "./textureBlock";
-export * from "./reflectionTextureBlock";
+export * from "./reflectionTextureBlock";
+export * from "./currentScreenBlock";

+ 15 - 6
src/Materials/Node/Blocks/Dual/textureBlock.ts

@@ -12,6 +12,7 @@ import { Nullable } from '../../../../types';
 import { _TypeStore } from '../../../../Misc/typeStore';
 import { Texture } from '../../../Textures/texture';
 import { Scene } from '../../../../scene';
+import { NodeMaterialModes } from '../../Enums/nodeMaterialModes';
 
 import "../../../../Shaders/ShadersInclude/helperFunctions";
 
@@ -165,13 +166,21 @@ export class TextureBlock extends NodeMaterialBlock {
 
     public autoConfigure(material: NodeMaterial) {
         if (!this.uv.isConnected) {
-            let uvInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "uv");
+            if (material.mode === NodeMaterialModes.PostProcess) {
+                let uvInput = material.getBlockByPredicate((b) => b.name === "uv");
 
-            if (!uvInput) {
-                uvInput = new InputBlock("uv");
-                uvInput.setAsAttribute();
+                if (uvInput) {
+                    uvInput.connectTo(this);
+                }
+            } else {
+                let uvInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "uv");
+
+                if (!uvInput) {
+                    uvInput = new InputBlock("uv");
+                    uvInput.setAsAttribute();
+                }
+                uvInput.output.connectTo(this.uv);
             }
-            uvInput.output.connectTo(this.uv);
         }
     }
 
@@ -215,7 +224,7 @@ export class TextureBlock extends NodeMaterialBlock {
     }
 
     public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
-        if (!mesh || !this.texture) {
+        if (!this.texture) {
             return;
         }
 

+ 2 - 1
src/Materials/Node/Blocks/Input/inputBlock.ts

@@ -95,6 +95,7 @@ export class InputBlock extends NodeMaterialBlock {
                         return this._type;
                     case "uv":
                     case "uv2":
+                    case "position2d":
                         this._type = NodeMaterialBlockConnectionPointTypes.Vector2;
                         return this._type;
                     case "matricesIndices":
@@ -443,7 +444,7 @@ export class InputBlock extends NodeMaterialBlock {
 
         // Attribute
         if (this.isAttribute) {
-            this.associatedVariableName = this.name;
+            this.associatedVariableName = this.name === 'position2d' ? 'position' : this.name;
 
             if (this.target === NodeMaterialBlockTargets.Vertex && state._vertexState) { // Attribute for fragment need to be carried over by varyings
                 this._emit(state._vertexState, define);

+ 2 - 1
src/Materials/Node/Enums/index.ts

@@ -1,4 +1,5 @@
 export * from "./nodeMaterialBlockTargets";
 export * from "./nodeMaterialBlockConnectionPointTypes";
 export * from "./nodeMaterialBlockConnectionPointMode";
-export * from "./nodeMaterialSystemValues";
+export * from "./nodeMaterialSystemValues";
+export * from "./nodeMaterialModes";

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

@@ -0,0 +1,9 @@
+/**
+ * Enum used to define the material modes
+ */
+export enum NodeMaterialModes {
+    /** Regular material */
+    Material = 0,
+    /** For post process */
+    PostProcess = 1,
+}

+ 229 - 45
src/Materials/Node/nodeMaterial.ts

@@ -2,7 +2,7 @@ import { NodeMaterialBlock } from './nodeMaterialBlock';
 import { PushMaterial } from '../pushMaterial';
 import { Scene } from '../../scene';
 import { AbstractMesh } from '../../Meshes/abstractMesh';
-import { Matrix } from '../../Maths/math.vector';
+import { Matrix, Vector2 } from '../../Maths/math.vector';
 import { Color4 } from '../../Maths/math.color';
 import { Mesh } from '../../Meshes/mesh';
 import { Engine } from '../../Engines/engine';
@@ -24,13 +24,22 @@ import { VertexOutputBlock } from './Blocks/Vertex/vertexOutputBlock';
 import { FragmentOutputBlock } from './Blocks/Fragment/fragmentOutputBlock';
 import { InputBlock } from './Blocks/Input/inputBlock';
 import { _TypeStore } from '../../Misc/typeStore';
-import { SerializationHelper } from '../../Misc/decorators';
+import { serialize, SerializationHelper } from '../../Misc/decorators';
 import { TextureBlock } from './Blocks/Dual/textureBlock';
 import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
 import { RefractionBlock } from './Blocks/PBR/refractionBlock';
+import { CurrentScreenBlock } from './Blocks/Dual/currentScreenBlock';
 import { EffectFallbacks } from '../effectFallbacks';
 import { WebRequest } from '../../Misc/webRequest';
 import { Effect } from '../effect';
+import { PostProcess, PostProcessOptions } from '../../PostProcesses/postProcess';
+import { Constants } from '../../Engines/constants';
+import { Camera } from '../../Cameras/camera';
+import { VectorMergerBlock } from './Blocks/vectorMergerBlock';
+import { RemapBlock } from './Blocks/remapBlock';
+import { MultiplyBlock } from './Blocks/multiplyBlock';
+import { NodeMaterialModes } from './Enums/nodeMaterialModes';
+import { Texture } from '../Textures/texture';
 
 const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
 
@@ -228,6 +237,20 @@ export class NodeMaterial extends PushMaterial {
     public attachedBlocks = new Array<NodeMaterialBlock>();
 
     /**
+     * Specifies the mode of the node material
+     * @hidden
+     */
+    @serialize("mode")
+    public _mode: NodeMaterialModes = NodeMaterialModes.Material;
+
+    /**
+     * Gets the mode property
+     */
+    public get mode(): NodeMaterialModes {
+        return this._mode;
+    }
+
+    /**
      * Create a new node based material
      * @param name defines the material name
      * @param scene defines the hosting scene
@@ -702,54 +725,96 @@ export class NodeMaterial extends PushMaterial {
     }
 
     /**
-      * Get if the submesh is ready to be used and all its information available.
-      * Child classes can use it to update shaders
-      * @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
-      */
-    public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances: boolean = false): boolean {
-        if (!this._buildWasSuccessful) {
-            return false;
-        }
+     * Create a post process from the material
+     * @param camera The camera to apply the render pass to.
+     * @param options The required width/height ratio to downsize to before computing the render pass. (Use 1.0 for full size)
+     * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
+     * @param engine The engine which the post process will be applied. (default: current engine)
+     * @param reusable If the post process can be reused on the same frame. (default: false)
+     * @param textureType Type of textures used when performing the post process. (default: 0)
+     * @param textureFormat Format of textures used when performing the post process. (default: TEXTUREFORMAT_RGBA)
+     * @returns the post process created
+     */
+    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 {
 
-        var scene = this.getScene();
-        if (this._sharedData.animatedInputs) {
-            let frameId = scene.getFrameId();
+        let tempName = this.name + this._buildId;
 
-            if (this._animationFrame !== frameId) {
-                for (var input of this._sharedData.animatedInputs) {
-                    input.animate(scene);
-                }
+        const defines = new NodeMaterialDefines();
 
-                this._animationFrame = frameId;
+        const dummyMesh = new AbstractMesh(tempName + "PostProcess", this.getScene());
+
+        let buildId = this._buildId;
+
+        this._processDefines(dummyMesh, defines);
+
+        Effect.ShadersStore[tempName + "VertexShader"] = this._vertexCompilationState._builtCompilationString;
+        Effect.ShadersStore[tempName + "PixelShader"] = this._fragmentCompilationState._builtCompilationString;
+
+        const postProcess = new PostProcess(
+            this.name + "PostProcess", tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers,
+            options, camera, samplingMode, engine, reusable, defines.toString(), textureType, tempName, { maxSimultaneousLights: this.maxSimultaneousLights }, false, textureFormat
+        );
+
+        postProcess.onApplyObservable.add((effect) => {
+            if (buildId !== this._buildId) {
+                delete Effect.ShadersStore[tempName + "VertexShader"];
+                delete Effect.ShadersStore[tempName + "PixelShader"];
+
+                tempName = this.name + this._buildId;
+
+                defines.markAsUnprocessed();
+
+                buildId = this._buildId;
             }
-        }
 
-        if (subMesh.effect && this.isFrozen) {
-            if (subMesh.effect._wasPreviouslyReady) {
-                return true;
+            const result = this._processDefines(dummyMesh, defines);
+
+            if (result) {
+                Effect.ShadersStore[tempName + "VertexShader"] = this._vertexCompilationState._builtCompilationString;
+                Effect.ShadersStore[tempName + "PixelShader"] = this._fragmentCompilationState._builtCompilationString;
+
+                postProcess.updateEffect(defines.toString(), this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, { maxSimultaneousLights: this.maxSimultaneousLights }, undefined, undefined, tempName, tempName);
             }
-        }
 
-        if (!subMesh._materialDefines) {
-            subMesh._materialDefines = new NodeMaterialDefines();
-        }
+            // Animated blocks
+            if (this._sharedData.animatedInputs) {
+                const scene = this.getScene();
 
-        var defines = <NodeMaterialDefines>subMesh._materialDefines;
-        if (this._isReadyForSubMesh(subMesh)) {
-            return true;
-        }
+                let frameId = scene.getFrameId();
 
-        var engine = scene.getEngine();
+                if (this._animationFrame !== frameId) {
+                    for (var input of this._sharedData.animatedInputs) {
+                        input.animate(scene);
+                    }
 
-        this._prepareDefinesForAttributes(mesh, defines);
+                    this._animationFrame = frameId;
+                }
+            }
 
-        // Check if blocks are ready
-        if (this._sharedData.blockingBlocks.some((b) => !b.isReady(mesh, this, defines, useInstances))) {
-            return false;
-        }
+            // 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());
+            }
+        });
+
+        return postProcess;
+    }
+
+    private _processDefines(mesh: AbstractMesh, defines: NodeMaterialDefines, useInstances = false): Nullable<{
+        lightDisposed: boolean,
+        uniformBuffers: string[],
+        mergedUniforms: string[],
+        mergedSamplers: string[],
+        fallbacks: EffectFallbacks,
+     }> {
+         let result = null;
 
         // Shared defines
         this._sharedData.blocksWithDefines.forEach((b) => {
@@ -806,6 +871,71 @@ export class NodeMaterial extends PushMaterial {
                 b.provideFallbacks(mesh, fallbacks);
             });
 
+            result = {
+                lightDisposed,
+                uniformBuffers,
+                mergedUniforms,
+                mergedSamplers,
+                fallbacks,
+            };
+        }
+
+        return result;
+    }
+
+    /**
+      * Get if the submesh is ready to be used and all its information available.
+      * Child classes can use it to update shaders
+      * @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
+      */
+    public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances: boolean = false): boolean {
+        if (!this._buildWasSuccessful) {
+            return false;
+        }
+
+        var scene = this.getScene();
+        if (this._sharedData.animatedInputs) {
+            let frameId = scene.getFrameId();
+
+            if (this._animationFrame !== frameId) {
+                for (var input of this._sharedData.animatedInputs) {
+                    input.animate(scene);
+                }
+
+                this._animationFrame = frameId;
+            }
+        }
+
+        if (subMesh.effect && this.isFrozen) {
+            if (subMesh.effect._wasPreviouslyReady) {
+                return true;
+            }
+        }
+
+        if (!subMesh._materialDefines) {
+            subMesh._materialDefines = new NodeMaterialDefines();
+        }
+
+        var defines = <NodeMaterialDefines>subMesh._materialDefines;
+        if (this._isReadyForSubMesh(subMesh)) {
+            return true;
+        }
+
+        var engine = scene.getEngine();
+
+        this._prepareDefinesForAttributes(mesh, defines);
+
+        // Check if blocks are ready
+        if (this._sharedData.blockingBlocks.some((b) => !b.isReady(mesh, this, defines, useInstances))) {
+            return false;
+        }
+
+        const result = this._processDefines(mesh, defines, useInstances);
+
+        if (result) {
             let previousEffect = subMesh.effect;
             // Compilation
             var join = defines.toString();
@@ -816,11 +946,11 @@ export class NodeMaterial extends PushMaterial {
                 fragmentSource: this._fragmentCompilationState.compilationString
             }, <IEffectCreationOptions>{
                 attributes: this._vertexCompilationState.attributes,
-                uniformsNames: mergedUniforms,
-                uniformBuffersNames: uniformBuffers,
-                samplers: mergedSamplers,
+                uniformsNames: result.mergedUniforms,
+                uniformBuffersNames: result.uniformBuffers,
+                samplers: result.mergedSamplers,
                 defines: join,
-                fallbacks: fallbacks,
+                fallbacks: result.fallbacks,
                 onCompiled: this.onCompiled,
                 onError: this.onError,
                 indexParameters: { maxSimultaneousLights: this.maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS }
@@ -838,7 +968,7 @@ export class NodeMaterial extends PushMaterial {
                     effect = previousEffect;
                     defines.markAsUnprocessed();
 
-                    if (lightDisposed) {
+                    if (result.lightDisposed) {
                         // re register in case it takes more than one frame.
                         defines._areLightsDisposed = true;
                         return false;
@@ -950,7 +1080,7 @@ export class NodeMaterial extends PushMaterial {
      * Gets the list of texture blocks
      * @returns an array of texture blocks
      */
-    public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock | RefractionBlock)[] {
+    public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock | RefractionBlock | CurrentScreenBlock)[] {
         if (!this._sharedData) {
             return [];
         }
@@ -1083,6 +1213,58 @@ export class NodeMaterial extends PushMaterial {
         // Add to nodes
         this.addOutputNode(vertexOutput);
         this.addOutputNode(fragmentOutput);
+
+        this._mode = NodeMaterialModes.Material;
+    }
+
+    /**
+     * Clear the current material and set it to a default state for post process
+     */
+    public setToDefaultPostProcess() {
+        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
+        const scale = new InputBlock("scale");
+        scale.visibleInInspector = true;
+        scale.value = new Vector2(1, 1);
+
+        const uv0 = new RemapBlock("uv0");
+        position.connectTo(uv0);
+
+        const uv = new MultiplyBlock("uv");
+        uv0.connectTo(uv);
+        scale.connectTo(uv);
+
+        const currentScreen = new CurrentScreenBlock("CurrentScreen");
+        uv.connectTo(currentScreen);
+
+        currentScreen.texture = new Texture("https://assets.babylonjs.com/nme/currentScreenPostProcess.png", this.getScene());
+
+        var fragmentOutput = new FragmentOutputBlock("FragmentOutput");
+        currentScreen.connectTo(fragmentOutput, { output: "rgba" });
+
+        // Add to nodes
+        this.addOutputNode(vertexOutput);
+        this.addOutputNode(fragmentOutput);
+
+        this._mode = NodeMaterialModes.PostProcess;
     }
 
     /**
@@ -1316,6 +1498,8 @@ export class NodeMaterial extends PushMaterial {
 
             this.editorData.map = blockMap;
         }
+
+        this._mode = source.mode ?? NodeMaterialModes.Material;
     }
 
     /**

+ 2 - 1
src/Materials/Node/nodeMaterialBuildStateSharedData.ts

@@ -4,6 +4,7 @@ import { InputBlock } from './Blocks/Input/inputBlock';
 import { TextureBlock } from './Blocks/Dual/textureBlock';
 import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
 import { RefractionBlock } from './Blocks/PBR/refractionBlock';
+import { CurrentScreenBlock } from './Blocks/Dual/currentScreenBlock';
 import { Scene } from '../../scene';
 
 /**
@@ -33,7 +34,7 @@ export class NodeMaterialBuildStateSharedData {
     /**
      * Input blocks
      */
-    public textureBlocks = new Array<TextureBlock | ReflectionTextureBaseBlock | RefractionBlock>();
+    public textureBlocks = new Array<TextureBlock | ReflectionTextureBaseBlock | RefractionBlock | CurrentScreenBlock>();
 
     /**
      * Bindable blocks (Blocks that need to set data to the effect)

+ 4 - 2
src/PostProcesses/postProcess.ts

@@ -402,10 +402,12 @@ export class 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 onCompiled Called when the shader has been compiled.
      * @param onError Called if there is an error when compiling a shader.
+     * @param vertexUrl The url of the vertex shader to be used (default: the one given at construction time)
+     * @param fragmentUrl The url of the fragment shader to be used (default: the one given at construction time)
      */
     public updateEffect(defines: Nullable<string> = null, uniforms: Nullable<string[]> = null, samplers: Nullable<string[]> = null, indexParameters?: any,
-        onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void) {
-        this._effect = this._engine.createEffect({ vertex: this._vertexUrl, fragment: this._fragmentUrl },
+        onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, vertexUrl?: string, fragmentUrl?: string) {
+        this._effect = this._engine.createEffect({ vertex: vertexUrl ?? this._vertexUrl, fragment: fragmentUrl ?? this._fragmentUrl },
             ["position"],
             uniforms || this._parameters,
             samplers || this._samplers,