diff --git a/Charts/Core/vtkScalarsToColorsItem.cxx b/Charts/Core/vtkScalarsToColorsItem.cxx
index 69d898520e96617d350e1e4135a99793cc5476aa..86fae042eb4b5298b8cc18dbb3cf223b6ba0bb85 100644
--- a/Charts/Core/vtkScalarsToColorsItem.cxx
+++ b/Charts/Core/vtkScalarsToColorsItem.cxx
@@ -173,7 +173,8 @@ bool vtkScalarsToColorsItem::Paint(vtkContext2D* painter)
     trapezoids->Delete();
   }
 
-  if (this->PolyLinePen->GetLineType() != vtkPen::NO_PEN)
+  if (this->PolyLinePen->GetLineType() != vtkPen::NO_PEN
+    && size >= 2)
   {
     const vtkRectd& ss = this->ShiftScale;
 
diff --git a/Rendering/Core/vtkVolumeProperty.cxx b/Rendering/Core/vtkVolumeProperty.cxx
index 66a587be33037c03c20a30bdb518288962a628c2..9665cd969904b470742f68e0fd77acb9f5c5860c 100644
--- a/Rendering/Core/vtkVolumeProperty.cxx
+++ b/Rendering/Core/vtkVolumeProperty.cxx
@@ -14,9 +14,14 @@
 =========================================================================*/
 #include "vtkVolumeProperty.h"
 
+#include "vtkColorTransferFunction.h"
+#include "vtkDataArray.h"
+#include "vtkImageData.h"
+#include "vtkMath.h"
 #include "vtkObjectFactory.h"
 #include "vtkPiecewiseFunction.h"
-#include "vtkColorTransferFunction.h"
+#include "vtkPointData.h"
+
 
 vtkStandardNewMacro(vtkVolumeProperty);
 
@@ -36,8 +41,10 @@ vtkVolumeProperty::vtkVolumeProperty()
     this->ScalarOpacity[i]                   = NULL;
     this->ScalarOpacityUnitDistance[i]       = 1.0;
     this->GradientOpacity[i]                 = NULL;
+    this->TransferFunction2D[i]              = NULL;
     this->DefaultGradientOpacity[i]          = NULL;
     this->DisableGradientOpacity[i]          = 0;
+    this->TransferFunctionMode               = vtkVolumeProperty::TF_1D;
 
     this->ComponentWeight[i]                 = 1.0;
 
@@ -74,6 +81,11 @@ vtkVolumeProperty::~vtkVolumeProperty()
       this->GradientOpacity[i]->UnRegister(this);
     }
 
+    if (this->TransferFunction2D[i] != NULL)
+    {
+      this->TransferFunction2D[i]->UnRegister(this);
+    }
+
     if (this->DefaultGradientOpacity[i] != NULL)
     {
       this->DefaultGradientOpacity[i]->UnRegister(this);
@@ -140,6 +152,7 @@ void vtkVolumeProperty::UpdateMTimes()
     this->RGBTransferFunctionMTime[i].Modified();
     this->ScalarOpacityMTime[i].Modified();
     this->GradientOpacityMTime[i].Modified();
+    this->TransferFunction2DMTime[i].Modified();
   }
 }
 
@@ -157,11 +170,11 @@ vtkMTimeType vtkVolumeProperty::GetMTime()
       {
         // time that Gray transfer function pointer was set
         time = this->GrayTransferFunctionMTime[i];
-        mTime = (mTime > time ? mTime : time);
+        mTime = vtkMath::Max(mTime, time);
 
         // time that Gray transfer function was last modified
         time = this->GrayTransferFunction[i]->GetMTime();
-        mTime = (mTime > time ? mTime : time);
+        mTime = vtkMath::Max(mTime, time);
       }
     }
     else if (this->ColorChannels[i] == 3)
@@ -170,11 +183,11 @@ vtkMTimeType vtkVolumeProperty::GetMTime()
       {
         // time that RGB transfer function pointer was set
         time = this->RGBTransferFunctionMTime[i];
-        mTime = (mTime > time ? mTime : time);
+        mTime = vtkMath::Max(mTime, time);
 
         // time that RGB transfer function was last modified
         time = this->RGBTransferFunction[i]->GetMTime();
-        mTime = (mTime > time ? mTime : time);
+        mTime = vtkMath::Max(mTime, time);
       }
     }
 
@@ -183,24 +196,36 @@ vtkMTimeType vtkVolumeProperty::GetMTime()
     {
       // time that Scalar opacity transfer function pointer was set
       time = this->ScalarOpacityMTime[i];
-      mTime = (mTime > time ? mTime : time);
+      mTime = vtkMath::Max(mTime, time);
 
       // time that Scalar opacity transfer function was last modified
       time = this->ScalarOpacity[i]->GetMTime();
-      mTime = (mTime > time ? mTime : time);
+      mTime = vtkMath::Max(mTime, time);
+    }
+
+    // 2D Transfer Function MTimes
+    if (this->TransferFunction2D[i])
+    {
+      // time that the TransferFunction2D pointer was set
+      time = this->TransferFunction2DMTime[i];
+      mTime = vtkMath::Max(mTime, time);
+
+      // time that the TransferFunction2D was last modified
+      time = this->TransferFunction2D[i]->GetMTime();
+      mTime = vtkMath::Max(mTime, time);
     }
 
     if (this->GradientOpacity[i])
     {
       // time that Gradient opacity transfer function pointer was set
       time = this->GradientOpacityMTime[i];
-      mTime = (mTime > time ? mTime : time);
+      mTime = vtkMath::Max(mTime, time);
 
       if (!this->DisableGradientOpacity[i])
       {
         // time that Gradient opacity transfer function was last modified
         time = this->GradientOpacity[i]->GetMTime();
-        mTime = (mTime > time ? mTime : time);
+        mTime = vtkMath::Max(mTime, time);
       }
     }
   }
@@ -237,6 +262,7 @@ void vtkVolumeProperty::SetColor( int index, vtkPiecewiseFunction *function )
 
     this->GrayTransferFunctionMTime[index].Modified();
     this->Modified();
+    this->TransferFunctionMode = vtkVolumeProperty::TF_1D;
   }
 
   if (this->ColorChannels[index] != 1)
@@ -282,6 +308,7 @@ void vtkVolumeProperty::SetColor( int index, vtkColorTransferFunction *function
     }
     this->RGBTransferFunctionMTime[index].Modified();
     this->Modified();
+    this->TransferFunctionMode = vtkVolumeProperty::TF_1D;
   }
 
   if (this->ColorChannels[index] != 3)
@@ -328,6 +355,7 @@ void vtkVolumeProperty::SetScalarOpacity( int index, vtkPiecewiseFunction *funct
 
     this->ScalarOpacityMTime[index].Modified();
     this->Modified();
+    this->TransferFunctionMode = vtkVolumeProperty::TF_1D;
   }
 }
 
@@ -390,6 +418,7 @@ void vtkVolumeProperty::SetGradientOpacity( int index, vtkPiecewiseFunction *fun
 
     this->GradientOpacityMTime[index].Modified();
     this->Modified();
+    this->TransferFunctionMode = vtkVolumeProperty::TF_1D;
   }
 }
 
@@ -421,6 +450,45 @@ vtkPiecewiseFunction *vtkVolumeProperty::GetGradientOpacity( int index )
   return this->GetStoredGradientOpacity(index);
 }
 
+void vtkVolumeProperty::SetTransferFunction2D(int index, vtkImageData* function)
+{
+  if (this->TransferFunction2D[index] != function)
+  {
+    vtkDataArray* dataArr = function->GetPointData()->GetScalars();
+    const int* dims = function->GetDimensions();
+    if (!dataArr || dataArr->GetNumberOfComponents() != 4 ||
+      dataArr->GetDataType() != VTK_FLOAT || dims[0] == 0)
+    {
+      const int type = dataArr->GetDataType();
+      const int comp = dataArr->GetNumberOfComponents();
+      vtkErrorMacro(<< "Invalid type (" << type << ") or number of components ("
+        << comp << ") or dimensions (" << dims[0] << ", " << dims[1] << ")."
+        " Expected VTK_FLOAT, 4 Components, dimensions > 0!");
+      return;
+    }
+
+    if (this->TransferFunction2D[index] != NULL)
+    {
+      this->TransferFunction2D[index]->UnRegister(this);
+    }
+
+    this->TransferFunction2D[index] = function;
+    if (this->TransferFunction2D[index] != NULL)
+    {
+      this->TransferFunction2D[index]->Register(this);
+    }
+
+    this->TransferFunction2DMTime[index].Modified();
+    this->Modified();
+    this->TransferFunctionMode = vtkVolumeProperty::TF_2D;
+  }
+}
+
+vtkImageData* vtkVolumeProperty::GetTransferFunction2D(int index)
+{
+  return this->TransferFunction2D[index];
+}
+
 // Get the gradient opacity transfer function. Create one if none set.
 vtkPiecewiseFunction *vtkVolumeProperty::GetStoredGradientOpacity( int index )
 {
@@ -597,6 +665,11 @@ vtkTimeStamp vtkVolumeProperty::GetRGBTransferFunctionMTime( int index )
   return this->RGBTransferFunctionMTime[index];
 }
 
+vtkTimeStamp vtkVolumeProperty::GetTransferFunction2DMTime(int index)
+{
+  return this->TransferFunction2DMTime[index];
+}
+
 vtkTimeStamp vtkVolumeProperty::GetGrayTransferFunctionMTime( int index )
 {
   return this->GrayTransferFunctionMTime[index];
@@ -639,6 +712,8 @@ void vtkVolumeProperty::PrintSelf(ostream& os, vtkIndent indent)
     os << indent << "DisableGradientOpacity: "
        << (this->DisableGradientOpacity[i] ? "On" : "Off") << "\n";
 
+    os << indent << "2D Transfer Function: "
+       << this->TransferFunction2D[i] << "\n";
 
     os << indent << "ComponentWeight: "
        << this->ComponentWeight[i] << "\n";
@@ -655,6 +730,4 @@ void vtkVolumeProperty::PrintSelf(ostream& os, vtkIndent indent)
   // this->GrayTransferFunctionMTime
   // this->RGBTransferFunctionMTime
   // this->ScalarOpacityMTime
-
 }
