瀏覽代碼

Merge pull request #8027 from Popov72/nme-pbr

NME: Support for the PBR material (first round)
David Catuhe 5 年之前
父節點
當前提交
e3db853b5e
共有 63 個文件被更改,包括 3595 次插入794 次删除
  1. 95 77
      nodeEditor/src/blockTools.ts
  2. 20 13
      nodeEditor/src/components/nodeList/nodeListComponent.tsx
  3. 1 0
      nodeEditor/src/diagram/displayLedger.ts
  4. 11 2
      nodeEditor/src/diagram/graphCanvas.tsx
  5. 2 2
      nodeEditor/src/diagram/graphNode.ts
  6. 3 0
      nodeEditor/src/diagram/nodePort.ts
  7. 0 24
      nodeEditor/src/diagram/properties/PerturbNormalNodePropertyComponent.tsx
  8. 0 33
      nodeEditor/src/diagram/properties/clampNodePropertyComponent.tsx
  9. 113 5
      nodeEditor/src/diagram/properties/genericNodePropertyComponent.tsx
  10. 2 2
      nodeEditor/src/diagram/properties/gradientNodePropertyComponent.tsx
  11. 2 2
      nodeEditor/src/diagram/properties/inputNodePropertyComponent.tsx
  12. 2 2
      nodeEditor/src/diagram/properties/lightInformationPropertyTabComponent.tsx
  13. 2 2
      nodeEditor/src/diagram/properties/lightPropertyTabComponent.tsx
  14. 0 33
      nodeEditor/src/diagram/properties/remapNodePropertyComponent.tsx
  15. 18 8
      nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx
  16. 2 2
      nodeEditor/src/diagram/properties/transformNodePropertyComponent.tsx
  17. 2 2
      nodeEditor/src/diagram/properties/trigonometryNodePropertyComponent.tsx
  18. 0 23
      nodeEditor/src/diagram/properties/worleyNoise3DNodePropertyComponent.tsx
  19. 1 8
      nodeEditor/src/diagram/propertyLedger.ts
  20. 462 0
      src/Materials/Node/Blocks/Dual/reflectionTextureBaseBlock.ts
  21. 16 297
      src/Materials/Node/Blocks/Dual/reflectionTextureBlock.ts
  22. 15 0
      src/Materials/Node/Blocks/Dual/textureBlock.ts
  23. 1 1
      src/Materials/Node/Blocks/Fragment/index.ts
  24. 10 1
      src/Materials/Node/Blocks/Fragment/perturbNormalBlock.ts
  25. 2 1
      src/Materials/Node/Blocks/Input/inputBlock.ts
  26. 146 0
      src/Materials/Node/Blocks/PBR/ambientOcclusionBlock.ts
  27. 196 0
      src/Materials/Node/Blocks/PBR/anisotropyBlock.ts
  28. 6 0
      src/Materials/Node/Blocks/PBR/index.ts
  29. 1109 0
      src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts
  30. 437 0
      src/Materials/Node/Blocks/PBR/reflectionBlock.ts
  31. 209 0
      src/Materials/Node/Blocks/PBR/reflectivityBlock.ts
  32. 226 0
      src/Materials/Node/Blocks/PBR/sheenBlock.ts
  33. 4 0
      src/Materials/Node/Blocks/clampBlock.ts
  34. 1 0
      src/Materials/Node/Blocks/index.ts
  35. 3 0
      src/Materials/Node/Blocks/remapBlock.ts
  36. 2 0
      src/Materials/Node/Blocks/worleyNoise3DBlock.ts
  37. 2 0
      src/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes.ts
  38. 2 1
      src/Materials/Node/index.ts
  39. 4 4
      src/Materials/Node/nodeMaterial.ts
  40. 9 5
      src/Materials/Node/nodeMaterialBlock.ts
  41. 12 0
      src/Materials/Node/nodeMaterialBlockConnectionPoint.ts
  42. 11 1
      src/Materials/Node/nodeMaterialBuildState.ts
  43. 2 2
      src/Materials/Node/nodeMaterialBuildStateSharedData.ts
  44. 46 0
      src/Materials/Node/nodeMaterialConnectionPointCustomObject.ts
  45. 78 0
      src/Materials/Node/nodeMaterialDecorator.ts
  46. 1 0
      src/Materials/Textures/cubeTexture.ts
  47. 4 3
      src/Materials/materialHelper.ts
  48. 1 49
      src/Shaders/ShadersInclude/bumpFragmentFunctions.fx
  49. 47 0
      src/Shaders/ShadersInclude/bumpFragmentMainFunctions.fx
  50. 10 0
      src/Shaders/ShadersInclude/helperFunctions.fx
  51. 1 5
      src/Shaders/ShadersInclude/pbrBlockAnisotropic.fx
  52. 23 43
      src/Shaders/ShadersInclude/pbrBlockClearcoat.fx
  53. 1 0
      src/Shaders/ShadersInclude/pbrBlockFinalLitComponents.fx
  54. 0 4
      src/Shaders/ShadersInclude/pbrBlockFinalUnlitComponents.fx
  55. 141 42
      src/Shaders/ShadersInclude/pbrBlockReflection.fx
  56. 12 24
      src/Shaders/ShadersInclude/pbrBlockReflectivity.fx
  57. 24 48
      src/Shaders/ShadersInclude/pbrBlockSheen.fx
  58. 4 3
      src/Shaders/ShadersInclude/pbrDebug.fx
  59. 2 2
      src/Shaders/ShadersInclude/pbrFragmentSamplersDeclaration.fx
  60. 1 0
      src/Shaders/default.fragment.fx
  61. 1 0
      src/Shaders/geometry.fragment.fx
  62. 31 14
      src/Shaders/pbr.fragment.fx
  63. 4 4
      src/Shaders/pbr.vertex.fx

+ 95 - 77
nodeEditor/src/blockTools.ts

