diff --git a/Documentation/release/dev/AnisotropyPBR.md b/Documentation/release/dev/AnisotropyPBR.md
new file mode 100644
index 0000000000000000000000000000000000000000..1bb80832ea09a1d7b7f15bdaa996f8f6c5a264fb
--- /dev/null
+++ b/Documentation/release/dev/AnisotropyPBR.md
@@ -0,0 +1,7 @@
+# Anisotropy in the PBR shader
+
+The PBR shader now supports anisotropic materials. This adds two parameters
+to the vtkProperty : Anisotropy (strength of the anisotropy, 1.0 means fully
+anisotropic), and AnisotropyRotation (rotate the direction of anisotropy
+counter-clockwise). It can also be textured with anisotropy strength in the
+red channel and anisotropy rotation in the green.
diff --git a/Rendering/Core/vtkProperty.cxx b/Rendering/Core/vtkProperty.cxx
index 2a77d121b9da43d8e7c51d70b441ac714346afa3..1cfae1d8cd710a1a4bcc155cde60d93703eb9388 100644
--- a/Rendering/Core/vtkProperty.cxx
+++ b/Rendering/Core/vtkProperty.cxx
@@ -76,6 +76,8 @@ vtkProperty::vtkProperty()
   this->OcclusionStrength = 1.0;
   this->Metallic = 0.0;
   this->Roughness = 0.5;
+  this->Anisotropy = 0.0;
+  this->AnisotropyRotation = 0.0;
   this->Ambient = 0.0;
   this->Diffuse = 1.0;
   this->Specular = 0.0;
@@ -143,6 +145,12 @@ void vtkProperty::DeepCopy(vtkProperty* p)
     this->SetRenderPointsAsSpheres(p->GetRenderPointsAsSpheres());
     this->SetRenderLinesAsTubes(p->GetRenderLinesAsTubes());
     this->SetShading(p->GetShading());
+    this->SetNormalScale(p->GetNormalScale());
+    this->SetOcclusionStrength(p->GetOcclusionStrength());
+    this->SetMetallic(p->GetMetallic());
+    this->SetRoughness(p->GetRoughness());
+    this->SetAnisotropy(p->GetAnisotropy());
+    this->SetAnisotropyRotation(p->GetAnisotropyRotation());
 
     this->RemoveAllTextures();
     auto iter = p->Textures.begin();
@@ -253,8 +261,9 @@ void vtkProperty::SetTexture(const char* name, vtkTexture* tex)
     vtkErrorMacro("The " << name << " texture is not in sRGB color space.");
     return;
   }