-
diff --git a/Rendering/Core/vtkVolumeProperty.h b/Rendering/Core/vtkVolumeProperty.h
index d52c909a6bf1eab8487207baa851e14147e5bb47..f83f7fe05f01eb6edc4a0a8abb35833bde194509 100644
--- a/Rendering/Core/vtkVolumeProperty.h
+++ b/Rendering/Core/vtkVolumeProperty.h
@@ -17,18 +17,30 @@
  * @class   vtkVolumeProperty
  * @brief   represents the common properties for rendering a volume.
  *
- *
  * vtkVolumeProperty is used to represent common properties associated
  * with volume rendering. This includes properties for determining the type
  * of interpolation to use when sampling a volume, the color of a volume,
  * the scalar opacity of a volume, the gradient opacity of a volume, and the
  * shading parameters of a volume.
  *
+ * Color, scalar opacity and gradient magnitude opacity transfer functions
+ * can be set as either 3 separate 1D functions  or as a single 2D transfer
+ * function.
+ *
+ * - 1D Transfer functions (vtkVolumeProperty::TF_1D)
+ * Color, scalar opacity and gradient magnitude opacity are defined by 1
+ * vtkColorTransferFunction and 2 vtkPiecewiseFunctions respectively.
  * When the scalar opacity or the gradient opacity of a volume is not set,
  * then the function is defined to be a constant value of 1.0. When a
  * scalar and gradient opacity are both set simultaneously, then the opacity
  * is defined to be the product of the scalar opacity and gradient opacity
- * transfer functions.
+ * transfer functions. 1D transfer functions is the legacy and default behavior.
+ *
+ * - 2D Transfer functions (vtkVolumeProperty::TF_2D)
+ * Color and scalar/gradient magnitude opacity are defined by a 4-component
+ * vtkImageData instance mapping scalar value vs. gradient magnitude on its
+ * x and y axis respectively. This mode is only available if a 2D TF has been
+ * explicitly set (see SetTransferFunction2D).
  *
  * Most properties can be set per "component" for volume mappers that
  * support multiple independent components. If you are using 2 component
@@ -37,9 +49,8 @@
  * will be used (and all color functions will be ignored). Omitting the
  * index parameter on the Set/Get methods will access index = 0.
  *
- * @sa
- * vtkPiecewiseFunction vtkColorTransferFunction
-*/
+ * @sa vtkPiecewiseFunction vtkColorTransferFunction
+ */
 
 #ifndef vtkVolumeProperty_h
 #define vtkVolumeProperty_h
@@ -47,9 +58,10 @@
 #include "vtkRenderingCoreModule.h" // For export macro
 #include "vtkObject.h"
 
+class vtkColorTransferFunction;
+class vtkImageData;
 class vtkPiecewiseFunction;
 class vtkTimeStamp;