@@ -62,20 +62,26 @@ import { DerivativeBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/deriva
 import { RefractBlock } from 'babylonjs/Materials/Node/Blocks/refractBlock';
 import { ReflectBlock } from 'babylonjs/Materials/Node/Blocks/reflectBlock';
 import { DesaturateBlock } from 'babylonjs/Materials/Node/Blocks/desaturateBlock';
+import { PBRMetallicRoughnessBlock } from 'babylonjs/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock';
+import { SheenBlock } from 'babylonjs/Materials/Node/Blocks/PBR/sheenBlock';
+import { AmbientOcclusionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/ambientOcclusionBlock';
+import { ReflectivityBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectivityBlock';
+import { AnisotropyBlock } from 'babylonjs/Materials/Node/Blocks/PBR/anisotropyBlock';
+import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionBlock';
 
 export class BlockTools {
     public static GetBlockFromString(data: string, scene: Scene, nodeMaterial: NodeMaterial) {
         switch (data) {
             case "DesaturateBlock":
-                return new DesaturateBlock("Desaturate");                  
+                return new DesaturateBlock("Desaturate");
             case "RefractBlock":
-                return new RefractBlock("Refract");               
+                return new RefractBlock("Refract");
             case "ReflectBlock":
-                return new ReflectBlock("Reflect");              
+                return new ReflectBlock("Reflect");
             case "DerivativeBlock":
-                return new DerivativeBlock("Derivative");               
+                return new DerivativeBlock("Derivative");
             case "Rotate2dBlock":
-                return new Rotate2dBlock("Rotate2d");            
+                return new Rotate2dBlock("Rotate2d");
             case "NormalBlendBlock":
                 return new NormalBlendBlock("NormalBlend");
             case "WorleyNoise3DBlock":
@@ -95,7 +101,7 @@ export class BlockTools {
             case "ColorMergerBlock":
                 return new ColorMergerBlock("ColorMerger");
             case "VectorMergerBlock":
-                return new VectorMergerBlock("VectorMerger");                
+                return new VectorMergerBlock("VectorMerger");
             case "ColorSplitterBlock":
                 return new ColorSplitterBlock("ColorSplitter");
             case "VectorSplitterBlock":
@@ -103,7 +109,7 @@ export class BlockTools {
             case "TextureBlock":
                 return new TextureBlock("Texture");
             case "ReflectionTextureBlock":
-                return new ReflectionTextureBlock("Reflection texture");                
+                return new ReflectionTextureBlock("Reflection texture");
             case "LightBlock":
                 return new LightBlock("Lights");
             case "FogBlock":
@@ -143,45 +149,45 @@ export class BlockTools {
             case "DivideBlock":
                 return new DivideBlock("Divide");
             case "SubtractBlock":
-                return new SubtractBlock("Subtract"); 
+                return new SubtractBlock("Subtract");
             case "StepBlock":
-                return new StepBlock("Step");        
+                return new StepBlock("Step");
             case "SmoothStepBlock":
-                return new SmoothStepBlock("Smooth step");        
+                return new SmoothStepBlock("Smooth step");
             case "OneMinusBlock":
-                return new OneMinusBlock("One minus");          
+                return new OneMinusBlock("One minus");
             case "ReciprocalBlock":
-                return new ReciprocalBlock("Reciprocal");    
+                return new ReciprocalBlock("Reciprocal");
             case "ViewDirectionBlock":
-                return new ViewDirectionBlock("View direction");    
+                return new ViewDirectionBlock("View direction");
             case "LightInformationBlock":
                 let lightInformationBlock = new LightInformationBlock("Light information");
                 lightInformationBlock.light = scene.lights.length ? scene.lights[0] : null;
                 return lightInformationBlock;
             case "MaxBlock":
-                return new MaxBlock("Max");       
+                return new MaxBlock("Max");
             case "MinBlock":
-                return new MinBlock("Min");      
+                return new MinBlock("Min");
             case "LengthBlock":
-                return new LengthBlock("Length");   
+                return new LengthBlock("Length");
             case "DistanceBlock":
-                return new DistanceBlock("Distance");     
+                return new DistanceBlock("Distance");
             case "NegateBlock":
-                return new NegateBlock("Negate");                                     
-            case "PerturbNormalBlock":                                          
-                return new PerturbNormalBlock("Perturb normal");                     
-            case "RandomNumberBlock":                                          
-                return new RandomNumberBlock("Random number");         
-            case "ReplaceColorBlock":                                          
-                return new ReplaceColorBlock("Replace color");      
-            case "PosterizeBlock":                                          
-                return new PosterizeBlock("Posterize");                              
-            case "ArcTan2Block":                                          
-                return new ArcTan2Block("ArcTan2");                            
-            case "GradientBlock":                                          
-                return new GradientBlock("Gradient");                             
-            case "FrontFacingBlock":                                          
-                return new FrontFacingBlock("Front facing");            
+                return new NegateBlock("Negate");
+            case "PerturbNormalBlock":
+                return new PerturbNormalBlock("Perturb normal");
+            case "RandomNumberBlock":
+                return new RandomNumberBlock("Random number");
+            case "ReplaceColorBlock":
+                return new ReplaceColorBlock("Replace color");
+            case "PosterizeBlock":
+                return new PosterizeBlock("Posterize");
+            case "ArcTan2Block":
+                return new ArcTan2Block("ArcTan2");
+            case "GradientBlock":
+                return new GradientBlock("Gradient");
+            case "FrontFacingBlock":
+                return new FrontFacingBlock("Front facing");
             case "CosBlock": {
                 let cosBlock = new TrigonometryBlock("Cos");
                 cosBlock.operation = TrigonometryBlockOperations.Cos;
@@ -196,7 +202,7 @@ export class BlockTools {
                 let absBlock = new TrigonometryBlock("Abs");
                 absBlock.operation = TrigonometryBlockOperations.Abs;
                 return absBlock;
-            }            
+            }
             case "SqrtBlock": {
                 let sqrtBlock = new TrigonometryBlock("Sqrt");
                 sqrtBlock.operation = TrigonometryBlockOperations.Sqrt;
@@ -231,12 +237,12 @@ export class BlockTools {
                 let signBlock = new TrigonometryBlock("Sign");
                 signBlock.operation = TrigonometryBlockOperations.Sign;
                 return signBlock;
-            }            
+            }
             case "LogBlock": {
                 let logBlock = new TrigonometryBlock("Log");
                 logBlock.operation = TrigonometryBlockOperations.Log;
                 return logBlock;
-            }                                                            
+            }
             case "ExpBlock": {
                 let expBlock = new TrigonometryBlock("Exp");
                 expBlock.operation = TrigonometryBlockOperations.Exp;
@@ -256,7 +262,7 @@ export class BlockTools {
                 let radiansToDegreesBlock = new TrigonometryBlock("Radians to degrees");
                 radiansToDegreesBlock.operation = TrigonometryBlockOperations.Degrees;
                 return radiansToDegreesBlock;
-            }                        
+            }
             case "RoundBlock": {
                 let roundBlock = new TrigonometryBlock("Round");
                 roundBlock.operation = TrigonometryBlockOperations.Round;
@@ -266,22 +272,22 @@ export class BlockTools {
                 let ceilingBlock = new TrigonometryBlock("Ceiling");
                 ceilingBlock.operation = TrigonometryBlockOperations.Ceiling;
                 return ceilingBlock;
-            }     
+            }
             case "FloorBlock": {
                 let floorBlock = new TrigonometryBlock("Floor");
                 floorBlock.operation = TrigonometryBlockOperations.Floor;
                 return floorBlock;
-            }       
+            }
             case "SawToothWaveBlock": {
                 let sawToothWaveBlock = new WaveBlock("SawTooth wave");
                 sawToothWaveBlock.kind = WaveBlockKind.SawTooth;
                 return sawToothWaveBlock;
-            }     
+            }
             case "SquareWaveBlock": {
                 let squareWaveBlock = new WaveBlock("Square wave");
                 squareWaveBlock.kind = WaveBlockKind.Square;
                 return squareWaveBlock;
-            }     
+            }
             case "TriangleWaveBlock": {
                 let triangleWaveBlock = new WaveBlock("Triangle wave");
                 triangleWaveBlock.kind = WaveBlockKind.Triangle;
@@ -291,95 +297,95 @@ export class BlockTools {
                 let worldMatrixBlock = new InputBlock("World");
                 worldMatrixBlock.setAsSystemValue(NodeMaterialSystemValues.World);
                 return worldMatrixBlock;
-            }             
+            }
             case "WorldViewMatrixBlock": {
                 let worldViewMatrixBlock = new InputBlock("World x View");
                 worldViewMatrixBlock.setAsSystemValue(NodeMaterialSystemValues.WorldView);
                 return worldViewMatrixBlock;
-            }             
+            }
             case "WorldViewProjectionMatrixBlock": {
                 let worldViewProjectionMatrixBlock = new InputBlock("World x View x Projection");
                 worldViewProjectionMatrixBlock.setAsSystemValue(NodeMaterialSystemValues.WorldViewProjection);
                 return worldViewProjectionMatrixBlock;
-            }                    
+            }
             case "ViewMatrixBlock": {
                 let viewMatrixBlock = new InputBlock("View");
                 viewMatrixBlock.setAsSystemValue(NodeMaterialSystemValues.View);
                 return viewMatrixBlock;
-            }                          
+            }
             case "ViewProjectionMatrixBlock": {
                 let viewProjectionMatrixBlock = new InputBlock("View x Projection");
                 viewProjectionMatrixBlock.setAsSystemValue(NodeMaterialSystemValues.ViewProjection);
                 return viewProjectionMatrixBlock;
-            }                              
+            }
             case "ProjectionMatrixBlock": {
                 let projectionMatrixBlock = new InputBlock("Projection");
                 projectionMatrixBlock.setAsSystemValue(NodeMaterialSystemValues.Projection);
                 return projectionMatrixBlock;
-            }                                 
+            }
             case "CameraPositionBlock": {
                 let cameraPosition = new InputBlock("Camera position");
                 cameraPosition.setAsSystemValue(NodeMaterialSystemValues.CameraPosition);
                 return cameraPosition;
-            }                              
+            }
             case "FogColorBlock": {
                 let FogColor = new InputBlock("Fog color");
                 FogColor.setAsSystemValue(NodeMaterialSystemValues.FogColor);
                 return FogColor;
-            }                                   
+            }
             case "PositionBlock": {
                 let meshPosition = new InputBlock("position");
                 meshPosition.setAsAttribute("position");
                 return meshPosition;
-            }                                        
+            }
             case "UVBlock": {
                 let meshUV = new InputBlock("uv");
                 meshUV.setAsAttribute("uv");
                 return meshUV;
-            }                                         
+            }
             case "ColorBlock": {
                 let meshColor = new InputBlock("color");
                 meshColor.setAsAttribute("color");
                 return meshColor;
-            }                                              
+            }
             case "NormalBlock": {
                 let meshNormal = new InputBlock("normal");
                 meshNormal.setAsAttribute("normal");
                 return meshNormal;
-            }                                                 
+            }
             case "TangentBlock": {
                 let meshTangent = new InputBlock("tangent");
                 meshTangent.setAsAttribute("tangent");
                 return meshTangent;
-            }                                                  
+            }
             case "MatrixIndicesBlock": {
                 let meshMatrixIndices = new InputBlock("matricesIndices");
                 meshMatrixIndices.setAsAttribute("matricesIndices");
                 return meshMatrixIndices;
-            }                                                    
+            }
             case "MatrixWeightsBlock": {
                 let meshMatrixWeights = new InputBlock("matricesWeights");
                 meshMatrixWeights.setAsAttribute("matricesWeights");
                 return meshMatrixWeights;
-            }                                                     
+            }
             case "TimeBlock": {
                 let timeBlock = new InputBlock("Time", undefined, NodeMaterialBlockConnectionPointTypes.Float);
                 timeBlock.animationType = AnimatedInputBlockTypes.Time;
                 return timeBlock;
-            }   
+            }
             case "DeltaTimeBlock": {
-                let deltaTimeBlock = new InputBlock("Delta time");                
+                let deltaTimeBlock = new InputBlock("Delta time");
                 deltaTimeBlock.setAsSystemValue(NodeMaterialSystemValues.DeltaTime);
                 return deltaTimeBlock;
-            }      
+            }
             case "WorldPositionBlock": {
-                let worldPositionBlock = nodeMaterial.getInputBlockByPredicate(b => b.isAttribute && b.name === "position");                
+                let worldPositionBlock = nodeMaterial.getInputBlockByPredicate((b) => b.isAttribute && b.name === "position");
                 if (!worldPositionBlock) {
                     worldPositionBlock = new InputBlock("position");
                     worldPositionBlock.setAsAttribute("position");
                 }
 
-                let worldMatrixBlock = nodeMaterial.getInputBlockByPredicate(b => b.isSystemValue && b.systemValue === NodeMaterialSystemValues.World);  
+                let worldMatrixBlock = nodeMaterial.getInputBlockByPredicate((b) => b.isSystemValue && b.systemValue === NodeMaterialSystemValues.World);
 
                 if (!worldMatrixBlock) {
                     worldMatrixBlock = new InputBlock("World");
@@ -391,15 +397,15 @@ export class BlockTools {
                 worldMatrixBlock.connectTo(transformBlock);
 
                 return transformBlock;
-            }        
+            }
             case "WorldNormalBlock": {
-                let worldNormalBlock = nodeMaterial.getInputBlockByPredicate(b => b.isAttribute && b.name === "normal");                
+                let worldNormalBlock = nodeMaterial.getInputBlockByPredicate((b) => b.isAttribute && b.name === "normal");
                 if (!worldNormalBlock) {
                     worldNormalBlock = new InputBlock("normal");
                     worldNormalBlock.setAsAttribute("normal");
                 }
 
-                let worldMatrixBlock = nodeMaterial.getInputBlockByPredicate(b => b.isSystemValue && b.systemValue === NodeMaterialSystemValues.World);  
+                let worldMatrixBlock = nodeMaterial.getInputBlockByPredicate((b) => b.isSystemValue && b.systemValue === NodeMaterialSystemValues.World);
 
                 if (!worldMatrixBlock) {
                     worldMatrixBlock = new InputBlock("World");
@@ -411,15 +417,15 @@ export class BlockTools {
                 worldMatrixBlock.connectTo(transformBlock);
 
                 return transformBlock;
-            }     
+            }
             case "WorldTangentBlock": {
-                let worldTangentBlock = nodeMaterial.getInputBlockByPredicate(b => b.isAttribute && b.name === "tangent");                
+                let worldTangentBlock = nodeMaterial.getInputBlockByPredicate((b) => b.isAttribute && b.name === "tangent");
                 if (!worldTangentBlock) {
                     worldTangentBlock = new InputBlock("tangent");
                     worldTangentBlock.setAsAttribute("tangent");
                 }
 
-                let worldMatrixBlock = nodeMaterial.getInputBlockByPredicate(b => b.isSystemValue && b.systemValue === NodeMaterialSystemValues.World);  
+                let worldMatrixBlock = nodeMaterial.getInputBlockByPredicate((b) => b.isSystemValue && b.systemValue === NodeMaterialSystemValues.World);
 
                 if (!worldMatrixBlock) {
                     worldMatrixBlock = new InputBlock("World");
@@ -431,7 +437,19 @@ export class BlockTools {
                 worldMatrixBlock.connectTo(transformBlock);
 
                 return transformBlock;
-            }              
+            }
+            case "PBRMetallicRoughnessBlock":
+                return new PBRMetallicRoughnessBlock("PBRMetallicRoughness");
+            case "SheenBlock":
+                return new SheenBlock("Sheen");
+            case "AmbientOcclusionBlock":
+                return new AmbientOcclusionBlock("AmbientOcclusion");
+            case "ReflectivityBlock":
+                return new ReflectivityBlock("Reflectivity");
+            case "AnisotropyBlock":
+                return new AnisotropyBlock("Anisotropy");
+            case "ReflectionBlock":
+                return new ReflectionBlock("Reflection");
         }
 
         return null;
@@ -441,21 +459,21 @@ export class BlockTools {
         let color = "#880000";
         switch (type) {
             case NodeMaterialBlockConnectionPointTypes.Float:
-				color = "#cb9e27";
+                color = "#cb9e27";
                 break;
-            case NodeMaterialBlockConnectionPointTypes.Vector2:                
-				color = "#16bcb1";
+            case NodeMaterialBlockConnectionPointTypes.Vector2:
+                color = "#16bcb1";
                 break;
-            case NodeMaterialBlockConnectionPointTypes.Vector3:                
-            case NodeMaterialBlockConnectionPointTypes.Color3:                
+            case NodeMaterialBlockConnectionPointTypes.Vector3:
+            case NodeMaterialBlockConnectionPointTypes.Color3:
                 color = "#b786cb";
                 break;
-            case NodeMaterialBlockConnectionPointTypes.Vector4:                
-            case NodeMaterialBlockConnectionPointTypes.Color4:                
-				color = "#be5126";
+            case NodeMaterialBlockConnectionPointTypes.Vector4:
+            case NodeMaterialBlockConnectionPointTypes.Color4:
+                color = "#be5126";
                 break;
-            case NodeMaterialBlockConnectionPointTypes.Matrix:                
-				color = "#591990";
+            case NodeMaterialBlockConnectionPointTypes.Matrix:
+                color = "#591990";
                 break;
         }
 

+ 20 - 13
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -12,7 +12,7 @@ interface INodeListComponentProps {
 
 export class NodeListComponent extends React.Component<INodeListComponentProps, {filter: string}> {
 
-    private static _Tooltips:{[key: string]: string} = {
+    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",
         "AddBlock": "Adds the left and right inputs of the same type together",
@@ -79,7 +79,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "CameraPositionBlock": "Outputs a Vector3 position of the active scene camera",
         "FogBlock": "Applies fog to the scene with an increasing opacity based on distance from the camera",
         "FogColorBlock": "The system value for fog color pulled from the scene",
-        "ImageProcessingBlock": "Provides access to all of the Babylon image processing properties",        
+        "ImageProcessingBlock": "Provides access to all of the Babylon image processing properties",
         "LightBlock": "Outputs diffuse and specular contributions from one or more scene lights",
         "LightInformationBlock": "Provides the direction, color and intensity of a selected light based on its world position",
         "ReflectionTextureBlock": "Creates a reflection from the input texture",
@@ -115,9 +115,15 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "SimplexPerlin3DBlock": "Creates a type of gradient noise with few directional artifacts.",
         "WorleyNoise3DBlock": "Creates a random pattern resembling cells.",
         "ReflectBlock": "Outputs the direction of the input vector reflected across the surface normal.",
-        "RefractBlock": "Outputs a direction simulating a deflection of the input vector.", 
-        "Rotate2dBlock": "Rotates UV coordinates around the W axis."
-    }
+        "RefractBlock": "Outputs a direction simulating a deflection of the input vector.",
+        "Rotate2dBlock": "Rotates UV coordinates around the W axis.",
+        "PBRMetallicRoughnessBlock": "PBR metallic/roughness material",
+        "SheenBlock": "PBR Sheen block",
+        "AmbientOcclusionBlock": "PBR Ambient occlusion block",
+        "ReflectivityBlock": "PBR Reflectivity block",
+        "AnisotropyBlock": "PBR Anisotropy block",
+        "ReflectionBlock": "PBR Reflection block"
+    };
 
     constructor(props: INodeListComponentProps) {
         super(props);
@@ -132,33 +138,34 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
     render() {
         // Block types used to create the menu from
         const allBlocks = {
-            
+
             Animation: ["BonesBlock", "MorphTargetsBlock"],
             Color_Management: ["ReplaceColorBlock", "PosterizeBlock", "GradientBlock", "DesaturateBlock"],
             Conversion_Blocks: ["ColorMergerBlock", "ColorSplitterBlock", "VectorMergerBlock", "VectorSplitterBlock"],
             Inputs: ["Float", "Vector2", "Vector3", "Vector4", "Color3", "Color4", "TextureBlock", "ReflectionTextureBlock", "TimeBlock", "DeltaTimeBlock"],
             Interpolation: ["LerpBlock", "StepBlock", "SmoothStepBlock", "NLerpBlock"],
-            Math__Standard: ["AddBlock", "DivideBlock", "MaxBlock", "MinBlock", "MultiplyBlock", "NegateBlock", "OneMinusBlock", "ReciprocalBlock", "ScaleBlock", "SignBlock", "SqrtBlock", "SubtractBlock"], 
+            Math__Standard: ["AddBlock", "DivideBlock", "MaxBlock", "MinBlock", "MultiplyBlock", "NegateBlock", "OneMinusBlock", "ReciprocalBlock", "ScaleBlock", "SignBlock", "SqrtBlock", "SubtractBlock"],
             Math__Scientific: ["AbsBlock", "ArcCosBlock", "ArcSinBlock", "ArcTanBlock", "ArcTan2Block", "CosBlock", "DegreesToRadiansBlock", "ExpBlock", "Exp2Block", "FractBlock", "LogBlock", "PowBlock", "RadiansToDegreesBlock", "SawToothWaveBlock", "SinBlock", "SquareWaveBlock", "TanBlock", "TriangleWaveBlock"],
             Math__Vector: ["CrossBlock", "DerivativeBlock", "DistanceBlock", "DotBlock", "FresnelBlock", "LengthBlock", "ReflectBlock", "RefractBlock", "Rotate2dBlock", "TransformBlock", ],
             Matrices: ["Matrix", "WorldMatrixBlock", "WorldViewMatrixBlock", "WorldViewProjectionMatrixBlock", "ViewMatrixBlock", "ViewProjectionMatrixBlock", "ProjectionMatrixBlock"],
-            Mesh: ["InstancesBlock", "PositionBlock", "UVBlock", "ColorBlock", "NormalBlock", "PerturbNormalBlock", "NormalBlendBlock" , "TangentBlock", "MatrixIndicesBlock", "MatrixWeightsBlock", "WorldPositionBlock", "WorldNormalBlock", "WorldTangentBlock", "FrontFacingBlock"], 
+            Mesh: ["InstancesBlock", "PositionBlock", "UVBlock", "ColorBlock", "NormalBlock", "PerturbNormalBlock", "NormalBlendBlock" , "TangentBlock", "MatrixIndicesBlock", "MatrixWeightsBlock", "WorldPositionBlock", "WorldNormalBlock", "WorldTangentBlock", "FrontFacingBlock"],
             Noises: ["RandomNumberBlock", "SimplexPerlin3DBlock", "WorleyNoise3DBlock"],
             Output_Nodes: ["VertexOutputBlock", "FragmentOutputBlock", "DiscardBlock"],
+            PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ReflectionBlock", "ReflectivityBlock", "SheenBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],
             Round: ["RoundBlock", "CeilingBlock", "FloorBlock"],
             Scene: ["FogBlock", "CameraPositionBlock", "FogColorBlock", "ImageProcessingBlock", "LightBlock", "LightInformationBlock", "ViewDirectionBlock"],
-        }
+        };
 
         // Create node menu
-        var blockMenu = []
+        var blockMenu = [];
         for (var key in allBlocks) {
             var blockList = (allBlocks as any)[key].filter((b: string) => !this.state.filter || b.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1)
             .sort((a: string, b: string) => a.localeCompare(b))
             .map((block: any, i: number) => {
                 let tooltip = NodeListComponent._Tooltips[block] || "";
 
-                return <DraggableLineComponent key={block} data={block} tooltip={tooltip}/>
+                return <DraggableLineComponent key={block} data={block} tooltip={tooltip}/>;
             });
 
             if (blockList.length) {
@@ -175,9 +182,9 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
                 <div className="panes">
                     <div className="pane">
                         <div className="filter">
-                            <input type="text" placeholder="Filter" 
+                            <input type="text" placeholder="Filter"
                                 onFocus={() => this.props.globalState.blockKeyboardEvents = true}
-                                onBlur={evt => {
+                                onBlur={(evt) => {
                                     this.props.globalState.blockKeyboardEvents = false;
                                 }}
                                 onChange={(evt) => this.filterContent(evt.target.value)} />

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

@@ -20,4 +20,5 @@ DisplayLedger.RegisteredControls["RemapBlock"] = RemapDisplayManager;
 DisplayLedger.RegisteredControls["TrigonometryBlock"] = TrigonometryDisplayManager;
 DisplayLedger.RegisteredControls["TextureBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["ReflectionTextureBlock"] = TextureDisplayManager;
+DisplayLedger.RegisteredControls["ReflectionBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["DiscardBlock"] = DiscardDisplayManager;

+ 11 - 2
nodeEditor/src/diagram/graphCanvas.tsx

@@ -739,9 +739,15 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
             }
 
             // No destination so let's spin a new input block
-            let inputBlock = new InputBlock(NodeMaterialBlockConnectionPointTypes[this._candidateLink!.portA.connectionPoint.type], undefined, this._candidateLink!.portA.connectionPoint.type);
+            let pointName = "output", inputBlock;
+            let customInputBlock = this._candidateLink!.portA.connectionPoint.createCustomInputBlock();
+            if (!customInputBlock) {
+                inputBlock = new InputBlock(NodeMaterialBlockConnectionPointTypes[this._candidateLink!.portA.connectionPoint.type], undefined, this._candidateLink!.portA.connectionPoint.type);
+            } else {
+                [inputBlock, pointName] = customInputBlock;
+            }
             this.props.globalState.nodeMaterial.attachedBlocks.push(inputBlock);
-            pointA = inputBlock.output;
+            pointA = (inputBlock as any)[pointName];
             nodeA = this.appendBlock(inputBlock);
             
             nodeA.x = this._dropPointX - 200;
@@ -777,6 +783,9 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
         // Check compatibility
         let isFragmentOutput = pointB.ownerBlock.getClassName() === "FragmentOutputBlock";
         let compatibilityState = pointA.checkCompatibilityState(pointB);
+        if ((pointA.needDualDirectionValidation || pointB.needDualDirectionValidation) && compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible && !(pointA instanceof InputBlock)) {
+            compatibilityState = pointB.checkCompatibilityState(pointA);
+        }
         if (compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible) {
             if (isFragmentOutput) {
                 let fragmentBlock = pointB.ownerBlock as FragmentOutputBlock;

+ 2 - 2
nodeEditor/src/diagram/graphNode.ts

@@ -6,7 +6,7 @@ import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMateri
 import { GraphCanvasComponent, FramePortData } from './graphCanvas';
 import { PropertyLedger } from './propertyLedger';
 import * as React from 'react';
-import { GenericPropertyTabComponent } from './properties/genericNodePropertyComponent';
+import { GenericPropertyComponent } from './properties/genericNodePropertyComponent';
 import { DisplayLedger } from './displayLedger';
 import { IDisplayManager } from './display/displayManager';
 import { NodeLink } from './nodeLink';
@@ -353,7 +353,7 @@ export class GraphNode {
         let control = PropertyLedger.RegisteredControls[this.block.getClassName()];
 
         if (!control) {
-            control = GenericPropertyTabComponent;
+            control = GenericPropertyComponent;
         }
 
         return React.createElement(control, {

File diff suppressed because it is too large
+ 3 - 0
nodeEditor/src/diagram/nodePort.ts


+ 0 - 24
nodeEditor/src/diagram/properties/PerturbNormalNodePropertyComponent.tsx

@@ -1,24 +0,0 @@
-
-import * as React from "react";
-import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
-import { IPropertyComponentProps } from './propertyComponentProps';
-import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
-
-export class PerturbNormalPropertyTabComponent extends React.Component<IPropertyComponentProps> {
-    constructor(props: IPropertyComponentProps) {
-        super(props)
-    }
-
-    render() {
-        return (
-            <>                
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
-                <LineContainerComponent title="PROPERTIES">
-                    <CheckBoxLineComponent label="Invert X axis" target={this.props.block} propertyName="invertX" onValueChanged={() => this.props.globalState.onRebuildRequiredObservable.notifyObservers()} />
-                    <CheckBoxLineComponent label="Invert Y axis" target={this.props.block} propertyName="invertY" onValueChanged={() => this.props.globalState.onRebuildRequiredObservable.notifyObservers()}/>                    
-                </LineContainerComponent>        
-            </>
-        );
-    }
-}

+ 0 - 33
nodeEditor/src/diagram/properties/clampNodePropertyComponent.tsx

@@ -1,33 +0,0 @@
-
-import * as React from "react";
-import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
-import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
-import { IPropertyComponentProps } from './propertyComponentProps';
-import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
-
-export class ClampPropertyTabComponent extends React.Component<IPropertyComponentProps> {
-
-    constructor(props: IPropertyComponentProps) {
-        super(props)
-    }
-
-    forceRebuild() {
-        this.props.globalState.onUpdateRequiredObservable.notifyObservers();
-        this.props.globalState.onRebuildRequiredObservable.notifyObservers();
-    }
-
-    render() {
-        let clampBlock = this.props.block as ClampBlock
-      
-        return (
-            <div>
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
-                <LineContainerComponent title="PROPERTIES">
-                  <FloatLineComponent globalState={this.props.globalState} label="Minimum" propertyName="minimum" target={clampBlock} onChange={() => this.forceRebuild()} />
-                  <FloatLineComponent globalState={this.props.globalState} label="Maximum" propertyName="maximum" target={clampBlock} onChange={() => this.forceRebuild()} />
-                </LineContainerComponent>
-            </div>
-        );
-    }
-}

+ 113 - 5
nodeEditor/src/diagram/properties/genericNodePropertyComponent.tsx

@@ -4,11 +4,32 @@ import { LineContainerComponent } from '../../sharedComponents/lineContainerComp
 import { IPropertyComponentProps } from './propertyComponentProps';
 import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
 import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
+import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
+import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent';
+import { Vector2LineComponent } from '../../sharedComponents/vector2LineComponent';
+import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
+import { PropertyTypeForEdition, IPropertyDescriptionForEdition, IEditablePropertyListOption } from 'babylonjs/Materials/Node/nodeMaterialDecorator';
 
-export class GenericPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+export class GenericPropertyComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props);
+    }
+
+    render() {
+        return (
+            <>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+            </>
+        );
+    }
+}
+
+export class GeneralPropertyTabComponent extends React.Component<IPropertyComponentProps> {
     constructor(props: IPropertyComponentProps) {
-        super(props)
+        super(props);
     }
 
     render() {
@@ -17,13 +38,100 @@ export class GenericPropertyTabComponent extends React.Component<IPropertyCompon
                 <LineContainerComponent title="GENERAL">
                     {
                         (!this.props.block.isInput || !(this.props.block as InputBlock).isAttribute) &&
-                        <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.block} 
+                        <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.block}
                             onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
                     }
                     <TextLineComponent label="Type" value={this.props.block.getClassName()} />
-                    <TextInputLineComponent globalState={this.props.globalState} label="Comments" propertyName="comments" target={this.props.block} 
+                    <TextInputLineComponent globalState={this.props.globalState} label="Comments" propertyName="comments" target={this.props.block}
                             onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
-                </LineContainerComponent>         
+                </LineContainerComponent>
+            </>
+        );
+    }
+}
+
+export class GenericPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props);
+    }
+
+    forceRebuild(notifiers?: { "rebuild"?: boolean; "update"?: boolean; }) {
+        if (!notifiers || notifiers.update) {
+            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
+        }
+
+        if (!notifiers || notifiers.rebuild) {
+            this.props.globalState.onRebuildRequiredObservable.notifyObservers();
+        }
+    }
+
+    render() {
+        const block = this.props.block,
+              propStore: IPropertyDescriptionForEdition[] = (block as any)._propStore;
+
+        if (!propStore) {
+            return (
+                <>
+                </>
+            );
+        }
+
+        const componentList: { [groupName: string]: JSX.Element[]} = {},
+              groups: string[] = [];
+
+        for (const { propertyName, displayName, type, groupName, options } of propStore) {
+            let components = componentList[groupName];
+
+            if (!components) {
+                components = [];
+                componentList[groupName] = components;
+                groups.push(groupName);
+            }
+
+            switch (type) {
+                case PropertyTypeForEdition.Boolean: {
+                    components.push(
+                        <CheckBoxLineComponent label={displayName} target={this.props.block} propertyName={propertyName} onValueChanged={() => this.forceRebuild(options.notifiers)} />
+                    );
+                    break;
+                }
+                case PropertyTypeForEdition.Float: {
+                    let cantDisplaySlider = (isNaN(options.min as number) || isNaN(options.max as number) || options.min === options.max);
+                    if (cantDisplaySlider) {
+                        components.push(
+                            <FloatLineComponent globalState={this.props.globalState} label={displayName} propertyName={propertyName} target={this.props.block} onChange={() => this.forceRebuild(options.notifiers)} />
+                        );
+                    } else {
+                        components.push(
+                            <SliderLineComponent label={displayName} target={this.props.block} propertyName={propertyName} step={Math.abs((options.max as number) - (options.min as number)) / 100.0} minimum={Math.min(options.min as number, options.max as number)} maximum={options.max as number} onChange={() => this.forceRebuild(options.notifiers)}/>
+                        );
+                    }
+                    break;
+                }
+                case PropertyTypeForEdition.Vector2: {
+                    components.push(
+                        <Vector2LineComponent globalState={this.props.globalState} label={displayName} propertyName={propertyName} target={this.props.block} onChange={() => this.forceRebuild(options.notifiers)} />
+                    );
+                    break;
+                }
+                case PropertyTypeForEdition.List: {
+                    components.push(
+                        <OptionsLineComponent label={displayName} options={options.options as IEditablePropertyListOption[]} target={this.props.block} propertyName={propertyName} onSelect={() => this.forceRebuild(options.notifiers)} />
+                    );
+                    break;
+                }
+            }
+        }
+
+        return (
+            <>
+            {
+                groups.map((group) =>
+                    <LineContainerComponent title={group}>
+                        {componentList[group]}
+                    </LineContainerComponent>
+                )
+            }
             </>
         );
     }

+ 2 - 2
nodeEditor/src/diagram/properties/gradientNodePropertyComponent.tsx

@@ -6,7 +6,7 @@ import { GradientStepComponent } from './gradientStepComponent';
 import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
 import { Color3 } from 'babylonjs/Maths/math.color';
 import { IPropertyComponentProps } from './propertyComponentProps';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { GeneralPropertyTabComponent } from './genericNodePropertyComponent';
 
 export class GradientPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
@@ -64,7 +64,7 @@ export class GradientPropertyTabComponent extends React.Component<IPropertyCompo
       
         return (
             <div>
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
                 <LineContainerComponent title="STEPS">
                     <ButtonLineComponent label="Add new step" onClick={() => this.addNewStep()} />
                     {

+ 2 - 2
nodeEditor/src/diagram/properties/inputNodePropertyComponent.tsx

@@ -16,7 +16,7 @@ import { NodeMaterialSystemValues } from 'babylonjs/Materials/Node/Enums/nodeMat
 import { AnimatedInputBlockTypes } from 'babylonjs/Materials/Node/Blocks/Input/animatedInputBlockTypes';
 import { IPropertyComponentProps } from './propertyComponentProps';
 import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { GeneralPropertyTabComponent } from './genericNodePropertyComponent';
 import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
 import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
 import { Color4PropertyTabComponent } from '../../components/propertyTab/properties/color4PropertyTabComponent';
@@ -206,7 +206,7 @@ export class InputPropertyTabComponent extends React.Component<IPropertyComponen
 
         return (
             <div>
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
                 <LineContainerComponent title="PROPERTIES">
                     {
                         inputBlock.isUniform && !inputBlock.isSystemValue && inputBlock.animationType === AnimatedInputBlockTypes.None &&

+ 2 - 2
nodeEditor/src/diagram/properties/lightInformationPropertyTabComponent.tsx

@@ -4,7 +4,7 @@ import { LineContainerComponent } from '../../sharedComponents/lineContainerComp
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { IPropertyComponentProps } from './propertyComponentProps';
 import { LightInformationBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/lightInformationBlock';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { GeneralPropertyTabComponent } from './genericNodePropertyComponent';
 
 export class LightInformationPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
@@ -18,7 +18,7 @@ export class LightInformationPropertyTabComponent extends React.Component<IPrope
 
         return (
             <div>               
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
                 <LineContainerComponent title="PROPERTIES">
                     <OptionsLineComponent label="Light" noDirectUpdate={true} valuesAreStrings={true} options={lightOptions} target={lightInformationBlock} propertyName="name" onSelect={(name: any) => {
                         lightInformationBlock.light = scene.getLightByName(name);

+ 2 - 2
nodeEditor/src/diagram/properties/lightPropertyTabComponent.tsx

@@ -4,7 +4,7 @@ import { LineContainerComponent } from '../../sharedComponents/lineContainerComp
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { IPropertyComponentProps } from './propertyComponentProps';
 import { LightBlock } from 'babylonjs/Materials/Node/Blocks/Dual/lightBlock';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { GeneralPropertyTabComponent } from './genericNodePropertyComponent';
 
 export class LightPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
@@ -20,7 +20,7 @@ export class LightPropertyTabComponent extends React.Component<IPropertyComponen
 
         return (
             <div>                
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
                 <LineContainerComponent title="PROPERTIES">
                     <OptionsLineComponent label="Light" defaultIfNull={0} noDirectUpdate={true} valuesAreStrings={true} options={lightOptions} target={lightBlock} propertyName="name" onSelect={(name: any) => {
                         if (name === "") {

+ 0 - 33
nodeEditor/src/diagram/properties/remapNodePropertyComponent.tsx

@@ -1,33 +0,0 @@
-
-import * as React from "react";
-import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
-import { Vector2LineComponent } from '../../sharedComponents/vector2LineComponent';
-import { IPropertyComponentProps } from './propertyComponentProps';
-import { RemapBlock } from 'babylonjs/Materials/Node/Blocks/remapBlock';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
-
-export class RemapPropertyTabComponent extends React.Component<IPropertyComponentProps> {
-
-    constructor(props: IPropertyComponentProps) {
-        super(props)
-    }
-
-    forceRebuild() {
-        this.props.globalState.onUpdateRequiredObservable.notifyObservers();
-        this.props.globalState.onRebuildRequiredObservable.notifyObservers();
-    }
-
-    render() {
-        let remapBlock = this.props.block as RemapBlock;
-      
-        return (
-            <div>                
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
-                <LineContainerComponent title="PROPERTIES">
-                  <Vector2LineComponent globalState={this.props.globalState} label="From" propertyName="sourceRange" target={remapBlock} onChange={() => this.forceRebuild()} />
-                  <Vector2LineComponent globalState={this.props.globalState} label="To" propertyName="targetRange" target={remapBlock} onChange={() => this.forceRebuild()} />
-                </LineContainerComponent>
-            </div>
-        );
-    }
-}

+ 18 - 8
nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx

@@ -14,13 +14,16 @@ import { CubeTexture } from 'babylonjs/Materials/Textures/cubeTexture';
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { IPropertyComponentProps } from './propertyComponentProps';
 import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
+import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionBlock';
 import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { GeneralPropertyTabComponent, GenericPropertyTabComponent } from './genericNodePropertyComponent';
+
+type ReflectionTexture = ReflectionTextureBlock | ReflectionBlock;
 
 export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
 
-    get textureBlock(): TextureBlock | ReflectionTextureBlock {
-        return this.props.block as TextureBlock | ReflectionTextureBlock;
+    get textureBlock(): TextureBlock | ReflectionTexture {
+        return this.props.block as TextureBlock | ReflectionTexture;
     }
 
     constructor(props: IPropertyComponentProps) {
@@ -33,7 +36,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 | ReflectionTextureBlock).texture as BaseTexture;
+            let texture = (nextProps.block as TextureBlock | ReflectionTexture).texture as BaseTexture;
 
             nextState.isEmbedded = !texture || texture.name.substring(0, 4) === "data";
             nextState.loadAsCubeTexture = texture && texture.isCube;
@@ -76,7 +79,7 @@ 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.texture = new Texture(null, this.props.globalState.nodeMaterial.getScene(), false, this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock);
                 texture = this.textureBlock.texture;
                 texture.coordinatesMode = Texture.EQUIRECTANGULAR_MODE;
             } else {
@@ -119,7 +122,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
         this._prepareTexture();
 
         let texture = this.textureBlock.texture as BaseTexture;       
-        if (texture.isCube || this.textureBlock instanceof ReflectionTextureBlock) {
+        if (texture.isCube || this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock) {
             let extension: string | undefined = undefined;
             if (url.toLowerCase().indexOf(".dds") > 0) {
                 extension = ".dds";
@@ -143,7 +146,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
 
         url = url.replace(/\?nocache=\d+/, "");
 
-        let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock;
+        let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock;
 
         var reflectionModeOptions: {label: string, value: number}[] = [
             {
@@ -177,7 +180,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
         
         return (
             <div>                
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
                 <LineContainerComponent title="PROPERTIES">
                     <CheckBoxLineComponent label="Auto select UV" propertyName="autoSelectUV" target={this.props.block} onValueChanged={() => {                        
                         this.props.globalState.onUpdateRequiredObservable.notifyObservers();
@@ -189,6 +192,12 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         }}/>
                     }
                     {
+                        texture && !isInReflectionMode &&
+                        <CheckBoxLineComponent label="Convert to linear space" propertyName="convertToLinearSpace" target={this.props.block} onValueChanged={() => {                        
+                            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
+                        }}/>
+                    }
+                    {
                         texture && isInReflectionMode &&
                         <OptionsLineComponent label="Reflection mode" options={reflectionModeOptions} target={texture} propertyName="coordinatesMode" onSelect={(value: any) => {
                             texture.coordinatesMode = value;
@@ -293,6 +302,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         <ButtonLineComponent label="Remove" onClick={() => this.removeTexture()}/>
                     }
                 </LineContainerComponent>
+                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
             </div>
         );
     }

+ 2 - 2
nodeEditor/src/diagram/properties/transformNodePropertyComponent.tsx

@@ -4,7 +4,7 @@ import { LineContainerComponent } from '../../sharedComponents/lineContainerComp
 import { IPropertyComponentProps } from './propertyComponentProps';
 import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
 import { TransformBlock } from 'babylonjs/Materials/Node/Blocks/transformBlock';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { GeneralPropertyTabComponent } from './genericNodePropertyComponent';
 
 export class TransformPropertyTabComponent extends React.Component<IPropertyComponentProps> {
     constructor(props: IPropertyComponentProps) {
@@ -14,7 +14,7 @@ export class TransformPropertyTabComponent extends React.Component<IPropertyComp
     render() {
         return (
             <>                
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
                 <LineContainerComponent title="PROPERTIES">
                     <CheckBoxLineComponent label="Transform as direction" onSelect={value => {
                         let transformBlock = this.props.block as TransformBlock;

+ 2 - 2
nodeEditor/src/diagram/properties/trigonometryNodePropertyComponent.tsx

@@ -4,7 +4,7 @@ import { LineContainerComponent } from '../../sharedComponents/lineContainerComp
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { TrigonometryBlockOperations, TrigonometryBlock } from 'babylonjs/Materials/Node/Blocks/trigonometryBlock';
 import { IPropertyComponentProps } from './propertyComponentProps';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
+import { GeneralPropertyTabComponent } from './genericNodePropertyComponent';
 
 export class TrigonometryPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
@@ -41,7 +41,7 @@ export class TrigonometryPropertyTabComponent extends React.Component<IPropertyC
         
         return (
             <div>                
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
                 <LineContainerComponent title="PROPERTIES">  
                     <OptionsLineComponent label="Operation" options={operationOptions} target={trigonometryBlock} propertyName="operation" onSelect={(value: any) => {
                         this.props.globalState.onUpdateRequiredObservable.notifyObservers();

+ 0 - 23
nodeEditor/src/diagram/properties/worleyNoise3DNodePropertyComponent.tsx

@@ -1,23 +0,0 @@
-
-import * as React from "react";
-import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
-import { IPropertyComponentProps } from './propertyComponentProps';
-import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
-import { GenericPropertyTabComponent } from './genericNodePropertyComponent';
-
-export class WorleyNoise3DNodePropertyComponent extends React.Component<IPropertyComponentProps> {
-    constructor(props: IPropertyComponentProps) {
-        super(props)
-    }
-
-    render() {
-        return (
-            <>
-                <GenericPropertyTabComponent globalState={this.props.globalState} block={this.props.block}/>
-                <LineContainerComponent title="PROPERTIES">
-                    <CheckBoxLineComponent label="Use Manhattan Distance" target={this.props.block} propertyName="manhattanDistance" onValueChanged={() => this.props.globalState.onRebuildRequiredObservable.notifyObservers()} />              
-                </LineContainerComponent>        
-            </>
-        );
-    }
-}

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

@@ -2,13 +2,9 @@ import { ComponentClass } from 'react';
 import { InputPropertyTabComponent } from './properties/inputNodePropertyComponent';
 import { IPropertyComponentProps } from './properties/propertyComponentProps';
 import { TransformPropertyTabComponent } from './properties/transformNodePropertyComponent';
-import { PerturbNormalPropertyTabComponent } from './properties/PerturbNormalNodePropertyComponent';
-import { WorleyNoise3DNodePropertyComponent } from './properties/worleyNoise3DNodePropertyComponent';
-import { ClampPropertyTabComponent } from './properties/clampNodePropertyComponent';
 import { GradientPropertyTabComponent } from './properties/gradientNodePropertyComponent';
 import { LightPropertyTabComponent } from './properties/lightPropertyTabComponent';
 import { LightInformationPropertyTabComponent } from './properties/lightInformationPropertyTabComponent';
-import { RemapPropertyTabComponent } from './properties/remapNodePropertyComponent';
 import { TexturePropertyTabComponent } from './properties/texturePropertyTabComponent';
 import { TrigonometryPropertyTabComponent } from './properties/trigonometryNodePropertyComponent';
 
@@ -18,13 +14,10 @@ export class PropertyLedger {
 
 PropertyLedger.RegisteredControls["TransformBlock"] = TransformPropertyTabComponent;
 PropertyLedger.RegisteredControls["InputBlock"] = InputPropertyTabComponent;
-PropertyLedger.RegisteredControls["PerturbNormalBlock"] = PerturbNormalPropertyTabComponent;
-PropertyLedger.RegisteredControls["WorleyNoise3DBlock"] = WorleyNoise3DNodePropertyComponent;
-PropertyLedger.RegisteredControls["ClampBlock"] = ClampPropertyTabComponent;
 PropertyLedger.RegisteredControls["GradientBlock"] = GradientPropertyTabComponent;
 PropertyLedger.RegisteredControls["LightBlock"] = LightPropertyTabComponent;
 PropertyLedger.RegisteredControls["LightInformationBlock"] = LightInformationPropertyTabComponent;
-PropertyLedger.RegisteredControls["RemapBlock"] = RemapPropertyTabComponent;
 PropertyLedger.RegisteredControls["TextureBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["ReflectionTextureBlock"] = TexturePropertyTabComponent;
+PropertyLedger.RegisteredControls["ReflectionBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["TrigonometryBlock"] = TrigonometryPropertyTabComponent;

+ 462 - 0
src/Materials/Node/Blocks/Dual/reflectionTextureBaseBlock.ts

@@ -0,0 +1,462 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { BaseTexture } from '../../../Textures/baseTexture';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { Effect } from '../../../effect';
+import { Mesh } from '../../../../Meshes/mesh';
+import { Nullable } from '../../../../types';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { Scene } from '../../../../scene';
+import { InputBlock } from '../Input/inputBlock';
+import { NodeMaterialSystemValues } from '../../Enums/nodeMaterialSystemValues';
+import { Constants } from '../../../../Engines/constants';
+
+import "../../../../Shaders/ShadersInclude/reflectionFunction";
+import { CubeTexture } from '../../../Textures/cubeTexture';
+import { Texture } from '../../../Textures/texture';
+
+/**
+ * Base block used to read a reflection texture from a sampler
+ */
+export abstract class ReflectionTextureBaseBlock extends NodeMaterialBlock {
+    /** @hidden */
+    public _define3DName: string;
+    /** @hidden */
+    public _defineCubicName: string;
+    /** @hidden */
+    public _defineExplicitName: string;
+    /** @hidden */
+    public _defineProjectionName: string;
+    /** @hidden */
+    public _defineLocalCubicName: string;
+    /** @hidden */
+    public _defineSphericalName: string;
+    /** @hidden */
+    public _definePlanarName: string;
+    /** @hidden */
+    public _defineEquirectangularName: string;
+    /** @hidden */
+    public _defineMirroredEquirectangularFixedName: string;
+    /** @hidden */
+    public _defineEquirectangularFixedName: string;
+    /** @hidden */
+    public _defineSkyboxName: string;
+    /** @hidden */
+    public _defineOppositeZ: string;
+    /** @hidden */
+    public _cubeSamplerName: string;
+    /** @hidden */
+    public _2DSamplerName: string;
+    protected _positionUVWName: string;
+    protected _directionWName: string;
+    protected _reflectionVectorName: string;
+    /** @hidden */
+    public _reflectionCoordsName: string;
+    protected _reflectionMatrixName: string;
+    protected _reflectionColorName: string;
+
+    /**
+     * Gets or sets the texture associated with the node
+     */
+    public texture: Nullable<BaseTexture>;
+
+    /**
+     * Create a new ReflectionTextureBaseBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.VertexAndFragment);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ReflectionTextureBaseBlock";
+    }
+
+    /**
+     * Gets the world position input component
+     */
+    public abstract get position(): NodeMaterialConnectionPoint;
+
+    /**
+     * Gets the world position input component
+     */
+    public abstract get worldPosition(): NodeMaterialConnectionPoint;
+
+    /**
+     * Gets the world normal input component
+     */
+    public abstract get worldNormal(): NodeMaterialConnectionPoint;
+
+    /**
+     * Gets the world input component
+     */
+    public abstract get world(): NodeMaterialConnectionPoint;
+
+    /**
+    * Gets the camera (or eye) position component
+    */
+    public abstract get cameraPosition(): NodeMaterialConnectionPoint;
+
+    /**
+     * Gets the view input component
+     */
+    public abstract get view(): NodeMaterialConnectionPoint;
+
+    protected _getTexture(): Nullable<BaseTexture> {
+        return this.texture;
+    }
+
+    public autoConfigure(material: NodeMaterial) {
+        if (!this.position.isConnected) {
+            let positionInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "position");
+
+            if (!positionInput) {
+                positionInput = new InputBlock("position");
+                positionInput.setAsAttribute();
+            }
+            positionInput.output.connectTo(this.position);
+        }
+
+        if (!this.world.isConnected) {
+            let worldInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.World);
+
+            if (!worldInput) {
+                worldInput = new InputBlock("world");
+                worldInput.setAsSystemValue(NodeMaterialSystemValues.World);
+            }
+            worldInput.output.connectTo(this.world);
+        }
+
+        if (!this.view.isConnected) {
+            let viewInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.View);
+
+            if (!viewInput) {
+                viewInput = new InputBlock("view");
+                viewInput.setAsSystemValue(NodeMaterialSystemValues.View);
+            }
+            viewInput.output.connectTo(this.view);
+        }
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (!defines._areTexturesDirty) {
+            return;
+        }
+
+        const texture = this._getTexture();
+
+        if (!texture || !texture.getTextureMatrix) {
+            return;
+        }
+
+        defines.setValue(this._define3DName, texture.isCube);
+        defines.setValue(this._defineLocalCubicName, (<any>texture).boundingBoxSize ? true : false);
+        defines.setValue(this._defineExplicitName, texture.coordinatesMode === Constants.TEXTURE_EXPLICIT_MODE);
+        defines.setValue(this._defineSkyboxName, texture.coordinatesMode === Constants.TEXTURE_SKYBOX_MODE);
+        defines.setValue(this._defineCubicName, texture.coordinatesMode === Constants.TEXTURE_CUBIC_MODE);
+        defines.setValue(this._defineSphericalName, texture.coordinatesMode === Constants.TEXTURE_SPHERICAL_MODE);
+        defines.setValue(this._definePlanarName, texture.coordinatesMode === Constants.TEXTURE_PLANAR_MODE);
+        defines.setValue(this._defineProjectionName, texture.coordinatesMode === Constants.TEXTURE_PROJECTION_MODE);
+        defines.setValue(this._defineEquirectangularName, texture.coordinatesMode === Constants.TEXTURE_EQUIRECTANGULAR_MODE);
+        defines.setValue(this._defineEquirectangularFixedName, texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MODE);
+        defines.setValue(this._defineMirroredEquirectangularFixedName, texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MIRRORED_MODE);
+    }
+
+    public isReady() {
+        const texture = this._getTexture();
+
+        if (texture && !texture.isReadyOrNotBlocking()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        const texture = this._getTexture();
+
+        if (!mesh || !texture) {
+            return;
+        }
+
+        effect.setMatrix(this._reflectionMatrixName, texture.getReflectionTextureMatrix());
+
+        if (texture.isCube) {
+            effect.setTexture(this._cubeSamplerName, texture);
+        } else {
+            effect.setTexture(this._2DSamplerName, texture);
+        }
+    }
+
+    /**
+     * Gets the code to inject in the vertex shader
+     * @param state current state of the node material building
+     * @returns the shader code
+     */
+    public handleVertexSide(state: NodeMaterialBuildState): string {
+        this._define3DName = state._getFreeDefineName("REFLECTIONMAP_3D");
+        this._defineCubicName = state._getFreeDefineName("REFLECTIONMAP_CUBIC");
+        this._defineSphericalName = state._getFreeDefineName("REFLECTIONMAP_SPHERICAL");
+        this._definePlanarName = state._getFreeDefineName("REFLECTIONMAP_PLANAR");
+        this._defineProjectionName = state._getFreeDefineName("REFLECTIONMAP_PROJECTION");
+        this._defineExplicitName = state._getFreeDefineName("REFLECTIONMAP_EXPLICIT");
+        this._defineEquirectangularName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR");
+        this._defineLocalCubicName = state._getFreeDefineName("USE_LOCAL_REFLECTIONMAP_CUBIC");
+        this._defineMirroredEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_MIRROREDEQUIRECTANGULAR_FIXED");
+        this._defineEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR_FIXED");
+        this._defineSkyboxName = state._getFreeDefineName("REFLECTIONMAP_SKYBOX");
+        this._defineOppositeZ = state._getFreeDefineName("REFLECTIONMAP_OPPOSITEZ");
+
+        this._reflectionMatrixName = state._getFreeVariableName("reflectionMatrix");
+
+        state._emitUniformFromString(this._reflectionMatrixName, "mat4");
+
+        let code = "";
+
+        let worldPosVaryingName = "v_" + this.worldPosition.associatedVariableName;
+        if (state._emitVaryingFromString(worldPosVaryingName, "vec4")) {
+            code += `${worldPosVaryingName} = ${this.worldPosition.associatedVariableName};\r\n`;
+        }
+
+        this._positionUVWName = state._getFreeVariableName("positionUVW");
+        this._directionWName = state._getFreeVariableName("directionW");
+
+        if (state._emitVaryingFromString(this._positionUVWName, "vec3", this._defineSkyboxName)) {
+            code += `#ifdef ${this._defineSkyboxName}\r\n`;
+            code += `${this._positionUVWName} = ${this.position.associatedVariableName}.xyz;\r\n`;
+            code += `#endif\r\n`;
+        }
+
+        if (state._emitVaryingFromString(this._directionWName, "vec3", `defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})`)) {
+            code += `#if defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})\r\n`;
+            code += `${this._directionWName} = normalize(vec3(${this.world.associatedVariableName} * vec4(${this.position.associatedVariableName}.xyz, 0.0)));\r\n`;
+            code += `#endif\r\n`;
+        }
+
+        return code;
+    }
+
+    /**
+     * Handles the inits for the fragment code path
+     * @param state node material build state
+     */
+    public handleFragmentSideInits(state: NodeMaterialBuildState) {
+        state.sharedData.blockingBlocks.push(this);
+        state.sharedData.textureBlocks.push(this);
+
+        // Samplers
+        this._cubeSamplerName = state._getFreeVariableName(this.name + "CubeSampler");
+        state.samplers.push(this._cubeSamplerName);
+
+        this._2DSamplerName = state._getFreeVariableName(this.name + "2DSampler");
+        state.samplers.push(this._2DSamplerName);
+
+        state._samplerDeclaration += `#ifdef ${this._define3DName}\r\n`;
+        state._samplerDeclaration += `uniform samplerCube ${this._cubeSamplerName};\r\n`;
+        state._samplerDeclaration += `#else\r\n`;
+        state._samplerDeclaration += `uniform sampler2D ${this._2DSamplerName};\r\n`;
+        state._samplerDeclaration += `#endif\r\n`;
+
+        // Fragment
+        state.sharedData.blocksWithDefines.push(this);
+        state.sharedData.bindableBlocks.push(this);
+
+        let comments = `//${this.name}`;
+        state._emitFunction("ReciprocalPI", "#define RECIPROCAL_PI2 0.15915494", "");
+        state._emitFunctionFromInclude("reflectionFunction", comments, {
+            replaceStrings: [
+                { search: /vec3 computeReflectionCoords/g, replace: "void DUMMYFUNC" }
+            ]
+        });
+
+        this._reflectionColorName = state._getFreeVariableName("reflectionColor");
+        this._reflectionVectorName = state._getFreeVariableName("reflectionUVW");
+        this._reflectionCoordsName = state._getFreeVariableName("reflectionCoords");
+    }
+
+    /**
+     * Generates the reflection coords code for the fragment code path
+     * @param worldNormalVarName name of the world normal variable
+     * @param worldPos name of the world position variable. If not provided, will use the world position connected to this block
+     * @param onlyReflectionVector if true, generates code only for the reflection vector computation, not for the reflection coordinates
+     * @returns the shader code
+     */
+    public handleFragmentSideCodeReflectionCoords(worldNormalVarName: string, worldPos?: string, onlyReflectionVector = false): string {
+        if (!worldPos) {
+            worldPos = `v_${this.worldPosition.associatedVariableName}`;
+        }
+        let reflectionMatrix = this._reflectionMatrixName;
+        let direction = `normalize(${this._directionWName})`;
+        let positionUVW = `${this._positionUVWName}`;
+        let vEyePosition = `${this.cameraPosition.associatedVariableName}`;
+        let view = `${this.view.associatedVariableName}`;
+
+        worldNormalVarName += ".xyz";
+
+        let code = `
+            #ifdef ${this._defineMirroredEquirectangularFixedName}
+                vec3 ${this._reflectionVectorName} = computeMirroredFixedEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${direction});
+            #endif
+
+            #ifdef ${this._defineEquirectangularFixedName}
+                vec3 ${this._reflectionVectorName} = computeFixedEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${direction});
+            #endif
+
+            #ifdef ${this._defineEquirectangularName}
+                vec3 ${this._reflectionVectorName} = computeEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix});
+            #endif
+
+            #ifdef ${this._defineSphericalName}
+                vec3 ${this._reflectionVectorName} = computeSphericalCoords(${worldPos}, ${worldNormalVarName}, ${view}, ${reflectionMatrix});
+            #endif
+
+            #ifdef ${this._definePlanarName}
+                vec3 ${this._reflectionVectorName} = computePlanarCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix});
+            #endif
+
+            #ifdef ${this._defineCubicName}
+                #ifdef ${this._defineLocalCubicName}
+                    vec3 ${this._reflectionVectorName} = computeCubicLocalCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix}, vReflectionSize, vReflectionPosition);
+                #else
+                vec3 ${this._reflectionVectorName} = computeCubicCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix});
+                #endif
+            #endif
+
+            #ifdef ${this._defineProjectionName}
+                vec3 ${this._reflectionVectorName} = computeProjectionCoords(${worldPos}, ${view}, ${reflectionMatrix});
+            #endif
+
+            #ifdef ${this._defineSkyboxName}
+                vec3 ${this._reflectionVectorName} = computeSkyBoxCoords(${positionUVW}, ${reflectionMatrix});
+            #endif
+
+            #ifdef ${this._defineExplicitName}
+                vec3 ${this._reflectionVectorName} = vec3(0, 0, 0);
+            #endif
+
+            #ifdef ${this._defineOppositeZ}
+                ${this._reflectionVectorName}.z *= -1.0;
+            #endif\r\n`;
+
+        if (!onlyReflectionVector) {
+            code += `
+                #ifdef ${this._define3DName}
+                    vec3 ${this._reflectionCoordsName} = ${this._reflectionVectorName};
+                #else
+                    vec2 ${this._reflectionCoordsName} = ${this._reflectionVectorName}.xy;
+                    #ifdef ${this._defineProjectionName}
+                        ${this._reflectionCoordsName} /= ${this._reflectionVectorName}.z;
+                    #endif
+                    ${this._reflectionCoordsName}.y = 1.0 - ${this._reflectionCoordsName}.y;
+                #endif\r\n`;
+        }
+
+        return code;
+    }
+
+    /**
+     * Generates the reflection color code for the fragment code path
+     * @param lodVarName name of the lod variable
+     * @param swizzleLookupTexture swizzle to use for the final color variable
+     * @returns the shader code
+     */
+    public handleFragmentSideCodeReflectionColor(lodVarName?: string, swizzleLookupTexture = ".rgb"): string {
+        const colorType = "vec" + (swizzleLookupTexture.length === 0 ? "4" : (swizzleLookupTexture.length - 1));
+
+        let code = `${colorType} ${this._reflectionColorName};
+            #ifdef ${this._define3DName}\r\n`;
+
+        if (lodVarName) {
+            code += `${this._reflectionColorName} = textureCubeLodEXT(${this._cubeSamplerName}, ${this._reflectionVectorName}, ${lodVarName})${swizzleLookupTexture};\r\n`;
+        } else {
+            code += `${this._reflectionColorName} = textureCube(${this._cubeSamplerName}, ${this._reflectionVectorName})${swizzleLookupTexture};\r\n`;
+        }
+
+        code += `
+            #else\r\n`;
+
+        if (lodVarName) {
+            code += `${this._reflectionColorName} = texture2DLodEXT(${this._2DSamplerName}, ${this._reflectionCoordsName}, ${lodVarName})${swizzleLookupTexture};\r\n`;
+        } else {
+            code += `${this._reflectionColorName} = texture2D(${this._2DSamplerName}, ${this._reflectionCoordsName})${swizzleLookupTexture};\r\n`;
+        }
+
+        code += `#endif\r\n`;
+
+        return code;
+    }
+
+    /**
+     * Generates the code corresponding to the connected output points
+     * @param state node material build state
+     * @param varName name of the variable to output
+     * @returns the shader code
+     */
+    public writeOutputs(state: NodeMaterialBuildState, varName: string): string {
+        let code = "";
+
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            for (var output of this._outputs) {
+                if (output.hasEndpoints) {
+                    code += `${this._declareOutput(output, state)} = ${varName}.${output.name};\r\n`;
+                }
+            }
+        }
+
+        return code;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+        return this;
+    }
+
+    protected _dumpPropertiesCode() {
+        if (!this.texture) {
+            return "";
+        }
+
+        let codeString: string;
+
+        if (this.texture.isCube) {
+            codeString = `${this._codeVariableName}.texture = new BABYLON.CubeTexture("${this.texture.name}");\r\n`;
+        } else {
+            codeString = `${this._codeVariableName}.texture = new BABYLON.Texture("${this.texture.name}");\r\n`;
+        }
+        codeString += `${this._codeVariableName}.texture.coordinatesMode = ${this.texture.coordinatesMode};\r\n`;
+
+        return codeString;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        if (this.texture) {
+            serializationObject.texture = this.texture.serialize();
+        }
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        if (serializationObject.texture) {
+            rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl;
+            if (serializationObject.texture.isCube) {
+                this.texture = CubeTexture.Parse(serializationObject.texture, scene, rootUrl);
+            } else {
+                this.texture = Texture.Parse(serializationObject.texture, scene, rootUrl);
+            }
+        }
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.ReflectionTextureBaseBlock"] = ReflectionTextureBaseBlock;

+ 16 - 297
src/Materials/Node/Blocks/Dual/reflectionTextureBlock.ts

@@ -1,59 +1,23 @@
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
 import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
 import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
 import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
 import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
-import { BaseTexture } from '../../../Textures/baseTexture';
-import { AbstractMesh } from '../../../../Meshes/abstractMesh';
-import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
-import { Effect } from '../../../effect';
-import { Mesh } from '../../../../Meshes/mesh';
-import { Nullable } from '../../../../types';
+import { NodeMaterial } from '../../nodeMaterial';
 import { _TypeStore } from '../../../../Misc/typeStore';
-import { Scene } from '../../../../scene';
 import { InputBlock } from '../Input/inputBlock';
 import { NodeMaterialSystemValues } from '../../Enums/nodeMaterialSystemValues';
-import { Constants } from '../../../../Engines/constants';
-
-import "../../../../Shaders/ShadersInclude/reflectionFunction";
-import { CubeTexture } from '../../../Textures/cubeTexture';
-import { Texture } from '../../../Textures/texture';
+import { ReflectionTextureBaseBlock } from './reflectionTextureBaseBlock';
 
 /**
  * Block used to read a reflection texture from a sampler
  */
-export class ReflectionTextureBlock extends NodeMaterialBlock {
-    private _define3DName: string;
-    private _defineCubicName: string;
-    private _defineExplicitName: string;
-    private _defineProjectionName: string;
-    private _defineLocalCubicName: string;
-    private _defineSphericalName: string;
-    private _definePlanarName: string;
-    private _defineEquirectangularName: string;
-    private _defineMirroredEquirectangularFixedName: string;
-    private _defineEquirectangularFixedName: string;
-    private _defineSkyboxName: string;
-    private _cubeSamplerName: string;
-    private _2DSamplerName: string;
-    private _positionUVWName: string;
-    private _directionWName: string;
-    private _reflectionCoordsName: string;
-    private _reflection2DCoordsName: string;
-    private _reflectionColorName: string;
-    private _reflectionMatrixName: string;
-
-    /**
-     * Gets or sets the texture associated with the node
-     */
-    public texture: Nullable<BaseTexture>;
-
+export class ReflectionTextureBlock extends ReflectionTextureBaseBlock {
     /**
-     * Create a new TextureBlock
+     * Create a new ReflectionTextureBlock
      * @param name defines the block name
      */
     public constructor(name: string) {
-        super(name, NodeMaterialBlockTargets.VertexAndFragment);
+        super(name);
 
         this.registerInput("position", NodeMaterialBlockConnectionPointTypes.Vector3, false, NodeMaterialBlockTargets.Vertex);
         this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex);
@@ -150,25 +114,7 @@ export class ReflectionTextureBlock extends NodeMaterialBlock {
     }
 
     public autoConfigure(material: NodeMaterial) {
-        if (!this.position.isConnected) {
-            let positionInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "position");
-
-            if (!positionInput) {
-                positionInput = new InputBlock("position");
-                positionInput.setAsAttribute();
-            }
-            positionInput.output.connectTo(this.position);
-        }
-
-        if (!this.world.isConnected) {
-            let worldInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.World);
-
-            if (!worldInput) {
-                worldInput = new InputBlock("world");
-                worldInput.setAsSystemValue(NodeMaterialSystemValues.World);
-            }
-            worldInput.output.connectTo(this.world);
-        }
+        super.autoConfigure(material);
 
         if (!this.cameraPosition.isConnected) {
             let cameraPositionInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.CameraPosition);
@@ -179,262 +125,35 @@ export class ReflectionTextureBlock extends NodeMaterialBlock {
             }
             cameraPositionInput.output.connectTo(this.cameraPosition);
         }
-
-        if (!this.view.isConnected) {
-            let viewInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.View);
-
-            if (!viewInput) {
-                viewInput = new InputBlock("view");
-                viewInput.setAsSystemValue(NodeMaterialSystemValues.View);
-            }
-            viewInput.output.connectTo(this.view);
-        }
-    }
-
-    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
-        if (!defines._areTexturesDirty) {
-            return;
-        }
-
-        if (!this.texture || !this.texture.getTextureMatrix) {
-            return;
-        }
-
-        defines.setValue(this._define3DName, this.texture.isCube);
-        defines.setValue(this._defineLocalCubicName, (<any>this.texture).boundingBoxSize ? true : false);
-        defines.setValue(this._defineExplicitName, this.texture.coordinatesMode === Constants.TEXTURE_EXPLICIT_MODE);
-        defines.setValue(this._defineSkyboxName, this.texture.coordinatesMode === Constants.TEXTURE_SKYBOX_MODE);
-        defines.setValue(this._defineCubicName, this.texture.coordinatesMode === Constants.TEXTURE_CUBIC_MODE);
-        defines.setValue(this._defineSphericalName, this.texture.coordinatesMode === Constants.TEXTURE_SPHERICAL_MODE);
-        defines.setValue(this._definePlanarName, this.texture.coordinatesMode === Constants.TEXTURE_PLANAR_MODE);
-        defines.setValue(this._defineProjectionName, this.texture.coordinatesMode === Constants.TEXTURE_PROJECTION_MODE);
-        defines.setValue(this._defineEquirectangularName, this.texture.coordinatesMode === Constants.TEXTURE_EQUIRECTANGULAR_MODE);
-        defines.setValue(this._defineEquirectangularFixedName, this.texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MODE);
-        defines.setValue(this._defineMirroredEquirectangularFixedName, this.texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MIRRORED_MODE);
-    }
-
-    public isReady() {
-        if (this.texture && !this.texture.isReadyOrNotBlocking()) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
-        if (!mesh || !this.texture) {
-            return;
-        }
-
-        effect.setMatrix(this._reflectionMatrixName, this.texture.getReflectionTextureMatrix());
-
-        if (this.texture.isCube) {
-            effect.setTexture(this._cubeSamplerName, this.texture);
-        } else {
-            effect.setTexture(this._2DSamplerName, this.texture);
-        }
-    }
-
-    private _injectVertexCode(state: NodeMaterialBuildState) {
-        let worldPosVaryingName = "v_" + this.worldPosition.associatedVariableName;
-        if (state._emitVaryingFromString(worldPosVaryingName, "vec4")) {
-            state.compilationString += `${worldPosVaryingName} = ${this.worldPosition.associatedVariableName};\r\n`;
-        }
-
-        this._positionUVWName = state._getFreeVariableName("positionUVW");
-        this._directionWName = state._getFreeVariableName("directionW");
-
-        if (state._emitVaryingFromString(this._positionUVWName, "vec3", this._defineSkyboxName)) {
-            state.compilationString += `#ifdef ${this._defineSkyboxName}\r\n`;
-            state.compilationString += `${this._positionUVWName} = ${this.position.associatedVariableName}.xyz;\r\n`;
-            state.compilationString += `#endif\r\n`;
-        }
-
-        if (state._emitVaryingFromString(this._directionWName, "vec3", `defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})`)) {
-            state.compilationString += `#if defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})\r\n`;
-            state.compilationString += `${this._directionWName} = normalize(vec3(${this.world.associatedVariableName} * vec4(${this.position.associatedVariableName}.xyz, 0.0)));\r\n`;
-            state.compilationString += `#endif\r\n`;
-        }
-    }
-
-    private _writeOutput(state: NodeMaterialBuildState, output: NodeMaterialConnectionPoint, swizzle: string) {
-        state.compilationString += `${this._declareOutput(output, state)} = ${this._reflectionColorName}.${swizzle};\r\n`;
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {
         super._buildBlock(state);
 
         if (!this.texture) {
-            if (state.target === NodeMaterialBlockTargets.Fragment) {
-                for (var output of this._outputs) {
-                    if (output.hasEndpoints) {
-                        state.compilationString += `${this._declareOutput(output, state)} = vec3(0.).${output.name};\r\n`;
-                    }
-                }
-            }
-            return;
+            state.compilationString += this.writeOutputs(state, "vec3(0.)");
+            return this;
         }
 
         if (state.target !== NodeMaterialBlockTargets.Fragment) {
-            this._define3DName = state._getFreeDefineName("REFLECTIONMAP_3D");
-            this._defineCubicName = state._getFreeDefineName("REFLECTIONMAP_CUBIC");
-            this._defineSphericalName = state._getFreeDefineName("REFLECTIONMAP_SPHERICAL");
-            this._definePlanarName = state._getFreeDefineName("REFLECTIONMAP_PLANAR");
-            this._defineProjectionName = state._getFreeDefineName("REFLECTIONMAP_PROJECTION");
-            this._defineExplicitName = state._getFreeDefineName("REFLECTIONMAP_EXPLICIT");
-            this._defineEquirectangularName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR");
-            this._defineLocalCubicName = state._getFreeDefineName("USE_LOCAL_REFLECTIONMAP_CUBIC");
-            this._defineMirroredEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_MIRROREDEQUIRECTANGULAR_FIXED");
-            this._defineEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR_FIXED");
-            this._defineSkyboxName = state._getFreeDefineName("REFLECTIONMAP_SKYBOX");
-
-            // Vertex
-            this._injectVertexCode(state);
-            return;
+            state.compilationString += this.handleVertexSide(state);
+            return this;
         }
 
-        state.sharedData.blockingBlocks.push(this);
-        state.sharedData.textureBlocks.push(this);
-
-        // Samplers
-        this._cubeSamplerName = state._getFreeVariableName(this.name + "CubeSampler");
-        state.samplers.push(this._cubeSamplerName);
-
-        this._2DSamplerName = state._getFreeVariableName(this.name + "2DSampler");
-        state.samplers.push(this._2DSamplerName);
-
-        state._samplerDeclaration += `#ifdef ${this._define3DName}\r\n`;
-        state._samplerDeclaration += `uniform samplerCube ${this._cubeSamplerName};\r\n`;
-        state._samplerDeclaration += `#else\r\n`;
-        state._samplerDeclaration += `uniform sampler2D ${this._2DSamplerName};\r\n`;
-        state._samplerDeclaration += `#endif\r\n`;
-
-        // Fragment
-        state.sharedData.blocksWithDefines.push(this);
-        state.sharedData.bindableBlocks.push(this);
-
-        let comments = `//${this.name}`;
-        state._emitFunction("ReciprocalPI", "#define RECIPROCAL_PI2 0.15915494", "");
-        state._emitFunctionFromInclude("reflectionFunction", comments);
-
-        this._reflectionColorName = state._getFreeVariableName("reflectionColor");
-        this._reflectionCoordsName = state._getFreeVariableName("reflectionUVW");
-        this._reflection2DCoordsName = state._getFreeVariableName("reflectionUV");
-        this._reflectionMatrixName = state._getFreeVariableName("reflectionMatrix");
-
-        state._emitUniformFromString(this._reflectionMatrixName, "mat4");
-
-        // Code
-        let worldPos = `v_${this.worldPosition.associatedVariableName}`;
-        let worldNormal = this.worldNormal.associatedVariableName + ".xyz";
-        let reflectionMatrix = this._reflectionMatrixName;
-        let direction = `normalize(${this._directionWName})`;
-        let positionUVW = `${this._positionUVWName}`;
-        let vEyePosition = `${this.cameraPosition.associatedVariableName}`;
-        let view = `${this.view.associatedVariableName}`;
-
-        state.compilationString += `vec3 ${this._reflectionColorName};\r\n`;
-        state.compilationString += `#ifdef ${this._defineMirroredEquirectangularFixedName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = computeMirroredFixedEquirectangularCoords(${worldPos}, ${worldNormal}, ${direction});\r\n`;
-        state.compilationString += `#endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._defineEquirectangularFixedName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = computeFixedEquirectangularCoords(${worldPos}, ${worldNormal}, ${direction});\r\n`;
-        state.compilationString += `#endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._defineEquirectangularName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = computeEquirectangularCoords(${worldPos}, ${worldNormal}, ${vEyePosition}.xyz, ${reflectionMatrix});\r\n`;
-        state.compilationString += ` #endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._defineSphericalName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = computeSphericalCoords(${worldPos}, ${worldNormal}, ${view}, ${reflectionMatrix});\r\n`;
-        state.compilationString += `#endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._definePlanarName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = computePlanarCoords(${worldPos}, ${worldNormal}, ${vEyePosition}.xyz, ${reflectionMatrix});\r\n`;
-        state.compilationString += `#endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._defineCubicName}\r\n`;
-        state.compilationString += `    #ifdef ${this._defineLocalCubicName}\r\n`;
-        state.compilationString += `        vec3 ${this._reflectionCoordsName} = computeCubicLocalCoords(${worldPos}, ${worldNormal}, ${vEyePosition}.xyz, ${reflectionMatrix}, vReflectionSize, vReflectionPosition);\r\n`;
-        state.compilationString += `    #else\r\n`;
-        state.compilationString += `       vec3 ${this._reflectionCoordsName} = computeCubicCoords(${worldPos}, ${worldNormal}, ${vEyePosition}.xyz, ${reflectionMatrix});\r\n`;
-        state.compilationString += `    #endif\r\n`;
-        state.compilationString += `#endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._defineProjectionName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = computeProjectionCoords(${worldPos}, ${view}, ${reflectionMatrix});\r\n`;
-        state.compilationString += `#endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._defineSkyboxName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = computeSkyBoxCoords(${positionUVW}, ${reflectionMatrix});\r\n`;
-        state.compilationString += `#endif\r\n`;
+        this.handleFragmentSideInits(state);
 
-        state.compilationString += `#ifdef ${this._defineExplicitName}\r\n`;
-        state.compilationString += `    vec3 ${this._reflectionCoordsName} = vec3(0, 0, 0);\r\n`;
-        state.compilationString += `#endif\r\n`;
+        const normalWUnit = state._getFreeVariableName("normalWUnit");
 
-        state.compilationString += `#ifdef ${this._define3DName}\r\n`;
-        state.compilationString += `${this._reflectionColorName} = textureCube(${this._cubeSamplerName}, ${this._reflectionCoordsName}).rgb;\r\n`;
-        state.compilationString += `#else\r\n`;
-        state.compilationString += `vec2 ${this._reflection2DCoordsName} = ${this._reflectionCoordsName}.xy;\r\n`;
+        state.compilationString += `vec4 ${normalWUnit} = normalize(${this.worldNormal.associatedVariableName});\r\n`;
 
-        state.compilationString += `#ifdef ${this._defineProjectionName}\r\n`;
-        state.compilationString += `${this._reflection2DCoordsName} /= ${this._reflectionCoordsName}.z;\r\n`;
-        state.compilationString += `#endif\r\n`;
+        state.compilationString += this.handleFragmentSideCodeReflectionCoords(normalWUnit);
 
-        state.compilationString += `${this._reflection2DCoordsName}.y = 1.0 - ${this._reflection2DCoordsName}.y;\r\n`;
-        state.compilationString += `${this._reflectionColorName} = texture2D(${this._2DSamplerName}, ${this._reflection2DCoordsName}).rgb;\r\n`;
-        state.compilationString += `#endif\r\n`;
+        state.compilationString += this.handleFragmentSideCodeReflectionColor();
 
-        for (var output of this._outputs) {
-            if (output.hasEndpoints) {
-                this._writeOutput(state, output, output.name);
-            }
-        }
+        state.compilationString += this.writeOutputs(state, this._reflectionColorName);
 
         return this;
     }
-
-    protected _dumpPropertiesCode() {
-        if (!this.texture) {
-            return "";
-        }
-
-        let codeString: string;
-
-        if (this.texture.isCube) {
-            codeString = `${this._codeVariableName}.texture = new BABYLON.CubeTexture("${this.texture.name}");\r\n`;
-        } else {
-            codeString = `${this._codeVariableName}.texture = new BABYLON.Texture("${this.texture.name}");\r\n`;
-        }
-        codeString += `${this._codeVariableName}.texture.coordinatesMode = ${this.texture.coordinatesMode};\r\n`;
-
-        return codeString;
-    }
-
-    public serialize(): any {
-        let serializationObject = super.serialize();
-
-        if (this.texture) {
-            serializationObject.texture = this.texture.serialize();
-        }
-
-        return serializationObject;
-    }
-
-    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
-        super._deserialize(serializationObject, scene, rootUrl);
-
-        if (serializationObject.texture) {
-            rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl;
-            if (serializationObject.texture.isCube) {
-                this.texture = CubeTexture.Parse(serializationObject.texture, scene, rootUrl);
-            } else {
-                this.texture = Texture.Parse(serializationObject.texture, scene, rootUrl);
-            }
-        }
-    }
 }
 
 _TypeStore.RegisteredTypes["BABYLON.ReflectionTextureBlock"] = ReflectionTextureBlock;

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

@@ -21,6 +21,7 @@ import "../../../../Shaders/ShadersInclude/helperFunctions";
 export class TextureBlock extends NodeMaterialBlock {
     private _defineName: string;
     private _linearDefineName: string;
+    private _gammaDefineName: string;
     private _tempTextureRead: string;
     private _samplerName: string;
     private _transformedUVName: string;
@@ -40,6 +41,11 @@ export class TextureBlock extends NodeMaterialBlock {
     public convertToGammaSpace = false;
 
     /**
+     * Gets or sets a boolean indicating if content needs to be converted to linear space
+     */
+    public convertToLinearSpace = false;
+
+    /**
      * Create a new TextureBlock
      * @param name defines the block name
      */
@@ -189,6 +195,7 @@ export class TextureBlock extends NodeMaterialBlock {
         }
 
         defines.setValue(this._linearDefineName, this.convertToGammaSpace);
+        defines.setValue(this._gammaDefineName, this.convertToLinearSpace);
         if (this._isMixed) {
             if (!this.texture.getTextureMatrix().isIdentityAs3x2()) {
                 defines.setValue(this._defineName, true);
@@ -314,6 +321,10 @@ export class TextureBlock extends NodeMaterialBlock {
         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) {
@@ -352,6 +363,7 @@ export class TextureBlock extends NodeMaterialBlock {
         }
 
         this._linearDefineName = state._getFreeDefineName("ISLINEAR");
+        this._gammaDefineName = state._getFreeDefineName("ISGAMMA");
 
         let comments = `//${this.name}`;
         state._emitFunctionFromInclude("helperFunctions", comments);
@@ -387,6 +399,7 @@ export class TextureBlock extends NodeMaterialBlock {
         codeString += `${this._codeVariableName}.texture.uScale = ${this.texture.uScale};\r\n`;
         codeString += `${this._codeVariableName}.texture.vScale = ${this.texture.vScale};\r\n`;
         codeString += `${this._codeVariableName}.convertToGammaSpace = ${this.convertToGammaSpace};\r\n`;
+        codeString += `${this._codeVariableName}.convertToLinearSpace = ${this.convertToLinearSpace};\r\n`;
 
         return codeString;
     }
@@ -395,6 +408,7 @@ export class TextureBlock extends NodeMaterialBlock {
         let serializationObject = super.serialize();
 
         serializationObject.convertToGammaSpace = this.convertToGammaSpace;
+        serializationObject.convertToLinearSpace = this.convertToLinearSpace;
         if (this.texture) {
             serializationObject.texture = this.texture.serialize();
         }
@@ -406,6 +420,7 @@ export class TextureBlock extends NodeMaterialBlock {
         super._deserialize(serializationObject, scene, rootUrl);
 
         this.convertToGammaSpace = serializationObject.convertToGammaSpace;
+        this.convertToLinearSpace = serializationObject.convertToLinearSpace;
 
         if (serializationObject.texture && !NodeMaterial.IgnoreTexturesAtLoadTime) {
             rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl;

+ 1 - 1
src/Materials/Node/Blocks/Fragment/index.ts

@@ -4,4 +4,4 @@ export * from "./imageProcessingBlock";
 export * from "./perturbNormalBlock";
 export * from "./discardBlock";
 export * from "./frontFacingBlock";
-export * from "./derivativeBlock";
+export * from "./derivativeBlock";

+ 10 - 1
src/Materials/Node/Blocks/Fragment/perturbNormalBlock.ts

@@ -10,7 +10,9 @@ import { InputBlock } from '../Input/inputBlock';
 import { Effect } from '../../../effect';
 import { Mesh } from '../../../../Meshes/mesh';
 import { Scene } from '../../../../scene';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
 
+import "../../../../Shaders/ShadersInclude/bumpFragmentMainFunctions";
 import "../../../../Shaders/ShadersInclude/bumpFragmentFunctions";
 import "../../../../Shaders/ShadersInclude/bumpFragment";
 
@@ -21,8 +23,10 @@ export class PerturbNormalBlock extends NodeMaterialBlock {
     private _tangentSpaceParameterName = "";
 
     /** Gets or sets a boolean indicating that normal should be inverted on X axis */
+    @editableInPropertyPage("Invert X axis", PropertyTypeForEdition.Boolean, "PROPERTIES", { "notifiers": { "update": false }})
     public invertX = false;
     /** Gets or sets a boolean indicating that normal should be inverted on Y axis */
+    @editableInPropertyPage("Invert Y axis", PropertyTypeForEdition.Boolean, "PROPERTIES", { "notifiers": { "update": false }})
     public invertY = false;
 
     /**
@@ -160,12 +164,17 @@ export class PerturbNormalBlock extends NodeMaterialBlock {
             state.compilationString += `mat3 vTBN = mat3(tbnTangent, tbnBitangent, tbnNormal);\r\n`;
         }
 
+        state._emitFunctionFromInclude("bumpFragmentMainFunctions", comments, {
+            replaceStrings: [
+                tangentReplaceString,
+            ]
+        });
+
         state._emitFunctionFromInclude("bumpFragmentFunctions", comments, {
             replaceStrings: [
                 { search: /vBumpInfos.y/g, replace: replaceForBumpInfos},
                 { search: /vTangentSpaceParams/g, replace: this._tangentSpaceParameterName},
                 { search: /vPositionW/g, replace: worldPosition.associatedVariableName + ".xyz"},
-                tangentReplaceString
             ]
         });
 

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

@@ -13,6 +13,7 @@ import { _TypeStore } from '../../../../Misc/typeStore';
 import { Color3, Color4 } from '../../../../Maths/math';
 import { AnimatedInputBlockTypes } from './animatedInputBlockTypes';
 import { Observable } from '../../../../Misc/observable';
+import { MaterialHelper } from '../../../../Materials/materialHelper';
 
 /**
  * Block used to expose an input value
@@ -507,7 +508,7 @@ export class InputBlock extends NodeMaterialBlock {
                     effect.setMatrix(variableName, scene.getTransformMatrix());
                     break;
                 case NodeMaterialSystemValues.CameraPosition:
-                    effect.setVector3(variableName, scene.activeCamera!.globalPosition);
+                    MaterialHelper.BindEyePosition(effect, scene, variableName);
                     break;
                 case NodeMaterialSystemValues.FogColor:
                     effect.setColor3(variableName, scene.fogColor);

+ 146 - 0
src/Materials/Node/Blocks/PBR/ambientOcclusionBlock.ts

@@ -0,0 +1,146 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { Nullable } from '../../../../types';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
+import { Scene } from '../../../../scene';
+
+/**
+ * Block used to implement the ambient occlusion module of the PBR material
+ */
+export class AmbientOcclusionBlock extends NodeMaterialBlock {
+
+    /**
+     * Create a new AmbientOcclusionBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("directLightIntensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("ambientOcclusion", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("ambientOcclusion", this, NodeMaterialConnectionPointDirection.Output, AmbientOcclusionBlock, "AOBlock"));
+    }
+
+    /**
+     * Specifies if the ambient texture contains the ambient occlusion information in its red channel only.
+     */
+    @editableInPropertyPage("Ambient in gray scale", PropertyTypeForEdition.Boolean, "AMBIENT", { "notifiers": { "update": true }})
+    public useAmbientInGrayScale: boolean = false;
+
+    /**
+     * 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("aoOut");
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "AmbientOcclusionBlock";
+    }
+
+    /**
+     * Gets the texture input component
+     */
+    public get texture(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the texture intensity component
+     */
+    public get intensity(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the direct light intensity input component
+     */
+    public get directLightIntensity(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the ambient occlusion object output component
+     */
+    public get ambientOcclusion(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Gets the main code of the block (fragment side)
+     * @param block instance of an AmbientOcclusionBlock or null if the code must be generated without an active ambient occlusion module
+     * @returns the shader code
+     */
+    public static GetCode(block: Nullable<AmbientOcclusionBlock>): string {
+        let code = `ambientOcclusionOutParams aoOut;\r\n`;
+
+        const aoTexture = block?.texture.isConnected ? block.texture.associatedVariableName : "vec3(0.)";
+        const aoIntensity = block?.intensity.isConnected ? block.intensity.associatedVariableName : "1.";
+
+        code += `ambientOcclusionBlock(
+            #ifdef AMBIENT
+                ${aoTexture},
+                vec4(0., 1.0, ${aoIntensity}, 0.),
+            #endif
+                aoOut
+            );\r\n`;
+
+        return code;
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        super.prepareDefines(mesh, nodeMaterial, defines);
+
+        defines.setValue("AMBIENT", this.texture.isConnected);
+        defines.setValue("AMBIENTINGRAYSCALE", this.useAmbientInGrayScale);
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            state.sharedData.blocksWithDefines.push(this);
+        }
+
+        return this;
+    }
+
+    protected _dumpPropertiesCode() {
+        let codeString: string;
+
+        codeString = `${this._codeVariableName}.useAmbientInGrayScale = ${this.useAmbientInGrayScale};\r\n`;
+
+        return codeString;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        serializationObject.useAmbientInGrayScale = this.useAmbientInGrayScale;
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        this.useAmbientInGrayScale = serializationObject.useAmbientInGrayScale;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.AmbientOcclusionBlock"] = AmbientOcclusionBlock;

+ 196 - 0
src/Materials/Node/Blocks/PBR/anisotropyBlock.ts

@@ -0,0 +1,196 @@
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
+
+/**
+ * Block used to implement the anisotropy module of the PBR material
+ */
+export class AnisotropyBlock extends NodeMaterialBlock {
+
+    /**
+     * The two properties below are set by the main PBR block prior to calling methods of this class.
+     * This is to avoid having to add them as inputs here whereas they are already inputs of the main block, so already known.
+     * It's less burden on the user side in the editor part.
+    */
+
+    /** @hidden */
+    public worldPositionConnectionPoint: NodeMaterialConnectionPoint;
+    /** @hidden */
+    public worldNormalConnectionPoint: NodeMaterialConnectionPoint;
+
+    /**
+     * Create a new AnisotropyBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("direction", NodeMaterialBlockConnectionPointTypes.Vector2, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, true); // need this property and the next one in case there's no PerturbNormal block connected to the main PBR block
+        this.registerInput("worldTangent", NodeMaterialBlockConnectionPointTypes.Vector4, true);
+
+        this.registerOutput("anisotropy", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("anisotropy", this, NodeMaterialConnectionPointDirection.Output, AnisotropyBlock, "AnisotropyBlock"));
+    }
+
+    /**
+     * 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("anisotropicOut");
+        state._excludeVariableName("TBN");
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "AnisotropyBlock";
+    }
+
+    /**
+     * Gets the intensity input component
+     */
+    public get intensity(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the direction input component
+     */
+    public get direction(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the texture input component
+     */
+    public get texture(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the uv input component
+     */
+    public get uv(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the worldTangent input component
+     */
+    public get worldTangent(): NodeMaterialConnectionPoint {
+        return this._inputs[4];
+    }
+
+    /**
+     * Gets the anisotropy object output component
+     */
+    public get anisotropy(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    private _generateTBNSpace(state: NodeMaterialBuildState) {
+        let code = "";
+
+        let comments = `//${this.name}`;
+        let uv = this.uv;
+        let worldPosition = this.worldPositionConnectionPoint;
+        let worldNormal = this.worldNormalConnectionPoint;
+        let worldTangent = this.worldTangent;
+
+        if (!uv.isConnected) {
+            // we must set the uv input as optional because we may not end up in this method (in case a PerturbNormal block is linked to the PBR material)
+            // in which case uv is not required. But if we do come here, we do need the uv, so we have to raise an error but not with throw, else
+            // it will stop the building of the node material and will lead to errors in the editor!
+            console.error("You must connect the 'uv' input of the Anisotropy block!");
+        }
+
+        state._emitExtension("derivatives", "#extension GL_OES_standard_derivatives : enable");
+
+        let tangentReplaceString = { search: /defined\(TANGENT\)/g, replace: worldTangent.isConnected ? "defined(TANGENT)" : "defined(IGNORE)" };
+
+        if (worldTangent.isConnected) {
+            code += `vec3 tbnNormal = normalize(${worldNormal.associatedVariableName}.xyz);\r\n`;
+            code += `vec3 tbnTangent = normalize(${worldTangent.associatedVariableName}.xyz);\r\n`;
+            code += `vec3 tbnBitangent = cross(tbnNormal, tbnTangent);\r\n`;
+            code += `mat3 vTBN = mat3(tbnTangent, tbnBitangent, tbnNormal);\r\n`;
+        }
+
+        code += `
+            #if defined(${worldTangent.isConnected ? "TANGENT" : "IGNORE"}) && defined(NORMAL)
+                mat3 TBN = vTBN;
+            #else
+                mat3 TBN = cotangent_frame(${worldNormal.associatedVariableName + ".xyz"}, ${"v_" + worldPosition.associatedVariableName + ".xyz"}, ${uv.isConnected ? uv.associatedVariableName : "vec2(0.)"}, vec2(1., 1.));
+            #endif\r\n`;
+
+        state._emitFunctionFromInclude("bumpFragmentMainFunctions", comments, {
+            replaceStrings: [
+                tangentReplaceString,
+            ]
+        });
+
+        return code;
+    }
+
+    /**
+     * Gets the main code of the block (fragment side)
+     * @param state current state of the node material building
+     * @param generateTBNSpace if true, the code needed to create the TBN coordinate space is generated
+     * @returns the shader code
+     */
+    public getCode(state: NodeMaterialBuildState, generateTBNSpace = false): string {
+        let code = "";
+
+        if (generateTBNSpace) {
+            code += this._generateTBNSpace(state);
+        }
+
+        const intensity = this.intensity.isConnected ? this.intensity.associatedVariableName : "1.0";
+        const direction = this.direction.isConnected ? this.direction.associatedVariableName : "vec2(1., 0.)";
+        const texture = this.texture.isConnected ? this.texture.associatedVariableName : "vec3(0.)";
+
+        code += `anisotropicOutParams anisotropicOut;
+            anisotropicBlock(
+                vec3(${direction}, ${intensity}),
+            #ifdef ANISOTROPIC_TEXTURE
+                ${texture},
+            #endif
+                TBN,
+                normalW,
+                viewDirectionW,
+                anisotropicOut
+            );\r\n`;
+
+        return code;
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        super.prepareDefines(mesh, nodeMaterial, defines);
+
+        defines.setValue("ANISOTROPIC", true);
+        defines.setValue("ANISOTROPIC_TEXTURE", this.texture.isConnected);
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            state.sharedData.blocksWithDefines.push(this);
+        }
+
+        return this;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.AnisotropyBlock"] = AnisotropyBlock;

+ 6 - 0
src/Materials/Node/Blocks/PBR/index.ts

@@ -0,0 +1,6 @@
+export * from "./pbrMetallicRoughnessBlock";
+export * from "./sheenBlock";
+export * from "./ambientOcclusionBlock";
+export * from "./reflectivityBlock";
+export * from "./anisotropyBlock";
+export * from "./reflectionBlock";

File diff suppressed because it is too large
+ 1109 - 0
src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts


+ 437 - 0
src/Materials/Node/Blocks/PBR/reflectionBlock.ts

@@ -0,0 +1,437 @@
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
+import { ReflectionTextureBaseBlock } from '../Dual/reflectionTextureBaseBlock';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { Nullable } from '../../../../types';
+import { Texture } from '../../../Textures/texture';
+import { BaseTexture } from '../../../Textures/baseTexture';
+import { Mesh } from '../../../../Meshes/mesh';
+import { SubMesh } from '../../../../Meshes/subMesh';
+import { Effect } from '../../../effect';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
+import { Scene } from '../../../../scene';
+
+/**
+ * Block used to implement the reflection module of the PBR material
+ */
+export class ReflectionBlock extends ReflectionTextureBaseBlock {
+
+    /** @hidden */
+    public _defineLODReflectionAlpha: string;
+    /** @hidden */
+    public _defineLinearSpecularReflection: string;
+    private _vEnvironmentIrradianceName: string;
+    /** @hidden */
+    public _vReflectionMicrosurfaceInfosName: string;
+    /** @hidden */
+    public _vReflectionInfosName: string;
+    private _scene: Scene;
+
+    /**
+     * The three properties below are set by the main PBR block prior to calling methods of this class.
+     * This is to avoid having to add them as inputs here whereas they are already inputs of the main block, so already known.
+     * It's less burden on the user side in the editor part.
+    */
+
+    /** @hidden */
+    public worldPositionConnectionPoint: NodeMaterialConnectionPoint;
+    /** @hidden */
+    public worldNormalConnectionPoint: NodeMaterialConnectionPoint;
+    /** @hidden */
+    public cameraPositionConnectionPoint: NodeMaterialConnectionPoint;
+
+    /**
+     * Defines if the material uses spherical harmonics vs spherical polynomials for the
+     * diffuse part of the IBL.
+     */
+    @editableInPropertyPage("Spherical Harmonics", PropertyTypeForEdition.Boolean, "ADVANCED", { "notifiers": { "update": true }})
+    public useSphericalHarmonics: boolean = true;
+
+    /**
+     * Force the shader to compute irradiance in the fragment shader in order to take bump in account.
+     */
+    @editableInPropertyPage("Force irradiance in fragment", PropertyTypeForEdition.Boolean, "ADVANCED", { "notifiers": { "update": true }})
+    public forceIrradianceInFragment: boolean = false;
+
+    /**
+     * Create a new ReflectionBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name);
+
+        this._isUnique = true;
+
+        this.registerInput("position", NodeMaterialBlockConnectionPointTypes.Vector3, false, NodeMaterialBlockTargets.Vertex);
+        this.registerInput("world", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Vertex);
+        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("reflection", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("reflection", this, NodeMaterialConnectionPointDirection.Output, ReflectionBlock, "ReflectionBlock"));
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ReflectionBlock";
+    }
+
+    /**
+     * Gets the position input component
+     */
+    public get position(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the world position input component
+     */
+    public get worldPosition(): NodeMaterialConnectionPoint {
+        return this.worldPositionConnectionPoint;
+    }
+
+    /**
+     * Gets the world normal input component
+     */
+    public get worldNormal(): NodeMaterialConnectionPoint {
+        return this.worldNormalConnectionPoint;
+    }
+
+    /**
+     * Gets the world input component
+     */
+    public get world(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+    * Gets the camera (or eye) position component
+    */
+    public get cameraPosition(): NodeMaterialConnectionPoint {
+        return this.cameraPositionConnectionPoint;
+    }
+
+    /**
+     * Gets the view input component
+     */
+    public get view(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the reflection object output component
+     */
+    public get reflection(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Returns true if the block has a texture (either its own texture or the environment texture from the scene, if set)
+     */
+    public get hasTexture(): boolean {
+        return !!this._getTexture();
+    }
+
+    /**
+     * Gets the reflection color (either the name of the variable if the color input is connected, else a default value)
+     */
+    public get reflectionColor(): string {
+        return this.color.isConnected ? this.color.associatedVariableName : "vec3(1., 1., 1.)";
+    }
+
+    protected _getTexture(): Nullable<BaseTexture> {
+        if (this.texture) {
+            return this.texture;
+        }
+
+        return this._scene.environmentTexture;
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        super.prepareDefines(mesh, nodeMaterial, defines);
+
+        const reflectionTexture = this._getTexture();
+        const reflection = reflectionTexture && reflectionTexture.getTextureMatrix;
+
+        defines.setValue("REFLECTION", reflection);
+
+        if (!reflection) {
+            return;
+        }
+
+        defines.setValue(this._defineLODReflectionAlpha, reflectionTexture!.lodLevelInAlpha);
+        defines.setValue(this._defineLinearSpecularReflection, reflectionTexture!.linearSpecularLOD);
+        defines.setValue(this._defineOppositeZ, this._scene.useRightHandedSystem ? !reflectionTexture!.invertZ : reflectionTexture!.invertZ);
+
+        defines.setValue("SPHERICAL_HARMONICS", this.useSphericalHarmonics);
+
+        if (reflectionTexture && reflectionTexture.coordinatesMode !== Texture.SKYBOX_MODE) {
+            if (reflectionTexture.isCube) {
+                defines.setValue("USESPHERICALFROMREFLECTIONMAP", true);
+                defines.setValue("USEIRRADIANCEMAP", false);
+                if (this.forceIrradianceInFragment || this._scene.getEngine().getCaps().maxVaryingVectors <= 8) {
+                    defines.setValue("USESPHERICALINVERTEX", false);
+                }
+                else {
+                    defines.setValue("USESPHERICALINVERTEX", true);
+                }
+            }
+        }
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh) {
+        super.bind(effect, nodeMaterial, mesh);
+
+        const reflectionTexture = this._getTexture();
+
+        if (!reflectionTexture || !subMesh) {
+            return;
+        }
+
+        if (reflectionTexture.isCube) {
+            effect.setTexture(this._cubeSamplerName, reflectionTexture);
+        } else {
+            effect.setTexture(this._2DSamplerName, reflectionTexture);
+        }
+
+        effect.setFloat3(this._vReflectionMicrosurfaceInfosName, reflectionTexture.getSize().width, reflectionTexture.lodGenerationScale, reflectionTexture.lodGenerationOffset);
+
+        const defines = subMesh._materialDefines as  NodeMaterialDefines;
+
+        const polynomials = reflectionTexture.sphericalPolynomial;
+        if (defines.USESPHERICALFROMREFLECTIONMAP && polynomials) {
+            if (defines.SPHERICAL_HARMONICS) {
+                const preScaledHarmonics = polynomials.preScaledHarmonics;
+                effect.setVector3("vSphericalL00", preScaledHarmonics.l00);
+                effect.setVector3("vSphericalL1_1", preScaledHarmonics.l1_1);
+                effect.setVector3("vSphericalL10", preScaledHarmonics.l10);
+                effect.setVector3("vSphericalL11", preScaledHarmonics.l11);
+                effect.setVector3("vSphericalL2_2", preScaledHarmonics.l2_2);
+                effect.setVector3("vSphericalL2_1", preScaledHarmonics.l2_1);
+                effect.setVector3("vSphericalL20", preScaledHarmonics.l20);
+                effect.setVector3("vSphericalL21", preScaledHarmonics.l21);
+                effect.setVector3("vSphericalL22", preScaledHarmonics.l22);
+            }
+            else {
+                effect.setFloat3("vSphericalX", polynomials.x.x, polynomials.x.y, polynomials.x.z);
+                effect.setFloat3("vSphericalY", polynomials.y.x, polynomials.y.y, polynomials.y.z);
+                effect.setFloat3("vSphericalZ", polynomials.z.x, polynomials.z.y, polynomials.z.z);
+                effect.setFloat3("vSphericalXX_ZZ", polynomials.xx.x - polynomials.zz.x,
+                    polynomials.xx.y - polynomials.zz.y,
+                    polynomials.xx.z - polynomials.zz.z);
+                effect.setFloat3("vSphericalYY_ZZ", polynomials.yy.x - polynomials.zz.x,
+                    polynomials.yy.y - polynomials.zz.y,
+                    polynomials.yy.z - polynomials.zz.z);
+                effect.setFloat3("vSphericalZZ", polynomials.zz.x, polynomials.zz.y, polynomials.zz.z);
+                effect.setFloat3("vSphericalXY", polynomials.xy.x, polynomials.xy.y, polynomials.xy.z);
+                effect.setFloat3("vSphericalYZ", polynomials.yz.x, polynomials.yz.y, polynomials.yz.z);
+                effect.setFloat3("vSphericalZX", polynomials.zx.x, polynomials.zx.y, polynomials.zx.z);
+            }
+        }
+    }
+
+    /**
+     * Gets the code to inject in the vertex shader
+     * @param state current state of the node material building
+     * @returns the shader code
+     */
+    public handleVertexSide(state: NodeMaterialBuildState): string {
+        let code = super.handleVertexSide(state);
+
+        state._emitFunctionFromInclude("harmonicsFunctions", `//${this.name}`, {
+            replaceStrings: [
+                { search: /uniform vec3 vSphericalL00;[\s\S]*?uniform vec3 vSphericalL22;/g, replace: "" },
+                { search: /uniform vec3 vSphericalX;[\s\S]*?uniform vec3 vSphericalZX;/g, replace: "" },
+            ]
+        });
+
+        const reflectionVectorName = state._getFreeVariableName("reflectionVector");
+
+        this._vEnvironmentIrradianceName = state._getFreeVariableName("vEnvironmentIrradiance");
+
+        state._emitVaryingFromString(this._vEnvironmentIrradianceName, "vec3", "defined(USESPHERICALFROMREFLECTIONMAP) && defined(USESPHERICALINVERTEX");
+
+        state._emitUniformFromString("vSphericalL00", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL1_1", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL10", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL11", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL2_2", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL2_1", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL20", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL21", "vec3", "SPHERICAL_HARMONICS");
+        state._emitUniformFromString("vSphericalL22", "vec3", "SPHERICAL_HARMONICS");
+
+        state._emitUniformFromString("vSphericalX", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalY", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalZ", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalXX_ZZ", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalYY_ZZ", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalZZ", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalXY", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalYZ", "vec3", "SPHERICAL_HARMONICS", true);
+        state._emitUniformFromString("vSphericalZX", "vec3", "SPHERICAL_HARMONICS", true);
+
+        code +=
+            `#if defined(USESPHERICALFROMREFLECTIONMAP) && defined(USESPHERICALINVERTEX)
+                vec3 ${reflectionVectorName} = vec3(${this._reflectionMatrixName} * vec4(normalize(${this.worldNormal.associatedVariableName}).xyz, 0)).xyz;
+                #ifdef ${this._defineOppositeZ}
+                    ${reflectionVectorName}.z *= -1.0;
+                #endif
+                ${this._vEnvironmentIrradianceName} = computeEnvironmentIrradiance(${reflectionVectorName});
+            #endif\r\n`;
+
+        return code;
+    }
+
+    /**
+     * Gets the main code of the block (fragment side)
+     * @param state current state of the node material building
+     * @param normalVarName name of the existing variable corresponding to the normal
+     * @returns the shader code
+     */
+    public getCode(state: NodeMaterialBuildState, normalVarName: string): string {
+        let code = "";
+
+        this.handleFragmentSideInits(state);
+
+        state._emitFunctionFromInclude("harmonicsFunctions", `//${this.name}`, {
+            replaceStrings: [
+                { search: /uniform vec3 vSphericalL00;[\s\S]*?uniform vec3 vSphericalL22;/g, replace: "" },
+                { search: /uniform vec3 vSphericalX;[\s\S]*?uniform vec3 vSphericalZX;/g, replace: "" },
+            ]
+        });
+
+        state._emitFunction("sampleReflection", `
+            #ifdef ${this._define3DName}
+                #define sampleReflection(s, c) textureCube(s, c)
+            #else
+                #define sampleReflection(s, c) texture2D(s, c)
+            #endif\r\n`, `//${this.name}`);
+
+        state._emitFunction("sampleReflectionLod", `
+            #ifdef ${this._define3DName}
+                #define sampleReflectionLod(s, c, l) textureCubeLodEXT(s, c, l)
+            #else
+                #define sampleReflectionLod(s, c, l) texture2DLodEXT(s, c, l)
+            #endif\r\n`, `//${this.name}`);
+
+        const computeReflectionCoordsFunc = `
+            vec3 computeReflectionCoordsPBR(vec4 worldPos, vec3 worldNormal) {
+                ${this.handleFragmentSideCodeReflectionCoords('worldNormal', 'worldPos', true)}
+                return ${this._reflectionVectorName};
+            }\r\n`;
+
+        state._emitFunction("computeReflectionCoordsPBR", computeReflectionCoordsFunc, `//${this.name}`);
+
+        this._vReflectionMicrosurfaceInfosName = state._getFreeVariableName("vReflectionMicrosurfaceInfos");
+
+        state._emitUniformFromString(this._vReflectionMicrosurfaceInfosName, "vec3");
+
+        this._vReflectionInfosName = state._getFreeVariableName("vReflectionInfos");
+
+        code += `#ifdef REFLECTION
+            vec2 ${this._vReflectionInfosName} = vec2(1., 0.);
+
+            reflectionOutParams reflectionOut;
+
+            reflectionBlock(
+                ${"v_" + this.worldPosition.associatedVariableName + ".xyz"},
+                ${normalVarName},
+                alphaG,
+                ${this._vReflectionMicrosurfaceInfosName},
+                ${this._vReflectionInfosName},
+                ${this.reflectionColor},
+            #ifdef ANISOTROPIC
+                anisotropicOut,
+            #endif
+            #if defined(${this._defineLODReflectionAlpha}) && !defined(${this._defineSkyboxName})
+                NdotVUnclamped,
+            #endif
+            #ifdef ${this._defineLinearSpecularReflection}
+                roughness,
+            #endif
+            #ifdef ${this._define3DName}
+                ${this._cubeSamplerName},
+            #else
+                ${this._2DSamplerName},
+            #endif
+            #if defined(NORMAL) && defined(USESPHERICALINVERTEX)
+                ${this._vEnvironmentIrradianceName},
+            #endif
+            #ifdef USESPHERICALFROMREFLECTIONMAP
+                #if !defined(NORMAL) || !defined(USESPHERICALINVERTEX)
+                    ${this._reflectionMatrixName},
+                #endif
+            #endif
+            #ifdef USEIRRADIANCEMAP
+                irradianceSampler, // ** not handled **
+            #endif
+            #ifndef LODBASEDMICROSFURACE
+                #ifdef ${this._define3DName}
+                    ${this._cubeSamplerName},
+                    ${this._cubeSamplerName},
+                #else
+                    ${this._2DSamplerName},
+                    ${this._2DSamplerName},
+                #endif
+            #endif
+                reflectionOut
+            );
+        #endif\r\n`;
+
+        return code;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        this._scene = state.sharedData.scene;
+
+        if (state.target !== NodeMaterialBlockTargets.Fragment) {
+            this._defineLODReflectionAlpha = state._getFreeDefineName("LODINREFLECTIONALPHA");
+            this._defineLinearSpecularReflection = state._getFreeDefineName("LINEARSPECULARREFLECTION");
+        }
+
+        return this;
+    }
+
+    protected _dumpPropertiesCode() {
+        let codeString: string = super._dumpPropertiesCode();
+
+        codeString += `${this._codeVariableName}.useSphericalHarmonics = ${this.useSphericalHarmonics};\r\n`;
+        codeString += `${this._codeVariableName}.forceIrradianceInFragment = ${this.forceIrradianceInFragment};\r\n`;
+
+        return codeString;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        serializationObject.useSphericalHarmonics = this.useSphericalHarmonics;
+        serializationObject.forceIrradianceInFragment = this.forceIrradianceInFragment;
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        this.useSphericalHarmonics = serializationObject.useSphericalHarmonics;
+        this.forceIrradianceInFragment = serializationObject.forceIrradianceInFragment;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.ReflectionBlock"] = ReflectionBlock;

+ 209 - 0
src/Materials/Node/Blocks/PBR/reflectivityBlock.ts

@@ -0,0 +1,209 @@
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
+import { Scene } from '../../../../scene';
+
+/**
+ * Block used to implement the reflectivity module of the PBR material
+ */
+export class ReflectivityBlock extends NodeMaterialBlock {
+
+    /**
+     * Specifies if the metallic texture contains the ambient occlusion information in its red channel.
+     */
+    @editableInPropertyPage("AO from red channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
+    public useAmbientOcclusionFromMetallicTextureRed: boolean = false;
+
+    /**
+     * Specifies if the metallic texture contains the metallness information in its blue channel.
+     */
+    @editableInPropertyPage("Metallness from blue channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
+    public useMetallnessFromMetallicTextureBlue: boolean = true;
+
+    /**
+     * Specifies if the metallic texture contains the roughness information in its alpha channel.
+     */
+    @editableInPropertyPage("Roughness from alpha channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
+    public useRoughnessFromMetallicTextureAlpha: boolean = false;
+
+    /**
+     * Specifies if the metallic texture contains the roughness information in its green channel.
+     */
+    @editableInPropertyPage("Roughness from green channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
+    public useRoughnessFromMetallicTextureGreen: boolean = true;
+
+    /**
+     * Specifies whether the F0 factor can be fetched from the mettalic texture.
+     */
+    @editableInPropertyPage("Metallic F0 from alpha channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
+    public useMetallicF0FactorFromMetallicTexture: boolean = false;
+
+    /**
+     * Create a new ReflectivityBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("metallic", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("roughness", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("reflectivity", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("reflectivity", this, NodeMaterialConnectionPointDirection.Output, ReflectivityBlock, "ReflectivityBlock"));
+    }
+
+    /**
+     * 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("baseColor");
+        state._excludeVariableName("reflectivityOut");
+        state._excludeVariableName("microSurface");
+        state._excludeVariableName("roughness");
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ReflectivityBlock";
+    }
+
+    /**
+     * Gets the metallic input component
+     */
+    public get metallic(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the roughness input component
+     */
+    public get roughness(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the texture input component
+     */
+    public get texture(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the reflectivity object output component
+     */
+    public get reflectivity(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Gets the main code of the block (fragment side)
+     * @param aoIntensityVarName name of the variable with the ambient occlusion intensity
+     * @returns the shader code
+     */
+    public getCode(aoIntensityVarName: string): string {
+        const metalRoughTexture = this.texture.isConnected ? this.texture.connectedPoint?.associatedVariableName : null;
+
+        // note: metallic F0 factor = 0.04
+        let code = `vec3 baseColor = surfaceAlbedo;
+            reflectivityOutParams reflectivityOut;
+
+            reflectivityBlock(
+                vec4(${this.metallic.associatedVariableName}, ${this.roughness.associatedVariableName}, 0., 0.04),
+            #ifdef METALLICWORKFLOW
+                surfaceAlbedo,
+            #endif
+            #ifdef REFLECTIVITY
+                vec3(0., 0., ${aoIntensityVarName}),
+                ${metalRoughTexture},
+            #endif
+            #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY)  && defined(AOSTOREINMETALMAPRED)
+                aoOut.ambientOcclusionColor,
+            #endif
+            #ifdef MICROSURFACEMAP
+                microSurfaceTexel, <== not handled!
+            #endif
+                reflectivityOut
+            );
+
+            float microSurface = reflectivityOut.microSurface;
+            float roughness = reflectivityOut.roughness;
+
+            #ifdef METALLICWORKFLOW
+                surfaceAlbedo = reflectivityOut.surfaceAlbedo;
+            #endif
+            #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY) && defined(AOSTOREINMETALMAPRED)
+                aoOut.ambientOcclusionColor = reflectivityOut.ambientOcclusionColor;
+            #endif\r\n`;
+
+        return code;
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        super.prepareDefines(mesh, nodeMaterial, defines);
+
+        defines.setValue("REFLECTIVITY", this.texture.isConnected);
+        defines.setValue("AOSTOREINMETALMAPRED", this.useAmbientOcclusionFromMetallicTextureRed);
+        defines.setValue("METALLNESSSTOREINMETALMAPBLUE", this.useMetallnessFromMetallicTextureBlue);
+        defines.setValue("ROUGHNESSSTOREINMETALMAPALPHA", this.useRoughnessFromMetallicTextureAlpha);
+        defines.setValue("ROUGHNESSSTOREINMETALMAPGREEN",  !this.useRoughnessFromMetallicTextureAlpha && this.useRoughnessFromMetallicTextureGreen);
+        defines.setValue("METALLICF0FACTORFROMMETALLICMAP", this.useMetallicF0FactorFromMetallicTexture);
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            state.sharedData.blocksWithDefines.push(this);
+        }
+
+        return this;
+    }
+
+    protected _dumpPropertiesCode() {
+        let codeString: string = "";
+
+        codeString += `${this._codeVariableName}.useAmbientOcclusionFromMetallicTextureRed = ${this.useAmbientOcclusionFromMetallicTextureRed};\r\n`;
+        codeString += `${this._codeVariableName}.useMetallnessFromMetallicTextureBlue = ${this.useMetallnessFromMetallicTextureBlue};\r\n`;
+        codeString += `${this._codeVariableName}.useRoughnessFromMetallicTextureAlpha = ${this.useRoughnessFromMetallicTextureAlpha};\r\n`;
+        codeString += `${this._codeVariableName}.useRoughnessFromMetallicTextureGreen = ${this.useRoughnessFromMetallicTextureGreen};\r\n`;
+        codeString += `${this._codeVariableName}.useMetallicF0FactorFromMetallicTexture = ${this.useMetallicF0FactorFromMetallicTexture};\r\n`;
+
+        return codeString;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        serializationObject.useAmbientOcclusionFromMetallicTextureRed = this.useAmbientOcclusionFromMetallicTextureRed;
+        serializationObject.useMetallnessFromMetallicTextureBlue = this.useMetallnessFromMetallicTextureBlue;
+        serializationObject.useRoughnessFromMetallicTextureAlpha = this.useRoughnessFromMetallicTextureAlpha;
+        serializationObject.useRoughnessFromMetallicTextureGreen = this.useRoughnessFromMetallicTextureGreen;
+        serializationObject.useMetallicF0FactorFromMetallicTexture = this.useMetallicF0FactorFromMetallicTexture;
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        this.useAmbientOcclusionFromMetallicTextureRed = serializationObject.useAmbientOcclusionFromMetallicTextureRed;
+        this.useMetallnessFromMetallicTextureBlue = serializationObject.useMetallnessFromMetallicTextureBlue;
+        this.useRoughnessFromMetallicTextureAlpha = serializationObject.useRoughnessFromMetallicTextureAlpha;
+        this.useRoughnessFromMetallicTextureGreen = serializationObject.useRoughnessFromMetallicTextureGreen;
+        this.useMetallicF0FactorFromMetallicTexture = serializationObject.useMetallicF0FactorFromMetallicTexture;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.ReflectivityBlock"] = ReflectivityBlock;

+ 226 - 0
src/Materials/Node/Blocks/PBR/sheenBlock.ts

@@ -0,0 +1,226 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
+import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { ReflectionBlock } from './reflectionBlock';
+import { Scene } from '../../../../scene';
+import { Nullable } from '../../../../types';
+
+/**
+ * Block used to implement the sheen module of the PBR material
+ */
+export class SheenBlock extends NodeMaterialBlock {
+
+    /**
+     * Create a new SheenBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("roughness", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("sheen", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("sheen", this, NodeMaterialConnectionPointDirection.Output, SheenBlock, "SheenBlock"));
+    }
+
+    /**
+     * If true, the sheen effect is layered above the base BRDF with the albedo-scaling technique.
+     * It allows the strength of the sheen effect to not depend on the base color of the material,
+     * making it easier to setup and tweak the effect
+     */
+    @editableInPropertyPage("Albedo scaling", PropertyTypeForEdition.Boolean, "PROPERTIES", { "notifiers": { "update": true }})
+    public albedoScaling: boolean = false;
+
+    /**
+     * Defines if the sheen is linked to the sheen color.
+     */
+    @editableInPropertyPage("Link sheen with albedo", PropertyTypeForEdition.Boolean, "PROPERTIES", { "notifiers": { "update": true }})
+    public linkSheenWithAlbedo: boolean = false;
+
+    /**
+     * 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("sheenOut");
+        state._excludeVariableName("sheenMapData");
+        state._excludeVariableName("vSheenColor");
+        state._excludeVariableName("vSheenRoughness");
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "SheenBlock";
+    }
+
+    /**
+     * Gets the intensity input component
+     */
+    public get intensity(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the roughness input component
+     */
+    public get roughness(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the texture input component
+     */
+    public get texture(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the sheen object output component
+     */
+    public get sheen(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        super.prepareDefines(mesh, nodeMaterial, defines);
+
+        defines.setValue("SHEEN", true);
+        defines.setValue("SHEEN_LINKWITHALBEDO", this.linkSheenWithAlbedo);
+        defines.setValue("SHEEN_ROUGHNESS", this.roughness.isConnected);
+        defines.setValue("SHEEN_ALBEDOSCALING", this.albedoScaling);
+        defines.setValue("SHEEN_TEXTURE", this.texture.isConnected);
+    }
+
+    /**
+     * Gets the main code of the block (fragment side)
+     * @param reflectionBlock instance of a ReflectionBlock null if the code must be generated without an active reflection module
+     * @returns the shader code
+     */
+    public getCode(reflectionBlock: Nullable<ReflectionBlock>): string {
+        let code = "";
+
+        const color = this.color.isConnected ? this.color.associatedVariableName : "vec3(1.)";
+        const intensity = this.intensity.isConnected ? this.intensity.associatedVariableName : "1.";
+        const roughness = this.roughness.isConnected ? this.roughness.associatedVariableName : "0.";
+        const texture = this.texture.isConnected ? this.texture.associatedVariableName : "vec4(0.)";
+
+        code = `#ifdef SHEEN
+            sheenOutParams sheenOut;
+
+            vec4 vSheenColor = vec4(${color}, ${intensity});
+
+            sheenBlock(
+                vSheenColor,
+            #ifdef SHEEN_ROUGHNESS
+                ${roughness},
+            #endif
+                roughness,
+            #ifdef SHEEN_TEXTURE
+                ${texture},
+            #endif
+                reflectance,
+            #ifdef SHEEN_LINKWITHALBEDO
+                baseColor,
+                surfaceAlbedo,
+            #endif
+            #ifdef ENVIRONMENTBRDF
+                NdotV,
+                environmentBrdf,
+            #endif
+            #if defined(REFLECTION) && defined(ENVIRONMENTBRDF)
+                AARoughnessFactors,
+                ${reflectionBlock?._vReflectionMicrosurfaceInfosName},
+                ${reflectionBlock?._vReflectionInfosName},
+                ${reflectionBlock?.reflectionColor},
+                vLightingIntensity,
+                #ifdef ${reflectionBlock?._define3DName}
+                    ${reflectionBlock?._cubeSamplerName},
+                #else
+                    ${reflectionBlock?._2DSamplerName},
+                #endif
+                reflectionOut.reflectionCoords,
+                NdotVUnclamped,
+                #ifndef LODBASEDMICROSFURACE
+                    #ifdef ${reflectionBlock?._define3DName}
+                        ${reflectionBlock?._cubeSamplerName},
+                        ${reflectionBlock?._cubeSamplerName},
+                    #else
+                        ${reflectionBlock?._2DSamplerName},
+                        ${reflectionBlock?._2DSamplerName},
+                    #endif
+                #endif
+                #if !defined(${reflectionBlock?._defineSkyboxName}) && defined(RADIANCEOCCLUSION)
+                    seo,
+                #endif
+                #if !defined(${reflectionBlock?._defineSkyboxName}) && defined(HORIZONOCCLUSION) && defined(BUMP) && defined(${reflectionBlock?._define3DName})
+                    eho,
+                #endif
+            #endif
+                sheenOut
+            );
+
+            #ifdef SHEEN_LINKWITHALBEDO
+                surfaceAlbedo = sheenOut.surfaceAlbedo;
+            #endif
+        #endif\r\n`;
+
+        return code;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            state.sharedData.blocksWithDefines.push(this);
+        }
+
+        return this;
+    }
+
+    protected _dumpPropertiesCode() {
+        let codeString: string = super._dumpPropertiesCode();
+
+        codeString += `${this._codeVariableName}.albedoScaling = ${this.albedoScaling};\r\n`;
+        codeString += `${this._codeVariableName}.linkSheenWithAlbedo = ${this.linkSheenWithAlbedo};\r\n`;
+
+        return codeString;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        serializationObject.albedoScaling = this.albedoScaling;
+        serializationObject.linkSheenWithAlbedo = this.linkSheenWithAlbedo;
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        this.albedoScaling = serializationObject.albedoScaling;
+        this.linkSheenWithAlbedo = serializationObject.linkSheenWithAlbedo;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.SheenBlock"] = SheenBlock;

+ 4 - 0
src/Materials/Node/Blocks/clampBlock.ts

@@ -5,14 +5,18 @@ import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint
 import { NodeMaterialBlockTargets } from '../Enums/nodeMaterialBlockTargets';
 import { _TypeStore } from '../../../Misc/typeStore';
 import { Scene } from '../../../scene';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../nodeMaterialDecorator";
+
 /**
  * Block used to clamp a float
  */
 export class ClampBlock extends NodeMaterialBlock {
 
     /** Gets or sets the minimum range */
+    @editableInPropertyPage("Minimum", PropertyTypeForEdition.Float)
     public minimum = 0.0;
     /** Gets or sets the maximum range */
+    @editableInPropertyPage("Maximum", PropertyTypeForEdition.Float)
     public maximum = 1.0;
 
     /**

+ 1 - 0
src/Materials/Node/Blocks/index.ts

@@ -45,3 +45,4 @@ export * from "./rotate2dBlock";
 export * from "./reflectBlock";
 export * from "./refractBlock";
 export * from "./desaturateBlock";
+export * from "./PBR/index";

+ 3 - 0
src/Materials/Node/Blocks/remapBlock.ts

@@ -6,6 +6,7 @@ import { NodeMaterialBlockTargets } from '../Enums/nodeMaterialBlockTargets';
 import { _TypeStore } from '../../../Misc/typeStore';
 import { Vector2 } from '../../../Maths/math.vector';
 import { Scene } from '../../../scene';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../nodeMaterialDecorator";
 /**
  * Block used to remap a float from a range to a new one
  */
@@ -13,11 +14,13 @@ export class RemapBlock extends NodeMaterialBlock {
     /**
      * Gets or sets the source range
      */
+    @editableInPropertyPage("From", PropertyTypeForEdition.Vector2)
     public sourceRange = new Vector2(-1, 1);
 
     /**
      * Gets or sets the target range
      */
+    @editableInPropertyPage("To", PropertyTypeForEdition.Vector2)
     public targetRange = new Vector2(0, 1);
 
     /**

+ 2 - 0
src/Materials/Node/Blocks/worleyNoise3DBlock.ts

@@ -5,6 +5,7 @@ import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint
 import { NodeMaterialBlockTargets } from '../Enums/nodeMaterialBlockTargets';
 import { _TypeStore } from '../../../Misc/typeStore';
 import { Scene } from '../../../scene';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../nodeMaterialDecorator";
 
 /**
  * block used to Generate a Worley Noise 3D Noise Pattern
@@ -18,6 +19,7 @@ import { Scene } from '../../../scene';
 
 export class WorleyNoise3DBlock extends NodeMaterialBlock {
     /** Gets or sets a boolean indicating that normal should be inverted on X axis */
+    @editableInPropertyPage("Use Manhattan Distance", PropertyTypeForEdition.Boolean, "PROPERTIES", { "notifiers": { "update": false }})
     public manhattanDistance = false;
 
     /**

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

@@ -18,6 +18,8 @@ export enum NodeMaterialBlockConnectionPointTypes {
     Color4 = 64,
     /** Matrix */
     Matrix = 128,
+    /** Custom object */
+    Object = 256,
     /** Detect type based on connection */
     AutoDetect = 1024,
     /** Output type that will be defined by input type */

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

@@ -3,4 +3,5 @@ export * from "./nodeMaterialBlockConnectionPoint";
 export * from "./nodeMaterialBlock";
 export * from "./nodeMaterial";
 export * from "./Blocks/index";
-export * from "./Optimizers/index";
+export * from "./Optimizers/index";
+export * from "./nodeMaterialDecorator";

+ 4 - 4
src/Materials/Node/nodeMaterial.ts

@@ -26,7 +26,7 @@ import { InputBlock } from './Blocks/Input/inputBlock';
 import { _TypeStore } from '../../Misc/typeStore';
 import { SerializationHelper } from '../../Misc/decorators';
 import { TextureBlock } from './Blocks/Dual/textureBlock';
-import { ReflectionTextureBlock } from './Blocks/Dual/reflectionTextureBlock';
+import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
 import { EffectFallbacks } from '../effectFallbacks';
 import { WebRequest } from '../../Misc/webRequest';
 import { Effect } from '../effect';
@@ -87,7 +87,7 @@ export class NodeMaterialDefines extends MaterialDefines implements IImageProces
         this.rebuild();
     }
 
-    public setValue(name: string, value: boolean) {
+    public setValue(name: string, value: any) {
         if (this[name] === undefined) {
             this._keys.push(name);
         }
@@ -906,7 +906,7 @@ export class NodeMaterial extends PushMaterial {
             if (effect && scene.getCachedEffect() !== effect) {
                 // Bindable blocks
                 for (var block of sharedData.bindableBlocks) {
-                    block.bind(effect, this, mesh);
+                    block.bind(effect, this, mesh, subMesh);
                 }
 
                 // Connection points
@@ -937,7 +937,7 @@ export class NodeMaterial extends PushMaterial {
      * Gets the list of texture blocks
      * @returns an array of texture blocks
      */
-    public getTextureBlocks(): (TextureBlock | ReflectionTextureBlock)[] {
+    public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock)[] {
         if (!this._sharedData) {
             return [];
         }

+ 9 - 5
src/Materials/Node/nodeMaterialBlock.ts

@@ -6,6 +6,7 @@ import { NodeMaterialBlockTargets } from './Enums/nodeMaterialBlockTargets';
 import { Effect } from '../effect';
 import { AbstractMesh } from '../../Meshes/abstractMesh';
 import { Mesh } from '../../Meshes/mesh';
+import { SubMesh } from '../../Meshes/subMesh';
 import { NodeMaterial, NodeMaterialDefines } from './nodeMaterial';
 import { InputBlock } from './Blocks/Input/inputBlock';
 import { UniqueIdGenerator } from '../../Misc/uniqueIdGenerator';
@@ -171,8 +172,9 @@ export class NodeMaterialBlock {
      * @param effect defines the effect to bind data to
      * @param nodeMaterial defines the hosting NodeMaterial
      * @param mesh defines the mesh that will be rendered
+     * @param subMesh defines the submesh that will be rendered
      */
-    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh) {
         // Do nothing
     }
 
@@ -213,10 +215,11 @@ export class NodeMaterialBlock {
      * @param type defines the connection point type
      * @param isOptional defines a boolean indicating that this input can be omitted
      * @param target defines the target to use to limit the connection point (will be VertexAndFragment by default)
+     * @param point an already created connection point. If not provided, create a new one
      * @returns the current block
      */
-    public registerInput(name: string, type: NodeMaterialBlockConnectionPointTypes, isOptional: boolean = false, target?: NodeMaterialBlockTargets) {
-        let point = new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Input);
+    public registerInput(name: string, type: NodeMaterialBlockConnectionPointTypes, isOptional: boolean = false, target?: NodeMaterialBlockTargets, point?: NodeMaterialConnectionPoint) {
+        point = point ?? new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Input);
         point.type = type;
         point.isOptional = isOptional;
         if (target) {
@@ -233,10 +236,11 @@ export class NodeMaterialBlock {
      * @param name defines the connection point name
      * @param type defines the connection point type
      * @param target defines the target to use to limit the connection point (will be VertexAndFragment by default)
+     * @param point an already created connection point. If not provided, create a new one
      * @returns the current block
      */
-    public registerOutput(name: string, type: NodeMaterialBlockConnectionPointTypes, target?: NodeMaterialBlockTargets) {
-        let point = new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Output);
+    public registerOutput(name: string, type: NodeMaterialBlockConnectionPointTypes, target?: NodeMaterialBlockTargets, point?: NodeMaterialConnectionPoint) {
+        point = point ?? new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Output);
         point.type = type;
         if (target) {
             point.target = target;

+ 12 - 0
src/Materials/Node/nodeMaterialBlockConnectionPoint.ts

@@ -57,6 +57,9 @@ export class NodeMaterialConnectionPoint {
         return this._direction;
     }
 
+    /** Indicates that this connection point needs dual validation before being connected to another point */
+    public needDualDirectionValidation: boolean = false;
+
     /**
      * Gets or sets the additional types supported by this connection point
      */
@@ -292,6 +295,15 @@ export class NodeMaterialConnectionPoint {
     }
 
     /**
+     * Creates a block suitable to be used as an input for this input point.
+     * If null is returned, a block based on the point type will be created.
+     * @returns The returned string parameter is the name of the output point of NodeMaterialBlock (first parameter of the returned array) that can be connected to the input
+     */
+    public createCustomInputBlock(): Nullable<[NodeMaterialBlock, string]> {
+        return null;
+    }
+
+    /**
      * Creates a new connection point
      * @param name defines the connection point name
      * @param ownerBlock defines the block hosting this connection point

+ 11 - 1
src/Materials/Node/nodeMaterialBuildState.ts

@@ -62,6 +62,8 @@ export class NodeMaterialBuildState {
     public _samplerDeclaration = "";
     /** @hidden */
     public _varyingTransfer = "";
+    /** @hidden */
+    public _injectAtEnd = "";
 
     private _repeatableContentAnchorIndex = 0;
     /** @hidden */
@@ -96,6 +98,10 @@ export class NodeMaterialBuildState {
             this.compilationString = `${this.compilationString}\r\n${this._varyingTransfer}`;
         }
 
+        if (this._injectAtEnd) {
+            this.compilationString = `${this.compilationString}\r\n${this._injectAtEnd}`;
+        }
+
         this.compilationString = `${this.compilationString}\r\n}`;
 
         if (this.sharedData.varyingDeclaration) {
@@ -346,7 +352,11 @@ export class NodeMaterialBuildState {
         this.uniforms.push(name);
 
         if (define) {
-            this._uniformDeclaration += `${notDefine ? "#ifndef" : "#ifdef"} ${define}\r\n`;
+            if (StringTools.StartsWith(define, "defined(")) {
+                this._uniformDeclaration += `#if ${define}\r\n`;
+            } else {
+                this._uniformDeclaration += `${notDefine ? "#ifndef" : "#ifdef"} ${define}\r\n`;
+            }
         }
         this._uniformDeclaration += `uniform ${type} ${name};\r\n`;
         if (define) {

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

@@ -2,7 +2,7 @@ import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint'
 import { NodeMaterialBlock } from './nodeMaterialBlock';
 import { InputBlock } from './Blocks/Input/inputBlock';
 import { TextureBlock } from './Blocks/Dual/textureBlock';
-import { ReflectionTextureBlock } from './Blocks/Dual/reflectionTextureBlock';
+import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
 import { Scene } from '../../scene';
 
 /**
@@ -32,7 +32,7 @@ export class NodeMaterialBuildStateSharedData {
     /**
      * Input blocks
      */
-    public textureBlocks = new Array<TextureBlock | ReflectionTextureBlock>();
+    public textureBlocks = new Array<TextureBlock | ReflectionTextureBaseBlock>();
 
     /**
      * Bindable blocks (Blocks that need to set data to the effect)

+ 46 - 0
src/Materials/Node/nodeMaterialConnectionPointCustomObject.ts

@@ -0,0 +1,46 @@
+import { NodeMaterialBlock } from './nodeMaterialBlock';
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection, NodeMaterialConnectionPointCompatibilityStates } from './nodeMaterialBlockConnectionPoint';
+import { Nullable } from '../../types';
+
+/**
+ * Defines a connection point to be used for points with a custom object type
+ */
+export class NodeMaterialConnectionPointCustomObject<T extends NodeMaterialBlock> extends NodeMaterialConnectionPoint {
+
+    /**
+     * Creates a new connection point
+     * @param name defines the connection point name
+     * @param ownerBlock defines the block hosting this connection point
+     * @param direction defines the direction of the connection point
+     */
+    public constructor(name: string, ownerBlock: NodeMaterialBlock, direction: NodeMaterialConnectionPointDirection,
+            private _blockType: new (...args: any[]) => T, private _blockName: string, private _nameForCheking?: string) {
+        super(name, ownerBlock, direction);
+
+        if (!this._nameForCheking) {
+            this._nameForCheking = name;
+        }
+
+        this.needDualDirectionValidation = true;
+    }
+
+    /**
+     * Gets a number indicating if the current point can be connected to another point
+     * @param connectionPoint defines the other connection point
+     * @returns a number defining the compatibility state
+     */
+    public checkCompatibilityState(connectionPoint: NodeMaterialConnectionPoint): NodeMaterialConnectionPointCompatibilityStates {
+        return connectionPoint instanceof NodeMaterialConnectionPointCustomObject && connectionPoint.name === this._nameForCheking ?
+                NodeMaterialConnectionPointCompatibilityStates.Compatible : NodeMaterialConnectionPointCompatibilityStates.TypeIncompatible;
+    }
+
+    /**
+     * Creates a block suitable to be used as an input for this input point.
+     * If null is returned, a block based on the point type will be created.
+     * @returns The returned string parameter is the name of the output point of NodeMaterialBlock (first parameter of the returned array) that can be connected to the input
+     */
+    public createCustomInputBlock(): Nullable<[NodeMaterialBlock, string]> {
+        return [new this._blockType(this._blockName), this.name];
+    }
+
+}

+ 78 - 0
src/Materials/Node/nodeMaterialDecorator.ts

@@ -0,0 +1,78 @@
+/**
+ * Enum defining the type of properties that can be edited in the property pages in the NME
+ */
+export enum PropertyTypeForEdition {
+    /** property is a boolean */
+    Boolean,
+    /** property is a float */
+    Float,
+    /** property is a Vector2 */
+    Vector2,
+    /** property is a list of values */
+    List,
+}
+
+/**
+ * Interface that defines an option in a variable of type list
+ */
+export interface IEditablePropertyListOption {
+    /** label of the option */
+    "label": string;
+    /** value of the option */
+    "value": number;
+}
+
+/**
+ * Interface that defines the options available for an editable property
+ */
+export interface IEditablePropertyOption {
+    /** min value */
+    "min"?: number;
+    /** max value */
+    "max"?: number;
+    /** notifiers: indicates which actions to take when the property is changed */
+    "notifiers"?: {
+        /** the material should be rebuilt */
+        "rebuild"?: boolean;
+        /** the preview should be updated */
+        "update"?: boolean;
+    };
+    /** list of the options for a variable of type list */
+    "options"?: IEditablePropertyListOption[];
+}
+
+/**
+ * Interface that describes an editable property
+ */
+export interface IPropertyDescriptionForEdition {
+    /** name of the property */
+    "propertyName": string;
+    /** display name of the property */
+    "displayName": string;
+    /** type of the property */
+    "type": PropertyTypeForEdition;
+    /** group of the property - all properties with the same group value will be displayed in a specific section */
+    "groupName": string;
+    /** options for the property */
+    "options": IEditablePropertyOption;
+}
+
+/**
+ * Decorator that flags a property in a node material block as being editable
+ */
+export function editableInPropertyPage(displayName: string, propertyType: PropertyTypeForEdition = PropertyTypeForEdition.Boolean, groupName: string = "PROPERTIES", options?: IEditablePropertyOption) {
+    return (target: any, propertyKey: string) => {
+        let propStore: IPropertyDescriptionForEdition[] = target._propStore;
+        if (!propStore) {
+            propStore = [];
+            target._propStore = propStore;
+        }
+        propStore.push({
+            "propertyName": propertyKey,
+            "displayName": displayName,
+            "type": propertyType,
+            "groupName": groupName,
+            "options": options ?? {}
+        });
+    };
+}

+ 1 - 0
src/Materials/Textures/cubeTexture.ts

@@ -26,6 +26,7 @@ export class CubeTexture extends BaseTexture {
     /**
      * The url of the texture
      */
+    @serialize()
     public url: string;
 
     /**

+ 4 - 3
src/Materials/materialHelper.ts

@@ -31,10 +31,11 @@ export class MaterialHelper {
      * Bind the current view position to an effect.
      * @param effect The effect to be bound
      * @param scene The scene the eyes position is used from
+     * @param variableName name of the shader variable that will hold the eye position
      */
-    public static BindEyePosition(effect: Effect, scene: Scene): void {
+    public static BindEyePosition(effect: Effect, scene: Scene, variableName = "vEyePosition"): void {
         if (scene._forcedViewPosition) {
-            effect.setVector3("vEyePosition", scene._forcedViewPosition);
+            effect.setVector3(variableName, scene._forcedViewPosition);
             return;
         }
         var globalPosition = scene.activeCamera!.globalPosition;
@@ -42,7 +43,7 @@ export class MaterialHelper {
             // Use WebVRFreecamera's device position as global position is not it's actual position in babylon space
             globalPosition = (scene.activeCamera! as WebVRFreeCamera).devicePosition;
         }
-        effect.setVector3("vEyePosition", scene._mirroredCameraPosition ? scene._mirroredCameraPosition : globalPosition);
+        effect.setVector3(variableName, scene._mirroredCameraPosition ? scene._mirroredCameraPosition : globalPosition);
     }
 
     /**

+ 1 - 49
src/Shaders/ShadersInclude/bumpFragmentFunctions.fx

@@ -1,52 +1,4 @@
-#if defined(BUMP) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC)
-	#if defined(TANGENT) && defined(NORMAL) 
-		varying mat3 vTBN;
-	#endif
-
-	#ifdef OBJECTSPACE_NORMALMAP
-		uniform mat4 normalMatrix;
-	#endif
-
-	vec3 perturbNormal(mat3 cotangentFrame, vec3 textureSample, float scale)
-	{
-		textureSample = textureSample * 2.0 - 1.0;
-
-		#ifdef NORMALXYSCALE
-			textureSample = normalize(textureSample * vec3(scale, scale, 1.0));
-		#endif
-
-		return normalize(cotangentFrame * textureSample);
-	}
-
-	// Thanks to http://www.thetenthplanet.de/archives/1180
-	mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv, vec2 tangentSpaceParams)
-	{
-		// flip the uv for the backface
-		uv = gl_FrontFacing ? uv : -uv;
-
-		// get edge vectors of the pixel triangle
-		vec3 dp1 = dFdx(p);
-		vec3 dp2 = dFdy(p);
-		vec2 duv1 = dFdx(uv);
-		vec2 duv2 = dFdy(uv);
-
-		// solve the linear system
-		vec3 dp2perp = cross(dp2, normal);
-		vec3 dp1perp = cross(normal, dp1);
-		vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;
-		vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y;
-
-		// invert the tangent/bitangent if requested
-		tangent *= tangentSpaceParams.x;
-		bitangent *= tangentSpaceParams.y;
-
-		// construct a scale-invariant frame
-		float invmax = inversesqrt(max(dot(tangent, tangent), dot(bitangent, bitangent)));
-		return mat3(tangent * invmax, bitangent * invmax, normal);
-	}
-#endif
-
-#if defined(BUMP)
+#if defined(BUMP)
 	#if BUMPDIRECTUV == 1
 		#define vBumpUV vMainUV1
 	#elif BUMPDIRECTUV == 2

+ 47 - 0
src/Shaders/ShadersInclude/bumpFragmentMainFunctions.fx

@@ -0,0 +1,47 @@
+#if defined(BUMP) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC)
+	#if defined(TANGENT) && defined(NORMAL) 
+		varying mat3 vTBN;
+	#endif
+
+	#ifdef OBJECTSPACE_NORMALMAP
+		uniform mat4 normalMatrix;
+	#endif
+
+	vec3 perturbNormal(mat3 cotangentFrame, vec3 textureSample, float scale)
+	{
+		textureSample = textureSample * 2.0 - 1.0;
+
+		#ifdef NORMALXYSCALE
+			textureSample = normalize(textureSample * vec3(scale, scale, 1.0));
+		#endif
+
+		return normalize(cotangentFrame * textureSample);
+	}
+
+	// Thanks to http://www.thetenthplanet.de/archives/1180
+	mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv, vec2 tangentSpaceParams)
+	{
+		// flip the uv for the backface
+		uv = gl_FrontFacing ? uv : -uv;
+
+		// get edge vectors of the pixel triangle
+		vec3 dp1 = dFdx(p);
+		vec3 dp2 = dFdy(p);
+		vec2 duv1 = dFdx(uv);
+		vec2 duv2 = dFdy(uv);
+
+		// solve the linear system
+		vec3 dp2perp = cross(dp2, normal);
+		vec3 dp1perp = cross(normal, dp1);
+		vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;
+		vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y;
+
+		// invert the tangent/bitangent if requested
+		tangent *= tangentSpaceParams.x;
+		bitangent *= tangentSpaceParams.y;
+
+		// construct a scale-invariant frame
+		float invmax = inversesqrt(max(dot(tangent, tangent), dot(bitangent, bitangent)));
+		return mat3(tangent * invmax, bitangent * invmax, normal);
+	}
+#endif

+ 10 - 0
src/Shaders/ShadersInclude/helperFunctions.fx

@@ -48,11 +48,21 @@ vec3 toLinearSpace(vec3 color)
     return pow(color, vec3(LinearEncodePowerApprox));
 }
 
+vec4 toLinearSpace(vec4 color)
+{
+    return vec4(pow(color.rgb, vec3(LinearEncodePowerApprox)), color.a);
+}
+
 vec3 toGammaSpace(vec3 color)
 {
     return pow(color, vec3(GammaEncodePowerApprox));
 }
 
+vec4 toGammaSpace(vec4 color)
+{
+    return vec4(pow(color.rgb, vec3(GammaEncodePowerApprox)), color.a);
+}
+
 float toGammaSpace(float color)
 {
     return pow(color, GammaEncodePowerApprox);

+ 1 - 5
src/Shaders/ShadersInclude/pbrBlockAnisotropic.fx

@@ -13,10 +13,7 @@
     void anisotropicBlock(
         const in vec3 vAnisotropy,
     #ifdef ANISOTROPIC_TEXTURE
-        const in vec2 vAnisotropyInfos,
-        const in vec2 vAnisotropyUV,
-        const in vec2 uvOffset,
-        const in sampler2D anisotropySampler,
+        const in vec3 anisotropyMapData,
     #endif
         const in mat3 TBN,
         const in vec3 normalW,
@@ -28,7 +25,6 @@
         vec3 anisotropyDirection = vec3(vAnisotropy.xy, 0.);
 
         #ifdef ANISOTROPIC_TEXTURE
-            vec3 anisotropyMapData = texture2D(anisotropySampler, vAnisotropyUV + uvOffset).rgb * vAnisotropyInfos.y;
             anisotropy *= anisotropyMapData.b;
             anisotropyDirection.rg *= anisotropyMapData.rg * 2.0 - 1.0;
             #if DEBUGMODE > 0

+ 23 - 43
src/Shaders/ShadersInclude/pbrBlockClearcoat.fx

@@ -68,6 +68,7 @@ struct clearcoatOutParams
     #endif
     #ifdef REFLECTION
         const in vec3 vReflectionMicrosurfaceInfos,
+        const in vec3 vReflectionColor,
         const in vec4 vLightingIntensity,
         #ifdef REFLECTIONMAP_3D
             const in samplerCube reflectionSampler,
@@ -75,8 +76,13 @@ struct clearcoatOutParams
             const in sampler2D reflectionSampler,
         #endif
         #ifndef LODBASEDMICROSFURACE
-            const in sampler2D reflectionSamplerLow,
-            const in sampler2D reflectionSamplerHigh,
+            #ifdef REFLECTIONMAP_3D
+                const in samplerCube reflectionSamplerLow,
+                const in samplerCube reflectionSamplerHigh,
+            #else
+                const in sampler2D reflectionSamplerLow,
+                const in sampler2D reflectionSamplerHigh,
+            #endif
         #endif
     #endif
     #if defined(ENVIRONMENTBRDF) && !defined(REFLECTIONMAP_SKYBOX)
@@ -217,51 +223,25 @@ struct clearcoatOutParams
                 clearCoatReflectionCoords.y = 1.0 - clearCoatReflectionCoords.y;
             #endif
 
+            sampleReflectionTexture(
+                clearCoatAlphaG,
+                vReflectionMicrosurfaceInfos,
+                vReflectionInfos,
+                vReflectionColor,
             #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
-                float clearCoatReflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, clearCoatAlphaG, clearCoatNdotVUnclamped);
-            #elif defined(LINEARSPECULARREFLECTION)
-                float clearCoatReflectionLOD = getLinearLodFromRoughness(vReflectionMicrosurfaceInfos.x, clearCoatRoughness);
-            #else
-                float clearCoatReflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, clearCoatAlphaG);
+                clearCoatNdotVUnclamped,
             #endif
-
-            #ifdef LODBASEDMICROSFURACE
-                // Apply environment convolution scale/offset filter tuning parameters to the mipmap LOD selection
-                clearCoatReflectionLOD = clearCoatReflectionLOD * vReflectionMicrosurfaceInfos.y + vReflectionMicrosurfaceInfos.z;
-                float requestedClearCoatReflectionLOD = clearCoatReflectionLOD;
-
-                environmentClearCoatRadiance = sampleReflectionLod(reflectionSampler, clearCoatReflectionCoords, requestedClearCoatReflectionLOD);
-            #else
-                float lodClearCoatReflectionNormalized = saturate(clearCoatReflectionLOD / log2(vReflectionMicrosurfaceInfos.x));
-                float lodClearCoatReflectionNormalizedDoubled = lodClearCoatReflectionNormalized * 2.0;
-
-                vec4 environmentClearCoatMid = sampleReflection(reflectionSampler, clearCoatReflectionCoords);
-                if (lodClearCoatReflectionNormalizedDoubled < 1.0) {
-                    environmentClearCoatRadiance = mix(
-                        sampleReflection(reflectionSamplerHigh, clearCoatReflectionCoords),
-                        environmentClearCoatMid,
-                        lodClearCoatReflectionNormalizedDoubled
-                    );
-                } else {
-                    environmentClearCoatRadiance = mix(
-                        environmentClearCoatMid,
-                        sampleReflection(reflectionSamplerLow, clearCoatReflectionCoords),
-                        lodClearCoatReflectionNormalizedDoubled - 1.0
-                    );
-                }
+            #ifdef LINEARSPECULARREFLECTION
+                clearCoatRoughness,
             #endif
-
-            #ifdef RGBDREFLECTION
-                environmentClearCoatRadiance.rgb = fromRGBD(environmentClearCoatRadiance);
+                reflectionSampler,
+                clearCoatReflectionCoords,
+            #ifndef LODBASEDMICROSFURACE
+                reflectionSamplerLow,
+                reflectionSamplerHigh,
             #endif
-
-            #ifdef GAMMAREFLECTION
-                environmentClearCoatRadiance.rgb = toLinearSpace(environmentClearCoatRadiance.rgb);
-            #endif
-
-            // _____________________________ Levels _____________________________________
-            environmentClearCoatRadiance.rgb *= vReflectionInfos.x;
-            environmentClearCoatRadiance.rgb *= vReflectionColor.rgb;
+                environmentClearCoatRadiance
+            );
 
             #if DEBUGMODE > 0
                 outParams.environmentClearCoatRadiance = environmentClearCoatRadiance;

+ 1 - 0
src/Shaders/ShadersInclude/pbrBlockFinalLitComponents.fx

@@ -39,6 +39,7 @@
 
     finalIrradiance *= surfaceAlbedo.rgb;
     finalIrradiance *= vLightingIntensity.z;
+    finalIrradiance *= aoOut.ambientOcclusionColor;
 #endif
 
 // _____________________________ Specular ________________________________________

+ 0 - 4
src/Shaders/ShadersInclude/pbrBlockFinalUnlitComponents.fx

@@ -26,7 +26,3 @@ vec3 ambientOcclusionForDirectDiffuse = aoOut.ambientOcclusionColor;
 
 finalAmbient *= aoOut.ambientOcclusionColor;
 finalDiffuse *= ambientOcclusionForDirectDiffuse;
-
-#if !defined(UNLIT) && defined(REFLECTION)
-finalIrradiance *= aoOut.ambientOcclusionColor;
-#endif

+ 141 - 42
src/Shaders/ShadersInclude/pbrBlockReflection.fx

@@ -17,47 +17,19 @@
     #endif
     };
 
-    void reflectionBlock(
+    void createReflectionCoords(
         const in vec3 vPositionW,
         const in vec3 normalW,
-        const in float alphaG,
-        const in vec3 vReflectionMicrosurfaceInfos,
-        const in vec2 vReflectionInfos,
     #ifdef ANISOTROPIC
         const in anisotropicOutParams anisotropicOut,
     #endif
-    #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
-        const in float NdotVUnclamped,
-    #endif
-    #ifdef LINEARSPECULARREFLECTION
-        const in float roughness,
-    #endif
     #ifdef REFLECTIONMAP_3D
-        const in samplerCube reflectionSampler,
+        out vec3 reflectionCoords
     #else
-        const in sampler2D reflectionSampler,
+        out vec2 reflectionCoords
     #endif
-    #if defined(NORMAL) && defined(USESPHERICALINVERTEX)
-        const in vec3 vEnvironmentIrradiance,
-    #endif
-    #ifdef USESPHERICALFROMREFLECTIONMAP
-        #if !defined(NORMAL) || !defined(USESPHERICALINVERTEX)
-            const in mat4 reflectionMatrix,
-        #endif
-    #endif
-    #ifdef USEIRRADIANCEMAP
-        #ifdef REFLECTIONMAP_3D
-            const in samplerCube irradianceSampler,
-        #else
-            const in sampler2D irradianceSampler,
-        #endif
-    #endif
-        out reflectionOutParams outParams
     )
     {
-        vec4 environmentRadiance = vec4(0., 0., 0., 0.);
-        vec3 environmentIrradiance = vec3(0., 0., 0.);
-
         #ifdef ANISOTROPIC
             vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), anisotropicOut.anisotropicNormal);
         #else
@@ -70,15 +42,47 @@
 
         // _____________________________ 2D vs 3D Maps ________________________________
         #ifdef REFLECTIONMAP_3D
-            vec3 reflectionCoords = reflectionVector;
+            reflectionCoords = reflectionVector;
         #else
-            vec2 reflectionCoords = reflectionVector.xy;
+            reflectionCoords = reflectionVector.xy;
             #ifdef REFLECTIONMAP_PROJECTION
                 reflectionCoords /= reflectionVector.z;
             #endif
             reflectionCoords.y = 1.0 - reflectionCoords.y;
         #endif
+    }
 
+    void sampleReflectionTexture(
+        const in float alphaG,
+        const in vec3 vReflectionMicrosurfaceInfos,
+        const in vec2 vReflectionInfos,
+        const in vec3 vReflectionColor,
+    #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
+        const in float NdotVUnclamped,
+    #endif
+    #ifdef LINEARSPECULARREFLECTION
+        const in float roughness,
+    #endif
+    #ifdef REFLECTIONMAP_3D
+        const in samplerCube reflectionSampler,
+        const vec3 reflectionCoords,
+    #else
+        const in sampler2D reflectionSampler,
+        const vec2 reflectionCoords,
+    #endif
+    #ifndef LODBASEDMICROSFURACE
+        #ifdef REFLECTIONMAP_3D
+            const in samplerCube reflectionSamplerLow,
+            const in samplerCube reflectionSamplerHigh,
+        #else
+            const in sampler2D reflectionSamplerLow,
+            const in sampler2D reflectionSamplerHigh,
+        #endif
+    #endif
+        out vec4 environmentRadiance
+    )
+    {
+        // _____________________________ 2D vs 3D Maps ________________________________
         #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
             float reflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, alphaG, NdotVUnclamped);
         #elif defined(LINEARSPECULARREFLECTION)
@@ -108,21 +112,21 @@
                 float requestedReflectionLOD = reflectionLOD;
             #endif
 
-            environmentRadiance = sampleReflectionLod(reflectionSampler, reflectionCoords, requestedReflectionLOD);
+            environmentRadiance = sampleReflectionLod(reflectionSampler, reflectionCoords, reflectionLOD);
         #else
             float lodReflectionNormalized = saturate(reflectionLOD / log2(vReflectionMicrosurfaceInfos.x));
             float lodReflectionNormalizedDoubled = lodReflectionNormalized * 2.0;
 
-            vec4 environmentSpecularMid = sampleReflection(reflectionSampler, reflectionCoords);
-            if(lodReflectionNormalizedDoubled < 1.0){
+            vec4 environmentMid = sampleReflection(reflectionSampler, reflectionCoords);
+            if (lodReflectionNormalizedDoubled < 1.0){
                 environmentRadiance = mix(
                     sampleReflection(reflectionSamplerHigh, reflectionCoords),
-                    environmentSpecularMid,
+                    environmentMid,
                     lodReflectionNormalizedDoubled
                 );
-            }else{
+            } else {
                 environmentRadiance = mix(
-                    environmentSpecularMid,
+                    environmentMid,
                     sampleReflection(reflectionSamplerLow, reflectionCoords),
                     lodReflectionNormalizedDoubled - 1.0
                 );
@@ -137,7 +141,105 @@
             environmentRadiance.rgb = toLinearSpace(environmentRadiance.rgb);
         #endif
 
+        // _____________________________ Levels _____________________________________
+        environmentRadiance.rgb *= vReflectionInfos.x;
+        environmentRadiance.rgb *= vReflectionColor.rgb;
+    }
+
+    void reflectionBlock(
+        const in vec3 vPositionW,
+        const in vec3 normalW,
+        const in float alphaG,
+        const in vec3 vReflectionMicrosurfaceInfos,
+        const in vec2 vReflectionInfos,
+        const in vec3 vReflectionColor,
+    #ifdef ANISOTROPIC
+        const in anisotropicOutParams anisotropicOut,
+    #endif
+    #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
+        const in float NdotVUnclamped,
+    #endif
+    #ifdef LINEARSPECULARREFLECTION
+        const in float roughness,
+    #endif
+    #ifdef REFLECTIONMAP_3D
+        const in samplerCube reflectionSampler,
+    #else
+        const in sampler2D reflectionSampler,
+    #endif
+    #if defined(NORMAL) && defined(USESPHERICALINVERTEX)
+        const in vec3 vEnvironmentIrradiance,
+    #endif
+    #ifdef USESPHERICALFROMREFLECTIONMAP
+        #if !defined(NORMAL) || !defined(USESPHERICALINVERTEX)
+            const in mat4 reflectionMatrix,
+        #endif
+    #endif
+    #ifdef USEIRRADIANCEMAP
+        #ifdef REFLECTIONMAP_3D
+            const in samplerCube irradianceSampler,
+        #else
+            const in sampler2D irradianceSampler,
+        #endif
+    #endif
+    #ifndef LODBASEDMICROSFURACE
+        #ifdef REFLECTIONMAP_3D
+            const in samplerCube reflectionSamplerLow,
+            const in samplerCube reflectionSamplerHigh,
+        #else
+            const in sampler2D reflectionSamplerLow,
+            const in sampler2D reflectionSamplerHigh,
+        #endif
+    #endif
+        out reflectionOutParams outParams
+    )
+    {
+        // _____________________________ Radiance ________________________________
+        vec4 environmentRadiance = vec4(0., 0., 0., 0.);
+
+        #ifdef REFLECTIONMAP_3D
+            vec3 reflectionCoords = vec3(0.);
+        #else
+            vec2 reflectionCoords = vec2(0.);
+        #endif
+
+        createReflectionCoords(
+            vPositionW,
+            normalW,
+        #ifdef ANISOTROPIC
+            anisotropicOut,
+        #endif
+            reflectionCoords
+        );
+
+        sampleReflectionTexture(
+            alphaG,
+            vReflectionMicrosurfaceInfos,
+            vReflectionInfos,
+            vReflectionColor,
+        #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
+            NdotVUnclamped,
+        #endif
+        #ifdef LINEARSPECULARREFLECTION
+            roughness,
+        #endif
+        #ifdef REFLECTIONMAP_3D
+            reflectionSampler,
+            reflectionCoords,
+        #else
+            reflectionSampler,
+            reflectionCoords,
+        #endif
+        #ifndef LODBASEDMICROSFURACE
+            reflectionSamplerLow,
+            reflectionSamplerHigh,
+        #endif
+            environmentRadiance
+        );
+
         // _____________________________ Irradiance ________________________________
+        vec3 environmentIrradiance = vec3(0., 0., 0.);
+
         #ifdef USESPHERICALFROMREFLECTIONMAP
             #if defined(NORMAL) && defined(USESPHERICALINVERTEX)
                 environmentIrradiance = vEnvironmentIrradiance;
@@ -170,9 +272,6 @@
             #endif
         #endif
 
-        // _____________________________ Levels _____________________________________
-        environmentRadiance.rgb *= vReflectionInfos.x;
-        environmentRadiance.rgb *= vReflectionColor.rgb;
         environmentIrradiance *= vReflectionColor.rgb;
 
         outParams.environmentRadiance = environmentRadiance;

+ 12 - 24
src/Shaders/ShadersInclude/pbrBlockReflectivity.fx

@@ -19,22 +19,18 @@ struct reflectivityOutParams
 
 void reflectivityBlock(
     const in vec4 vReflectivityColor,
-    const in vec2 uvOffset,
 #ifdef METALLICWORKFLOW
     const in vec3 surfaceAlbedo,
 #endif
 #ifdef REFLECTIVITY
     const in vec3 vReflectivityInfos,
-    const in vec2 vReflectivityUV,
-    const in sampler2D reflectivitySampler,
+    const in vec4 surfaceMetallicOrReflectivityColorMap,
 #endif
 #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY)  && defined(AOSTOREINMETALMAPRED)
     const in vec3 ambientOcclusionColor,
 #endif
 #ifdef MICROSURFACEMAP
-    const in vec2 vMicroSurfaceSamplerUV_,
-    const in vec2 vMicroSurfaceSamplerInfos,
-    const in sampler2D microSurfaceSampler,
+    const in vec4 microSurfaceTexel,
 #endif
     out reflectivityOutParams outParams
 )
@@ -46,34 +42,31 @@ void reflectivityBlock(
         vec2 metallicRoughness = surfaceReflectivityColor.rg;
 
         #ifdef REFLECTIVITY
-            vec4 surfaceMetallicColorMap = texture2D(reflectivitySampler, vReflectivityUV + uvOffset);
-
             #if DEBUGMODE > 0
-                outParams.surfaceMetallicColorMap = surfaceMetallicColorMap;
+                outParams.surfaceMetallicColorMap = surfaceMetallicOrReflectivityColorMap;
             #endif
 
             #ifdef AOSTOREINMETALMAPRED
-                vec3 aoStoreInMetalMap = vec3(surfaceMetallicColorMap.r, surfaceMetallicColorMap.r, surfaceMetallicColorMap.r);
+                vec3 aoStoreInMetalMap = vec3(surfaceMetallicOrReflectivityColorMap.r, surfaceMetallicOrReflectivityColorMap.r, surfaceMetallicOrReflectivityColorMap.r);
                 outParams.ambientOcclusionColor = mix(ambientOcclusionColor, aoStoreInMetalMap, vReflectivityInfos.z);
             #endif
 
             #ifdef METALLNESSSTOREINMETALMAPBLUE
-                metallicRoughness.r *= surfaceMetallicColorMap.b;
+                metallicRoughness.r *= surfaceMetallicOrReflectivityColorMap.b;
             #else
-                metallicRoughness.r *= surfaceMetallicColorMap.r;
+                metallicRoughness.r *= surfaceMetallicOrReflectivityColorMap.r;
             #endif
 
             #ifdef ROUGHNESSSTOREINMETALMAPALPHA
-                metallicRoughness.g *= surfaceMetallicColorMap.a;
+                metallicRoughness.g *= surfaceMetallicOrReflectivityColorMap.a;
             #else
                 #ifdef ROUGHNESSSTOREINMETALMAPGREEN
-                    metallicRoughness.g *= surfaceMetallicColorMap.g;
+                    metallicRoughness.g *= surfaceMetallicOrReflectivityColorMap.g;
                 #endif
             #endif
         #endif
 
         #ifdef MICROSURFACEMAP
-            vec4 microSurfaceTexel = texture2D(microSurfaceSampler, vMicroSurfaceSamplerUV_ + uvOffset) * vMicroSurfaceSamplerInfos.y;
             metallicRoughness.g *= microSurfaceTexel.r;
         #endif
 
@@ -105,7 +98,7 @@ void reflectivityBlock(
             vec3 metallicF0 = vec3(vReflectivityColor.a, vReflectivityColor.a, vReflectivityColor.a);
             #ifdef METALLICF0FACTORFROMMETALLICMAP
                 #ifdef REFLECTIVITY
-                    metallicF0 *= surfaceMetallicColorMap.a;
+                    metallicF0 *= surfaceMetallicOrReflectivityColorMap.a;
                 #endif
             #endif
 
@@ -121,18 +114,14 @@ void reflectivityBlock(
         #endif
     #else
         #ifdef REFLECTIVITY
-            vec4 surfaceReflectivityColorMap = texture2D(reflectivitySampler, vReflectivityUV + uvOffset);
-            surfaceReflectivityColor *= toLinearSpace(surfaceReflectivityColorMap.rgb);
-            surfaceReflectivityColor *= vReflectivityInfos.y;
+            surfaceReflectivityColor *= surfaceMetallicOrReflectivityColorMap.rgb;
 
             #if DEBUGMODE > 0
-                outParams.surfaceReflectivityColorMap = surfaceReflectivityColorMap;
-                vec2 metallicRoughness;
-                vec3 metallicF0;
+                outParams.surfaceReflectivityColorMap = surfaceMetallicOrReflectivityColorMap;
             #endif
 
             #ifdef MICROSURFACEFROMREFLECTIVITYMAP
-                microSurface *= surfaceReflectivityColorMap.a;
+                microSurface *= surfaceMetallicOrReflectivityColorMap.a;
                 microSurface *= vReflectivityInfos.z;
             #else
                 #ifdef MICROSURFACEAUTOMATIC
@@ -140,7 +129,6 @@ void reflectivityBlock(
                 #endif
 
                 #ifdef MICROSURFACEMAP
-                    vec4 microSurfaceTexel = texture2D(microSurfaceSampler, vMicroSurfaceSamplerUV_ + uvOffset) * vMicroSurfaceSamplerInfos.y;
                     microSurface *= microSurfaceTexel.r;
                 #endif
                 

+ 24 - 48
src/Shaders/ShadersInclude/pbrBlockSheen.fx

@@ -26,10 +26,7 @@
     #endif
         const in float roughness,
     #ifdef SHEEN_TEXTURE
-        const in vec2 vSheenUV,
-        const in vec2 vSheenInfos,
-        const in vec2 uvOffset,
-        const in sampler2D sheenSampler,
+        const in vec4 sheenMapData,
     #endif
         const in float reflectance,
     #ifdef SHEEN_LINKWITHALBEDO
@@ -55,8 +52,13 @@
         #endif
         const in float NdotVUnclamped,
         #ifndef LODBASEDMICROSFURACE
-            const in sampler2D reflectionSamplerLow,
-            const in sampler2D reflectionSamplerHigh,
+            #ifdef REFLECTIONMAP_3D
+                const in samplerCube reflectionSamplerLow,
+                const in samplerCube reflectionSamplerHigh,
+            #else
+                const in sampler2D reflectionSamplerLow,
+                const in sampler2D reflectionSamplerHigh,
+            #endif
         #endif
         #if !defined(REFLECTIONMAP_SKYBOX) && defined(RADIANCEOCCLUSION)
             const in float seo,
@@ -71,7 +73,6 @@
         float sheenIntensity = vSheenColor.a;
 
         #ifdef SHEEN_TEXTURE
-            vec4 sheenMapData = texture2D(sheenSampler, vSheenUV + uvOffset) * vSheenInfos.y;
             sheenIntensity *= sheenMapData.a;
             #if DEBUGMODE > 0
                 outParams.sheenMapData = sheenMapData;
@@ -86,7 +87,7 @@
         #else
             vec3 sheenColor = vSheenColor.rgb;
             #ifdef SHEEN_TEXTURE
-                sheenColor.rgb *= toLinearSpace(sheenMapData.rgb);
+                sheenColor.rgb *= sheenMapData.rgb;
             #endif
             
             #ifdef SHEEN_ROUGHNESS
@@ -127,50 +128,25 @@
 
             vec4 environmentSheenRadiance = vec4(0., 0., 0., 0.);
 
-            // _____________________________ 2D vs 3D Maps ________________________________
+            sampleReflectionTexture(
+                sheenAlphaG,
+                vReflectionMicrosurfaceInfos,
+                vReflectionInfos,
+                vReflectionColor,
             #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
-                float sheenReflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, sheenAlphaG, NdotVUnclamped);
-            #elif defined(LINEARSPECULARREFLECTION)
-                float sheenReflectionLOD = getLinearLodFromRoughness(vReflectionMicrosurfaceInfos.x, sheenRoughness);
-            #else
-                float sheenReflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, sheenAlphaG);
+                NdotVUnclamped,
             #endif
-
-            #ifdef LODBASEDMICROSFURACE
-                // Apply environment convolution scale/offset filter tuning parameters to the mipmap LOD selection
-                sheenReflectionLOD = sheenReflectionLOD * vReflectionMicrosurfaceInfos.y + vReflectionMicrosurfaceInfos.z;
-                environmentSheenRadiance = sampleReflectionLod(reflectionSampler, reflectionCoords, sheenReflectionLOD);
-            #else
-                float lodSheenReflectionNormalized = saturate(sheenReflectionLOD / log2(vReflectionMicrosurfaceInfos.x));
-                float lodSheenReflectionNormalizedDoubled = lodSheenReflectionNormalized * 2.0;
-
-                vec4 environmentSheenMid = sampleReflection(reflectionSampler, reflectionCoords);
-                if (lodSheenReflectionNormalizedDoubled < 1.0){
-                    environmentSheenRadiance = mix(
-                        sampleReflection(reflectionSamplerHigh, reflectionCoords),
-                        environmentSheenMid,
-                        lodSheenReflectionNormalizedDoubled
-                    );
-                } else {
-                    environmentSheenRadiance = mix(
-                        environmentSheenMid,
-                        sampleReflection(reflectionSamplerLow, reflectionCoords),
-                        lodSheenReflectionNormalizedDoubled - 1.0
-                    );
-                }
+            #ifdef LINEARSPECULARREFLECTION
+                sheenRoughness,
             #endif
-
-            #ifdef RGBDREFLECTION
-                environmentSheenRadiance.rgb = fromRGBD(environmentSheenRadiance);
-            #endif
-
-            #ifdef GAMMAREFLECTION
-                environmentSheenRadiance.rgb = toLinearSpace(environmentSheenRadiance.rgb);
+                reflectionSampler,
+                reflectionCoords,
+            #ifndef LODBASEDMICROSFURACE
+                reflectionSamplerLow,
+                reflectionSamplerHigh,
             #endif
-
-            // _____________________________ Levels _____________________________________
-            environmentSheenRadiance.rgb *= vReflectionInfos.x;
-            environmentSheenRadiance.rgb *= vReflectionColor.rgb;
+                environmentSheenRadiance
+            );
 
             vec3 sheenEnvironmentReflectance = getSheenReflectanceFromBRDFLookup(sheenColor, environmentSheenBrdf);
 

+ 4 - 3
src/Shaders/ShadersInclude/pbrDebug.fx

@@ -1,7 +1,6 @@
 #if  DEBUGMODE > 0
-    if (vClipSpacePosition.x / vClipSpacePosition.w < vDebugMode.x) {
-        return;
-    }
+if (vClipSpacePosition.x / vClipSpacePosition.w >= vDebugMode.x) {
+
 // Geometry
     #if   DEBUGMODE == 1
         gl_FragColor.rgb = vPositionW.rgb;
@@ -160,4 +159,6 @@
     #endif
 
     gl_FragColor.a = 1.0;
+    return;
+}
 #endif

+ 2 - 2
src/Shaders/ShadersInclude/pbrFragmentSamplersDeclaration.fx

@@ -205,8 +205,8 @@
             #ifdef LODBASEDMICROSFURACE
                 #define sampleRefractionLod(s, c, l) texture2DLodEXT(s, c, l)
             #else
-                uniform samplerCube refractionSamplerLow;
-                uniform samplerCube refractionSamplerHigh;
+                uniform sampler2D refractionSamplerLow;
+                uniform sampler2D refractionSamplerHigh;
             #endif
         #endif
     #endif

+ 1 - 0
src/Shaders/default.fragment.fx

@@ -153,6 +153,7 @@ varying vec3 vDirectionW;
 
 #include<imageProcessingFunctions>
 
+#include<bumpFragmentMainFunctions>
 #include<bumpFragmentFunctions>
 #include<clipPlaneFragmentDeclaration>
 #include<logDepthDeclaration>

+ 1 - 0
src/Shaders/geometry.fragment.fx

@@ -44,6 +44,7 @@ uniform sampler2D diffuseSampler;
 #endif
 
 #include<mrtFragmentDeclaration>[RENDER_TARGET_COUNT]
+#include<bumpFragmentMainFunctions>
 #include<bumpFragmentFunctions>
 
 void main() {

+ 31 - 14
src/Shaders/pbr.fragment.fx

@@ -40,6 +40,7 @@ precision highp float;
 #include<pbrBRDFFunctions>
 #include<pbrDirectLightingFunctions>
 #include<pbrIBLFunctions>
+#include<bumpFragmentMainFunctions>
 #include<bumpFragmentFunctions>
 
 #ifdef REFLECTION
@@ -129,24 +130,32 @@ void main(void) {
 
     reflectivityOutParams reflectivityOut;
 
+#if defined(REFLECTIVITY)
+    vec4 surfaceMetallicOrReflectivityColorMap = texture2D(reflectivitySampler, vReflectivityUV + uvOffset);
+    #ifndef METALLICWORKFLOW
+        surfaceMetallicOrReflectivityColorMap = toLinearSpace(surfaceMetallicOrReflectivityColorMap);
+        surfaceMetallicOrReflectivityColorMap.rgb *= vReflectivityInfos.y;
+    #endif
+#endif
+
+#if defined(MICROSURFACEMAP)
+    vec4 microSurfaceTexel = texture2D(microSurfaceSampler, vMicroSurfaceSamplerUV + uvOffset) * vMicroSurfaceSamplerInfos.y;
+#endif
+
     reflectivityBlock(
         vReflectivityColor,
-        uvOffset,
     #ifdef METALLICWORKFLOW
         surfaceAlbedo,
     #endif
     #ifdef REFLECTIVITY
         vReflectivityInfos,
-        vReflectivityUV,
-        reflectivitySampler,
+        surfaceMetallicOrReflectivityColorMap,
     #endif
     #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY)  && defined(AOSTOREINMETALMAPRED)
         aoOut.ambientOcclusionColor,
     #endif
     #ifdef MICROSURFACEMAP
-        vMicroSurfaceSamplerUV,
-        vMicroSurfaceSamplerInfos,
-        microSurfaceSampler,
+        microSurfaceTexel,
     #endif
         reflectivityOut
     );
@@ -185,13 +194,14 @@ void main(void) {
     #ifdef ANISOTROPIC
         anisotropicOutParams anisotropicOut;
 
+        #ifdef ANISOTROPIC_TEXTURE
+            vec3 anisotropyMapData = texture2D(anisotropySampler, vAnisotropyUV + uvOffset).rgb * vAnisotropyInfos.y;
+        #endif
+
         anisotropicBlock(
             vAnisotropy,
         #ifdef ANISOTROPIC_TEXTURE
-            vAnisotropyInfos,
-            vAnisotropyUV,
-            uvOffset,
-            anisotropySampler,
+            anisotropyMapData,
         #endif
             TBN,
             normalW,
@@ -210,6 +220,7 @@ void main(void) {
             alphaG,
             vReflectionMicrosurfaceInfos,
             vReflectionInfos,
+            vReflectionColor,
         #ifdef ANISOTROPIC
             anisotropicOut,
         #endif
@@ -231,6 +242,10 @@ void main(void) {
         #ifdef USEIRRADIANCEMAP
             irradianceSampler,
         #endif
+        #ifndef LODBASEDMICROSFURACE
+            reflectionSamplerLow,
+            reflectionSamplerHigh,
+        #endif
             reflectionOut
         );
     #endif
@@ -242,6 +257,10 @@ void main(void) {
     #ifdef SHEEN
         sheenOutParams sheenOut;
 
+        #ifdef SHEEN_TEXTURE
+            vec4 sheenMapData = toLinearSpace(texture2D(sheenSampler, vSheenUV + uvOffset)) * vSheenInfos.y;
+        #endif
+
         sheenBlock(
             vSheenColor,
         #ifdef SHEEN_ROUGHNESS
@@ -249,10 +268,7 @@ void main(void) {
         #endif
             roughness,
         #ifdef SHEEN_TEXTURE
-            vSheenUV,
-            vSheenInfos,
-            uvOffset,
-            sheenSampler,
+            sheenMapData,
         #endif
             reflectance,
         #ifdef SHEEN_LINKWITHALBEDO
@@ -334,6 +350,7 @@ void main(void) {
         #endif
         #ifdef REFLECTION
             vReflectionMicrosurfaceInfos,
+            vReflectionColor,
             vLightingIntensity,
             reflectionSampler,
             #ifndef LODBASEDMICROSFURACE

+ 4 - 4
src/Shaders/pbr.vertex.fx

@@ -163,10 +163,6 @@ void main(void) {
 #include<instancesVertex>
 #include<bonesVertex>
 
-#if DEBUGMODE > 0
-    vClipSpacePosition = gl_Position;
-#endif
-
     vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
     vPositionW = vec3(worldPos);
 
@@ -200,6 +196,10 @@ void main(void) {
 	gl_Position = viewProjection * worldPos;
 #endif
 
+#if DEBUGMODE > 0
+    vClipSpacePosition = gl_Position;
+#endif
+
 #if defined(REFLECTIONMAP_EQUIRECTANGULAR_FIXED) || defined(REFLECTIONMAP_MIRROREDEQUIRECTANGULAR_FIXED)
     vDirectionW = normalize(vec3(finalWorld * vec4(positionUpdated, 0.0)));
 #endif