浏览代码

Merge pull request #8062 from Popov72/nme-pbr

PBR NME: Support of SubSurface
David Catuhe 5 年之前
父节点
当前提交
9c1d59a684

+ 6 - 0
nodeEditor/src/blockTools.ts

@@ -69,6 +69,8 @@ import { ReflectivityBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectiv
 import { AnisotropyBlock } from 'babylonjs/Materials/Node/Blocks/PBR/anisotropyBlock';
 import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionBlock';
 import { ClearCoatBlock } from 'babylonjs/Materials/Node/Blocks/PBR/clearCoatBlock';
+import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
+import { SubSurfaceBlock } from 'babylonjs/Materials/Node/Blocks/PBR/subSurfaceBlock';
 
 export class BlockTools {
     public static GetBlockFromString(data: string, scene: Scene, nodeMaterial: NodeMaterial) {
@@ -453,6 +455,10 @@ export class BlockTools {
                 return new ReflectionBlock("Reflection");
             case "ClearCoatBlock":
                 return new ClearCoatBlock("ClearCoat");
+            case "RefractionBlock":
+                return new RefractionBlock("Refraction");
+            case "SubSurfaceBlock":
+                return new SubSurfaceBlock("SubSurface");
         }
 
         return null;

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

@@ -124,6 +124,8 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "AnisotropyBlock": "PBR Anisotropy block",
         "ReflectionBlock": "PBR Reflection block",
         "ClearCoatBlock": "PBR ClearCoat block",
+        "RefractionBlock": "PBR Refraction block",
+        "SubSurfaceBlock": "PBR SubSurface block",
     };
 
     constructor(props: INodeListComponentProps) {
@@ -152,7 +154,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             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", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "SheenBlock"],
+            PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],
             Round: ["RoundBlock", "CeilingBlock", "FloorBlock"],
             Scene: ["FogBlock", "CameraPositionBlock", "FogColorBlock", "ImageProcessingBlock", "LightBlock", "LightInformationBlock", "ViewDirectionBlock"],

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

@@ -2,6 +2,7 @@ import { IDisplayManager } from './displayManager';
 import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
 import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
 import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
+import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractioneBlock';
 import { TextureLineComponent } from '../../sharedComponents/textureLineComponent';
 
 export class TextureDisplayManager implements IDisplayManager {
@@ -25,7 +26,7 @@ export class TextureDisplayManager implements IDisplayManager {
     }
 
     public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
-        const textureBlock = block as TextureBlock | ReflectionTextureBlock;
+        const textureBlock = block as TextureBlock | ReflectionTextureBlock | RefractionBlock;
 
         if (!this._previewCanvas) {
             contentArea.classList.add("texture-block");

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

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

+ 5 - 4
nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx

@@ -15,10 +15,11 @@ import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponen
 import { IPropertyComponentProps } from './propertyComponentProps';
 import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
 import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionBlock';
+import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
 import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
 import { GeneralPropertyTabComponent, GenericPropertyTabComponent } from './genericNodePropertyComponent';
 
-type ReflectionTexture = ReflectionTextureBlock | ReflectionBlock;
+type ReflectionTexture = ReflectionTextureBlock | ReflectionBlock | RefractionBlock;
 
 export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
 
@@ -79,7 +80,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 instanceof ReflectionBlock);
+                this.textureBlock.texture = new Texture(null, this.props.globalState.nodeMaterial.getScene(), false, this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock);
                 texture = this.textureBlock.texture;
                 texture.coordinatesMode = Texture.EQUIRECTANGULAR_MODE;
             } else {
@@ -122,7 +123,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
         this._prepareTexture();
 
         let texture = this.textureBlock.texture as BaseTexture;       
-        if (texture.isCube || this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock) {
+        if (texture.isCube || this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock) {
             let extension: string | undefined = undefined;
             if (url.toLowerCase().indexOf(".dds") > 0) {
                 extension = ".dds";
@@ -146,7 +147,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
 
         url = url.replace(/\?nocache=\d+/, "");
 
-        let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock;
+        let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock;
 
         var reflectionModeOptions: {label: string, value: number}[] = [
             {

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

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

+ 13 - 12
src/Materials/Node/Blocks/Dual/reflectionTextureBaseBlock.ts

@@ -55,7 +55,8 @@ export abstract class ReflectionTextureBaseBlock extends NodeMaterialBlock {
     protected _reflectionVectorName: string;
     /** @hidden */
     public _reflectionCoordsName: string;
-    protected _reflectionMatrixName: string;
+    /** @hidden */
+    public _reflectionMatrixName: string;
     protected _reflectionColorName: string;
 
     /**
@@ -156,17 +157,17 @@ export abstract class ReflectionTextureBaseBlock extends NodeMaterialBlock {
             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);
+        defines.setValue(this._define3DName, texture.isCube, true);
+        defines.setValue(this._defineLocalCubicName, (<any>texture).boundingBoxSize ? true : false, true);
+        defines.setValue(this._defineExplicitName, texture.coordinatesMode === Constants.TEXTURE_EXPLICIT_MODE, true);
+        defines.setValue(this._defineSkyboxName, texture.coordinatesMode === Constants.TEXTURE_SKYBOX_MODE, true);
+        defines.setValue(this._defineCubicName, texture.coordinatesMode === Constants.TEXTURE_CUBIC_MODE, true);
+        defines.setValue(this._defineSphericalName, texture.coordinatesMode === Constants.TEXTURE_SPHERICAL_MODE, true);
+        defines.setValue(this._definePlanarName, texture.coordinatesMode === Constants.TEXTURE_PLANAR_MODE, true);
+        defines.setValue(this._defineProjectionName, texture.coordinatesMode === Constants.TEXTURE_PROJECTION_MODE, true);
+        defines.setValue(this._defineEquirectangularName, texture.coordinatesMode === Constants.TEXTURE_EQUIRECTANGULAR_MODE, true);
+        defines.setValue(this._defineEquirectangularFixedName, texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MODE, true);
+        defines.setValue(this._defineMirroredEquirectangularFixedName, texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MIRRORED_MODE, true);
     }
 
     public isReady() {

+ 2 - 2
src/Materials/Node/Blocks/PBR/ambientOcclusionBlock.ts

@@ -108,8 +108,8 @@ export class AmbientOcclusionBlock extends NodeMaterialBlock {
     public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
         super.prepareDefines(mesh, nodeMaterial, defines);
 
-        defines.setValue("AMBIENT", this.texture.isConnected);
-        defines.setValue("AMBIENTINGRAYSCALE", this.useAmbientInGrayScale);
+        defines.setValue("AMBIENT", this.texture.isConnected, true);
+        defines.setValue("AMBIENTINGRAYSCALE", this.useAmbientInGrayScale, true);
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {

+ 1 - 1
src/Materials/Node/Blocks/PBR/anisotropyBlock.ts

@@ -181,7 +181,7 @@ export class AnisotropyBlock extends NodeMaterialBlock {
         super.prepareDefines(mesh, nodeMaterial, defines);
 
         defines.setValue("ANISOTROPIC", true);
-        defines.setValue("ANISOTROPIC_TEXTURE", this.texture.isConnected);
+        defines.setValue("ANISOTROPIC_TEXTURE", this.texture.isConnected, true);
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {

+ 50 - 9
src/Materials/Node/Blocks/PBR/clearCoatBlock.ts

@@ -43,6 +43,7 @@ export class ClearCoatBlock extends NodeMaterialBlock {
         this.registerInput("tintAtDistance", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintThickness", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("worldTangent", NodeMaterialBlockConnectionPointTypes.Vector4, true);
 
         this.registerOutput("clearcoat", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("clearcoat", this, NodeMaterialConnectionPointDirection.Output, ClearCoatBlock, "ClearCoatBlock"));
@@ -139,6 +140,13 @@ export class ClearCoatBlock extends NodeMaterialBlock {
     }
 
     /**
+     * Gets the world tangent input component
+     */
+    public get worldTangent(): NodeMaterialConnectionPoint {
+        return this._inputs[10];
+    }
+
+    /**
      * Gets the clear coat object output component
      */
     public get clearcoat(): NodeMaterialConnectionPoint {
@@ -157,12 +165,11 @@ export class ClearCoatBlock extends NodeMaterialBlock {
         super.prepareDefines(mesh, nodeMaterial, defines);
 
         defines.setValue("CLEARCOAT", true);
-        defines.setValue("CLEARCOAT_TEXTURE", this.texture.isConnected);
-        defines.setValue("CLEARCOAT_TINT", this.tintColor.isConnected || this.tintThickness.isConnected || this.tintAtDistance.isConnected || this.tintTexture.isConnected);
-        defines.setValue("CLEARCOAT_TINT_TEXTURE", this.tintTexture.isConnected);
-        defines.setValue("CLEARCOAT_BUMP", this.bumpTexture.isConnected);
-        defines.setValue("CLEARCOAT_DEFAULTIOR", this.ior.isConnected ? this.ior.connectInputBlock!.value === 1.5 : false);
-
+        defines.setValue("CLEARCOAT_TEXTURE", this.texture.isConnected, true);
+        defines.setValue("CLEARCOAT_TINT", this.tintColor.isConnected || this.tintThickness.isConnected || this.tintAtDistance.isConnected || this.tintTexture.isConnected, true);
+        defines.setValue("CLEARCOAT_TINT_TEXTURE", this.tintTexture.isConnected, true);
+        defines.setValue("CLEARCOAT_BUMP", this.bumpTexture.isConnected, true);
+        defines.setValue("CLEARCOAT_DEFAULTIOR", this.ior.isConnected ? this.ior.connectInputBlock!.value === 1.5 : false, true);
     }
 
     public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh) {
@@ -189,15 +196,44 @@ export class ClearCoatBlock extends NodeMaterialBlock {
         }
     }
 
+    private _generateTBNSpace(state: NodeMaterialBuildState, worldPositionVarName: string, worldNormalVarName: string) {
+        let code = "";
+
+        let comments = `//${this.name}`;
+        let worldTangent = this.worldTangent;
+
+        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(${worldNormalVarName}.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`;
+        }
+
+        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 ccBlock instance of a ClearCoatBlock or null if the code must be generated without an active clear coat module
      * @param reflectionBlock instance of a ReflectionBlock null if the code must be generated without an active reflection module
      * @param worldPosVarName name of the variable holding the world position
+     * @param generateTBNSpace if true, the code needed to create the TBN coordinate space is generated
+     * @param vTBNAvailable indicate that the vTBN variable is already existing because it has already been generated by another block (PerturbNormal or Anisotropy)
+     * @param worldNormalVarName name of the variable holding the world normal
      * @returns the shader code
      */
-    public static GetCode(state: NodeMaterialBuildState, ccBlock: Nullable<ClearCoatBlock>, reflectionBlock: Nullable<ReflectionBlock>, worldPosVarName: string): string {
+    public static GetCode(state: NodeMaterialBuildState, ccBlock: Nullable<ClearCoatBlock>, reflectionBlock: Nullable<ReflectionBlock>, worldPosVarName: string, generateTBNSpace: boolean, vTBNAvailable: boolean, worldNormalVarName: string): string {
         let code = "";
 
         const intensity = ccBlock?.intensity.isConnected ? ccBlock.intensity.associatedVariableName : "1.";
@@ -216,7 +252,12 @@ export class ClearCoatBlock extends NodeMaterialBlock {
             state._emitUniformFromString("vClearCoatTangentSpaceParams", "vec2");
         }
 
-        code = `clearcoatOutParams clearcoatOut;
+        if (generateTBNSpace && ccBlock) {
+            code += ccBlock._generateTBNSpace(state, worldPosVarName, worldNormalVarName);
+            vTBNAvailable = ccBlock.worldTangent.isConnected;
+        }
+
+        code += `clearcoatOutParams clearcoatOut;
 
         #ifdef CLEARCOAT
             vec2 vClearCoatParams = vec2(${intensity}, ${roughness});
@@ -243,7 +284,7 @@ export class ClearCoatBlock extends NodeMaterialBlock {
                 vec2(0., 1.),
                 ${bumpTexture},
                 ${uv},
-                #if defined(TANGENT) && defined(NORMAL)
+                #if defined(${vTBNAvailable ? "TANGENT" : "IGNORE"}) && defined(NORMAL)
                     vTBN,
                 #else
                     vClearCoatTangentSpaceParams,

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

@@ -5,3 +5,5 @@ export * from "./reflectivityBlock";
 export * from "./anisotropyBlock";
 export * from "./reflectionBlock";
 export * from "./clearCoatBlock";
+export * from "./refractionBlock";
+export * from "./subSurfaceBlock";

+ 75 - 29
src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts

@@ -26,6 +26,9 @@ import { MaterialFlags } from '../../../materialFlags';
 import { AnisotropyBlock } from './anisotropyBlock';
 import { ReflectionBlock } from './reflectionBlock';
 import { ClearCoatBlock } from './clearCoatBlock';
+import { SubSurfaceBlock } from './subSurfaceBlock';
+import { RefractionBlock } from './refractionBlock';
+import { PerturbNormalBlock } from '../Fragment/perturbNormalBlock';
 
 const mapOutputToVariable: { [name: string] : [string, string] } = {
     "ambient":      ["finalAmbient", ""],
@@ -84,9 +87,10 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
             new NodeMaterialConnectionPointCustomObject("reflection", this, NodeMaterialConnectionPointDirection.Input, ReflectionBlock, "ReflectionBlock"));
         this.registerInput("sheen", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("sheen", this, NodeMaterialConnectionPointDirection.Input, SheenBlock, "SheenBlock"));
-        this.registerInput("clearCoat", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
+        this.registerInput("clearcoat", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("clearcoat", this, NodeMaterialConnectionPointDirection.Input, ClearCoatBlock, "ClearCoatBlock"));
-        this.registerInput("subSurface", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("subsurface", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("subsurface", this, NodeMaterialConnectionPointDirection.Input, SubSurfaceBlock, "SubSurfaceBlock"));
         this.registerInput("anisotropy", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("anisotropy", this, NodeMaterialConnectionPointDirection.Input, AnisotropyBlock, "AnisotropyBlock"));
 
@@ -106,6 +110,27 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     }
 
     /**
+     * Intensity of the direct lights e.g. the four lights available in your scene.
+     * This impacts both the direct diffuse and specular highlights.
+     */
+    @editableInPropertyPage("Direct lights", PropertyTypeForEdition.Float, "INTENSITY", { min: 0, max: 1, "notifiers": { "update": true }})
+    public directIntensity: number = 1.0;
+
+    /**
+     * Intensity of the environment e.g. how much the environment will light the object
+     * either through harmonics for rough material or through the refelction for shiny ones.
+     */
+    @editableInPropertyPage("Environment lights", PropertyTypeForEdition.Float, "INTENSITY", { min: 0, max: 1, "notifiers": { "update": true }})
+    public environmentIntensity: number = 1.0;
+
+    /**
+     * This is a special control allowing the reduction of the specular highlights coming from the
+     * four lights of the scene. Those highlights may not be needed in full environment lighting.
+     */
+    @editableInPropertyPage("Specular highlights", PropertyTypeForEdition.Float, "INTENSITY", { min: 0, max: 1, "notifiers": { "update": true }})
+    public specularIntensity: number = 1.0;
+
+    /**
      * Defines the  falloff type used in this material.
      * It by default is Physical.
      */
@@ -431,7 +456,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     /**
      * Gets the sub surface object parameters
      */
-    public get subSurface(): NodeMaterialConnectionPoint {
+    public get subsurface(): NodeMaterialConnectionPoint {
         return this._inputs[13];
     }
 
@@ -549,14 +574,14 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         // General
         defines.setValue("PBR", true);
         defines.setValue("METALLICWORKFLOW", true);
-        defines.setValue("DEBUGMODE", this.debugMode);
+        defines.setValue("DEBUGMODE", this.debugMode, true);
         defines.setValue("NORMALXYSCALE", true);
-        defines.setValue("BUMP", this.perturbedNormal.isConnected);
+        defines.setValue("BUMP", this.perturbedNormal.isConnected, true);
         defines.setValue("LODBASEDMICROSFURACE", this._scene.getEngine().getCaps().textureLOD);
 
         // Albedo & Opacity
-        defines.setValue("ALBEDO", this.baseTexture.isConnected);
-        defines.setValue("OPACITY", this.opacityTexture.isConnected);
+        defines.setValue("ALBEDO", this.baseTexture.isConnected, true);
+        defines.setValue("OPACITY", this.opacityTexture.isConnected, true);
 
         // Lighting & colors
         if (this.lightFalloff === PBRBaseMaterial.LIGHTFALLOFF_STANDARD) {
@@ -571,28 +596,28 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         }
 
         // Transparency
-        defines.setValue("ALPHABLEND", this.useAlphaBlending);
-        defines.setValue("ALPHAFROMALBEDO", this.useAlphaFromAlbedoTexture);
-        defines.setValue("ALPHATEST", this.useAlphaTest);
-        defines.setValue("ALPHATESTVALUE", this.alphaTestCutoff);
-        defines.setValue("OPACITYRGB", this.opacityRGB);
+        defines.setValue("ALPHABLEND", this.useAlphaBlending, true);
+        defines.setValue("ALPHAFROMALBEDO", this.useAlphaFromAlbedoTexture, true);
+        defines.setValue("ALPHATEST", this.useAlphaTest, true);
+        defines.setValue("ALPHATESTVALUE", this.alphaTestCutoff, true);
+        defines.setValue("OPACITYRGB", this.opacityRGB, true);
 
         // Rendering
-        defines.setValue("RADIANCEOVERALPHA", this.useRadianceOverAlpha);
-        defines.setValue("SPECULAROVERALPHA", this.useSpecularOverAlpha);
-        defines.setValue("SPECULARAA", this._scene.getEngine().getCaps().standardDerivatives && this.enableSpecularAntiAliasing);
+        defines.setValue("RADIANCEOVERALPHA", this.useRadianceOverAlpha, true);
+        defines.setValue("SPECULAROVERALPHA", this.useSpecularOverAlpha, true);
+        defines.setValue("SPECULARAA", this._scene.getEngine().getCaps().standardDerivatives && this.enableSpecularAntiAliasing, true);
 
         // Advanced
         defines.setValue("BRDF_V_HEIGHT_CORRELATED", true);
-        defines.setValue("MS_BRDF_ENERGY_CONSERVATION", this.useEnergyConservation);
-        defines.setValue("RADIANCEOCCLUSION", this.useRadianceOcclusion);
-        defines.setValue("HORIZONOCCLUSION", this.useHorizonOcclusion);
-        defines.setValue("UNLIT", this.unlit);
-        defines.setValue("FORCENORMALFORWARD", this.forceNormalForward);
+        defines.setValue("MS_BRDF_ENERGY_CONSERVATION", this.useEnergyConservation, true);
+        defines.setValue("RADIANCEOCCLUSION", this.useRadianceOcclusion, true);
+        defines.setValue("HORIZONOCCLUSION", this.useHorizonOcclusion, true);
+        defines.setValue("UNLIT", this.unlit, true);
+        defines.setValue("FORCENORMALFORWARD", this.forceNormalForward, true);
 
         if (this._environmentBRDFTexture && MaterialFlags.ReflectionTextureEnabled) {
             defines.setValue("ENVIRONMENTBRDF", true);
-            defines.setValue("ENVIRONMENTBRDF_RGBD", this._environmentBRDFTexture.isRGBD);
+            defines.setValue("ENVIRONMENTBRDF_RGBD", this._environmentBRDFTexture.isRGBD, true);
         } else {
             defines.setValue("ENVIRONMENTBRDF" , false);
             defines.setValue("ENVIRONMENTBRDF_RGBD", false);
@@ -664,6 +689,8 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         const invertNormal = (scene.useRightHandedSystem === (scene._mirroredCameraPosition != null));
 
         effect.setFloat(this._invertNormalName, invertNormal ? -1 : 1);
+
+        effect.setFloat4("vLightingIntensity", this.directIntensity, 1, this.environmentIntensity * this._scene.environmentIntensity, this.specularIntensity);
     }
 
     private _injectVertexCode(state: NodeMaterialBuildState) {
@@ -836,13 +863,12 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         state._emitFunctionFromInclude("pbrBlockAmbientOcclusion", comments);
         state._emitFunctionFromInclude("pbrBlockAlphaFresnel", comments);
         state._emitFunctionFromInclude("pbrBlockAnisotropic", comments);
-        state._emitFunctionFromInclude("pbrBlockSubSurface", comments);
 
         //
         // code
         //
 
-        state.compilationString += `vec4 vLightingIntensity = vec4(1.);\r\n`;
+        state._emitUniformFromString("vLightingIntensity", "vec4");
 
         // _____________________________ Geometry Information ____________________________
         this._vNormalWName = state._getFreeVariableName("vNormalW");
@@ -945,8 +971,16 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
 
         // _____________________________ Clear Coat ____________________________
         const clearcoatBlock = this.clearcoat.isConnected ? this.clearcoat.connectedPoint?.ownerBlock as ClearCoatBlock : null;
+        const generateTBNSpace = !this.perturbedNormal.isConnected && !this.anisotropy.isConnected;
+        const isTangentConnectedToPerturbNormal = this.perturbedNormal.isConnected && (this.perturbedNormal.connectedPoint?.ownerBlock as PerturbNormalBlock).worldTangent.isConnected;
+        const isTangentConnectedToAnisotropy = this.anisotropy.isConnected && (this.anisotropy.connectedPoint?.ownerBlock as AnisotropyBlock).worldTangent.isConnected;
+        let vTBNAvailable = isTangentConnectedToPerturbNormal || (!this.perturbedNormal.isConnected && isTangentConnectedToAnisotropy);
 
-        state.compilationString += ClearCoatBlock.GetCode(state, clearcoatBlock, reflectionBlock, worldPosVarName);
+        state.compilationString += ClearCoatBlock.GetCode(state, clearcoatBlock, reflectionBlock, worldPosVarName, generateTBNSpace, vTBNAvailable, this.worldNormal.associatedVariableName);
+
+        if (generateTBNSpace) {
+            vTBNAvailable = clearcoatBlock?.worldTangent.isConnected ?? false;
+        }
 
         state._emitFunctionFromInclude("pbrBlockClearcoat", comments, {
             replaceStrings: [
@@ -957,6 +991,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
                 { search: /REFLECTIONMAP_SKYBOX/g, replace: reflectionBlock?._defineSkyboxName ?? "REFLECTIONMAP_SKYBOX" },
                 { search: /LODINREFLECTIONALPHA/g, replace: reflectionBlock?._defineLODReflectionAlpha ?? "LODINREFLECTIONALPHA" },
                 { search: /LINEARSPECULARREFLECTION/g, replace: reflectionBlock?._defineLinearSpecularReflection ?? "LINEARSPECULARREFLECTION" },
+                { search: /defined\(TANGENT\)/g, replace: vTBNAvailable ? "defined(TANGENT)" : "defined(IGNORE)" },
             ]
         });
 
@@ -969,11 +1004,22 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         });
 
         // ___________________________________ SubSurface ______________________________________
-        state.compilationString += `subSurfaceOutParams subSurfaceOut;
-            #ifdef SUBSURFACE
-            #else
-                subSurfaceOut.specularEnvironmentReflectance = specularEnvironmentReflectance;
-            #endif\r\n`;
+        const subsurfaceBlock = this.subsurface.isConnected ? this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock : null;
+        const refractionBlock = this.subsurface.isConnected ? (this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock).refraction.connectedPoint?.ownerBlock as RefractionBlock : null;
+
+        state.compilationString += SubSurfaceBlock.GetCode(state, subsurfaceBlock, reflectionBlock, worldPosVarName);
+
+        state._emitFunctionFromInclude("pbrBlockSubSurface", comments, {
+            replaceStrings: [
+                { search: /REFLECTIONMAP_3D/g, replace: reflectionBlock?._define3DName ?? "REFLECTIONMAP_3D" },
+                { search: /REFLECTIONMAP_OPPOSITEZ/g, replace: reflectionBlock?._defineOppositeZ ?? "REFLECTIONMAP_OPPOSITEZ" },
+                { search: /REFLECTIONMAP_PROJECTION/g, replace: reflectionBlock?._defineProjectionName ?? "REFLECTIONMAP_PROJECTION" },
+                { search: /SS_REFRACTIONMAP_3D/g, replace: refractionBlock?._define3DName ?? "SS_REFRACTIONMAP_3D" },
+                { search: /SS_LODINREFRACTIONALPHA/g, replace: refractionBlock?._defineLODRefractionAlpha ?? "SS_LODINREFRACTIONALPHA" },
+                { search: /SS_LINEARSPECULARREFRACTION/g, replace: refractionBlock?._defineLinearSpecularRefraction ?? "SS_LINEARSPECULARREFRACTION" },
+                { search: /SS_REFRACTIONMAP_OPPOSITEZ/g, replace: refractionBlock?._defineOppositeZ ?? "SS_REFRACTIONMAP_OPPOSITEZ" },
+            ]
+        });
 
         // _____________________________ Direct Lighting Info __________________________________
         state.compilationString += state._emitCodeFromInclude("pbrBlockDirectLighting", comments);

+ 5 - 5
src/Materials/Node/Blocks/PBR/reflectionBlock.ts

@@ -168,17 +168,17 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
         const reflectionTexture = this._getTexture();
         const reflection = reflectionTexture && reflectionTexture.getTextureMatrix;
 
-        defines.setValue("REFLECTION", reflection);
+        defines.setValue("REFLECTION", reflection, true);
 
         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(this._defineLODReflectionAlpha, reflectionTexture!.lodLevelInAlpha, true);
+        defines.setValue(this._defineLinearSpecularReflection, reflectionTexture!.linearSpecularLOD, true);
+        defines.setValue(this._defineOppositeZ, this._scene.useRightHandedSystem ? !reflectionTexture!.invertZ : reflectionTexture!.invertZ, true);
 
-        defines.setValue("SPHERICAL_HARMONICS", this.useSphericalHarmonics);
+        defines.setValue("SPHERICAL_HARMONICS", this.useSphericalHarmonics, true);
 
         if (reflectionTexture && reflectionTexture.coordinatesMode !== Texture.SKYBOX_MODE) {
             if (reflectionTexture.isCube) {

+ 6 - 6
src/Materials/Node/Blocks/PBR/reflectivityBlock.ts

@@ -155,12 +155,12 @@ export class ReflectivityBlock extends NodeMaterialBlock {
     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);
+        defines.setValue("REFLECTIVITY", this.texture.isConnected, true);
+        defines.setValue("AOSTOREINMETALMAPRED", this.useAmbientOcclusionFromMetallicTextureRed, true);
+        defines.setValue("METALLNESSSTOREINMETALMAPBLUE", this.useMetallnessFromMetallicTextureBlue, true);
+        defines.setValue("ROUGHNESSSTOREINMETALMAPALPHA", this.useRoughnessFromMetallicTextureAlpha, true);
+        defines.setValue("ROUGHNESSSTOREINMETALMAPGREEN",  !this.useRoughnessFromMetallicTextureAlpha && this.useRoughnessFromMetallicTextureGreen, true);
+        defines.setValue("METALLICF0FACTORFROMMETALLICMAP", this.useMetallicF0FactorFromMetallicTexture, true);
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {

+ 338 - 0
src/Materials/Node/Blocks/PBR/refractionBlock.ts

@@ -0,0 +1,338 @@
+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 { InputBlock } from '../Input/inputBlock';
+import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { Nullable } from '../../../../types';
+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';
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { CubeTexture } from '../../../Textures/cubeTexture';
+import { Texture } from '../../../Textures/texture';
+import { NodeMaterialSystemValues } from '../../Enums/nodeMaterialSystemValues';
+
+/**
+ * Block used to implement the refraction part of the sub surface module of the PBR material
+ */
+export class RefractionBlock extends NodeMaterialBlock {
+
+    /** @hidden */
+    public _define3DName: string;
+    /** @hidden */
+    public _refractionMatrixName: string;
+    /** @hidden */
+    public _defineLODRefractionAlpha: string;
+    /** @hidden */
+    public _defineLinearSpecularRefraction: string;
+    /** @hidden */
+    public _defineOppositeZ: string;
+    /** @hidden */
+    public _cubeSamplerName: string;
+    /** @hidden */
+    public _2DSamplerName: string;
+    /** @hidden */
+    public _vRefractionMicrosurfaceInfosName: string;
+    /** @hidden */
+    public _vRefractionInfosName: string;
+
+    private _scene: Scene;
+
+    /**
+     * This parameters will make the material used its opacity to control how much it is refracting aginst not.
+     * Materials half opaque for instance using refraction could benefit from this control.
+     */
+    @editableInPropertyPage("Link refraction to transparency", PropertyTypeForEdition.Boolean, "ADVANCED", { "notifiers": { "update": true }})
+    public linkRefractionWithTransparency: boolean = false;
+
+    /**
+     * Controls if refraction needs to be inverted on Y. This could be useful for procedural texture.
+     */
+    @editableInPropertyPage("Invert refraction Y", PropertyTypeForEdition.Boolean, "ADVANCED", { "notifiers": { "update": true }})
+    public invertRefractionY: boolean = false;
+
+    /**
+     * Gets or sets the texture associated with the node
+     */
+    public texture: Nullable<BaseTexture>;
+
+    /**
+     * Create a new RefractionBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("indexOfRefraction", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("tintAtDistance", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("refraction", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("refraction", this, NodeMaterialConnectionPointDirection.Output, RefractionBlock, "RefractionBlock"));
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "RefractionBlock";
+    }
+
+    /**
+     * Gets the intensity input component
+     */
+    public get intensity(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the index of refraction input component
+     */
+    public get indexOfRefraction(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the tint at distance input component
+     */
+    public get tintAtDistance(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the view input component
+     */
+    public get view(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the refraction object output component
+     */
+    public get refraction(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Returns true if the block has a texture
+     */
+    public get hasTexture(): boolean {
+        return !!this._getTexture();
+    }
+
+    protected _getTexture(): Nullable<BaseTexture> {
+        if (this.texture) {
+            return this.texture;
+        }
+
+        return this._scene.environmentTexture;
+    }
+
+    public autoConfigure(material: NodeMaterial) {
+        if (!this.intensity.isConnected) {
+            let intensityInput = new InputBlock("Refraction intensity", NodeMaterialBlockTargets.Fragment, NodeMaterialBlockConnectionPointTypes.Float);
+            intensityInput.value = 1;
+            intensityInput.output.connectTo(this.intensity);
+        }
+
+        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) {
+        super.prepareDefines(mesh, nodeMaterial, defines);
+
+        const refractionTexture = this._getTexture();
+        const refraction = refractionTexture && refractionTexture.getTextureMatrix;
+
+        defines.setValue("SS_REFRACTION", refraction, true);
+
+        if (!refraction) {
+            return;
+        }
+
+        defines.setValue(this._define3DName, refractionTexture!.isCube, true);
+        defines.setValue(this._defineLODRefractionAlpha, refractionTexture!.lodLevelInAlpha, true);
+        defines.setValue(this._defineLinearSpecularRefraction, refractionTexture!.linearSpecularLOD, true);
+        defines.setValue(this._defineOppositeZ, this._scene.useRightHandedSystem ? !refractionTexture!.invertZ : refractionTexture!.invertZ, true);
+
+        defines.setValue("SS_LINKREFRACTIONTOTRANSPARENCY", this.linkRefractionWithTransparency, true);
+    }
+
+    public isReady() {
+        const texture = this._getTexture();
+
+        if (texture && !texture.isReadyOrNotBlocking()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh) {
+        super.bind(effect, nodeMaterial, mesh);
+
+        const refractionTexture = this._getTexture();
+
+        if (!refractionTexture) {
+            return;
+        }
+
+        if (refractionTexture.isCube) {
+            effect.setTexture(this._cubeSamplerName, refractionTexture);
+        } else {
+            effect.setTexture(this._2DSamplerName, refractionTexture);
+        }
+
+        effect.setMatrix(this._refractionMatrixName, refractionTexture.getReflectionTextureMatrix());
+
+        let depth = 1.0;
+        if (!refractionTexture.isCube) {
+            if ((<any>refractionTexture).depth) {
+                depth = (<any>refractionTexture).depth;
+            }
+        }
+
+        const indexOfRefraction = this.indexOfRefraction.connectInputBlock?.value ?? 1.0;
+
+        effect.setFloat4(this._vRefractionInfosName, refractionTexture.level, 1 / indexOfRefraction, depth, this.invertRefractionY ? -1 : 1);
+
+        effect.setFloat3(this._vRefractionMicrosurfaceInfosName, refractionTexture.getSize().width, refractionTexture.lodGenerationScale, refractionTexture.lodGenerationOffset);
+    }
+
+    /**
+     * Gets the main code of the block (fragment side)
+     * @param state current state of the node material building
+     * @returns the shader code
+     */
+    public getCode(state: NodeMaterialBuildState): string {
+        let code = "";
+
+        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);
+
+        this._define3DName = state._getFreeDefineName("SS_REFRACTIONMAP_3D");
+
+        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);
+
+        this._defineLODRefractionAlpha = state._getFreeDefineName("SS_LODINREFRACTIONALPHA");
+        this._defineLinearSpecularRefraction = state._getFreeDefineName("SS_LINEARSPECULARREFRACTION");
+        this._defineOppositeZ = state._getFreeDefineName("SS_REFRACTIONMAP_OPPOSITEZ");
+
+        this._refractionMatrixName = state._getFreeVariableName("refractionMatrix");
+
+        state._emitUniformFromString(this._refractionMatrixName, "mat4");
+
+        state._emitFunction("sampleRefraction", `
+            #ifdef ${this._define3DName}
+                #define sampleRefraction(s, c) textureCube(s, c)
+            #else
+                #define sampleRefraction(s, c) texture2D(s, c)
+            #endif\r\n`, `//${this.name}`);
+
+        state._emitFunction("sampleRefractionLod", `
+            #ifdef ${this._define3DName}
+                #define sampleRefractionLod(s, c, l) textureCubeLodEXT(s, c, l)
+            #else
+                #define sampleRefractionLod(s, c, l) texture2DLodEXT(s, c, l)
+            #endif\r\n`, `//${this.name}`);
+
+        this._vRefractionMicrosurfaceInfosName = state._getFreeVariableName("vRefractionMicrosurfaceInfos");
+
+        state._emitUniformFromString(this._vRefractionMicrosurfaceInfosName, "vec3");
+
+        this._vRefractionInfosName = state._getFreeVariableName("vRefractionInfos");
+
+        state._emitUniformFromString(this._vRefractionInfosName, "vec4");
+
+        return code;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        this._scene = state.sharedData.scene;
+
+        return this;
+    }
+
+    protected _dumpPropertiesCode() {
+        let codeString: string = super._dumpPropertiesCode();
+
+        if (this.texture) {
+            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`;
+        }
+
+        codeString += `${this._codeVariableName}.linkRefractionWithTransparency = ${this.linkRefractionWithTransparency};\r\n`;
+        codeString += `${this._codeVariableName}.invertRefractionY = ${this.invertRefractionY};\r\n`;
+
+        return codeString;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        if (this.texture) {
+            serializationObject.texture = this.texture.serialize();
+        }
+
+        serializationObject.linkRefractionWithTransparency = this.linkRefractionWithTransparency;
+        serializationObject.invertRefractionY = this.invertRefractionY;
+
+        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);
+            }
+        }
+
+        this.linkRefractionWithTransparency = serializationObject.linkRefractionWithTransparency;
+        this.invertRefractionY = serializationObject.invertRefractionY;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.RefractionBlock"] = RefractionBlock;

+ 4 - 4
src/Materials/Node/Blocks/PBR/sheenBlock.ts

@@ -107,10 +107,10 @@ export class SheenBlock extends NodeMaterialBlock {
         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);
+        defines.setValue("SHEEN_LINKWITHALBEDO", this.linkSheenWithAlbedo, true);
+        defines.setValue("SHEEN_ROUGHNESS", this.roughness.isConnected, true);
+        defines.setValue("SHEEN_ALBEDOSCALING", this.albedoScaling, true);
+        defines.setValue("SHEEN_TEXTURE", this.texture.isConnected, true);
     }
 
     /**

+ 267 - 0
src/Materials/Node/Blocks/PBR/subSurfaceBlock.ts

@@ -0,0 +1,267 @@
+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 { InputBlock } from '../Input/inputBlock';
+import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { ReflectionBlock } from './reflectionBlock';
+import { Nullable } from '../../../../types';
+import { RefractionBlock } from './refractionBlock';
+
+/**
+ * Block used to implement the sub surface module of the PBR material
+ */
+export class SubSurfaceBlock extends NodeMaterialBlock {
+
+    /**
+     * Create a new SubSurfaceBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("minThickness", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("maxThickness", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("thicknessTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("tintColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("translucencyIntensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("translucencyDiffusionDistance", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("refraction", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("refraction", this, NodeMaterialConnectionPointDirection.Input, RefractionBlock, "RefractionBlock"));
+
+        this.registerOutput("subsurface", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("subsurface", this, NodeMaterialConnectionPointDirection.Output, SubSurfaceBlock, "SubSurfaceBlock"));
+    }
+
+    /**
+     * Stores the intensity of the different subsurface effects in the thickness texture.
+     * * the green channel is the translucency intensity.
+     * * the blue channel is the scattering intensity.
+     * * the alpha channel is the refraction intensity.
+     */
+    @editableInPropertyPage("Mask from thickness texture", PropertyTypeForEdition.Boolean, "PROPERTIES", { "notifiers": { "update": true }})
+    public useMaskFromThicknessTexture: 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("subSurfaceOut");
+        state._excludeVariableName("vThicknessParam");
+        state._excludeVariableName("vTintColor");
+        state._excludeVariableName("vSubSurfaceIntensity");
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "SubSurfaceBlock";
+    }
+
+    /**
+     * Gets the min thickness input component
+     */
+    public get minThickness(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the max thickness input component
+     */
+    public get maxThickness(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the thickness texture component
+     */
+    public get thicknessTexture(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the tint color input component
+     */
+    public get tintColor(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the translucency intensity input component
+     */
+    public get translucencyIntensity(): NodeMaterialConnectionPoint {
+        return this._inputs[4];
+    }
+
+    /**
+     * Gets the translucency diffusion distance input component
+     */
+    public get translucencyDiffusionDistance(): NodeMaterialConnectionPoint {
+        return this._inputs[5];
+    }
+
+    /**
+     * Gets the refraction object parameters
+     */
+    public get refraction(): NodeMaterialConnectionPoint {
+        return this._inputs[6];
+    }
+
+    /**
+     * Gets the sub surface object output component
+     */
+    public get subsurface(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    public autoConfigure(material: NodeMaterial) {
+        if (!this.minThickness.isConnected) {
+            let minThicknessInput = new InputBlock("SubSurface min thickness", NodeMaterialBlockTargets.Fragment, NodeMaterialBlockConnectionPointTypes.Float);
+            minThicknessInput.value = 0;
+            minThicknessInput.output.connectTo(this.minThickness);
+        }
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        super.prepareDefines(mesh, nodeMaterial, defines);
+
+        const translucencyEnabled = this.translucencyDiffusionDistance.isConnected || this.translucencyIntensity.isConnected;
+
+        defines.setValue("SUBSURFACE", translucencyEnabled || this.refraction.isConnected, true);
+        defines.setValue("SS_TRANSLUCENCY", translucencyEnabled, true);
+        defines.setValue("SS_THICKNESSANDMASK_TEXTURE", this.thicknessTexture.isConnected, true);
+        defines.setValue("SS_MASK_FROM_THICKNESS_TEXTURE", this.useMaskFromThicknessTexture, true);
+    }
+
+    /**
+     * Gets the main code of the block (fragment side)
+     * @param state current state of the node material building
+     * @param ssBlock instance of a SubSurfaceBlock or null if the code must be generated without an active sub surface module
+     * @param reflectionBlock instance of a ReflectionBlock null if the code must be generated without an active reflection module
+     * @param worldPosVarName name of the variable holding the world position
+     * @returns the shader code
+     */
+    public static GetCode(state: NodeMaterialBuildState, ssBlock: Nullable<SubSurfaceBlock>, reflectionBlock: Nullable<ReflectionBlock>, worldPosVarName: string): string {
+        let code = "";
+
+        const minThickness = ssBlock?.minThickness.isConnected ? ssBlock.minThickness.associatedVariableName : "0.";
+        const maxThickness = ssBlock?.maxThickness.isConnected ? ssBlock.maxThickness.associatedVariableName : "1.";
+        const thicknessTexture = ssBlock?.thicknessTexture.isConnected ? ssBlock.thicknessTexture.associatedVariableName : "vec4(0.)";
+        const tintColor = ssBlock?.tintColor.isConnected ? ssBlock.tintColor.associatedVariableName : "vec3(1.)";
+        const translucencyIntensity = ssBlock?.translucencyIntensity.isConnected ? ssBlock?.translucencyIntensity.associatedVariableName : "1.";
+        const translucencyDiffusionDistance = ssBlock?.translucencyDiffusionDistance.isConnected ? ssBlock?.translucencyDiffusionDistance.associatedVariableName : "vec3(1.)";
+
+        const refractionBlock: Nullable<RefractionBlock> = (ssBlock?.refraction.isConnected ? ssBlock?.refraction.connectedPoint?.ownerBlock : null) as Nullable<RefractionBlock>;
+
+        const refractionTintAtDistance = refractionBlock?.tintAtDistance.isConnected ? refractionBlock.tintAtDistance.associatedVariableName : "1.";
+        const refractionIntensity = refractionBlock?.intensity.isConnected ? refractionBlock.intensity.associatedVariableName : "1.";
+        const refractionView = refractionBlock?.view.isConnected ? refractionBlock.view.associatedVariableName : "";
+
+        code += refractionBlock?.getCode(state) ?? "";
+
+        code += `subSurfaceOutParams subSurfaceOut;
+
+        #ifdef SUBSURFACE
+            vec2 vThicknessParam = vec2(${minThickness}, ${maxThickness} - ${minThickness});
+            vec4 vTintColor = vec4(${tintColor}, ${refractionTintAtDistance});
+            vec3 vSubSurfaceIntensity = vec3(${refractionIntensity}, ${translucencyIntensity}, 0.);
+
+            subSurfaceBlock(
+                vSubSurfaceIntensity,
+                vThicknessParam,
+                vTintColor,
+                normalW,
+                specularEnvironmentReflectance,
+            #ifdef SS_THICKNESSANDMASK_TEXTURE
+                ${thicknessTexture},
+            #endif
+            #ifdef REFLECTION
+                #ifdef SS_TRANSLUCENCY
+                    ${reflectionBlock?._reflectionMatrixName},
+                    #ifdef USESPHERICALFROMREFLECTIONMAP
+                        #if !defined(NORMAL) || !defined(USESPHERICALINVERTEX)
+                            reflectionOut.irradianceVector,
+                        #endif
+                    #endif
+                    #ifdef USEIRRADIANCEMAP
+                        irradianceSampler,
+                    #endif
+                #endif
+            #endif
+            #ifdef SS_REFRACTION
+                ${worldPosVarName}.xyz,
+                viewDirectionW,
+                ${refractionView},
+                surfaceAlbedo,
+                ${refractionBlock?._vRefractionInfosName ?? ""},
+                ${refractionBlock?._refractionMatrixName ?? ""},
+                ${refractionBlock?._vRefractionMicrosurfaceInfosName ?? ""},
+                vLightingIntensity,
+                #ifdef SS_LINKREFRACTIONTOTRANSPARENCY
+                    alpha,
+                #endif
+                #ifdef ${refractionBlock?._defineLODRefractionAlpha ?? "IGNORE"}
+                    NdotVUnclamped,
+                #endif
+                #ifdef ${refractionBlock?._defineLinearSpecularRefraction ?? "IGNORE"}
+                    roughness,
+                #else
+                    alphaG,
+                #endif
+                #ifdef ${refractionBlock?._define3DName ?? "IGNORE"}
+                    ${refractionBlock?._cubeSamplerName ?? ""},
+                #else
+                    ${refractionBlock?._2DSamplerName ?? ""},
+                #endif
+                #ifndef LODBASEDMICROSFURACE
+                    #ifdef ${refractionBlock?._define3DName ?? "IGNORE"}
+                        ${refractionBlock?._cubeSamplerName ?? ""},
+                        ${refractionBlock?._cubeSamplerName ?? ""},
+                    #else
+                        ${refractionBlock?._2DSamplerName ?? ""},
+                        ${refractionBlock?._2DSamplerName ?? ""},
+                    #endif
+                #endif
+                #ifdef ANISOTROPIC
+                    anisotropicOut,
+                #endif
+            #endif
+            #ifdef SS_TRANSLUCENCY
+                ${translucencyDiffusionDistance},
+            #endif
+                subSurfaceOut
+            );
+
+            #ifdef SS_REFRACTION
+                surfaceAlbedo = subSurfaceOut.surfaceAlbedo;
+                #ifdef SS_LINKREFRACTIONTOTRANSPARENCY
+                    alpha = subSurfaceOut.alpha;
+                #endif
+            #endif
+        #else
+            subSurfaceOut.specularEnvironmentReflectance = specularEnvironmentReflectance;
+        #endif\r\n`;
+
+        return code;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            state.sharedData.blocksWithDefines.push(this);
+        }
+
+        return this;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.SubSurfaceBlock"] = SubSurfaceBlock;

+ 7 - 2
src/Materials/Node/nodeMaterial.ts

@@ -27,6 +27,7 @@ import { _TypeStore } from '../../Misc/typeStore';
 import { SerializationHelper } from '../../Misc/decorators';
 import { TextureBlock } from './Blocks/Dual/textureBlock';
 import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
+import { RefractionBlock } from './Blocks/PBR/refractionBlock';
 import { EffectFallbacks } from '../effectFallbacks';
 import { WebRequest } from '../../Misc/webRequest';
 import { Effect } from '../effect';
@@ -87,11 +88,15 @@ export class NodeMaterialDefines extends MaterialDefines implements IImageProces
         this.rebuild();
     }
 
-    public setValue(name: string, value: any) {
+    public setValue(name: string, value: any, markAsUnprocessedIfDirty = false) {
         if (this[name] === undefined) {
             this._keys.push(name);
         }
 
+        if (markAsUnprocessedIfDirty && this[name] !== value) {
+            this.markAsUnprocessed();
+        }
+
         this[name] = value;
     }
 }
@@ -945,7 +950,7 @@ export class NodeMaterial extends PushMaterial {
      * Gets the list of texture blocks
      * @returns an array of texture blocks
      */
-    public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock)[] {
+    public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock | RefractionBlock)[] {
         if (!this._sharedData) {
             return [];
         }

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

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

+ 2 - 4
src/Shaders/ShadersInclude/pbrBlockSubSurface.fx

@@ -26,14 +26,13 @@ struct subSurfaceOutParams
 
 #ifdef SUBSURFACE
     void subSurfaceBlock(
+        const in vec3 vSubSurfaceIntensity,
         const in vec2 vThicknessParam,
         const in vec4 vTintColor,
         const in vec3 normalW,
         const in vec3 specularEnvironmentReflectance,
     #ifdef SS_THICKNESSANDMASK_TEXTURE
-        const in vec2 vThicknessUV,
-        const in vec2 uvOffset,
-        const in sampler2D thicknessSampler,
+        const in vec4 thicknessMap,
     #endif
     #ifdef REFLECTION
         #ifdef SS_TRANSLUCENCY
@@ -116,7 +115,6 @@ struct subSurfaceOutParams
     #endif
 
     #ifdef SS_THICKNESSANDMASK_TEXTURE
-        vec4 thicknessMap = texture2D(thicknessSampler, vThicknessUV + uvOffset);
         float thickness = thicknessMap.r * vThicknessParam.y + vThicknessParam.x;
 
         #if DEBUGMODE > 0

+ 6 - 3
src/Shaders/pbr.fragment.fx

@@ -385,15 +385,18 @@ void main(void) {
     subSurfaceOutParams subSurfaceOut;
 
     #ifdef SUBSURFACE
+        #ifdef SS_THICKNESSANDMASK_TEXTURE
+            vec4 thicknessMap = texture2D(thicknessSampler, vThicknessUV + uvOffset);
+        #endif
+
         subSurfaceBlock(
+            vSubSurfaceIntensity,
             vThicknessParam,
             vTintColor,
             normalW,
             specularEnvironmentReflectance,
         #ifdef SS_THICKNESSANDMASK_TEXTURE
-            vThicknessUV,
-            uvOffset,
-            thicknessSampler,
+            thicknessMap,
         #endif
         #ifdef REFLECTION
             #ifdef SS_TRANSLUCENCY