-class vtkColorTransferFunction;
 
 class VTKRENDERINGCORE_EXPORT vtkVolumeProperty : public vtkObject
 {
@@ -202,6 +214,42 @@ public:
   void SetGradientOpacity(vtkPiecewiseFunction *function)
     { this->SetGradientOpacity(0, function); }
 
+  //@{
+  /**
+   * Set/Get a 2D transfer function. Volume mappers interpret the x-axis of
+   * of this transfer function as scalar value and the y-axis as gradient
+   * magnitude. The value at (X, Y) corresponds to the color and opacity
+   * for a salar value of X and a gradient magnitude of Y.
+   */
+  void SetTransferFunction2D(int index, vtkImageData* function);
+  void SetTransferFunction2D(vtkImageData* function)
+  {
+    this->SetTransferFunction2D(0, function);
+  };
+
+  vtkImageData* GetTransferFunction2D(int index);
+  vtkImageData* GetTransferFunction2D()
+  {
+    return this->GetTransferFunction2D(0);
+  };
+
+  /**
+   * Color-opacity transfer function mode. TF_1D is its default value.
+   *  - TF_1D Mappers will use 3 separate 1D functions for color, scalar opacity
+   *  and gradient mag. opacity.
+   *  - TF_2D Mappers will use a single 2D function for color and scalar/gradient mag.
+   *  opacity.
+   */
+  enum TransferMode
+  {
+    TF_1D = 0,
+    TF_2D
+  };
+
+  vtkSetClampMacro(TransferFunctionMode, int, 0, 1)
+  vtkGetMacro(TransferFunctionMode, int)
+  //@}
+
   /**
    * Get the gradient magnitude opacity transfer function for
    * the given component.
@@ -247,8 +295,12 @@ public:
    * will not work as in the former case,  GetDisableGradientOpacity returns
    * false by default and in the later case, a default gradient opacity will be created.
    */
-  bool HasGradientOpacity(int index=0) {
-    return (this->GradientOpacity[index] != NULL);
+  bool HasGradientOpacity(int index = 0) {
+    switch(this->TransferFunctionMode) {
+      case TF_1D: return (this->GradientOpacity[index] != NULL);
+      case TF_2D: return true;
+    }
+    return false;
   }
 
   //@{
@@ -379,6 +431,18 @@ protected:
   vtkVolumeProperty();
   ~vtkVolumeProperty() VTK_OVERRIDE;
 
+  /**
+   * WARNING: INTERNAL METHOD - NOT INTENDED FOR GENERAL USE
+   * Get the time when the TransferFunction2D was set.
+   */
+  vtkTimeStamp GetTransferFunction2DMTime(int index);
+  vtkTimeStamp GetTransferFunction2DMTime()
+  {
+    return this->GetTransferFunction2DMTime(0);
+  }
+
+  virtual void CreateDefaultGradientOpacity(int index);
+
   int IndependentComponents;
   double ComponentWeight[VTK_MAX_VRCOMP];
 
@@ -398,17 +462,20 @@ protected:
 
   vtkPiecewiseFunction *GradientOpacity[VTK_MAX_VRCOMP];
   vtkTimeStamp GradientOpacityMTime[VTK_MAX_VRCOMP];
+
   vtkPiecewiseFunction *DefaultGradientOpacity[VTK_MAX_VRCOMP];
   int DisableGradientOpacity[VTK_MAX_VRCOMP];
 
+  int TransferFunctionMode;
+  vtkImageData* TransferFunction2D[VTK_MAX_VRCOMP];
+  vtkTimeStamp TransferFunction2DMTime[VTK_MAX_VRCOMP];
+
   int Shade[VTK_MAX_VRCOMP];
   double Ambient[VTK_MAX_VRCOMP];
   double Diffuse[VTK_MAX_VRCOMP];
   double Specular[VTK_MAX_VRCOMP];
   double SpecularPower[VTK_MAX_VRCOMP];
 
-  virtual void CreateDefaultGradientOpacity(int index);
-
 private:
   vtkVolumeProperty(const vtkVolumeProperty&) VTK_DELETE_FUNCTION;
   void operator=(const vtkVolumeProperty&) VTK_DELETE_FUNCTION;
diff --git a/Rendering/Volume/Testing/Cxx/CMakeLists.txt b/Rendering/Volume/Testing/Cxx/CMakeLists.txt
index e24c51332b028107a938b384e8b77342238b98ca..d2855a834e84c1513eef5849f9742fb6c438de9d 100644
--- a/Rendering/Volume/Testing/Cxx/CMakeLists.txt
+++ b/Rendering/Volume/Testing/Cxx/CMakeLists.txt
@@ -88,6 +88,7 @@ set (VolumeOpenGL2CxxTests
   TestGPURayCastTextureStreaming.cxx
   TestGPURayCastThreeComponentsAdditive.cxx
   TestGPURayCastThreeComponentsIndependent.cxx
+  TestGPURayCastTransfer2D.cxx
   TestGPURayCastTwoComponentsDependent.cxx
   TestGPURayCastTwoComponentsDependentGradient.cxx
   TestGPURayCastTwoComponentsGradient.cxx
diff --git a/Rendering/Volume/Testing/Cxx/TestGPURayCastTransfer2D.cxx b/Rendering/Volume/Testing/Cxx/TestGPURayCastTransfer2D.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f736ccf856bd9018154a7147a3fa43b64dfe6042
--- /dev/null
+++ b/Rendering/Volume/Testing/Cxx/TestGPURayCastTransfer2D.cxx
@@ -0,0 +1,164 @@
+/*=========================================================================
+
+  Program:   Visualization Toolkit
+  Module:    TestGPURayCastTransfer2D.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.
+
+=========================================================================*/
+/**
+ * Test 2D transfer function support in GPUVolumeRayCastMapper.  The transfer
+ * function is created manually using known value/gradient histogram information
+ * of the test data (tooth.hdr). A filter to create these histograms will be
+ * added in the future.
+ */
+
+#include "vtkCamera.h"
+#include "vtkColorTransferFunction.h"
+#include "vtkGPUVolumeRayCastMapper.h"
+#include "vtkImageData.h"
+#include "vtkInteractorStyleTrackballCamera.h"
+#include "vtkNew.h"
+#include "vtkNrrdReader.h"
+#include "vtkPiecewiseFunction.h"
+#include "vtkPointData.h"
+#include "vtkRegressionTestImage.h"
+#include "vtkRenderer.h"
+#include "vtkRenderWindow.h"
+#include "vtkRenderWindowInteractor.h"
+#include "vtkTestUtilities.h"
+#include "vtkFloatArray.h"
+#include "vtkVolume.h"
+#include "vtkVolumeProperty.h"
+
+
+typedef vtkSmartPointer<vtkImageData> Transfer2DPtr;
+Transfer2DPtr Create2DTransfer()
+{
+  int bins[2] = {256, 256};
+  Transfer2DPtr image = Transfer2DPtr::New();
+  image->SetDimensions(bins[0], bins[1], 1);
+  image->AllocateScalars(VTK_FLOAT, 4);
+  vtkFloatArray* arr = vtkFloatArray::SafeDownCast(
+    image->GetPointData()->GetScalars());
+
+  // Initialize to zero
+  void* dataPtr = arr->GetVoidPointer(0);
+  memset(dataPtr, 0, bins[0] * bins[1] * 4 * sizeof(float));
+
+  // Setting RGBA [1.0, 0,0, 0.05] for a square in the histogram (known)
+  // containing some of the interesting edges (e.g. tooth root).
+  for (int j = 0; j < bins[1]; j++)
+    for (int i = 0; i < bins[0]; i++)
+    {
+      if (i > 130 && i < 190 && j < 50)
+      {
+        double const jFactor = 256.0 / 50;
+
+        vtkIdType const index = bins[0] * j + i;
+        double const red = static_cast<double>(i) / bins[0];
+        double const green = jFactor * static_cast<double>(j) / bins[1];
+        double const blue = jFactor * static_cast<double>(j) / bins[1];
+        double const alpha = 0.25 * jFactor * static_cast<double>(j) / bins[0];
+
+        double color[4] = {red, green, blue, alpha};
+        arr->SetTuple(index, color);
+      }
+    }
+
+  return image;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int TestGPURayCastTransfer2D(int argc, char* argv[])
+{
+  cout << "CTEST_FULL_OUTPUT (Avoid ctest truncation of output)" << endl;
+
+  // Load data
+  char* fname = vtkTestUtilities::ExpandDataFileName(argc, argv,
+    "Data/tooth.nhdr");
+  vtkNew<vtkNrrdReader> reader;
+  reader->SetFileName(fname);
+  reader->Update();
+  delete[] fname;
+
+  vtkNew<vtkVolumeProperty> volumeProperty;
+  volumeProperty->ShadeOn();
+  volumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION);
+
+  vtkDataArray* arr = reader->GetOutput()->GetPointData()->GetScalars();
+  double range[2];
+  arr->GetRange(range);
+
+  // Prepare 1D Transfer Functions
+  vtkNew<vtkColorTransferFunction> ctf;
+  ctf->AddRGBPoint(0, 0.0, 0.0, 0.0);
+  ctf->AddRGBPoint(510, 0.4, 0.4, 1.0);
+  ctf->AddRGBPoint(640, 1.0, 1.0, 1.0);
+  ctf->AddRGBPoint(range[1], 0.9, 0.1, 0.1);
+
+  vtkNew<vtkPiecewiseFunction> pf;
+  pf->AddPoint(0, 0.00);
+  pf->AddPoint(510, 0.00);
+  pf->AddPoint(640, 0.5);
+  pf->AddPoint(range[1], 0.4);
+
+  vtkNew<vtkPiecewiseFunction> gf;
+  gf->AddPoint(0,   0.0);
+  gf->AddPoint(range[1] / 4.0, 1.0);
+
+  volumeProperty->SetScalarOpacity(pf.GetPointer());
+  volumeProperty->SetGradientOpacity(gf.GetPointer());
+  volumeProperty->SetColor(ctf.GetPointer());
+
+  // Prepare 2D Transfer Functions
+  Transfer2DPtr tf2d = Create2DTransfer();
+
+  volumeProperty->SetTransferFunction2D(tf2d);
+
+  // Setup rendering context
+  vtkNew<vtkRenderWindow> renWin;
+  renWin->SetSize(512, 512);
+  renWin->SetMultiSamples(0);
+
+  vtkNew<vtkRenderer> ren;
+  renWin->AddRenderer(ren.GetPointer());
+  ren->SetBackground(0.0, 0.0, 0.0);
+
+  vtkNew<vtkGPUVolumeRayCastMapper> mapper;
+  mapper->SetInputConnection(reader->GetOutputPort());
+  mapper->SetUseJittering(1);
+
+  vtkNew<vtkVolume> volume;
+  volume->SetMapper(mapper.GetPointer());
+  volume->SetProperty(volumeProperty.GetPointer());
+  ren->AddVolume(volume.GetPointer());
+
+  ren->ResetCamera();
+  ren->GetActiveCamera()->Elevation(-90.0);
+  ren->GetActiveCamera()->Zoom(1.4);
+
+  // Interactor
+  vtkNew<vtkRenderWindowInteractor> iren;
+  iren->SetRenderWindow(renWin.GetPointer());
+
+  vtkNew<vtkInteractorStyleTrackballCamera> style;
+  iren->SetInteractorStyle(style.GetPointer());
+
+  renWin->Render();
+
+  int retVal = vtkTesting::Test(argc, argv, renWin.GetPointer(), 90);
+  if (retVal == vtkRegressionTester::DO_INTERACTOR)
+    {
+    iren->Start();
+    }
+
+  return !((retVal == vtkTesting::PASSED) ||
+           (retVal == vtkTesting::DO_INTERACTOR));
+}
diff --git a/Rendering/Volume/Testing/Data/Baseline/TestGPURayCastTransfer2D.png.md5 b/Rendering/Volume/Testing/Data/Baseline/TestGPURayCastTransfer2D.png.md5
new file mode 100644
index 0000000000000000000000000000000000000000..ca65ba6dda98da4b97d0a3d7b3f27507a1e816fb
--- /dev/null
+++ b/Rendering/Volume/Testing/Data/Baseline/TestGPURayCastTransfer2D.png.md5
@@ -0,0 +1 @@
+528e99e377f471a6d00515fa2af93c82
diff --git a/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl b/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl
index 121a847a4215c5dc5c7c116e2c02758dc0583c29..068f763b9507cfc485d8a10987964542552b98db 100644
--- a/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl
+++ b/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl
@@ -66,10 +66,14 @@ uniform vec4 in_volume_bias;
 
 //VTK::CompositeMask::Dec
 
+//VTK::GradientCache::Dec
+
 //VTK::ComputeOpacity::Dec
 
 //VTK::ComputeGradient::Dec
 
+//VTK::ComputeGradientOpacity1D::Dec
+
 //VTK::ComputeLighting::Dec
 
 //VTK::ComputeColor::Dec
@@ -190,6 +194,8 @@ vec4 castRay(const float zStart, const float zEnd)
 
     //VTK::CompositeMask::Impl
 
+    //VTK::PreComputeGradients::Impl
+
     //VTK::Shading::Impl
 
     //VTK::RenderToImage::Impl
diff --git a/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx b/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx
index 4186f0ae75f424d9fb22e685cd3e2e2bcbe7dcd8..d009ed280053b0d536abb63121cf933d44570b2e 100644
--- a/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx
+++ b/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx
@@ -18,6 +18,7 @@
 #include "vtkOpenGLVolumeGradientOpacityTable.h"
 #include "vtkOpenGLVolumeOpacityTable.h"
 #include "vtkOpenGLVolumeRGBTable.h"
+#include "vtkOpenGLTransferFunction2D.h"
 #include "vtkVolumeShaderComposer.h"
 #include "vtkVolumeStateRAII.h"
 
@@ -117,6 +118,7 @@ public:
     this->Mask1RGBTable = 0;
     this->Mask2RGBTable =  0;
     this->GradientOpacityTables = 0;
+    this->TransferFunctions2D = NULL;
     this->CurrentMask = 0;
     this->Dimensions[0] = this->Dimensions[1] = this->Dimensions[2] = -1;
     this->TextureSize[0] = this->TextureSize[1] = this->TextureSize[2] = -1;
@@ -238,7 +240,8 @@ public:
       this->ImageSampleVAO->Delete();
       this->ImageSampleVAO = NULL;
     }
-    this->DeleteTransferFunctions();
+    this->DeleteTransfer1D();
+    this->DeleteTransfer2D();
 
     delete this->MaskTextures;
 
@@ -267,9 +270,33 @@ public:
   template<typename T>
   static void ToFloat(T (&in)[4][2], float (&out)[4][2]);
 
-  void Initialize(vtkRenderer* ren, vtkVolume* vol,
+  ///@{
+  /**
+   * \brief Setup and clean-up 1D and 2D transfer functions.
+   */
+  void InitializeTransferFunction(vtkRenderer* ren, vtkVolume* vol,
                   int noOfComponents, int independentComponents);
 
+  void UpdateTransferFunction(vtkRenderer* ren, vtkVolume* vol,
+                  int noOfComponents, int independentComponents);
+
+  void ActivateTransferFunction(vtkShaderProgram* prog, vtkVolumeProperty* volProp,
+    int numSamplers);
+
+  void DeactivateTransferFunction(vtkVolumeProperty* volumeProperty,
+    int numSamplers);
+
+  void SetupTransferFunction1D(vtkRenderer* ren, int noOfComponents,
+    int independentComponents);
+  void ReleaseGraphicsTransfer1D(vtkWindow* window);
+  void DeleteTransfer1D();
+
+  void SetupTransferFunction2D(vtkRenderer* ren, int noOfComponents,
+    int independentComponents);
+  void ReleaseGraphicsTransfer2D(vtkWindow* window);
+  void DeleteTransfer2D();
+  ///@}
+
   bool LoadMask(vtkRenderer* ren, vtkImageData* input,
                 vtkImageData* maskInput, int textureExtent[6],
                 vtkVolume* volume);
@@ -277,8 +304,6 @@ public:
   bool LoadData(vtkRenderer* ren, vtkVolume* vol, vtkVolumeProperty* volProp,
     vtkImageData* input, vtkDataArray* scalars);
 
-  void DeleteTransferFunctions();
-
   void ComputeBounds(vtkImageData* input);
 
   // Update OpenGL volume information
@@ -290,16 +315,18 @@ public:
                                   vtkVolume* vol,
                                   unsigned int component);
 
-  // Update opacity transfer function (not gradient opacity)
+  // Scalar opacity
   int UpdateOpacityTransferFunction(vtkRenderer* ren,
                                     vtkVolume* vol,
                                     unsigned int component);
 
-  // Update gradient opacity function
   int UpdateGradientOpacityTransferFunction(vtkRenderer* ren,
                                             vtkVolume* vol,
                                             unsigned int component);
 
+  void UpdateTransferFunction2D(vtkRenderer* ren, vtkVolume* vol,
+                                unsigned int component);
+
   // Update noise texture (used to reduce rendering artifacts
   // specifically banding effects)
   void CreateNoiseTexture(vtkRenderer* ren);
@@ -452,6 +479,10 @@ public:
   vtkOpenGLVolumeGradientOpacityTables* GradientOpacityTables;
   std::map<int, std::string> GradientOpacityTablesMap;
 
+  vtkOpenGLTransferFunctions2D* TransferFunctions2D;
+  std::map<int, std::string> TransferFunctions2DMap;
+  vtkTimeStamp Transfer2DTime;
+
   vtkTimeStamp ShaderBuildTime;
 
   vtkNew<vtkMatrix4x4> TextureToDataSetMat;
@@ -585,21 +616,26 @@ void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::ToFloat(
 }
 
 //----------------------------------------------------------------------------
-void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::Initialize(
-  vtkRenderer* vtkNotUsed(ren), vtkVolume* vol, int
-  noOfComponents, int independentComponents)
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::SetupTransferFunction1D(
+  vtkRenderer* ren, int noOfComponents, int independentComponents)
 {
-  this->DeleteTransferFunctions();
+  this->ReleaseGraphicsTransfer1D(ren->GetRenderWindow());
+  this->DeleteTransfer1D();
 
-  // Create RGB lookup table
-  if (noOfComponents > 1 && independentComponents)
-  {
-    this->RGBTables = new vtkOpenGLVolumeRGBTables(noOfComponents);
-  }
-  else
-  {
-    this->RGBTables = new vtkOpenGLVolumeRGBTables(1);
-  }
+  // Check component mode (independent or dependent)
+  noOfComponents = noOfComponents > 1 && independentComponents ?
+    noOfComponents : 1;
+
+  // Create RGB and opacity (scalar and gradient) lookup tables. We support up
+  // to four components in independentComponents mode.
+  this->RGBTables = new vtkOpenGLVolumeRGBTables(noOfComponents);
+  this->OpacityTables = new vtkOpenGLVolumeOpacityTables(noOfComponents);
+  this->GradientOpacityTables = new vtkOpenGLVolumeGradientOpacityTables(
+    noOfComponents);
+
+  this->OpacityTablesMap.clear();
+  this->RGBTablesMap.clear();
+  this->GradientOpacityTablesMap.clear();
 
   if (this->Parent->MaskInput != 0 &&
       this->Parent->MaskType == LabelMapMaskType)
@@ -614,35 +650,6 @@ void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::Initialize(
     }
   }
 
-  // We support up to four components
-  if (noOfComponents > 1 && independentComponents)
-  {
-    this->OpacityTables = new vtkOpenGLVolumeOpacityTables(noOfComponents);
-  }
-  else
-  {
-    this->OpacityTables = new vtkOpenGLVolumeOpacityTables(1);
-  }
-
-  if (noOfComponents > 1 && independentComponents)
-  {
-    // Assuming that all four components has gradient opacity for now
-    this->GradientOpacityTables =
-      new vtkOpenGLVolumeGradientOpacityTables(noOfComponents);
-  }
-  else
-  {
-    if (vol->GetProperty()->HasGradientOpacity())
-    {
-      this->GradientOpacityTables =
-        new vtkOpenGLVolumeGradientOpacityTables(1);
-    }
-  }
-
-  this->OpacityTablesMap.clear();
-  this->RGBTablesMap.clear();
-  this->GradientOpacityTablesMap.clear();
-
   std::ostringstream numeric;
   for (int i = 0; i < noOfComponents; ++i)
   {
@@ -669,6 +676,176 @@ void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::Initialize(
   this->InitializationTime.Modified();
 }
 
+//----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::SetupTransferFunction2D(
+  vtkRenderer* ren, int noOfComponents, int independentComponents)
+{
+  this->ReleaseGraphicsTransfer2D(ren->GetRenderWindow());
+  this->DeleteTransfer2D();
+
+  unsigned int const num = (noOfComponents > 1 && independentComponents) ?
+    noOfComponents : 1;
+  this->TransferFunctions2D = new vtkOpenGLTransferFunctions2D(num);
+
+  std::ostringstream indexStream;
+  const std::string baseName = "in_transfer2D";
+  for (unsigned int i = 0; i < num; i++)
+  {
+    if (i > 0)
+    {
+      indexStream << i;
+    }
+    this->TransferFunctions2DMap[0] = baseName + indexStream.str();
+    indexStream.str("");
+    indexStream.clear();
+  }
+
+  this->Transfer2DTime.Modified();
+}
+
+//----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::InitializeTransferFunction(
+  vtkRenderer* ren, vtkVolume* vol, int noOfComponents,
+  int independentComponents)
+{
+  const int transferMode = vol->GetProperty()->GetTransferFunctionMode();
+  switch(transferMode)
+  {
+    case vtkVolumeProperty::TF_2D:
+      this->SetupTransferFunction2D(ren, noOfComponents, independentComponents);
+      break;
+
+    case vtkVolumeProperty::TF_1D:
+    default:
+      this->SetupTransferFunction1D(ren, noOfComponents, independentComponents);
+  }
+}
+
+//----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::UpdateTransferFunction(
+  vtkRenderer* ren, vtkVolume* vol, int noOfComponents,
+  int independentComponents)
+{
+  int const transferMode = vol->GetProperty()->GetTransferFunctionMode();
+  switch(transferMode)
+  {
+    case vtkVolumeProperty::TF_1D:
+      if (independentComponents)
+      {
+        for (int i = 0; i < noOfComponents; ++i)
+        {
+          this->UpdateOpacityTransferFunction(ren, vol, i);
+          this->UpdateGradientOpacityTransferFunction(ren, vol, i);
+          this->UpdateColorTransferFunction(ren, vol, i);
+        }
+      }
+      else
+      {
+        if (noOfComponents == 2 || noOfComponents == 4)
+        {
+          this->UpdateOpacityTransferFunction(ren, vol, noOfComponents - 1);
+          this->UpdateGradientOpacityTransferFunction(ren, vol,
+                                                            noOfComponents - 1);
+          this->UpdateColorTransferFunction(ren, vol, 0);
+        }
+      }
+      break;
+
+    case vtkVolumeProperty::TF_2D:
+      if (independentComponents)
+      {
+        for (int i = 0; i < noOfComponents; ++i)
+        {
+          this->UpdateTransferFunction2D(ren, vol, i);
+        }
+      }
+      else
+      {
+        if (noOfComponents == 2 || noOfComponents == 4)
+        {
+          this->UpdateTransferFunction2D(ren, vol, 0);
+        }
+      }
+      break;
+  }
+}
+
+//----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::ActivateTransferFunction(
+  vtkShaderProgram* prog, vtkVolumeProperty* volumeProperty,
+  int numberOfSamplers)
+{
+  int const transferMode = volumeProperty->GetTransferFunctionMode();
+  switch (transferMode)
+  {
+    case vtkVolumeProperty::TF_1D:
+      for (int i = 0; i < numberOfSamplers; ++i)
+      {
+        this->OpacityTables->GetTable(i)->Activate();
+        prog->SetUniformi(
+          this->OpacityTablesMap[i].c_str(),
+          this->OpacityTables->GetTable(i)->GetTextureUnit());
+
+        if (this->Parent->BlendMode != vtkGPUVolumeRayCastMapper::ADDITIVE_BLEND)
+        {
+          this->RGBTables->GetTable(i)->Activate();
+          prog->SetUniformi(
+            this->RGBTablesMap[i].c_str(),
+            this->RGBTables->GetTable(i)->GetTextureUnit());
+        }
+
+        if (this->GradientOpacityTables)
+        {
+          this->GradientOpacityTables->GetTable(i)->Activate();
+          prog->SetUniformi(
+            this->GradientOpacityTablesMap[i].c_str(),
+            this->GradientOpacityTables->GetTable(i)->GetTextureUnit());
+        }
+      }
+      break;
+    case vtkVolumeProperty::TF_2D:
+      for (int i = 0; i < numberOfSamplers; ++i)
+      {
+        vtkOpenGLTransferFunction2D* table =
+        this->TransferFunctions2D->GetTable(i);
+        table->Activate();
+        prog->SetUniformi(this->TransferFunctions2DMap[i].c_str(),
+          table->GetTextureUnit());
+      }
+      break;
+  }
+}
+
+//-----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::DeactivateTransferFunction(
+  vtkVolumeProperty* volumeProperty, int numberOfSamplers)
+{
+  int const transferMode = volumeProperty->GetTransferFunctionMode();
+  switch(transferMode)
+  {
+    case vtkVolumeProperty::TF_1D:
+      for (int i = 0; i < numberOfSamplers; ++i)
+      {
+        this->OpacityTables->GetTable(i)->Deactivate();
+        if (this->Parent->BlendMode != vtkGPUVolumeRayCastMapper::ADDITIVE_BLEND)
+        {
+          this->RGBTables->GetTable(i)->Deactivate();
+        }
+        if (this->GradientOpacityTables)
+        {
+          this->GradientOpacityTables->GetTable(i)->Deactivate();
+        }
+      }
+      break;
+    case vtkVolumeProperty::TF_2D:
+      for (int i = 0; i < numberOfSamplers; ++i)
+      {
+        this->TransferFunctions2D->GetTable(i)->Deactivate();
+      }
+      break;
+  }
+}
+
 //-----------------------------------------------------------------------------
 bool vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::LoadMask(vtkRenderer* ren,
   vtkImageData* vtkNotUsed(input), vtkImageData* maskInput,
@@ -733,7 +910,37 @@ bool vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::LoadData(vtkRenderer* ren,
 }
 
 //----------------------------------------------------------------------------
-void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::DeleteTransferFunctions()
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::ReleaseGraphicsTransfer1D(
+  vtkWindow* window)
+{
+  if (this->RGBTables)
+  {
+    this->RGBTables->ReleaseGraphicsResources(window);
+  }
+
+  if (this->Mask1RGBTable)
+  {
+    this->Mask1RGBTable->ReleaseGraphicsResources(window);
+  }
+
+  if (this->Mask2RGBTable)
+  {
+    this->Mask2RGBTable->ReleaseGraphicsResources(window);
+  }
+
+  if (this->OpacityTables)
+  {
+    this->OpacityTables->ReleaseGraphicsResources(window);
+  }
+
+  if (this->GradientOpacityTables)
+  {
+    this->GradientOpacityTables->ReleaseGraphicsResources(window);
+  }
+}
+
+//----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::DeleteTransfer1D()
 {
   delete this->RGBTables;
   this->RGBTables = NULL;
@@ -757,6 +964,23 @@ void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::DeleteTransferFunctions()
   this->GradientOpacityTables = NULL;
 }
 
+//----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::ReleaseGraphicsTransfer2D(
+  vtkWindow* window)
+{
+  if (this->TransferFunctions2D)
+  {
+    this->TransferFunctions2D->ReleaseGraphicsResources(window);
+  }
+}
+
+//----------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::DeleteTransfer2D()
+{
+  delete this->TransferFunctions2D;
+  this->TransferFunctions2D = NULL;
+}
+
 //----------------------------------------------------------------------------
 void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::ComputeBounds(
   vtkImageData* input)