-  if ((strcmp(name, "materialTex") == 0 || strcmp(name, "normalTex") == 0) &&
-    tex->GetUseSRGBColorSpace())
+  const bool texNeedLinear = strcmp(name, "materialTex") == 0 || strcmp(name, "normalTex") == 0 ||
+    strcmp(name, "anisotropyTex") == 0;
+  if (texNeedLinear && tex->GetUseSRGBColorSpace())
   {
     vtkErrorMacro("The " << name << " texture is not in linear color space.");
     return;
diff --git a/Rendering/Core/vtkProperty.h b/Rendering/Core/vtkProperty.h
index 663afe44f139a2e25cfda70e695391440a52d463..a986f771205c9186890ec422789b0375d4673737 100644
--- a/Rendering/Core/vtkProperty.h
+++ b/Rendering/Core/vtkProperty.h
@@ -196,6 +196,28 @@ public:
   vtkGetMacro(Roughness, double);
   //@}
 
+  //@{
+  /**
+   * Set/Get the anisotropy coefficient.
+   * This value controls the anisotropy of the material (0.0 means isotropic)
+   * This parameter is only used by PBR Interpolation.
+   * Default value is 0.0
+   */
+  vtkSetClampMacro(Anisotropy, double, 0.0, 1.0);
+  vtkGetMacro(Anisotropy, double);
+  //@}
+
+  //@{
+  /**
+   * Set/Get the anisotropy rotation coefficient.
+   * This value controls the rotation of the direction of the anisotropy.
+   * This parameter is only used by PBR Interpolation.
+   * Default value is 0.0
+   */
+  vtkSetClampMacro(AnisotropyRotation, double, 0.0, 1.0);
+  vtkGetMacro(AnisotropyRotation, double);
+  //@}
+
   //@{
   /**
    * Set/Get the normal scale coefficient.
@@ -512,10 +534,10 @@ public:
    * must be assigned unique names. Note that for texture blending the
    * textures will be rendering is alphabetical order and after any texture
    * defined in the actor.
-   * There exists 4 special textures with reserved names: "albedoTex", "materialTex", "normalTex"
-   * and "emissiveTex". While these textures can be added with the regular SetTexture method, it is
-   * prefered to use to method SetBaseColorTexture, SetORMTexture, SetNormalTexture and
-   * SetEmissiveTexture respectively.
+   * There exists 4 special textures with reserved names: "albedoTex", "materialTex", "normalTex",
+   * "emissiveTex" and "anisotropyTex". While these textures can be added with the regular
+   * SetTexture method, it is preferred to use the methods SetBaseColorTexture, SetORMTexture,
+   * SetNormalTexture, SetEmissiveTexture and SetAnisotropyTexture respectively.
    */
   void SetTexture(const char* name, vtkTexture* texture);
   vtkTexture* GetTexture(const char* name);
@@ -540,6 +562,18 @@ public:
    */
   void SetORMTexture(vtkTexture* texture) { this->SetTexture("materialTex", texture); }
 
+  /**
+   * Set the anisotropy texture. This texture contains two independent components corresponding to
+   * the anisotropy value and anisotropy rotation. The last component (blue channel) is discarded.
+   * The anisotropy value is scaled by the anisotropy coefficient of the material. The anisotropy
+   * rotation rotates the direction of the anisotropy (ie. the tangent) around the normal and is not
+   * scaled by the anisotropy rotation coefficient.
+   * This texture must be in linear color space.
+   * This is only used by the PBR shading model.
+   * @sa SetInterpolationToPBR SetAnisotropy
+   */
+  void SetAnisotropyTexture(vtkTexture* texture) { this->SetTexture("anisotropyTex", texture); }
+
   /**
    * Set the normal texture. This texture is required for normal mapping. It is valid for both PBR
    * and Phong interpolation.
@@ -615,6 +649,8 @@ protected:
   double Diffuse;
   double Metallic;
   double Roughness;
+  double Anisotropy;
+  double AnisotropyRotation;
   double NormalScale;
   double OcclusionStrength;
   double EmissiveFactor[3];
diff --git a/Rendering/OpenGL2/Testing/CMakeLists.txt b/Rendering/OpenGL2/Testing/CMakeLists.txt
index cd4adb399dcf870561fd5504748a555f3ce6b17e..b0fd0c0ad0cf90aba0e5c6e3b84a00f8b077a881 100644
--- a/Rendering/OpenGL2/Testing/CMakeLists.txt
+++ b/Rendering/OpenGL2/Testing/CMakeLists.txt
@@ -1,6 +1,7 @@
 vtk_module_test_data(
   Data/GIS/raster.tif
   Data/autoshop.jpg
+  Data/anisotropyTex.png
   Data/bunny.ply
   Data/clouds.jpeg
   Data/dragon.ply
diff --git a/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt b/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt
index 437ce4fb6b5cf334b98efad46e021789ddc75a36..f4e73e6381d9f78257899f7e7b4d828cf95516cc 100644
--- a/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt
+++ b/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt
@@ -49,6 +49,7 @@ vtk_add_test_cxx(vtkRenderingOpenGL2CxxTests tests
   TestPanoramicProjectionPass.cxx,NO_DATA
   TestPBREdgeTint.cxx
   TestPBRHdrEnvironment.cxx
+  TestPBRAnisotropy.cxx
   TestPBRIrradianceHDR.cxx
   TestPBRMapping.cxx
   TestPBRMaterials.cxx
diff --git a/Rendering/OpenGL2/Testing/Cxx/TestPBRAnisotropy.cxx b/Rendering/OpenGL2/Testing/Cxx/TestPBRAnisotropy.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..203fe820f11ac5e73fa06e43db583b81a17b5e3b
--- /dev/null
+++ b/Rendering/OpenGL2/Testing/Cxx/TestPBRAnisotropy.cxx
@@ -0,0 +1,155 @@
+/*=========================================================================
+
+  Program:   Visualization Toolkit
+  Module:    TestPBRAnisotropy.cxx
+
+  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+  All rights reserved.
+  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+// This test covers the PBR Anisotropy feature
+// It renders spheres with different anisotropy values
+
+#include "vtkActor.h"
+#include "vtkActorCollection.h"
+#include "vtkCamera.h"
+#include "vtkGenericOpenGLRenderWindow.h"
+#include "vtkImageData.h"
+#include "vtkImageFlip.h"
+#include "vtkInteractorStyleTrackballCamera.h"
+#include "vtkJPEGReader.h"
+#include "vtkLight.h"
+#include "vtkNew.h"
+#include "vtkOpenGLPolyDataMapper.h"
+#include "vtkOpenGLRenderer.h"
+#include "vtkOpenGLSkybox.h"
+#include "vtkOpenGLTexture.h"
+#include "vtkPBRIrradianceTexture.h"
+#include "vtkPBRPrefilterTexture.h"
+#include "vtkPNGReader.h"
+#include "vtkPolyDataTangents.h"
+#include "vtkProperty.h"
+#include "vtkRegressionTestImage.h"
+#include "vtkRenderWindow.h"
+#include "vtkRenderWindowInteractor.h"
+#include "vtkRendererCollection.h"
+#include "vtkSphereSource.h"
+#include "vtkTestUtilities.h"
+#include "vtkTexture.h"
+#include "vtkTextureMapToSphere.h"
+
+//----------------------------------------------------------------------------
+int TestPBRAnisotropy(int argc, char* argv[])
+{
+  vtkNew<vtkOpenGLRenderer> renderer;
+
+  vtkNew<vtkRenderWindow> renWin;
+  renWin->SetSize(600, 600);
+  renWin->AddRenderer(renderer);
+
+  vtkNew<vtkRenderWindowInteractor> iren;
+  iren->SetRenderWindow(renWin);
+
+  vtkSmartPointer<vtkPBRIrradianceTexture> irradiance = renderer->GetEnvMapIrradiance();
+  irradiance->SetIrradianceStep(0.3);
+  renderer->UseSphericalHarmonicsOff();
+
+  vtkNew<vtkOpenGLTexture> textureCubemap;
+  textureCubemap->CubeMapOn();
+  textureCubemap->UseSRGBColorSpaceOn();
+
+  std::string pathSkybox[6] = { "Data/skybox/posx.jpg", "Data/skybox/negx.jpg",
+    "Data/skybox/posy.jpg", "Data/skybox/negy.jpg", "Data/skybox/posz.jpg",
+    "Data/skybox/negz.jpg" };
+
+  for (int i = 0; i < 6; i++)
+  {
+    vtkNew<vtkJPEGReader> jpg;
+    char* fname = vtkTestUtilities::ExpandDataFileName(argc, argv, pathSkybox[i].c_str());
+    jpg->SetFileName(fname);
+    delete[] fname;
+    vtkNew<vtkImageFlip> flip;
+    flip->SetInputConnection(jpg->GetOutputPort());
+    flip->SetFilteredAxis(1); // flip y axis
+    textureCubemap->SetInputConnection(i, flip->GetOutputPort());
+  }
+
+  renderer->SetEnvironmentTexture(textureCubemap);
+  renderer->UseImageBasedLightingOn();
+
+  vtkNew<vtkSphereSource> sphere;
+  sphere->SetThetaResolution(75);
+  sphere->SetPhiResolution(75);
+
+  vtkNew<vtkTextureMapToSphere> textureMap;
+  textureMap->SetInputConnection(sphere->GetOutputPort());
+  textureMap->PreventSeamOff();
+
+  vtkNew<vtkPolyDataTangents> tangents;
+  tangents->SetInputConnection(textureMap->GetOutputPort());
+
+  vtkNew<vtkPolyDataMapper> mapper;
+  mapper->SetInputConnection(tangents->GetOutputPort());
+
+  vtkNew<vtkActor> actor;
+  actor->SetMapper(mapper);
+  actor->GetProperty()->SetInterpolationToPBR();
+
+  for (int i = 0; i < 6; i++)
+  {
+    vtkNew<vtkActor> actorSphere;
+    actorSphere->SetPosition(i, 0.0, 0.0);
+    actorSphere->RotateX(20);
+    actorSphere->RotateY(20);
+    actorSphere->SetMapper(mapper);
+    actorSphere->GetProperty()->SetInterpolationToPBR();
+    actorSphere->GetProperty()->SetMetallic(1.0);
+    actorSphere->GetProperty()->SetAnisotropy(1.0);
+    actorSphere->GetProperty()->SetRoughness(i / 5.0);
+    renderer->AddActor(actorSphere);
+  }
+
+  for (int i = 0; i < 6; i++)
+  {
+    vtkNew<vtkActor> actorSphere;
+    actorSphere->SetPosition(i, 1.0, 0.0);
+    actorSphere->RotateX(20);
+    actorSphere->RotateY(20);
+    actorSphere->SetMapper(mapper);
+    actorSphere->GetProperty()->SetInterpolationToPBR();
+    actorSphere->GetProperty()->SetMetallic(1.0);
+    actorSphere->GetProperty()->SetRoughness(0.1);
+    actorSphere->GetProperty()->SetAnisotropy(i / 5.0);
+    renderer->AddActor(actorSphere);
+  }
+
+  for (int i = 0; i < 6; i++)
+  {
+    vtkNew<vtkActor> actorSphere;
+    actorSphere->SetPosition(i, 2.0, 0.0);
+    actorSphere->RotateX(20);
+    actorSphere->RotateY(20);
+    actorSphere->SetMapper(mapper);
+    actorSphere->GetProperty()->SetInterpolationToPBR();
+    actorSphere->GetProperty()->SetMetallic(1.0);
+    actorSphere->GetProperty()->SetRoughness(0.1);
+    actorSphere->GetProperty()->SetAnisotropy(1.0);
+    actorSphere->GetProperty()->SetAnisotropyRotation(i / 5.0);
+    renderer->AddActor(actorSphere);
+  }
+
+  renWin->Render();
+
+  int retVal = vtkRegressionTestImage(renWin);
+  if (retVal == vtkRegressionTester::DO_INTERACTOR)
+  {
+    iren->Start();
+  }
+
+  return !retVal;
+}
diff --git a/Rendering/OpenGL2/Testing/Cxx/TestPBRMapping.cxx b/Rendering/OpenGL2/Testing/Cxx/TestPBRMapping.cxx
index 710c89f03b900100b0cd236efb7b7109b5ec040f..9a3ea11a3c7cff18296fab8c1e45a8c9b3f049c6 100644
--- a/Rendering/OpenGL2/Testing/Cxx/TestPBRMapping.cxx
+++ b/Rendering/OpenGL2/Testing/Cxx/TestPBRMapping.cxx
@@ -128,18 +128,32 @@ int TestPBRMapping(int argc, char* argv[])
   normal->InterpolateOn();
   normal->SetInputConnection(normalReader->GetOutputPort());
 
+  vtkNew<vtkPNGReader> anisotropyReader;
+  char* anisotropyname =
+    vtkTestUtilities::ExpandDataFileName(argc, argv, "Data/vtk_Anisotropy.png");
+  anisotropyReader->SetFileName(anisotropyname);
+  delete[] anisotropyname;
+
+  vtkNew<vtkTexture> anisotropy;
+  anisotropy->InterpolateOn();
+  anisotropy->SetInputConnection(anisotropyReader->GetOutputPort());
+
   vtkNew<vtkActor> actor;
   actor->SetOrientation(0.0, 25.0, 0.0);
   actor->SetMapper(mapper);
   actor->GetProperty()->SetInterpolationToPBR();
 
-  // set metallic and roughness to 1.0 as they act as multipliers with texture value
+  // set metallic, roughness, anisotropy and anisotropyRotation
+  // to 1.0 as they act as multipliers with texture value
   actor->GetProperty()->SetMetallic(1.0);
   actor->GetProperty()->SetRoughness(1.0);
+  actor->GetProperty()->SetAnisotropy(1.0);
+  actor->GetProperty()->SetAnisotropyRotation(1.0);
 
   actor->GetProperty()->SetBaseColorTexture(albedo);
   actor->GetProperty()->SetORMTexture(material);
   actor->GetProperty()->SetNormalTexture(normal);
+  actor->GetProperty()->SetAnisotropyTexture(anisotropy);
 
   renderer->AddActor(actor);
 
diff --git a/Rendering/OpenGL2/Testing/Data/Baseline/TestPBRAnisotropy.png.sha512 b/Rendering/OpenGL2/Testing/Data/Baseline/TestPBRAnisotropy.png.sha512
new file mode 100644
index 0000000000000000000000000000000000000000..c50fffeb09df9591fe856062d703faccb15077cd
--- /dev/null
+++ b/Rendering/OpenGL2/Testing/Data/Baseline/TestPBRAnisotropy.png.sha512
@@ -0,0 +1 @@
+c084c3f1cf66f2ff25b7560e13c61b76cb541b2e9572410fc9ff3386dcee63c571c7d716d8928f2f6880a3c0162235435cc6e49c836255a748ad7681d2ba6e34
diff --git a/Rendering/OpenGL2/Testing/Data/Baseline/TestPBRMapping.png.sha512 b/Rendering/OpenGL2/Testing/Data/Baseline/TestPBRMapping.png.sha512
index cfc01009f6e6910d85633754cb9fee73d7c1ebbb..398aebb30a4b5f7552adf5ae6f2562be5c39dc55 100644
--- a/Rendering/OpenGL2/Testing/Data/Baseline/TestPBRMapping.png.sha512
+++ b/Rendering/OpenGL2/Testing/Data/Baseline/TestPBRMapping.png.sha512
@@ -1 +1 @@
-d52f17c7322a3f6cf4e88f68fb61c90c3789f5ab1530a253343edad8d6bbd8844f0a7885b5a85094d221803e4a229891fc6bd3c29b72b02e841d578de4247bc9
+9015c94269f3c4d7e3a9479b770088e517c6f6806edb9d661235ebd1e88ad4e314a8aac2e46cd86519be3a34cfeb4a71bd15c11565b369060f511bc1084200ca
diff --git a/Rendering/OpenGL2/vtk.module b/Rendering/OpenGL2/vtk.module
index fe92b86208cecfe9af426569a78caf0f9b8f4170..04949608247e85080ff70dcf94e8d8001140190b 100644
--- a/Rendering/OpenGL2/vtk.module
+++ b/Rendering/OpenGL2/vtk.module
@@ -28,6 +28,7 @@ TEST_DEPENDS
   VTK::FiltersGeometry
   VTK::FiltersProgrammable
   VTK::FiltersSources
+  VTK::FiltersTexture
   VTK::IOExodus
   VTK::IOExport
   VTK::IOImage
diff --git a/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx b/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx
index c59e512dc2e1eec851a24fa27f5b24bddb1f4da0..25da8348cfe317a5d47d84a5840a695b47336fb7 100644
--- a/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx
+++ b/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx
@@ -915,6 +915,7 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
   }
 
   bool hasIBL = false;
+  bool hasAnisotropy = false;
 
   if (actor->GetProperty()->GetInterpolation() == VTK_PBR && lastLightComplexity > 0)
   {
@@ -927,6 +928,7 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
       "uniform vec3 emissiveFactorUniform;\n"
       "uniform float aoStrengthUniform;\n"
       "uniform vec3 edgeTintUniform;\n"
+      "uniform float anisotropyUniform;\n\n"
       "float D_GGX(float NdH, float roughness)\n"
       "{\n"
       "  float a = roughness * roughness;\n"
@@ -941,14 +943,23 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
       "  float ggxL = NdV * sqrt(a2 + NdL * (NdL - a2 * NdL));\n"
       "  return 0.5 / (ggxV + ggxL);\n"
       "}\n"
-      "vec3 F_Schlick(vec3 F0, vec3 F90, float HdV)\n"
+      "vec3 F_Schlick(vec3 F0, vec3 F90, float HdL)\n"
       "{\n"
-      "  return F0 + (F90 - F0) * pow(1.0 - HdV, 5.0);\n"
+      "  return F0 + (F90 - F0) * pow(1.0 - HdL, 5.0);\n"
       "}\n"
       "vec3 DiffuseLambert(vec3 albedo)\n"
       "{\n"
       "  return albedo * recPI;\n"
-      "}\n",
+      "}\n"
+      "vec3 SpecularIsotropic(float NdH, float NdV, float NdL, float HdL, float roughness, vec3 "
+      "F0, vec3 F90, out vec3 F)\n"
+      "{\n"
+      "  float D = D_GGX(NdH, roughness);\n"
+      "  float V = V_SmithCorrelated(NdV, NdL, roughness);\n"
+      "  F = F_Schlick(F0, F90, HdL);\n"
+      "  return (D * V) * F;\n"
+      "}\n"
+      "//VTK::Anisotropy::Dec\n",
       false);
 
     // disable default behavior with textures
@@ -988,6 +999,7 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
           toString << "  vec3 emissiveColor = texture(emissiveTex, tcoordVCVSOutput).rgb;\n"
                       "  emissiveColor = emissiveColor * emissiveFactorUniform;\n";
         }
+        // Anisotropy texture is sampled in ReplaceShaderNormal
       }
     }
 
@@ -1020,6 +1032,59 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
                 "  vec3 V = normalize(-vertexVC.xyz);\n"
                 "  float NdV = clamp(dot(N, V), 1e-5, 1.0);\n";
 
+    if (actor->GetProperty()->GetAnisotropy() != 0.0 &&
+      this->VBOs->GetNumberOfComponents("normalMC") == 3 &&
+      this->VBOs->GetNumberOfComponents("tangentMC") == 3)
+    {
+      // anisotropy, tangentVC and bitangentVC are defined
+      hasAnisotropy = true;
+
+      // Add functions to handle specular and anisotropic lobe
+      vtkShaderProgram::Substitute(FSSource, "//VTK::Anisotropy::Dec\n",
+        "// Anisotropy functions\n"
+        "float D_GGX_Anisotropic(float at, float ab, float TdH, float BdH, float NdH)\n"
+        "{\n"
+        "  float a2 = at * ab;\n"
+        "  vec3 d = vec3(ab * TdH, at * BdH, a2 * NdH);\n"
+        "  float d2 = dot(d, d);\n"
+        "  float b2 = a2 / d2;\n"
+        "  return a2 * b2 * b2 * recPI;\n"
+        "}\n"
+        "float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float TdV, float BdV, float "
+        "TdL, float BdL, float NdV, float NdL)\n"
+        "{\n"
+        "  float lambdaV = NdL * length(vec3(at * TdV, ab * BdV, NdV));\n"
+        "  float lambdaL = NdV * length(vec3(at * TdL, ab * BdL, NdL));\n"
+        "  return 0.5 / (lambdaV + lambdaL);\n"
+        "}\n"
+        "vec3 SpecularAnisotropic(float at, float ab, vec3 l, vec3 t, vec3 b, vec3 h, float TdV, "
+        "float BdV, float NdH, float NdV, float NdL,\n"
+        "float HdL, float roughness, float anisotropy, vec3 F0, vec3 F90, out vec3 F)\n"
+        "{\n"
+        "  float TdL = dot(t, l);\n"
+        "  float BdL = dot(b, l);\n"
+        "  float TdH = dot(t, h);\n"
+        "  float BdH = dot(b, h);\n"
+        "  // specular anisotropic BRDF\n"
+        "  float D = D_GGX_Anisotropic(at, ab, TdH, BdH, NdH);\n"
+        "  float V = V_SmithGGXCorrelated_Anisotropic(at, ab, TdV, BdV, TdL, BdL, NdV, NdL);\n"
+        "  F = F_Schlick(F0, F90, HdL);\n"
+        "  return (D * V) * F;\n"
+        "}\n\n\n",
+        false);
+
+      // Precompute anisotropic parameters
+      // at and ab are the roughness along the tangent and bitangent
+      // Disney, as in OSPray
+      toString << "  float r2 = roughness * roughness;\n"
+                  "  float aspect = sqrt(1.0 - 0.9 * anisotropy);\n";
+      toString << "  float at = max(r2 / aspect, 0.001);\n"
+                  "  float ab = max(r2 * aspect, 0.001);\n";
+
+      toString << "  float TdV = dot(tangentVC, V);\n"
+                  "  float BdV = dot(bitangentVC, V);\n";
+    }
+
     if (hasIBL)
     {
       if (!oglRen->GetUseSphericalHarmonics())
@@ -1032,8 +1097,20 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
         toString << "  vec3 irradiance = vec3(ComputeSH(rotN, shRed), ComputeSH(rotN, shGreen), "
                     "ComputeSH(rotN, shBlue));\n";
       }
-      toString << "  vec3 worldReflect = normalize(envMatrix*reflect(-V, N));\n"
-                  "  vec3 prefilteredColor = textureLod(prefilterTex, worldReflect,"
+
+      if (hasAnisotropy)
+      {
+        toString << "  vec3 anisotropicTangent = cross(bitangentVC, V);\n"
+                    "  vec3 anisotropicNormal = cross(anisotropicTangent, bitangentVC);\n"
+                    "  vec3 bentNormal = normalize(mix(N, anisotropicNormal, anisotropy));\n"
+                    "  vec3 worldReflect = normalize(envMatrix*reflect(-V, bentNormal));\n";
+      }
+      else
+      {
+        toString << "  vec3 worldReflect = normalize(envMatrix*reflect(-V, N));\n";
+      }
+
+      toString << "  vec3 prefilteredColor = textureLod(prefilterTex, worldReflect,"
                   " roughness * prefilterMaxLevel).rgb;\n";
       toString << "  vec2 brdf = texture(brdfTex, vec2(NdV, roughness)).rg;\n";
     }
@@ -1054,7 +1131,7 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
                   "  float f90 = clamp(dot(F0, vec3(50.0 * 0.33)), 0.0, 1.0);\n"
                   "  vec3 F90 = mix(vec3(f90), edgeTintUniform, metallic);\n"
                   "  vec3 L, H, radiance, F, specular, diffuse;\n"
-                  "  float NdL, NdH, HdV, distanceVC, attenuation, D, Vis;\n\n";
+                  "  float NdL, NdH, HdL, distanceVC, attenuation, D, Vis;\n\n";
     }
 
     toString << "//VTK::Light::Impl\n";
@@ -1119,13 +1196,20 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
       if (actor->GetProperty()->GetInterpolation() == VTK_PBR)
       {
         // L = V = H for headlights
-        toString << "  NdV = clamp(dot(N, V), 1e-5, 1.0);\n"
-                    "  D = D_GGX(NdV, roughness);\n"
-                    "  Vis = V_SmithCorrelated(NdV, NdV, roughness);\n"
-                    "  F = F_Schlick(F0, F90, 1.0);\n"
-                    "  specular = D * Vis * F;\n"
-                    "  diffuse = (1.0 - metallic) * (1.0 - F) * DiffuseLambert(albedo);\n"
-                    "  Lo += (diffuse + specular) * lightColor0 * NdV;\n\n"
+        if (hasAnisotropy)
+        {
+          // When V=H, maybe can be optimised
+          toString << "specular = SpecularAnisotropic(at, ab, V, tangentVC, bitangentVC, V, TdV, "
+                      "BdV, NdV, NdV, NdV,\n"
+                      "1.0, roughness, anisotropy, F0, F90, F);\n";
+        }
+        else
+        {
+          toString << "specular = SpecularIsotropic(NdV, NdV, NdV, 1.0, roughness, F0, F90, F);\n";
+        }
+        toString << "  diffuse = (1.0 - metallic) * (1.0 - F) * DiffuseLambert(albedo);\n"
+                    "  \n\n"
+                    "  Lo += lightColor0 * ((diffuse + specular) * NdV);\n"
                     "//VTK::Light::Impl\n";
       }
       else
@@ -1152,18 +1236,24 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
           toString << "  L = normalize(-lightDirectionVC" << i
                    << ");\n"
                       "  H = normalize(V + L);\n"
+                      "  HdL = clamp(dot(H, L), 1e-5, 1.0);\n"
                       "  NdL = clamp(dot(N, L), 1e-5, 1.0);\n"
                       "  NdH = clamp(dot(N, H), 1e-5, 1.0);\n"
-                      "  HdV = clamp(dot(H, V), 1e-5, 1.0);\n"
                       "  radiance = lightColor"
-                   << i
-                   << ";\n"
-                      "  D = D_GGX(NdH, roughness);\n"
-                      "  Vis = V_SmithCorrelated(NdV, NdL, roughness);\n"
-                      "  F = F_Schlick(F0, F90, HdV);\n"
-                      "  specular = D * Vis * F;\n"
-                      "  diffuse = (1.0 - metallic) * (1.0 - F) * DiffuseLambert(albedo);\n"
-                      "  Lo += (diffuse + specular) * radiance * NdL;\n";
+                   << i << ";\n";
+
+          if (hasAnisotropy)
+          {
+            toString << "  specular = SpecularAnisotropic(at, ab, L, tangentVC, bitangentVC, H, "
+                        "TdV, BdV, NdH, NdV, NdL, HdL, roughness, anisotropy, F0, F90, F);\n";
+          }
+          else
+          {
+            toString
+              << "  specular = SpecularIsotropic(NdH, NdV, NdL, HdL, roughness, F0, F90, F);\n";
+          }
+          toString << "  diffuse = (1.0 - metallic) * (1.0 - F) * DiffuseLambert(albedo);\n"
+                      "  Lo += (diffuse + specular) * NdL * radiance;\n";
         }
         toString << "//VTK::Light::Impl\n";
       }
@@ -1210,7 +1300,7 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
                       "  H = normalize(V + L);\n"
                       "  NdL = clamp(dot(N, L), 1e-5, 1.0);\n"
                       "  NdH = clamp(dot(N, H), 1e-5, 1.0);\n"
-                      "  HdV = clamp(dot(H, V), 1e-5, 1.0);\n"
+                      "  HdL = clamp(dot(H, L), 1e-5, 1.0);\n"
                       "  if (lightPositional"
                    << i
                    << " == 0)\n"
@@ -1251,13 +1341,20 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderLight(
                       "    }\n"
                       "  }\n"
                       "  radiance = lightColor"
-                   << i
-                   << " * attenuation;\n"
-                      "  D = D_GGX(NdH, roughness);\n"
-                      "  Vis = V_SmithCorrelated(NdV, NdL, roughness);\n"
-                      "  F = F_Schlick(F0, F90, HdV);\n"
-                      "  specular = D * Vis * F;\n"
-                      "  diffuse = (1.0 - metallic) * (1.0 - F) * DiffuseLambert(albedo);\n"
+                   << i << " * attenuation;\n";
+
+          if (hasAnisotropy)
+          {
+            toString << "  specular = SpecularAnisotropic(at, ab, L, tangentVC, bitangentVC, H, "
+                        "TdV, BdV, NdH, NdV, NdL, HdL, roughness, anisotropy, F0, F90, F);\n";
+          }
+          else
+          {
+            toString
+              << "  specular = SpecularIsotropic(NdH, NdV, NdL, HdL, roughness, F0, F90, F);\n";
+          }
+
+          toString << "  diffuse = (1.0 - metallic) * (1.0 - F) * DiffuseLambert(albedo);\n"
                       "  Lo += (diffuse + specular) * radiance * NdL;\n\n";
         }
         toString << "//VTK::Light::Impl\n";
@@ -1559,7 +1656,7 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderTCoord(
     // ignore special textures
     if (textures[i].second == "albedoTex" || textures[i].second == "normalTex" ||
       textures[i].second == "materialTex" || textures[i].second == "brdfTex" ||
-      textures[i].second == "emissiveTex")
+      textures[i].second == "emissiveTex" || textures[i].second == "anisotropyTex")
     {
       continue;
     }
@@ -1948,53 +2045,34 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderNormal(
     std::string VSSource = shaders[vtkShader::Vertex]->GetSource();
     std::string GSSource = shaders[vtkShader::Geometry]->GetSource();
 
-    // if we have point normals provided
+    // normal mapping
+    std::vector<texinfo> textures = this->GetTextures(actor);
+    bool normalMapping = std::find_if(textures.begin(), textures.end(), [](const texinfo& tex) {
+      return tex.second == "normalTex";
+    }) != textures.end();
+    bool rotationMap = std::find_if(textures.begin(), textures.end(), [](const texinfo& tex) {
+      return tex.second == "anisotropyTex";
+    }) != textures.end();
+
     if (this->VBOs->GetNumberOfComponents("normalMC") == 3)
     {
-      // normal mapping
-      std::vector<texinfo> textures = this->GetTextures(actor);
-      bool normalTex = std::find_if(textures.begin(), textures.end(), [](const texinfo& tex) {
-        return tex.second == "normalTex";
-      }) != textures.end();
-      if (normalTex && this->VBOs->GetNumberOfComponents("tangentMC") == 3 &&
-        !this->DrawingVertices)
-      {
-        vtkShaderProgram::Substitute(VSSource, "//VTK::Normal::Dec",
-          "//VTK::Normal::Dec\n"
-          "in vec3 tangentMC;\n"
-          "out vec3 tangentVCVSOutput;\n");
-
-        vtkShaderProgram::Substitute(VSSource, "//VTK::Normal::Impl",
-          "//VTK::Normal::Impl\n"
-          "  tangentVCVSOutput = normalMatrix * tangentMC;\n");
-
-        vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Dec",
-          "//VTK::Normal::Dec\n"
-          "uniform float normalScaleUniform;\n"
-          "in vec3 tangentVCVSOutput;");
-
-        vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
-          "//VTK::Normal::Impl\n"
-          "  vec3 normalTS = texture(normalTex, tcoordVCVSOutput).xyz * 2.0 - 1.0;\n"
-          "  normalTS = normalize(normalTS * vec3(normalScaleUniform, normalScaleUniform, 1.0));\n"
-          "  vec3 tangentVC = normalize(tangentVCVSOutput - dot(tangentVCVSOutput, "
-          "normalVCVSOutput) * normalVCVSOutput);\n"
-          "  vec3 bitangentVC = cross(normalVCVSOutput, tangentVC);\n"
-          "  mat3 tbn = mat3(tangentVC, bitangentVC, normalVCVSOutput);\n"
-          "  normalVCVSOutput = normalize(tbn * normalTS);\n");
-      }
       vtkShaderProgram::Substitute(VSSource, "//VTK::Normal::Dec",
+        "//VTK::Normal::Dec\n"
         "in vec3 normalMC;\n"
         "uniform mat3 normalMatrix;\n"
         "out vec3 normalVCVSOutput;");
-      vtkShaderProgram::Substitute(
-        VSSource, "//VTK::Normal::Impl", "normalVCVSOutput = normalMatrix * normalMC;");
+      vtkShaderProgram::Substitute(VSSource, "//VTK::Normal::Impl",
+        "normalVCVSOutput = normalMatrix * normalMC;\n"
+        "//VTK::Normal::Impl");
       vtkShaderProgram::Substitute(GSSource, "//VTK::Normal::Dec",
+        "//VTK::Normal::Dec\n"
         "in vec3 normalVCVSOutput[];\n"
         "out vec3 normalVCGSOutput;");
-      vtkShaderProgram::Substitute(
-        GSSource, "//VTK::Normal::Impl", "normalVCGSOutput = normalVCVSOutput[i];");
+      vtkShaderProgram::Substitute(GSSource, "//VTK::Normal::Impl",
+        "//VTK::Normal::Impl\n"
+        "normalVCGSOutput = normalVCVSOutput[i];");
       vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Dec",
+        "//VTK::Normal::Dec\n"
         "uniform mat3 normalMatrix;\n"
         "in vec3 normalVCVSOutput;");
       vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
@@ -2003,7 +2081,89 @@ void vtkOpenGLPolyDataMapper::ReplaceShaderNormal(
         //  if (int(gl_FrontFacing) == 0) does not work on mesa
         "  if (gl_FrontFacing == false) { normalVCVSOutput = -normalVCVSOutput; }\n"
         //"normalVC = normalVCVarying;"
-      );
+        "//VTK::Normal::Impl");
+
+      if ((normalMapping || actor->GetProperty()->GetAnisotropy() != 0.0) &&
+        this->VBOs->GetNumberOfComponents("tangentMC") == 3 && !this->DrawingVertices)
+      {
+        vtkShaderProgram::Substitute(GSSource, "//VTK::Normal::Dec",
+          "//VTK::Normal::Dec\n"
+          "in vec3 tangentVCVSOutput[];\n"
+          "out vec3 tangentVCGSOutput;");
+        vtkShaderProgram::Substitute(GSSource, "//VTK::Normal::Impl",
+          "//VTK::Normal::Impl\n"
+          "tangentVCGSOutput = tangentVCVSOutput[i];");
+        vtkShaderProgram::Substitute(VSSource, "//VTK::Normal::Dec",
+          "//VTK::Normal::Dec\n"
+          "in vec3 tangentMC;\n"
+          "out vec3 tangentVCVSOutput;\n");
+        vtkShaderProgram::Substitute(VSSource, "//VTK::Normal::Impl",
+          "//VTK::Normal::Impl\n"
+          "  tangentVCVSOutput = normalMatrix * tangentMC;\n");
+        vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Dec",
+          "//VTK::Normal::Dec\n"
+          "in vec3 tangentVCVSOutput;\n");
+        vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
+          " vec3 tangentVC = tangentVCVSOutput;\n"
+          "//VTK::Normal::Impl");
+
+        if (actor->GetProperty()->GetAnisotropy() != 0.0)
+        {
+          // We need to rotate the anisotropy direction (the tangent) by anisotropyRotation * 2 *
+          // PI
+          vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Dec",
+            "//VTK::Normal::Dec\n"
+            "uniform float anisotropyRotationUniform;\n");
+          if (rotationMap)
+          {
+            // Sample the texture
+            vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
+              "  vec2 anisotropySample = texture(anisotropyTex, tcoordVCVSOutput).rg;\n"
+              "  float anisotropy = anisotropySample.x * anisotropyUniform;\n"
+              "  float anisotropyRotation = anisotropySample.y * anisotropyRotationUniform;\n"
+              "//VTK::Normal::Impl");
+          }
+          else
+          {
+            vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
+              "  float anisotropy = anisotropyUniform;\n"
+              "  float anisotropyRotation = anisotropyRotationUniform;\n"
+              "//VTK::Normal::Impl");
+          }
+          vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
+            "  // Rotate the anisotropy direction (tangent) around the normal with a rotation "
+            "factor\n"
+            "  float r2pi = anisotropyRotation * 2 * PI;\n"
+            "  float s = - sin(r2pi);\n" // Counter clockwise (as in
+                                         // OSPray)
+            "  float c = cos(r2pi);\n"
+            "  vec3 Nn = normalize(normalVCVSOutput);\n"
+            "  tangentVC = (1.0-c) * dot(tangentVCVSOutput,Nn) * Nn\n"
+            "+ c * tangentVCVSOutput - s * cross(Nn, tangentVCVSOutput);\n"
+            "//VTK::Normal::Impl");
+        }
+
+        vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
+          "  tangentVC = normalize(tangentVC - dot(tangentVC, "
+          "normalVCVSOutput) * normalVCVSOutput);\n"
+          "  vec3 bitangentVC = cross(normalVCVSOutput, tangentVC);\n"
+          "//VTK::Normal::Impl");
+
+        if (normalMapping)
+        {
+          vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Dec",
+            "//VTK::Normal::Dec\n"
+            "uniform float normalScaleUniform;\n");
+
+          vtkShaderProgram::Substitute(FSSource, "//VTK::Normal::Impl",
+            "  vec3 normalTS = texture(normalTex, tcoordVCVSOutput).xyz * 2.0 - 1.0;\n"
+            "  normalTS = normalize(normalTS * vec3(normalScaleUniform, normalScaleUniform, "
+            "1.0));\n"
+            "  mat3 tbn = mat3(tangentVC, bitangentVC, normalVCVSOutput);\n"
+            "  normalVCVSOutput = normalize(tbn * normalTS);\n"
+            "//VTK::Normal::Impl");
+        }
+      }
 
       shaders[vtkShader::Vertex]->SetSource(VSSource);
       shaders[vtkShader::Geometry]->SetSource(GSSource);
@@ -2895,6 +3055,9 @@ void vtkOpenGLPolyDataMapper::SetPropertyShaderParameters(
       program->SetUniformf("aoStrengthUniform", static_cast<float>(ppty->GetOcclusionStrength()));
       program->SetUniform3f("emissiveFactorUniform", ppty->GetEmissiveFactor());
       program->SetUniform3f("edgeTintUniform", ppty->GetEdgeTint());
+      program->SetUniformf("anisotropyUniform", static_cast<float>(ppty->GetAnisotropy()));
+      program->SetUniformf(
+        "anisotropyRotationUniform", static_cast<float>(ppty->GetAnisotropyRotation()));
     }
 
     // handle specular
diff --git a/Rendering/OpenGL2/vtkToneMappingPass.h b/Rendering/OpenGL2/vtkToneMappingPass.h
index d5348ad939be97955232ce574f9a8c5f6e9b6900..6ce55a98f70b29d311d02d95b2d22752b4444c26 100644
--- a/Rendering/OpenGL2/vtkToneMappingPass.h
+++ b/Rendering/OpenGL2/vtkToneMappingPass.h
@@ -139,7 +139,7 @@ public:
   //@{
   /**
    * Maximum HDR input that is not clipped.
-   * Defalut is 11.0785
+   * Default is 11.0785
    */
   vtkSetClampMacro(HdrMax, float, 1.f, VTK_FLOAT_MAX);
   vtkGetMacro(HdrMax, float);
diff --git a/Rendering/RayTracing/vtkOSPRayPolyDataMapperNode.cxx b/Rendering/RayTracing/vtkOSPRayPolyDataMapperNode.cxx
index 1175cb9260ac8b616dfdb5c97e0ec0ebc4c5de34..ac307110b0813a3be7c0946fd08f9fa391227b49 100644
--- a/Rendering/RayTracing/vtkOSPRayPolyDataMapperNode.cxx
+++ b/Rendering/RayTracing/vtkOSPRayPolyDataMapperNode.cxx
@@ -459,10 +459,10 @@ OSPGeometricModel RenderAsTriangles(OSPData vertices, std::vector<unsigned int>&
   std::vector<unsigned int>& rIndexArray, bool useCustomMaterial, OSPMaterial actorMaterial,
   int numNormals, const std::vector<osp::vec3f>& normals, int interpolationType,
   vtkImageData* vColorTextureMap, vtkImageData* vNormalTextureMap,
-  vtkImageData* vMaterialTextureMap, int numTextureCoordinates, float* textureCoordinates,
-  const osp::vec4f& textureTransform, int numCellMaterials, std::vector<OSPMaterial>& CellMaterials,
-  int numPointColors, osp::vec4f* PointColors, int numPointValueTextureCoords,
-  float* pointValueTextureCoords, RTW::Backend* backend)
+  vtkImageData* vMaterialTextureMap, vtkImageData* vAnisotropyTextureMap, int numTextureCoordinates,
+  float* textureCoordinates, const osp::vec4f& textureTransform, int numCellMaterials,
+  std::vector<OSPMaterial>& CellMaterials, int numPointColors, osp::vec4f* PointColors,
+  int numPointValueTextureCoords, float* pointValueTextureCoords, RTW::Backend* backend)
 {
   if (backend == nullptr)
     return OSPGeometry();
@@ -561,34 +561,70 @@ OSPGeometricModel RenderAsTriangles(OSPData vertices, std::vector<unsigned int>&
       ospCommit(actorMaterial);
     }
 
-    if (interpolationType == VTK_PBR && vMaterialTextureMap && _hastm)
+    if (interpolationType == VTK_PBR && _hastm)
     {
-      vtkNew<vtkImageExtractComponents> extractRoughness;
-      extractRoughness->SetInputData(vMaterialTextureMap);
-      extractRoughness->SetComponents(1);
-      extractRoughness->Update();
-
-      vtkNew<vtkImageExtractComponents> extractMetallic;
-      extractMetallic->SetInputData(vMaterialTextureMap);
-      extractMetallic->SetComponents(2);
-      extractMetallic->Update();
-
-      vtkImageData* vRoughnessTextureMap = extractRoughness->GetOutput();
-      vtkImageData* vMetallicTextureMap = extractMetallic->GetOutput();
-
-      OSPTexture t2dR = vtkOSPRayMaterialHelpers::VTKToOSPTexture(backend, vRoughnessTextureMap);
-      ospSetObject(actorMaterial, "roughnessMap", t2dR);
-      ospSetVec4f(actorMaterial, "roughnessMap.transform", textureTransform.x, textureTransform.y,
-        textureTransform.z, textureTransform.w);
-      ospRelease(t2dR);
-
-      OSPTexture t2dM = vtkOSPRayMaterialHelpers::VTKToOSPTexture(backend, vMetallicTextureMap);
-      ospSetObject(actorMaterial, "metallicMap", t2dM);
-      ospSetVec4f(actorMaterial, "metallicMap.transform", textureTransform.x, textureTransform.y,
-        textureTransform.z, textureTransform.w);
-      ospRelease(t2dM);
 
-      ospCommit(actorMaterial);
+      if (vMaterialTextureMap)
+      {
+        vtkNew<vtkImageExtractComponents> extractRoughness;
+        extractRoughness->SetInputData(vMaterialTextureMap);
+        extractRoughness->SetComponents(1);
+        extractRoughness->Update();
+
+        vtkNew<vtkImageExtractComponents> extractMetallic;
+        extractMetallic->SetInputData(vMaterialTextureMap);
+        extractMetallic->SetComponents(2);
+        extractMetallic->Update();
+
+        vtkImageData* vRoughnessTextureMap = extractRoughness->GetOutput();
+        vtkImageData* vMetallicTextureMap = extractMetallic->GetOutput();
+
+        OSPTexture t2dR = vtkOSPRayMaterialHelpers::VTKToOSPTexture(backend, vRoughnessTextureMap);
+        ospSetObject(actorMaterial, "roughnessMap", t2dR);
+        ospSetVec4f(actorMaterial, "roughnessMap.transform", textureTransform.x, textureTransform.y,
+          textureTransform.z, textureTransform.w);
+        ospRelease(t2dR);
+
+        OSPTexture t2dM = vtkOSPRayMaterialHelpers::VTKToOSPTexture(backend, vMetallicTextureMap);
+        ospSetObject(actorMaterial, "metallicMap", t2dM);
+        ospSetVec4f(actorMaterial, "metallicMap.transform", textureTransform.x, textureTransform.y,
+          textureTransform.z, textureTransform.w);
+        ospRelease(t2dM);
+
+        ospCommit(actorMaterial);
+      }
+
+      if (vAnisotropyTextureMap)
+      {
+        vtkNew<vtkImageExtractComponents> extractAnisotropyValue;
+        extractAnisotropyValue->SetInputData(vAnisotropyTextureMap);
+        extractAnisotropyValue->SetComponents(0);
+        extractAnisotropyValue->Update();
+
+        vtkNew<vtkImageExtractComponents> extractAnisotropyRotation;
+        extractAnisotropyRotation->SetInputData(vAnisotropyTextureMap);
+        extractAnisotropyRotation->SetComponents(1);
+        extractAnisotropyRotation->Update();
+
+        vtkImageData* vAnisotropyValueTextureMap = extractAnisotropyValue->GetOutput();
+        vtkImageData* vAnisotropyRotationTextureMap = extractAnisotropyRotation->GetOutput();
+
+        OSPTexture t2dA =
+          vtkOSPRayMaterialHelpers::VTKToOSPTexture(backend, vAnisotropyValueTextureMap);
+        ospSetObject(actorMaterial, "anisotropyMap", t2dA);
+        ospSetVec4f(actorMaterial, "anisotropyMap.transform", textureTransform.x,
+          textureTransform.y, textureTransform.z, textureTransform.w);
+        ospRelease(t2dA);
+
+        OSPTexture t2dR =
+          vtkOSPRayMaterialHelpers::VTKToOSPTexture(backend, vAnisotropyRotationTextureMap);
+        ospSetObject(actorMaterial, "rotationMap", t2dR);
+        ospSetVec4f(actorMaterial, "rotationMap.transform", textureTransform.x, textureTransform.y,
+          textureTransform.z, textureTransform.w);
+        ospRelease(t2dR);
+
+        ospCommit(actorMaterial);
+      }
     }
 
     if (vColorTextureMap && _hastm)
@@ -702,6 +738,8 @@ OSPMaterial MakeActorMaterial(vtkOSPRayRendererNode* orn, OSPRenderer oRenderer,
       static_cast<float>(property->GetEdgeTint()[1]),
       static_cast<float>(property->GetEdgeTint()[2]) };
     ospSetVec3f(oMaterial, "edgeColor", edgeColor[0], edgeColor[1], edgeColor[2]);
+    ospSetFloat(oMaterial, "anisotropy", static_cast<float>(property->GetAnisotropy()));
+    ospSetFloat(oMaterial, "rotation", static_cast<float>(property->GetAnisotropyRotation()));
   }
   else
   {
@@ -899,6 +937,7 @@ void vtkOSPRayPolyDataMapperNode::ORenderPoly(void* renderer, vtkOSPRayActorNode
   vtkImageData* vColorTextureMap = nullptr;
   vtkImageData* vNormalTextureMap = nullptr;
   vtkImageData* vMaterialTextureMap = nullptr;
+  vtkImageData* vAnisotropyTextureMap = nullptr;
 
   if (texture)
   {
@@ -1123,14 +1162,20 @@ void vtkOSPRayPolyDataMapperNode::ORenderPoly(void* renderer, vtkOSPRayActorNode
           {
             vMaterialTextureMap = texture->GetInput();
           }
+
+          texture = property->GetTexture("anisotropyTex");
+          if (texture)
+          {
+            vAnisotropyTextureMap = texture->GetInput();
+          }
         }
 
         this->GeometricModels.emplace_back(vtkosp::RenderAsTriangles(position, conn.triangle_index,
           conn.triangle_reverse, useCustomMaterial, oMaterial, numNormals, normals,
           property->GetInterpolation(), vColorTextureMap, vNormalTextureMap, vMaterialTextureMap,
-          numTextureCoordinates, (float*)textureCoordinates.data(), texTransform, numCellMaterials,
-          cellMaterials, numPointColors, pointColors.data(), numPointValueTextureCoords,
-          (float*)pointValueTextureCoords.data(), backend));
+          vAnisotropyTextureMap, numTextureCoordinates, (float*)textureCoordinates.data(),
+          texTransform, numCellMaterials, cellMaterials, numPointColors, pointColors.data(),
+          numPointValueTextureCoords, (float*)pointValueTextureCoords.data(), backend));
       }
     }
   }
