Sfoglia il codice sorgente

Merge pull request #5846 from sebavan/master

Add Anisotropy to PBR
David Catuhe 6 anni fa
parent
commit
0ed4a65d9b

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

@@ -26,6 +26,7 @@
   - InvalidateRect added to AdvancedDynamicTexture to improve perf for heavily populated GUIs, works with shadows ([TrevorDev](https://github.com/TrevorDev))
 - Migrate the code to modules and deploy es6 npm packages ([Sebavan](https://github.com/Sebavan))
 - Add clear coat support to PBR ([Sebavan](https://github.com/Sebavan))
+- Add anisotropy support to PBR ([Sebavan](https://github.com/Sebavan))
 
 ## Updates
 

+ 148 - 0
src/Materials/PBR/pbrAnisotropicConfiguration.ts

@@ -0,0 +1,148 @@
+import { SerializationHelper, serialize, expandToProperty } from "../../Misc/decorators";
+import { EffectFallbacks } from "../../Materials/effect";
+import { UniformBuffer } from "../../Materials/uniformBuffer";
+import { AbstractMesh } from "../../Meshes/abstractMesh";
+import { VertexBuffer } from "../../Meshes/buffer";
+
+/**
+ * @hidden
+ */
+export interface IMaterialAnisotropicDefines {
+    ANISOTROPIC: boolean;
+    MAINUV1: boolean;
+
+    _areMiscDirty: boolean;
+    _needUVs: boolean;
+}
+
+/**
+ * Define the code related to the anisotropic parameters of the pbr material.
+ */
+export class PBRAnisotropicConfiguration {
+
+    @serialize()
+    private _isEnabled = false;
+    /**
+     * Defines if the anisotropy is enabled in the material.
+     */
+    @expandToProperty("_markAllSubMeshesAsMiscDirty")
+    public isEnabled = false;
+
+    /**
+     * Defines the anisotropy strength (between 0 and 1) it defaults to 1.
+     */
+    @serialize()
+    public intensity: number = 1;
+
+    /**
+     * Defines if the effect is along the tangents or bitangents.
+     * By default, the effect is "strectching" the highlights along the tangents.
+     */
+    @serialize()
+    public followTangents = true;
+
+    /** @hidden */
+    private _internalMarkAllSubMeshesAsMiscDirty: () => void;
+
+    /** @hidden */
+    public _markAllSubMeshesAsMiscDirty(): void {
+        this._internalMarkAllSubMeshesAsMiscDirty();
+    }
+
+    /**
+     * Instantiate a new istance of clear coat configuration.
+     * @param markAllSubMeshesAsMiscDirty Callback to flag the material to dirty
+     */
+    constructor(markAllSubMeshesAsMiscDirty: () => void) {
+        this._internalMarkAllSubMeshesAsMiscDirty = markAllSubMeshesAsMiscDirty;
+    }
+
+    /**
+     * Checks to see if a texture is used in the material.
+     * @param defines the list of "defines" to update.
+     * @param mesh the mesh we are preparing the defines for.
+     */
+    public prepareDefines(defines: IMaterialAnisotropicDefines, mesh: AbstractMesh): void {
+        if (defines._areMiscDirty) {
+            defines.ANISOTROPIC = this._isEnabled;
+            if (this._isEnabled && !mesh.isVerticesDataPresent(VertexBuffer.TangentKind)) {
+                defines._needUVs = true;
+                defines.MAINUV1 = true;
+            }
+        }
+    }
+
+    /**
+     * Binds the material data.
+     * @param uniformBuffer defines the Uniform buffer to fill in.
+     * @param isFrozen defines wether the material is frozen or not.
+     */
+    public bindForSubMesh(uniformBuffer: UniformBuffer, isFrozen: boolean): void {
+        if (!uniformBuffer.useUbo || !isFrozen || !uniformBuffer.isSync) {
+            // Clear Coat
+            uniformBuffer.updateFloat("anisotropy", this.followTangents ? this.intensity : -this.intensity);
+        }
+    }
+
+    /**
+    * Get the current class name of the texture useful for serialization or dynamic coding.
+    * @returns "PBRAnisotropicConfiguration"
+    */
+    public getClassName(): string {
+        return "PBRAnisotropicConfiguration";
+    }
+
+    /**
+     * Makes a duplicate of the current configuration into another one.
+     * @param anisotropicConfiguration define the config where to copy the info
+     */
+    public copyTo(anisotropicConfiguration: PBRAnisotropicConfiguration): void {
+        SerializationHelper.Clone(() => anisotropicConfiguration, this);
+    }
+
+    /**
+     * Serializes this clear coat configuration.
+     * @returns - An object with the serialized config.
+     */
+    public serialize(): any {
+        return SerializationHelper.Serialize(this);
+    }
+
+    /**
+     * Parses a Clear Coat Configuration from a serialized object.
+     * @param source - Serialized object.
+     */
+    public parse(source: any): void {
+        SerializationHelper.Parse(() => this, source, null);
+    }
+
+    /**
+     * Add fallbacks to the effect fallbacks list.
+     * @param defines defines the Base texture to use.
+     * @param fallbacks defines the current fallback list.
+     * @param currentRank defines the current fallback rank.
+     * @returns the new fallback rank.
+     */
+    public static AddFallbacks(defines: IMaterialAnisotropicDefines, fallbacks: EffectFallbacks, currentRank: number): number {
+        if (defines.ANISOTROPIC) {
+            fallbacks.addFallback(currentRank++, "ANISOTROPIC");
+        }
+        return currentRank;
+    }
+
+    /**
+     * Add the required uniforms to the current list.
+     * @param uniforms defines the current uniform list.
+     */
+    public static AddUniforms(uniforms: string[]): void {
+        uniforms.push("anisotropy");
+    }
+
+    /**
+     * Add the required uniforms to the current buffer.
+     * @param uniformBuffer defines the current uniform buffer.
+     */
+    public static PrepareUniformBuffer(uniformBuffer: UniformBuffer): void {
+        uniformBuffer.addUniform("anisotropy", 1);
+    }
+}

+ 19 - 3
src/Materials/PBR/pbrBaseMaterial.ts

@@ -15,6 +15,7 @@ import { Mesh } from "../../Meshes/mesh";
 import { _TimeToken } from "../../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../../States/index";
 import { IMaterialClearCoatDefines, PBRClearCoatConfiguration } from "./pbrClearCoatConfiguration";
+import { IMaterialAnisotropicDefines, PBRAnisotropicConfiguration } from "./pbrAnisotropicConfiguration";
 
 import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from "../../Materials/imageProcessingConfiguration";
 import { Effect, EffectFallbacks, EffectCreationOptions } from "../../Materials/effect";
@@ -38,7 +39,8 @@ import "../../Shaders/pbr.vertex";
  * Manages the defines for the PBR Material.
  * @hiddenChildren
  */
-class PBRMaterialDefines extends MaterialDefines implements IImageProcessingConfigurationDefines, IMaterialClearCoatDefines {
+class PBRMaterialDefines extends MaterialDefines
+    implements IImageProcessingConfigurationDefines, IMaterialClearCoatDefines, IMaterialAnisotropicDefines {
     public PBR = true;
 
     public MAINUV1 = false;
@@ -184,6 +186,8 @@ class PBRMaterialDefines extends MaterialDefines implements IImageProcessingConf
     public CLEARCOAT_BUMP = false;
     public CLEARCOAT_BUMPDIRECTUV = 0;
 
+    public ANISOTROPIC = false;
+
     public UNLIT = false;
 
     /**
@@ -655,6 +659,11 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     public readonly clearCoat = new PBRClearCoatConfiguration(this._markAllSubMeshesAsTexturesDirty.bind(this));
 
     /**
+     * Defines the anisotropic parameters for the material.
+     */
+    public readonly anisotropy = new PBRAnisotropicConfiguration(this._markAllSubMeshesAsMiscDirty.bind(this));
+
+    /**
      * Instantiates a new PBRMaterial instance.
      *
      * @param name The material name
@@ -1010,6 +1019,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             fallbacks.addFallback(fallbackRank++, "PARALLAXOCCLUSION");
         }
 
+        fallbackRank = PBRAnisotropicConfiguration.AddFallbacks(defines, fallbacks, fallbackRank);
         fallbackRank = PBRClearCoatConfiguration.AddFallbacks(defines, fallbacks, fallbackRank);
 
         if (defines.ENVIRONMENTBRDF) {
@@ -1115,6 +1125,8 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         PBRClearCoatConfiguration.AddUniforms(uniforms);
         PBRClearCoatConfiguration.AddSamplers(samplers);
 
+        PBRAnisotropicConfiguration.AddUniforms(uniforms);
+
         if (ImageProcessingConfiguration) {
             ImageProcessingConfiguration.PrepareUniforms(uniforms, defines);
             ImageProcessingConfiguration.PrepareSamplers(samplers, defines);
@@ -1391,8 +1403,6 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             defines.SPECULARAA = scene.getEngine().getCaps().standardDerivatives && this._enableSpecularAntiAliasing;
         }
 
-        this.clearCoat.prepareDefines(defines, scene);
-
         if (defines._areImageProcessingDirty && this._imageProcessingConfiguration) {
             this._imageProcessingConfiguration.prepareDefines(defines);
         }
@@ -1409,6 +1419,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             defines.UNLIT = this._unlit || ((this.pointsCloud || this.wireframe) && !mesh.isVerticesDataPresent(VertexBuffer.NormalKind));
         }
 
+        // External config
+        this.clearCoat.prepareDefines(defines, scene);
+        this.anisotropy.prepareDefines(defines, mesh);
+
         // Values that need to be evaluated on every frame
         MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances ? true : false, useClipPlane);
 
@@ -1482,6 +1496,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         this._uniformBuffer.addUniform("pointSize", 1);
 
         PBRClearCoatConfiguration.PrepareUniformBuffer(this._uniformBuffer);
+        PBRAnisotropicConfiguration.PrepareUniformBuffer(this._uniformBuffer);
 
         this._uniformBuffer.create();
     }
@@ -1748,6 +1763,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             }
 
             this.clearCoat.bindForSubMesh(this._uniformBuffer, scene, engine, this._disableBumpMap, this.isFrozen, this._invertNormalMapX, this._invertNormalMapY);
+            this.anisotropy.bindForSubMesh(this._uniformBuffer, this.isFrozen);
 
             // Clip plane
             MaterialHelper.BindClipPlane(this._activeEffect, scene);

+ 5 - 5
src/Materials/PBR/pbrClearCoatConfiguration.ts

@@ -83,7 +83,7 @@ export class PBRClearCoatConfiguration {
 
     /**
      * Specifies that the submesh is ready to be used.
-     * @param defines defines the Base texture to use.
+     * @param defines the list of "defines" to update.
      * @param scene defines the scene the material belongs to.
      * @param engine defines the engine the material belongs to.
      * @param disableBumpMap defines wether the material disables bump or not.
@@ -112,7 +112,7 @@ export class PBRClearCoatConfiguration {
 
     /**
      * Checks to see if a texture is used in the material.
-     * @param defines defines the Base texture to use.
+     * @param defines the list of "defines" to update.
      * @param scene defines the scene to the material belongs to.
      */
     public prepareDefines(defines: IMaterialClearCoatDefines, scene: Scene): void {
@@ -257,10 +257,10 @@ export class PBRClearCoatConfiguration {
 
     /**
      * Makes a duplicate of the current configuration into another one.
-     * @param clearCoatconfiguration define the config where to copy the info
+     * @param clearCoatConfiguration define the config where to copy the info
      */
-    public copyTo(clearCoatconfiguration: PBRClearCoatConfiguration): void {
-        SerializationHelper.Clone(() => clearCoatconfiguration, this);
+    public copyTo(clearCoatConfiguration: PBRClearCoatConfiguration): void {
+        SerializationHelper.Clone(() => clearCoatConfiguration, this);
     }
 
     /**

+ 1 - 1
src/Materials/effect.ts

@@ -775,7 +775,7 @@ export class Effect {
         this.onCompiled = () => {
             var scenes = this.getEngine().scenes;
             for (var i = 0; i < scenes.length; i++) {
-                scenes[i].markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+                scenes[i].markAllMaterialsAsDirty(Constants.MATERIAL_AllDirtyFlag);
             }
 
             if (onCompiled) {

+ 6 - 0
src/Shaders/ShadersInclude/bumpFragment.fx

@@ -12,6 +12,12 @@
 	#else
 		mat3 TBN = cotangent_frame(normalW * normalScale, vPositionW, vBumpUV);
 	#endif
+#elif defined(ANISOTROPIC)
+	#if defined(TANGENT) && defined(NORMAL)
+		mat3 TBN = vTBN;
+	#else
+		mat3 TBN = cotangent_frame(normalW, vPositionW, vMainUV1, vec2(1., 1.));
+	#endif
 #endif
 
 #ifdef PARALLAX

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

@@ -1,4 +1,4 @@
-#if defined(BUMP) || defined(CLEARCOAT_BUMP)
+#if defined(BUMP) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC)
 	#if defined(TANGENT) && defined(NORMAL) 
 		varying mat3 vTBN;
 	#endif

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

@@ -62,7 +62,11 @@
 
             // Specular contribution
             #ifdef SPECULARTERM
-                info.specular = computeSpecularLighting(preInfo, normalW, specularEnvironmentR0, specularEnvironmentR90, AARoughnessFactors.x, light{X}.vLightDiffuse.rgb);
+                #ifdef ANISOTROPIC
+                    info.specular = computeAnisotropicSpecularLighting(preInfo, viewDirectionW, normalW, normalize(TBN[0]), normalize(TBN[1]), anisotropy, specularEnvironmentR0, specularEnvironmentR90, AARoughnessFactors.x, light{X}.vLightDiffuse.rgb);
+                #else
+                    info.specular = computeSpecularLighting(preInfo, normalW, specularEnvironmentR0, specularEnvironmentR90, AARoughnessFactors.x, light{X}.vLightDiffuse.rgb);
+                #endif
             #endif
 
             // Clear Coat contribution

+ 5 - 0
src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx

@@ -79,4 +79,9 @@ uniform mat4 view;
         uniform vec2 vClearCoatTangentSpaceParams;
         uniform mat4 clearCoatBumpMatrix;
     #endif
+#endif
+
+// Anisotropy
+#ifdef ANISOTROPIC
+    uniform float anisotropy;
 #endif

+ 47 - 9
src/Shaders/ShadersInclude/pbrFunctions.fx

@@ -1,13 +1,13 @@
 // Constants
 #define RECIPROCAL_PI2 0.15915494
 #define FRESNEL_MAXIMUM_ON_ROUGH 0.25
+// AlphaG epsilon to avoid numerical issues
+#define MINIMUMVARIANCE 0.0005
 
 float convertRoughnessToAverageSlope(float roughness)
 {
     // Calculate AlphaG as square of roughness; add epsilon to avoid numerical issues
-    const float kMinimumVariance = 0.0005;
-    float alphaG = square(roughness) + kMinimumVariance;
-    return alphaG;
+    return square(roughness) + MINIMUMVARIANCE;
 }
 
 vec2 getAARoughnessFactors(vec3 normalVector) {
@@ -19,10 +19,9 @@ vec2 getAARoughnessFactors(vec3 normalVector) {
         // Vive analytical lights roughness factor.
         float geometricRoughnessFactor = pow(clamp(slopeSquare , 0., 1.), 0.333);
 
-        // BJS factor.
-        float geometricAlphaGFactor = sqrt(slopeSquare);
         // Adapt linear roughness (alphaG) to geometric curvature of the current pixel.
-        // 75% accounts a bit for the bigger tail linked to Gaussian Filtering.
+        float geometricAlphaGFactor = sqrt(slopeSquare);
+        // BJS factor.
         geometricAlphaGFactor *= 0.75;
 
         return vec2(geometricRoughnessFactor, geometricAlphaGFactor);
@@ -32,7 +31,7 @@ vec2 getAARoughnessFactors(vec3 normalVector) {
 }
 
 // From Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007
-// Kepp for references
+// Keep for references
 // float smithVisibilityG1_TrowbridgeReitzGGX(float dot, float alphaG)
 // {
 //     float tanSquared = (1.0 - dot * dot) / (dot * dot);
@@ -88,6 +87,33 @@ float normalDistributionFunction_TrowbridgeReitzGGX(float NdotH, float alphaG)
     return a2 / (PI * d * d);
 }
 
+// Aniso parameter remapping
+// https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf page 24
+vec2 getAnisotropicRoughness(float alphaG, float anisotropy) {
+    float alphaT = max(alphaG * (1.0 + anisotropy), MINIMUMVARIANCE);
+    float alphaB = max(alphaG * (1.0 - anisotropy), MINIMUMVARIANCE);
+    return vec2(alphaT, alphaB);
+}
+
+// GGX Distribution Anisotropic
+// https://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf Addenda
+float normalDistributionFunction_BurleyGGX_Anisotropic(float NdotH, float TdotH, float BdotH, const vec2 alphaTB) {
+    float a2 = alphaTB.x * alphaTB.y;
+    vec3 v = vec3(alphaTB.y * TdotH, alphaTB.x  * BdotH, a2 * NdotH);
+    float v2 = dot(v, v);
+    float w2 = a2 / v2;
+    return a2 * w2 * w2 * (1.0 / PI);
+}
+
+// GGX Mask/Shadowing Anisotropic 
+// Heitz http://jcgt.org/published/0003/02/03/paper.pdf
+float smithVisibility_GGXCorrelated_Anisotropic(float NdotV, float NdotL, float TdotV, float BdotV, float TdotL, float BdotL, const vec2 alphaTB) {
+    float lambdaV = NdotL * length(vec3(alphaTB.x * TdotV, alphaTB.y * BdotV, NdotV));
+    float lambdaL = NdotV * length(vec3(alphaTB.x * TdotL, alphaTB.y * BdotL, NdotL));
+    float v = 0.5 / (lambdaV + lambdaL);
+    return v;
+}
+
 vec3 fresnelSchlickGGXVec3(float VdotH, vec3 reflectance0, vec3 reflectance90)
 {
     return reflectance0 + (reflectance90 - reflectance0) * pow(1.0 - VdotH, 5.0);
@@ -120,8 +146,7 @@ float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
 }
 
 // Cook Torance Specular computation.
-vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, float roughness, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor)
-{
+vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, float roughness, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor) {
     roughness = max(roughness, geometricRoughnessFactor);
     float alphaG = convertRoughnessToAverageSlope(roughness);
 
@@ -133,6 +158,19 @@ vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, flo
     return fresnel * specTerm;
 }
 
+vec3 computeAnisotropicSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, float TdotH, float BdotH, float TdotV, float BdotV, float TdotL, float BdotL, float roughness, float anisotropy, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor) {
+    float alphaG = convertRoughnessToAverageSlope(roughness);
+    vec2 alphaTB = getAnisotropicRoughness(alphaG, anisotropy);
+    alphaTB = max(alphaTB, geometricRoughnessFactor * geometricRoughnessFactor);
+
+    float distribution = normalDistributionFunction_BurleyGGX_Anisotropic(NdotH, TdotH, BdotH, alphaTB);
+    float visibility = smithVisibility_GGXCorrelated_Anisotropic(NdotV, NdotL, TdotV, BdotV, TdotL, BdotL, alphaTB);
+    float specTerm = max(0., visibility * distribution);
+
+    vec3 fresnel = fresnelSchlickGGXVec3(VdotH, reflectance0, reflectance90);
+    return fresnel * specTerm;
+}
+
 vec2 computeClearCoatTerm(float NdotH, float VdotH, float clearCoatRoughness, float geometricRoughnessFactor, float clearCoatIntensity) {
     clearCoatRoughness = max(clearCoatRoughness, geometricRoughnessFactor);
     float alphaG = convertRoughnessToAverageSlope(clearCoatRoughness);

+ 20 - 5
src/Shaders/ShadersInclude/pbrLightingFunctions.fx

@@ -21,16 +21,31 @@ vec3 computeDiffuseLighting(preLightingInfo info, vec3 lightColor) {
     return diffuseTerm * info.attenuation * info.NdotL * lightColor;
 }
 
-vec3 computeSpecularLighting(preLightingInfo info, vec3 normal, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, vec3 lightColor) {
-    float NdotH = clamp(dot(normal, info.H), 0.000000000001, 1.0);
+vec3 computeSpecularLighting(preLightingInfo info, vec3 N, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, vec3 lightColor) {
+    float NdotH = clamp(dot(N, info.H), 0.000000000001, 1.0);
 
     vec3 specTerm = computeSpecularTerm(NdotH, info.NdotL, info.NdotV, info.VdotH, info.roughness, reflectance0, reflectance90, geometricRoughnessFactor);
     return specTerm * info.attenuation * info.NdotL * lightColor;
 }
 
-vec4 computeClearCoatLighting(preLightingInfo info, vec3 normalClearCoat, float geometricRoughnessFactor, float clearCoatIntensity, vec3 lightColor) {
-    float NccdotL = clamp(dot(normalClearCoat, info.L), 0.00000000001, 1.0);
-    float NccdotH = clamp(dot(normalClearCoat, info.H), 0.000000000001, 1.0);
+vec3 computeAnisotropicSpecularLighting(preLightingInfo info, vec3 V, vec3 N, vec3 T, vec3 B, float anisotropy, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, vec3 lightColor) {
+    float NdotH = clamp(dot(N, info.H), 0.000000000001, 1.0);
+
+    float TdotH = dot(T, info.H);
+    float BdotH = dot(B, info.H);
+
+    float TdotV = dot(T, V);
+    float BdotV = dot(B, V);
+    float TdotL = dot(T, info.L);
+    float BdotL = dot(B, info.L);
+
+    vec3 specTerm = computeAnisotropicSpecularTerm(NdotH, info.NdotL, info.NdotV, info.VdotH, TdotH, BdotH, TdotV, BdotV, TdotL, BdotL, info.roughness, anisotropy, reflectance0, reflectance90, geometricRoughnessFactor);
+    return specTerm * info.attenuation * info.NdotL * lightColor;
+}
+
+vec4 computeClearCoatLighting(preLightingInfo info, vec3 Ncc, float geometricRoughnessFactor, float clearCoatIntensity, vec3 lightColor) {
+    float NccdotL = clamp(dot(Ncc, info.L), 0.00000000001, 1.0);
+    float NccdotH = clamp(dot(Ncc, info.H), 0.000000000001, 1.0);
 
     vec2 clearCoatTerm = computeClearCoatTerm(NccdotH, info.VdotH, info.roughness, geometricRoughnessFactor, clearCoatIntensity);
 

+ 2 - 0
src/Shaders/ShadersInclude/pbrUboDeclaration.fx

@@ -44,6 +44,8 @@ uniform Material
     uniform vec2 vClearCoatBumpInfos;
     uniform vec2 vClearCoatTangentSpaceParams;
     uniform mat4 clearCoatBumpMatrix;
+
+    uniform float anisotropy;
 };
 
 uniform Scene {

+ 34 - 7
src/Shaders/pbr.fragment.fx

@@ -1,4 +1,4 @@
-#if defined(BUMP) || !defined(NORMAL) || defined(FORCENORMALFORWARD) || defined(SPECULARAA) || defined(CLEARCOAT_BUMP)
+#if defined(BUMP) || !defined(NORMAL) || defined(FORCENORMALFORWARD) || defined(SPECULARAA) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC)
 #extension GL_OES_standard_derivatives : enable
 #endif
 
@@ -447,7 +447,7 @@ void main(void) {
         #endif
     #endif
 
-    // _____________________________ Compute LODs Fetch ____________________________________
+    // _____________________________ Compute Geometry info _________________________________
     float NdotVUnclamped = dot(normalW, viewDirectionW);
     float NdotV = clamp(NdotVUnclamped,0., 1.) + 0.00001;
     float alphaG = convertRoughnessToAverageSlope(roughness);
@@ -455,15 +455,26 @@ void main(void) {
 
     #ifdef SPECULARAA
         // Adapt linear roughness (alphaG) to geometric curvature of the current pixel.
-        // 75% accounts a bit for the bigger tail linked to Gaussian Filtering.
         alphaG += AARoughnessFactors.y;
     #endif
 
+    #ifdef ANISOTROPIC
+        vec3 anisotropicFrameDirection = anisotropy >= 0.0 ? TBN[1] : TBN[0];
+        vec3 anisotropicFrameTangent = cross(normalize(anisotropicFrameDirection), viewDirectionW);
+        vec3 anisotropicFrameNormal = cross(anisotropicFrameTangent, anisotropicFrameDirection);
+        vec3 anisotropicNormal = normalize(mix(normalW, anisotropicFrameNormal, abs(anisotropy)));
+    #endif
+
     // _____________________________ Refraction Info _______________________________________
     #ifdef REFRACTION
         vec4 environmentRefraction = vec4(0., 0., 0., 0.);
 
-        vec3 refractionVector = refract(-viewDirectionW, normalW, vRefractionInfos.y);
+        #ifdef ANISOTROPIC
+            vec3 refractionVector = refract(-viewDirectionW, anisotropicNormal, vRefractionInfos.y);
+        #else
+            vec3 refractionVector = refract(-viewDirectionW, normalW, vRefractionInfos.y);
+        #endif
+
         #ifdef REFRACTIONMAP_OPPOSITEZ
             refractionVector.z *= -1.0;
         #endif
@@ -544,7 +555,12 @@ void main(void) {
         vec4 environmentRadiance = vec4(0., 0., 0., 0.);
         vec3 environmentIrradiance = vec3(0., 0., 0.);
 
-        vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), normalW);
+        #ifdef ANISOTROPIC
+            vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), anisotropicNormal);
+        #else
+            vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), normalW);
+        #endif
+
         #ifdef REFLECTIONMAP_OPPOSITEZ
             reflectionVector.z *= -1.0;
         #endif
@@ -621,10 +637,16 @@ void main(void) {
             #if defined(NORMAL) && defined(USESPHERICALINVERTEX)
                 environmentIrradiance = vEnvironmentIrradiance;
             #else
-                vec3 irradianceVector = vec3(reflectionMatrix * vec4(normalW, 0)).xyz;
+                #ifdef ANISOTROPIC
+                    vec3 irradianceVector = vec3(reflectionMatrix * vec4(anisotropicNormal, 0)).xyz;
+                #else
+                    vec3 irradianceVector = vec3(reflectionMatrix * vec4(normalW, 0)).xyz;
+                #endif
+
                 #ifdef REFLECTIONMAP_OPPOSITEZ
                     irradianceVector.z *= -1.0;
                 #endif
+
                 environmentIrradiance = environmentIrradianceJones(irradianceVector);
             #endif
         #endif
@@ -693,7 +715,6 @@ void main(void) {
 
             #ifdef SPECULARAA
                 // Adapt linear roughness (alphaG) to geometric curvature of the current pixel.
-                // 75% accounts a bit for the bigger tail linked to Gaussian Filtering.
                 clearCoatAlphaG += clearCoatAARoughnessFactors.y;
             #endif
 
@@ -1138,4 +1159,10 @@ void main(void) {
     //gl_FragColor = vec4(eho, eho, eho, 1.0);
 
     //gl_FragColor = vec4(seo * eho, seo * eho, seo * eho, 1.0);
+
+    //gl_FragColor = vec4(normalize(-TBN[0]), 1.0);
+
+    //gl_FragColor = vec4(vPositionW * 0.5 + 0.5, 1.0);
+
+    //gl_FragColor = vec4(vMainUV1, 0., 1.0);
 }

BIN
tests/validation/ReferenceImages/anisotropic.png


+ 5 - 0
tests/validation/config.json

@@ -2,6 +2,11 @@
   "root": "https://rawgit.com/BabylonJS/Website/master",
   "tests": [
     {
+      "title": "Anisotropic",
+      "playgroundId": "#MAXCNU#1",
+      "referenceImage": "anisotropic.png"
+    },
+    {
       "title": "Clear Coat",
       "playgroundId": "#YACNQS#2",
       "referenceImage": "clearCoat.png"