@@ -894,11 +1118,6 @@ int vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::
   UpdateOpacityTransferFunction(vtkRenderer* ren, vtkVolume* vol,
                                 unsigned int component)
 {
-  if (!vol)
-  {
-    return 1;
-  }
-
   vtkVolumeProperty* volumeProperty = vol->GetProperty();
 
   // Transfer function table index based on whether independent / dependent
@@ -939,25 +1158,46 @@ int vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::
   return 0;
 }
 
+//------------------------------------------------------------------------------
+void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::
+  UpdateTransferFunction2D(vtkRenderer* ren, vtkVolume* vol,
+  unsigned int component)
+{
+  vtkVolumeProperty* prop = vol->GetProperty();
+  int const transferMode = prop->GetTransferFunctionMode();
+  if (transferMode != vtkVolumeProperty::TF_2D)
+  {
+    return;
+  }
+
+  // Use the first LUT when using dependent components
+  unsigned int const lutIndex = prop->GetIndependentComponents() ?
+    component : 0;
+
+  vtkImageData* transfer2D = prop->GetTransferFunction2D(lutIndex);
+#if GL_ES_VERSION_3_0 != 1
+  int const interp = prop->GetInterpolationType() == VTK_LINEAR_INTERPOLATION ?
+    vtkTextureObject::Linear : vtkTextureObject::Nearest;
+#else
+  int const interp = vtkTextureObject::Nearest;
+#endif
+
+  this->TransferFunctions2D->GetTable(lutIndex)->Update(transfer2D, interp,
+      vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow()));
+}
+
 //----------------------------------------------------------------------------
 int vtkOpenGLGPUVolumeRayCastMapper::vtkInternal::
   UpdateGradientOpacityTransferFunction(vtkRenderer* ren, vtkVolume* vol,
                                         unsigned int component)
 {
-  if (!vol)
-  {
-    return 1;
-  }
-
   vtkVolumeProperty* volumeProperty = vol->GetProperty();
 
   // Transfer function table index based on whether independent / dependent
   // components. If dependent, use the first gradient opacity transfer function
   unsigned int lookupTableIndex = volumeProperty->GetIndependentComponents() ?
                                   component : 0;
-  // TODO Currently we expect the all of the tables will
-  // be initialized once and if at that time, the gradient
-  // opacity was not enabled then it is not used later.
+
   if (!volumeProperty->HasGradientOpacity(lookupTableIndex) ||
       !this->GradientOpacityTables)
   {
@@ -2584,40 +2824,11 @@ void vtkOpenGLGPUVolumeRayCastMapper::ReleaseGraphicsResources(
     }
   }
 
-  if(this->Impl->RGBTables)
-  {
-    this->Impl->RGBTables->ReleaseGraphicsResources(window);
-    delete this->Impl->RGBTables;
-    this->Impl->RGBTables = NULL;
-  }
-
-  if(this->Impl->Mask1RGBTable)
-  {
-    this->Impl->Mask1RGBTable->ReleaseGraphicsResources(window);
-    this->Impl->Mask1RGBTable->Delete();
-    this->Impl->Mask1RGBTable = NULL;
-  }
-
-  if(this->Impl->Mask2RGBTable)
-  {
-    this->Impl->Mask2RGBTable->ReleaseGraphicsResources(window);
-    this->Impl->Mask2RGBTable->Delete();
-    this->Impl->Mask2RGBTable = NULL;
-  }
+  this->Impl->ReleaseGraphicsTransfer1D(window);
+  this->Impl->DeleteTransfer1D();
 
-  if(this->Impl->OpacityTables)
-  {
-    this->Impl->OpacityTables->ReleaseGraphicsResources(window);
-    delete this->Impl->OpacityTables;
-    this->Impl->OpacityTables = NULL;
-  }
-
-  if (this->Impl->GradientOpacityTables)
-  {
-    this->Impl->GradientOpacityTables->ReleaseGraphicsResources(window);
-    delete this->Impl->GradientOpacityTables;
-    this->Impl->GradientOpacityTables = NULL;
-  }
+  this->Impl->ReleaseGraphicsTransfer2D(window);
+  this->Impl->DeleteTransfer2D();
 
   this->Impl->ReleaseResourcesTime.Modified();
 }