@@ -1207,9 +1252,9 @@ void vtkOSPRayPolyDataMapperNode::ORenderPoly(void* renderer, vtkOSPRayActorNode
         this->GeometricModels.emplace_back(vtkosp::RenderAsTriangles(position, conn.strip_index,
           conn.strip_reverse, useCustomMaterial, oMaterial, numNormals, normals,
           property->GetInterpolation(), vColorTextureMap, vNormalTextureMap, vMaterialTextureMap,
-          numTextureCoordinates, (float*)textureCoordinates.data(), texTransform, numCellMaterials,
-          cellMaterials, numPointColors, pointColors.data(), numPointValueTextureCoords,
-          (float*)pointValueTextureCoords.data(), backend));
+          vAnisotropyTextureMap, numTextureCoordinates, (float*)textureCoordinates.data(),
+          texTransform, numCellMaterials, cellMaterials, numPointColors, pointColors.data(),
+          numPointValueTextureCoords, (float*)pointValueTextureCoords.data(), backend));
       }
     }
   }
diff --git a/Testing/Data/anisotropyTex.png.sha512 b/Testing/Data/anisotropyTex.png.sha512
new file mode 100644
index 0000000000000000000000000000000000000000..9b4acde0120698c5a32a5004ef4ce2e85b32c92a
--- /dev/null
+++ b/Testing/Data/anisotropyTex.png.sha512
@@ -0,0 +1 @@
+1d9a36cbfd4d181f78d9213a5f283872cffffdf64d11a8b989da08e355d7d6a961d1e2813ca79df140c91c2e84c766efe2c9cfe374c1bf3a594b365053733753
diff --git a/Testing/Data/vtk_Anisotropy.png.sha512 b/Testing/Data/vtk_Anisotropy.png.sha512
new file mode 100644
index 0000000000000000000000000000000000000000..a865efedb1c8ba794ed5b49cc63ddf7e084f9433
--- /dev/null
+++ b/Testing/Data/vtk_Anisotropy.png.sha512
@@ -0,0 +1 @@
+18cce1f08f522172d430912eb14502928aa4783bc45b593949a05b8a313cf5d051d32a5b49dc2a439b5a86597c3afce3bd6f0a64fac08494bc2aa6ef69d2c72b