@@ -2791,32 +3002,67 @@ void vtkOpenGLGPUVolumeRayCastMapper::BuildShader(vtkRenderer* ren,
                            independentComponents),
     true);
 
-
   // Compute methods replacements
   //--------------------------------------------------------------------------
-  fragmentShader = vtkvolume::replace(
-    fragmentShader,
-    "//VTK::ComputeOpacity::Dec",
-    vtkvolume::ComputeOpacityDeclaration(ren, this, vol, noOfComponents,
-                                         independentComponents,
-                                         this->Impl->OpacityTablesMap),
-    true);
-
   fragmentShader = vtkvolume::replace(
     fragmentShader,
     "//VTK::ComputeGradient::Dec",
-    vtkvolume::ComputeGradientDeclaration(ren, this, vol, noOfComponents,
-                                          independentComponents,
-                                          this->Impl->GradientOpacityTablesMap),
+    vtkvolume::ComputeGradientDeclaration(vol),
     true);
 
-  fragmentShader = vtkvolume::replace(
-    fragmentShader,
-    "//VTK::ComputeColor::Dec",
-    vtkvolume::ComputeColorDeclaration(ren, this, vol, noOfComponents,
-                                       independentComponents,
-                                       this->Impl->RGBTablesMap),
-    true);
+  switch(volumeProperty->GetTransferFunctionMode())
+  {
+    case vtkVolumeProperty::TF_1D:
+      fragmentShader = vtkvolume::replace(
+        fragmentShader,
+        "//VTK::ComputeOpacity::Dec",
+        vtkvolume::ComputeOpacityDeclaration(ren, this, vol, noOfComponents,
+                                             independentComponents,
+                                             this->Impl->OpacityTablesMap),
+        true);
+
+      fragmentShader = vtkvolume::replace(
+        fragmentShader,
+        "//VTK::ComputeGradientOpacity1D::Dec",
+        vtkvolume::ComputeGradientOpacity1DDecl(vol, noOfComponents,
+                                        independentComponents,
+                                        this->Impl->GradientOpacityTablesMap),
+        true);
+
+      fragmentShader = vtkvolume::replace(
+        fragmentShader,
+        "//VTK::ComputeColor::Dec",
+        vtkvolume::ComputeColorDeclaration(ren, this, vol, noOfComponents,
+                                           independentComponents,
+                                           this->Impl->RGBTablesMap),
+        true);
+      break;
+    case vtkVolumeProperty::TF_2D:
+      fragmentShader = vtkvolume::replace(fragmentShader,
+        "//VTK::ComputeOpacity::Dec",
+        vtkvolume::ComputeOpacity2DDeclaration(ren, this, vol, noOfComponents,
+          independentComponents, this->Impl->TransferFunctions2DMap),
+        true);
+
+      fragmentShader = vtkvolume::replace(fragmentShader,
+        "//VTK::ComputeColor::Dec",
+        vtkvolume::ComputeColor2DDeclaration(ren, this, vol, noOfComponents,
+          independentComponents, this->Impl->TransferFunctions2DMap),
+        true);
+
+      fragmentShader = vtkvolume::replace(fragmentShader,
+        "//VTK::GradientCache::Dec",
+        vtkvolume::GradientCacheDec(ren, vol, noOfComponents,
+          independentComponents),
+        true);
+
+      fragmentShader = vtkvolume::replace(fragmentShader,
+        "//VTK::PreComputeGradients::Impl",
+        vtkvolume::PreComputeGradientsImpl(ren, vol, noOfComponents,
+          independentComponents),
+        true);
+      break;
+  }
 
   fragmentShader = vtkvolume::replace(
     fragmentShader,
@@ -3120,18 +3366,14 @@ void vtkOpenGLGPUVolumeRayCastMapper::GPURender(vtkRenderer* ren,
 
   // Update in_volume first to make sure states are current
   vol->Update();
-
-  // Get the input
   vtkImageData* input = this->GetTransformedInput();
 
-  // Get the volume property (must have one)
+  // vtkVolume ensures the property will be valid
   vtkVolumeProperty* volumeProperty = vol->GetProperty();
-
-  // Get the camera
   vtkOpenGLCamera* cam = vtkOpenGLCamera::SafeDownCast(ren->GetActiveCamera());
 
   // Check whether we have independent components or not
-  int independentComponents = volumeProperty->GetIndependentComponents();
+  int const independentComponents = volumeProperty->GetIndependentComponents();
 
   this->Impl->CheckPropertyKeys(vol);
 
@@ -3159,17 +3401,14 @@ void vtkOpenGLGPUVolumeRayCastMapper::GPURender(vtkRenderer* ren,
                           this->ArrayName,
                           this->CellFlag);
 
-  // How many components are there?
-  int noOfComponents = scalars->GetNumberOfComponents();
-
   // Allocate important variables
+  int const noOfComponents = scalars->GetNumberOfComponents();
   this->Impl->Bias.resize(noOfComponents, 0.0);
 
   if (this->Impl->NeedToInitializeResources ||
-      (volumeProperty->GetMTime() >
-       this->Impl->InitializationTime.GetMTime()))
+    (volumeProperty->GetMTime() > this->Impl->InitializationTime.GetMTime()))
   {
-    this->Impl->Initialize(ren, vol, noOfComponents,
+    this->Impl->InitializeTransferFunction(ren, vol, noOfComponents,
                            independentComponents);
   }
 
@@ -3210,27 +3449,8 @@ void vtkOpenGLGPUVolumeRayCastMapper::GPURender(vtkRenderer* ren,
 
   this->ComputeReductionFactor(vol->GetAllocatedRenderTime());
   this->Impl->UpdateSamplingDistance(input, ren, vol);
-
-  // Update the transfer functions
-  if (independentComponents)
-  {
-    for (int i = 0; i < noOfComponents; ++i)
-    {
-      this->Impl->UpdateOpacityTransferFunction(ren, vol, i);
-      this->Impl->UpdateGradientOpacityTransferFunction(ren, vol, i);
-      this->Impl->UpdateColorTransferFunction(ren, vol, i);
-    }
-  }
-  else
-  {
-    if (noOfComponents == 2 || noOfComponents == 4)
-    {
-      this->Impl->UpdateOpacityTransferFunction(ren, vol, noOfComponents - 1);
-      this->Impl->UpdateGradientOpacityTransferFunction(ren, vol,
-                                                        noOfComponents - 1);
-      this->Impl->UpdateColorTransferFunction(ren, vol, 0);
-    }
-  }
+  this->Impl->UpdateTransferFunction(ren, vol, noOfComponents,
+    independentComponents);
 
   // Update noise sampler texture
   if (this->UseJittering)
@@ -3503,31 +3723,8 @@ void vtkOpenGLGPUVolumeRayCastMapper::DoGPURender(vtkRenderer* ren,
   // Bind textures
   //--------------------------------------------------------------------------
   // Opacity, color, and gradient opacity samplers / textures
-  int numberOfSamplers = (independentComponents ? noOfComponents : 1);
-
-  for (int i = 0; i < numberOfSamplers; ++i)
-  {
-    this->Impl->OpacityTables->GetTable(i)->Activate();
-    prog->SetUniformi(
-      this->Impl->OpacityTablesMap[i].c_str(),
-      this->Impl->OpacityTables->GetTable(i)->GetTextureUnit());
-
-    if (this->BlendMode != vtkGPUVolumeRayCastMapper::ADDITIVE_BLEND)
-    {
-      this->Impl->RGBTables->GetTable(i)->Activate();
-      prog->SetUniformi(
-        this->Impl->RGBTablesMap[i].c_str(),
-        this->Impl->RGBTables->GetTable(i)->GetTextureUnit());
-    }
-
-    if (this->Impl->GradientOpacityTables)
-    {
-      this->Impl->GradientOpacityTables->GetTable(i)->Activate();
-      prog->SetUniformi(
-        this->Impl->GradientOpacityTablesMap[i].c_str(),
-        this->Impl->GradientOpacityTables->GetTable(i)->GetTextureUnit());
-    }
-  }
+  int const numberOfSamplers = (independentComponents ? noOfComponents : 1);
+  this->Impl->ActivateTransferFunction(prog, volumeProperty, numberOfSamplers);
 
   if (this->Impl->NoiseTextureObject)
   {
@@ -3786,18 +3983,7 @@ void vtkOpenGLGPUVolumeRayCastMapper::DoGPURender(vtkRenderer* ren,
   }
 #endif
 
-  for (int i = 0; i < numberOfSamplers; ++i)
-  {
-    this->Impl->OpacityTables->GetTable(i)->Deactivate();
-    if (this->BlendMode != vtkGPUVolumeRayCastMapper::ADDITIVE_BLEND)
-    {
-      this->Impl->RGBTables->GetTable(i)->Deactivate();
-    }
-    if (this->Impl->GradientOpacityTables)
-    {
-      this->Impl->GradientOpacityTables->GetTable(i)->Deactivate();
-    }
-  }
+  this->Impl->DeactivateTransferFunction(volumeProperty, numberOfSamplers);
 
   if (this->Impl->CurrentMask)
   {
diff --git a/Rendering/VolumeOpenGL2/vtkOpenGLTransferFunction2D.h b/Rendering/VolumeOpenGL2/vtkOpenGLTransferFunction2D.h
new file mode 100644
index 0000000000000000000000000000000000000000..379c2914147b15ed41de68864192f9c8c6c74699
--- /dev/null
+++ b/Rendering/VolumeOpenGL2/vtkOpenGLTransferFunction2D.h
@@ -0,0 +1,277 @@
+/*=========================================================================
+
+  Program:   Visualization Toolkit
+  Module:    vtkOpenGLTransferFunction2D.h
+
+  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.
+
+=========================================================================*/
+
+#ifndef vtkOpenGLTransferFunction2D_h
+#define vtkOpenGLTransferFunction2D_h
+
+#include <vtkDataArray.h>
+#include <vtkImageData.h>
+#include <vtkImageResize.h>
+#include <vtkMath.h>
+#include <vtkNew.h>
+#include <vtkObjectFactory.h>
+#include <vtkPointData.h>
+#include <vtkTextureObject.h>
+#include <vtk_glew.h>
+
+
+/**
+ * \brief 2D Transfer function container.
+ *
+ * Manages the texture fetched by the fragment shader when TransferFunction2D
+ * mode is active. Update() assumes the vtkImageData instance used as source
+ * is of type VTK_FLOAT and has 4 components (vtkVolumeProperty ensures this
+ * is the case when the function is set).
+ *
+ * \sa vtkVolumeProperty::SetTransferFunction2D
+ */
+class vtkOpenGLTransferFunction2D : public vtkObject
+{
+public:
+
+  static vtkOpenGLTransferFunction2D* New();
+
+  //--------------------------------------------------------------------------
+  void Activate()
+  {
+    if (!this->TextureObject)
+    {
+      return;
+    }
+    this->TextureObject->Activate();
+  };
+
+  //--------------------------------------------------------------------------
+  void Deactivate()
+  {
+    if (!this->TextureObject)
+    {
+      return;
+    }
+    this->TextureObject->Deactivate();
+  };
+
+  //--------------------------------------------------------------------------
+  void Update(vtkImageData* transfer2D, int interpolation,
+    vtkOpenGLRenderWindow* renWin)
+  {
+    if (!this->TextureObject)
+    {
+      this->TextureObject = vtkTextureObject::New();
+    }
+    this->TextureObject->SetContext(renWin);
+
+    // Reload texture
+    if (transfer2D->GetMTime() > this->BuildTime ||
+      this->TextureObject->GetMTime() > this->BuildTime ||
+      !this->TextureObject->GetHandle())
+    {
+      int* dims = transfer2D->GetDimensions();
+      int const width = this->GetMaximumSupportedTextureWidth(renWin, dims[0]);
+      int const height = this->GetMaximumSupportedTextureWidth(renWin, dims[1]);
+
+      // Resample if there is a size restriction
+      void* data = transfer2D->GetPointData()->GetScalars()->GetVoidPointer(0);
+      if (dims[0] != width || dims[1] != height)
+      {
+        this->ResizeFilter->SetInputData(transfer2D);
+        this->ResizeFilter->SetResizeMethodToOutputDimensions();
+        this->ResizeFilter->SetOutputDimensions(width, height, 1);
+        this->ResizeFilter->Update();
+        data = this->ResizeFilter->GetOutput()->GetPointData()->GetScalars(
+          )->GetVoidPointer(0);
+      }
+
+      this->TextureObject->SetWrapS(vtkTextureObject::ClampToEdge);
+      this->TextureObject->SetWrapT(vtkTextureObject::ClampToEdge);
+      this->TextureObject->SetMagnificationFilter(interpolation);
+      this->TextureObject->SetMinificationFilter(interpolation);
+      this->TextureObject->Create2DFromRaw(width, height, 4, VTK_FLOAT,
+        data);
+      this->LastInterpolation = interpolation;
+      this->BuildTime.Modified();
+    }
+
+    // Update filtering
+    if (this->LastInterpolation != interpolation)
+    {
+      this->LastInterpolation = interpolation;
+      this->TextureObject->SetMagnificationFilter(interpolation);
+      this->TextureObject->SetMinificationFilter(interpolation);
+    }
+  }
+
+  //--------------------------------------------------------------------------
+  inline int GetMaximumSupportedTextureWidth(vtkOpenGLRenderWindow* renWin,
+    int idealWidth)
+  {
+    if (!this->TextureObject)
+    {
+      vtkErrorMacro("vtkTextureObject not initialized!");
+      return -1;
+    }
+
+    // Try to match the next power of two.
+    idealWidth = vtkMath::NearestPowerOfTwo(idealWidth);
+    int const maxWidth = this->TextureObject->GetMaximumTextureSize(renWin);
+    if (maxWidth < 0)
+    {
+      vtkErrorMacro("Failed to query max texture size! using default 1024.");
+      return 256;
+    }
+
+    if (maxWidth >= idealWidth)
+    {
+      idealWidth = vtkMath::Max(256, idealWidth);
+      return idealWidth;
+    }
+
+    vtkWarningMacro("This OpenGL implementation does not support the required "
+      "texture size of " << idealWidth << ", falling back to maximum allowed, "
+      << maxWidth << "." << "This may cause an incorrect color table mapping.");
+
+    return maxWidth;
+  }
+
+  //--------------------------------------------------------------------------
+  int GetTextureUnit(void)
+  {
+    if (!this->TextureObject)
+    {
+      return -1;
+    }
+    return this->TextureObject->GetTextureUnit();
+  }
+
+  //--------------------------------------------------------------------------
+  void ReleaseGraphicsResources(vtkWindow *window)
+  {
+    if (this->TextureObject)
+    {
+      this->TextureObject->ReleaseGraphicsResources(window);
+      this->TextureObject->Delete();
+      this->TextureObject = NULL;
+    }
+  }
+
+protected:
+
+  //--------------------------------------------------------------------------
+  vtkOpenGLTransferFunction2D()
+  : vtkObject()
+  , TextureObject(NULL)
+  , LastInterpolation(-1)
+  {
+  }
+
+  //--------------------------------------------------------------------------
+  ~vtkOpenGLTransferFunction2D() VTK_OVERRIDE
+  {
+    if (this->TextureObject)
+    {
+      this->TextureObject->Delete();
+      this->TextureObject = NULL;
+    }
+  }
+
+  vtkNew<vtkImageResize> ResizeFilter;
+  vtkTextureObject* TextureObject;
+  int LastInterpolation;
+  vtkTimeStamp BuildTime;
+
+private:
+  vtkOpenGLTransferFunction2D(const vtkOpenGLTransferFunction2D&)
+    VTK_DELETE_FUNCTION;
+  vtkOpenGLTransferFunction2D& operator=(const vtkOpenGLTransferFunction2D&)
+    VTK_DELETE_FUNCTION;
+};
+
+vtkStandardNewMacro(vtkOpenGLTransferFunction2D);
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  \brief Container for a set of TransferFunction2D instances.
+ *
+ *  Used as a convenience class to instantiate functions for each component.
+ *
+ *  \note This class will be merged with other VolumeOpenGL2/vtk*Tables to reduce
+ *  code duplication.
+ *
+ *  \sa vtkOpenGLVolumeRGBTables
+ */
+class vtkOpenGLTransferFunctions2D
+{
+public:
+  //--------------------------------------------------------------------------
+  vtkOpenGLTransferFunctions2D(unsigned int numberOfTables)
+  {
+    this->Tables.reserve(static_cast<size_t>(numberOfTables));
+
+    for (unsigned int i = 0; i < numberOfTables; i++)
+    {
+      vtkOpenGLTransferFunction2D* table = vtkOpenGLTransferFunction2D::New();
+      this->Tables.push_back(table);
+    }
+  }
+
+  //--------------------------------------------------------------------------
+  ~vtkOpenGLTransferFunctions2D()
+  {
+    size_t const size = this->Tables.size();
+    for (size_t i = 0; i < size; i++)
+    {
+      this->Tables[i]->Delete();
+    }
+  }
+
+  //--------------------------------------------------------------------------
+  vtkOpenGLTransferFunction2D* GetTable(unsigned int i)
+  {
+    if (i >= this->Tables.size())
+    {
+      return NULL;
+    }
+    return this->Tables[i];
+  }
+
+  //--------------------------------------------------------------------------
+  size_t GetNumberOfTables()
+  {
+    return this->Tables.size();
+  }
+
+  //--------------------------------------------------------------------------
+  void ReleaseGraphicsResources(vtkWindow *window)
+  {
+    size_t const size = this->Tables.size();
+    for (size_t i = 0; i < size; ++i)
+    {
+      this->Tables[i]->ReleaseGraphicsResources(window);
+    }
+  }
+
+private:
+  vtkOpenGLTransferFunctions2D() VTK_DELETE_FUNCTION;
+  vtkOpenGLTransferFunctions2D(const vtkOpenGLTransferFunctions2D& other)
+    VTK_DELETE_FUNCTION;
+  vtkOpenGLTransferFunctions2D& operator=(const vtkOpenGLTransferFunctions2D& other)
+    VTK_DELETE_FUNCTION;
+
+  std::vector<vtkOpenGLTransferFunction2D*> Tables;
+};
+
+#endif // vtkOpenGLTransferFunction2D_h
+// VTK-HeaderTest-Exclude: vtkOpenGLTransferFunction2D.h
diff --git a/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h b/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h
index 6bb1d6a1ad68afdf5c34e830c49a84d11f44058f..dc5e31a790af81381abf453c07d839622a432b51 100644
--- a/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h
+++ b/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h
@@ -424,13 +424,9 @@ namespace vtkvolume
   }
 
   //--------------------------------------------------------------------------
-  std::string ComputeGradientDeclaration(vtkRenderer* vtkNotUsed(ren),
-                                         vtkVolumeMapper* vtkNotUsed(mapper),
-                                         vtkVolume* vol,
-                                         int noOfComponents,
-                                         int independentComponents,
-                                         std::map<int, std::string>
-                                           gradientTableMap)
+  std::string ComputeGradientOpacity1DDecl(vtkVolume* vol,
+                                int noOfComponents, int independentComponents,
+                                std::map<int, std::string> gradientTableMap)
   {
     std::string shaderStr;
     if (vol->GetProperty()->HasGradientOpacity() &&
@@ -479,6 +475,13 @@ namespace vtkvolume
         \n  }");
     }
 
+    return shaderStr;
+  }
+
+  //--------------------------------------------------------------------------
+  std::string ComputeGradientDeclaration(vtkVolume* vol)
+  {
+    std::string shaderStr;
     if (vol->GetProperty()->GetShade() &&
         !vol->GetProperty()->HasGradientOpacity())
     {
@@ -579,16 +582,35 @@ namespace vtkvolume
     );
 
     // Shading for composite blending only
-    int shadeReqd = volProperty->GetShade() &&
-                    (mapper->GetBlendMode() ==
-                     vtkVolumeMapper::COMPOSITE_BLEND);
+    int const shadeReqd = volProperty->GetShade() &&
+                  (mapper->GetBlendMode() == vtkVolumeMapper::COMPOSITE_BLEND);
+
+    int const transferMode = volProperty->GetTransferFunctionMode();
 
     if (shadeReqd || volProperty->HasGradientOpacity())
     {
-      shaderStr += std::string("\
-        \n  // Compute gradient function only once\
-        \n  vec4 gradient = computeGradient(component);"
-      );
+      switch (transferMode)
+      {
+        case vtkVolumeProperty::TF_1D:  shaderStr += std::string(
+          "  // Compute gradient function only once\n"
+          "  vec4 gradient = computeGradient(component);\n");
+          break;
+        case vtkVolumeProperty::TF_2D:
+          shaderStr += std::string(
+          "  // TransferFunction2D is enabled so the gradient for\n"
+          "  // each component has already been cached\n");
+          if (independentComponents && noOfComponents > 1)
+          {
+            shaderStr +=
+          "  vec4 gradient = g_gradients[component];\n";
+          }
+          else
+          {
+            shaderStr +=
+          "  vec4 gradient = g_gradients;\n";
+          }
+          break;
+      }
     }
 
     if (shadeReqd)
@@ -771,31 +793,35 @@ namespace vtkvolume
       );
     }
 
-    if (volProperty->HasGradientOpacity() &&
-        (noOfComponents == 1 || !independentComponents))
+    // A 2D transfer function holds scalar and gradient-magnitude combined
+    if (transferMode == vtkVolumeProperty::TF_1D)
     {
-      shaderStr += std::string("\
+      if (volProperty->HasGradientOpacity() &&
+          (noOfComponents == 1 || !independentComponents))
+      {
+        shaderStr += std::string("\
+          \n  if (gradient.w >= 0.0)\
+          \n    {\
+          \n    color.a = color.a *\
+          \n              computeGradientOpacity(gradient);\
+          \n    }"
+        );
+      }
+      else if (noOfComponents > 1 && independentComponents &&
+              volProperty->HasGradientOpacity())
+      {
+        shaderStr += std::string("\
         \n  if (gradient.w >= 0.0)\
         \n    {\
-        \n    color.a = color.a *\
-        \n              computeGradientOpacity(gradient);\
+        \n    for (int i = 0; i < in_noOfComponents; ++i)\
+        \n      {\
+        \n      color.a = color.a *\
+        \n      computeGradientOpacity(gradient, i) * in_componentWeight[i];\
+        \n      }\
         \n    }"
-      );
+        );
+      }
     }
-     else if (noOfComponents > 1 && independentComponents &&
-             volProperty->HasGradientOpacity())
-     {
-      shaderStr += std::string("\
-      \n  if (gradient.w >= 0.0)\
-      \n    {\
-      \n    for (int i = 0; i < in_noOfComponents; ++i)\
-      \n      {\
-      \n      color.a = color.a *\
-      \n      computeGradientOpacity(gradient, i) * in_componentWeight[i];\
-      \n      }\
-      \n    }"
-      );
-     }
 
     shaderStr += std::string("\
       \n  finalColor.a = color.a;\
@@ -928,48 +954,186 @@ namespace vtkvolume
 
       }
 
+      shaderStr += std::string("\
+        \nfloat computeOpacity(vec4 scalar, int component)\
+        \n{");
+
+      for (int i = 0; i < noOfComponents; ++i)
+      {
+        toString << i;
         shaderStr += std::string("\
-          \nfloat computeOpacity(vec4 scalar, int component)\
-          \n  {");
+          \n  if (component == " + toString.str() + ")");
 
-        for (int i = 0; i < noOfComponents; ++i)
-        {
-          toString << i;
-          shaderStr += std::string("\
-            \n  if (component == " + toString.str() + ")");
+        shaderStr += std::string("\
+          \n  {\
+          \n    return texture2D(" + opacityTableMap[i]);
 
-          shaderStr += std::string("\
-            \n    {\
-            \n    return texture2D(in_opacityTransferFunc");
-          shaderStr += (i == 0 ? "" : toString.str());
-          shaderStr += std::string(",vec2(scalar[" + toString.str() + "],0)).r;\
-            \n    }");
+        shaderStr += std::string(",vec2(scalar[" + toString.str() + "], 0)).r;\
+          \n  }");
 
-           // Reset
-           toString.str("");
-           toString.clear();
-        }
+         // Reset
+         toString.str("");
+         toString.clear();
+      }
 
-        shaderStr += std::string("\n  }");
-        return shaderStr;
+      shaderStr += std::string("\n}");
+      return shaderStr;
     }
     else if (noOfComponents == 2 && !independentComponents)
     {
       return std::string("\
-        \nuniform sampler2D in_opacityTransferFunc;\
+        \nuniform sampler2D " + opacityTableMap[0] + ";\
         \nfloat computeOpacity(vec4 scalar)\
-        \n  {\
-        \n  return texture2D(in_opacityTransferFunc, vec2(scalar.y, 0)).r;\
-        \n  }");
+        \n{\
+        \n  return texture2D(" + opacityTableMap[0] + ", vec2(scalar.y, 0)).r;\
+        \n}");
     }
     else
     {
       return std::string("\
-        \nuniform sampler2D in_opacityTransferFunc;\
+        \nuniform sampler2D " + opacityTableMap[0] + ";\
         \nfloat computeOpacity(vec4 scalar)\
-        \n  {\
-        \n  return texture2D(in_opacityTransferFunc, vec2(scalar.w, 0)).r;\
-        \n  }");
+        \n{\
+        \n  return texture2D(" + opacityTableMap[0] + ", vec2(scalar.w, 0)).r;\
+        \n}");
+    }
+  }
+
+  //--------------------------------------------------------------------------
+  std::string ComputeColor2DDeclaration(vtkRenderer* vtkNotUsed(ren),
+                                      vtkVolumeMapper* vtkNotUsed(mapper),
+                                      vtkVolume* vtkNotUsed(vol),
+                                      int noOfComponents,
+                                      int independentComponents,
+                                      std::map<int, std::string> colorTableMap)
+  {
+      if (noOfComponents == 1)
+      {
+        // Single component
+        return std::string(
+          "vec4 computeColor(vec4 scalar, float opacity)\n"
+          "{\n"
+          "  vec4 color = texture2D(" + colorTableMap[0]  + ",\n"
+          "    vec2(scalar.w, g_gradients.w));\n"
+          "  return computeLighting(color, 0);\n"
+          "}\n");
+      }
+      else if (noOfComponents > 1 && independentComponents)
+      {
+        // Multiple independent components
+        std::string shaderStr;
+        std::ostringstream toString;
+        shaderStr += std::string(
+          "vec4 computeColor(vec4 scalar, float opacity, int component)\n"
+          "{\n");
+
+        for (int i = 0; i < noOfComponents; ++i)
+        {
+          toString << i;
+          std::string const num = toString.str();
+          shaderStr += std::string(
+              "  if (component == " + num + ")\n"
+              "  {\n"
+              "    vec4 color = texture2D(" + colorTableMap[i] + ",\n"
+              "      vec2(scalar[" + num + "], g_gradients[" + num + "].w));\n"
+              "    return computeLighting(color, " + num + ");\n"
+              "  }\n");
+
+          toString.str("");
+          toString.clear();
+        }
+        shaderStr += std::string("}\n");
+
+        return shaderStr;
+      }
+      else if (noOfComponents == 2 && !independentComponents)
+      {
+        // Dependent components (Luminance/ Opacity)
+        return std::string(
+          "vec4 computeColor(vec4 scalar, float opacity)\n"
+          "{\n"
+          "  vec4 color = texture2D(" + colorTableMap[0]  + ",\n"
+          "    vec2(scalar.x, g_gradients.w));\n"
+          "  return computeLighting(color, 0);\n"
+          "}\n");
+      }
+      else
+      {
+        return std::string(
+          "vec4 computeColor(vec4 scalar, float opacity)\n"
+          "{\n"
+          "  return computeLighting(vec4(scalar.xyz, opacity), 0);\n"
+          "}\n");
+      }
+  }
+
+  //--------------------------------------------------------------------------
+  std::string ComputeOpacity2DDeclaration(vtkRenderer* vtkNotUsed(ren),
+                                        vtkVolumeMapper* vtkNotUsed(mapper),
+                                        vtkVolume* vtkNotUsed(vol),
+                                        int noOfComponents,
+                                        int independentComponents,
+                                        std::map<int, std::string> opacityTableMap)
+  {
+    // Declarations of <uniform sampler2D> variables are also used in
+    // ComputeColor2DDeclaration(), given that a single texture is fetched
+    // for RGBA.
+    if (noOfComponents > 1 && independentComponents)
+    {
+      // Multiple independent components
+      std::string shaderStr;
+      std::ostringstream toString;
+
+      for (int i = 0; i < noOfComponents; ++i)
+      {
+        shaderStr += std::string("\nuniform sampler2D ") +
+                     opacityTableMap[i] + std::string(";");
+
+      }
+
+      shaderStr += std::string(
+        "float computeOpacity(vec4 scalar, int component)\n"
+        "{\n");
+
+      for (int i = 0; i < noOfComponents; ++i)
+      {
+        toString << i;
+        std::string const num = toString.str();
+        shaderStr += std::string(
+          "  if (component == " + num + ")\n"
+          "  {\n"
+          "    return texture2D(" + opacityTableMap[i]+ ",\n"
+          "      vec2(scalar[" + num + "], g_gradients[" + num + "].w)).a;\n"
+          "  }\n");
+
+         toString.str("");
+         toString.clear();
+      }
+
+      shaderStr += std::string("}\n");
+      return shaderStr;
+    }
+    else if (noOfComponents == 2 && !independentComponents)
+    {
+      // Dependent components (Luminance/ Opacity)
+      return std::string(
+        "uniform sampler2D " + opacityTableMap[0] + ";\n"
+        "float computeOpacity(vec4 scalar)\n"
+        "{\n"
+        "  return texture2D(" + opacityTableMap[0] + ",\n"
+        "    vec2(scalar.y, g_gradients.w)).a;\n"
+        "}\n");
+    }
+    else
+    {
+      // Dependent compoennts (RGBA) || Single component
+      return std::string(
+        "uniform sampler2D " + opacityTableMap[0] + ";\n"
+        "float computeOpacity(vec4 scalar)\n"
+        "{\n"
+        "  return texture2D(" + opacityTableMap[0] + ",\n"
+        "    vec2(scalar.a, g_gradients.w)).a;\n"
+        "}\n");
     }
   }
 
@@ -1060,6 +1224,80 @@ namespace vtkvolume
     }
   }
 
+  //--------------------------------------------------------------------------
+  std::string GradientCacheDec(vtkRenderer* vtkNotUsed(ren),
+                                    vtkVolume* vtkNotUsed(vol),
+                                    int noOfComponents,
+                                    int independentComponents = 0)
+  {
+    std::string shader;
+    if (independentComponents)
+    {
+      if (noOfComponents == 1)
+      {
+        shader = std::string("vec4 g_gradients;");
+      }
+      else
+      {
+        // Multiple components
+        std::ostringstream numss;
+        numss << noOfComponents;
+        std::string const num = numss.str();
+        shader = std::string(
+          "vec4 g_gradients[" + num  + "];");
+      }
+    }
+    else
+    {
+      // Dependent components (only Luminance/Opacity since RGBA does not
+      // use a look-up-table).
+      if (noOfComponents == 2)
+      {
+        shader = std::string("vec4 g_gradients;");
+      }
+    }
+
+    return shader;
+  }
+
+  //--------------------------------------------------------------------------
+  std::string PreComputeGradientsImpl(vtkRenderer* vtkNotUsed(ren),
+                                    vtkVolume* vtkNotUsed(vol),
+                                    int noOfComponents,
+                                    int independentComponents = 0)
+  {
+    std::string shader;
+    if (independentComponents)
+    {
+      if (noOfComponents == 1)
+      {
+        shader = std::string(
+          "g_gradients = computeGradient(0);\n");
+      }
+      else
+      {
+        // Multiple components
+        shader = std::string(
+          "for (int comp = 0; comp < in_noOfComponents; comp++)\n"
+          "{\n"
+          "  g_gradients[comp] = computeGradient(comp); \n"
+          "}\n");
+      }
+    }
+    else
+    {
+      // Dependent components (only Luminance/Opacity since RGBA does not
+      // use a look-up-table).
+      if (noOfComponents == 2)
+      {
+        shader = std::string(
+          "g_gradients = computeGradient(0);\n");
+      }
+    }
+
+    return shader;
+  }
+
   //--------------------------------------------------------------------------
   std::string ShadingImplementation(vtkRenderer* vtkNotUsed(ren),
                                     vtkVolumeMapper* mapper,
diff --git a/Testing/Data/tooth.nhdr.md5 b/Testing/Data/tooth.nhdr.md5
new file mode 100644
index 0000000000000000000000000000000000000000..06c44d1663f724e403f9ff021d569a1d1ead22d0
--- /dev/null
+++ b/Testing/Data/tooth.nhdr.md5
@@ -0,0 +1 @@
+7777ca90d9ee0ce9777fadf56d76fbae
diff --git a/Testing/Data/tooth.raw.md5 b/Testing/Data/tooth.raw.md5
new file mode 100644
index 0000000000000000000000000000000000000000..765db12750af8417e78f71e2ad555ccf35bbe834
--- /dev/null
+++ b/Testing/Data/tooth.raw.md5
@@ -0,0 +1 @@
+3cc1b1d8882ea6b61a4e36c19042a189