diff --git a/CMake/External/CMakeLists.txt b/CMake/External/CMakeLists.txt
index 86c2dfee0e38a9350c8fbf593257328ce48af780..a5f1537100f340172c81fa71ea488fcfb89e6f87 100644
--- a/CMake/External/CMakeLists.txt
+++ b/CMake/External/CMakeLists.txt
@@ -121,5 +121,6 @@ ExternalProject_Add( ${PROJECT_NAME}
     -D${PROJECT_NAME}_USE_OMNI:BOOL=${${PROJECT_NAME}_USE_OMNI}
     -D${PROJECT_NAME}_USE_ODE:BOOL=${${PROJECT_NAME}_USE_ODE}
     -DODE_ROOT_DIR:PATH=${ODE_ROOT_DIR}
+    -D${PROJECT_NAME}_USE_Vulkan:BOOL=${${PROJECT_NAME}_USE_Vulkan}
   DEPENDS ${${PROJECT_NAME}_DEPENDENCIES}
   )
diff --git a/CMake/External/External_glfw.cmake b/CMake/External/External_glfw.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..25b847293047ac67fa752e106342e4b6bf329dee
--- /dev/null
+++ b/CMake/External/External_glfw.cmake
@@ -0,0 +1,12 @@
+#-----------------------------------------------------------------------------
+# Add External Project
+#-----------------------------------------------------------------------------
+include(imstkAddExternalProject)
+imstk_add_external_project( glfw
+  GIT_REPOSITORY https://github.com/glfw/glfw.git
+  GIT_TAG 3.2.1
+  INSTALL_COMMAND ${SKIP_STEP_COMMAND}
+  RELATIVE_INCLUDE_PATH ""
+  DEPENDENCIES ""
+  #VERBOSE
+  )
diff --git a/CMake/External/External_gli.cmake b/CMake/External/External_gli.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..90a5faa0865b92a12514cee1b8eb842abe4a302a
--- /dev/null
+++ b/CMake/External/External_gli.cmake
@@ -0,0 +1,12 @@
+#-----------------------------------------------------------------------------
+# Add External Project
+#-----------------------------------------------------------------------------
+include(imstkAddExternalProject)
+imstk_add_external_project( gli
+  GIT_REPOSITORY https://github.com/g-truc/gli.git
+  GIT_TAG 0.8.2
+  INSTALL_COMMAND ${SKIP_STEP_COMMAND}
+  RELATIVE_INCLUDE_PATH ""
+  DEPENDENCIES ""
+  #VERBOSE
+  )
diff --git a/CMake/External/External_glm.cmake b/CMake/External/External_glm.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..0ab26775feb06587cc1c173d42704d1675a00396
--- /dev/null
+++ b/CMake/External/External_glm.cmake
@@ -0,0 +1,12 @@
+#-----------------------------------------------------------------------------
+# Add External Project
+#-----------------------------------------------------------------------------
+include(imstkAddExternalProject)
+imstk_add_external_project( glm
+  GIT_REPOSITORY https://github.com/g-truc/glm.git
+  GIT_TAG 0.9.8.3
+  INSTALL_COMMAND ${SKIP_STEP_COMMAND}
+  RELATIVE_INCLUDE_PATH ""
+  DEPENDENCIES ""
+  #VERBOSE
+  )
diff --git a/CMake/FindVulkanSDK.cmake b/CMake/FindVulkanSDK.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f217fb135b4b36675e2595945479538fa429dc64
--- /dev/null
+++ b/CMake/FindVulkanSDK.cmake
@@ -0,0 +1,32 @@
+#-----------------------------------------------------------------------------
+# Vulkan renderer
+#-----------------------------------------------------------------------------
+message(STATUS "Superbuild -   Vulkan SDK => ENABLING Vulkan renderer")
+if(NOT DEFINED VulkanSDK_ROOT_DIR)
+  set(VulkanSDK_ROOT_DIR "$ENV{VULKAN_SDK}" CACHE PATH "Path to Vulkan SDK install directory." FORCE)
+endif()
+if(NOT EXISTS ${VulkanSDK_ROOT_DIR})
+  message(FATAL_ERROR "\nCan not support Vulkan renderer without Vulkan SDK.\nSet VulkanSDK_ROOT_DIR to Vulkan SDK installation directory.\n\n")
+endif()
+
+#-----------------------------------------------------------------------------
+# Find path
+#-----------------------------------------------------------------------------
+find_path(VulkanSDK_INCLUDE_DIR
+  NAMES
+    vulkan/vulkan.h
+  PATHS
+    ${VulkanSDK_ROOT_DIR}/Include
+    )
+mark_as_advanced(VulkanSDK_INCLUDE_DIR)
+
+#-----------------------------------------------------------------------------
+# Find library
+#-----------------------------------------------------------------------------
+find_library(VulkanSDK_LIBRARY
+  NAMES
+    vulkan-1
+  )
+mark_as_advanced(VulkanSDK_LIBRARY)
+
+set(VulkanSDK_LIBRARIES ${VulkanSDK_LIBRARY})
\ No newline at end of file
diff --git a/CMake/Findglfw.cmake b/CMake/Findglfw.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..50a654b62261548f52b867ff1294bb8ee87ba0bb
--- /dev/null
+++ b/CMake/Findglfw.cmake
@@ -0,0 +1,43 @@
+#-----------------------------------------------------------------------------
+# Find path
+#-----------------------------------------------------------------------------
+find_path(glfw_INCLUDE_DIR
+  NAMES
+    GLFW/glfw3.h
+  PATH_SUFFIXES
+    include
+    )
+mark_as_advanced(glfw_INCLUDE_DIR)
+message(STATUS "glfw_INCLUDE_DIR : ${glfw_INCLUDE_DIR}")
+
+#-----------------------------------------------------------------------------
+# Find library
+#-----------------------------------------------------------------------------
+find_library(glfw_LIBRARY
+  NAMES
+    glfw3
+  )
+mark_as_advanced(glfw_LIBRARY)
+#message(STATUS "glfw_LIBRARY : ${glfw_LIBRARY}")
+
+set(glfw_LIBRARIES ${glfw_LIBRARY})
+
+#-----------------------------------------------------------------------------
+# Find package
+#-----------------------------------------------------------------------------
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(glfw
+  REQUIRED_VARS
+    glfw_INCLUDE_DIR
+    glfw_LIBRARIES)
+
+#-----------------------------------------------------------------------------
+# If missing target, create it
+#-----------------------------------------------------------------------------
+if(GLFW_FOUND AND NOT TARGET glfw)
+  add_library(glfw INTERFACE IMPORTED)
+  set_target_properties(glfw PROPERTIES
+    INTERFACE_LINK_LIBRARIES "${glfw_LIBRARIES}"
+    INTERFACE_INCLUDE_DIRECTORIES "${glfw_INCLUDE_DIR}"
+  )
+endif()
diff --git a/CMake/Findgli.cmake b/CMake/Findgli.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..7391ccbb12347a57cfd42fcad76092211fc18dee
--- /dev/null
+++ b/CMake/Findgli.cmake
@@ -0,0 +1,28 @@
+#-----------------------------------------------------------------------------
+# Find path
+#-----------------------------------------------------------------------------
+find_path(gli_INCLUDE_DIR
+  NAMES
+    gli/gli.hpp
+    )
+mark_as_advanced(gli_INCLUDE_DIR)
+message(STATUS "gli_INCLUDE_DIR : ${gli_INCLUDE_DIR}")
+
+#-----------------------------------------------------------------------------
+# Find package
+#-----------------------------------------------------------------------------
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(gli
+  REQUIRED_VARS
+    gli_INCLUDE_DIR)
+
+#-----------------------------------------------------------------------------
+# If missing target, create it
+#-----------------------------------------------------------------------------
+if(GLM_FOUND AND NOT TARGET gli)
+  add_library(gli INTERFACE IMPORTED)
+  set_target_properties(gli PROPERTIES
+    INTERFACE_LINK_LIBRARIES "${gli_LIBRARIES}"
+    INTERFACE_INCLUDE_DIRECTORIES "${gli_INCLUDE_DIR}"
+  )
+endif()
diff --git a/CMake/Findglm.cmake b/CMake/Findglm.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..82c4d05a662c5ca0642fa3d8560f209ad068d95d
--- /dev/null
+++ b/CMake/Findglm.cmake
@@ -0,0 +1,28 @@
+#-----------------------------------------------------------------------------
+# Find path
+#-----------------------------------------------------------------------------
+find_path(glm_INCLUDE_DIR
+  NAMES
+    glm/glm.hpp
+    )
+mark_as_advanced(glm_INCLUDE_DIR)
+message(STATUS "glm_INCLUDE_DIR : ${glm_INCLUDE_DIR}")
+
+#-----------------------------------------------------------------------------
+# Find package
+#-----------------------------------------------------------------------------
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(glm
+  REQUIRED_VARS
+    glm_INCLUDE_DIR)
+
+#-----------------------------------------------------------------------------
+# If missing target, create it
+#-----------------------------------------------------------------------------
+if(GLM_FOUND AND NOT TARGET glm)
+  add_library(glm INTERFACE IMPORTED)
+  set_target_properties(glm PROPERTIES
+    INTERFACE_LINK_LIBRARIES "${glm_LIBRARIES}"
+    INTERFACE_INCLUDE_DIRECTORIES "${glm_INCLUDE_DIR}"
+  )
+endif()
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9b4b63dfdf59d3ba29369430bb1fd9069f5dce26..a17c8a969c9f0e394ad80e9947f3ad44e0641f09 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -88,6 +88,14 @@ if(${PROJECT_NAME}_SUPERBUILD)
     imstk_define_dependency(Uncrustify)
   endif()
 
+  option(${PROJECT_NAME}_USE_Vulkan "Use the custom Vulkan renderer." OFF)
+
+  if(${PROJECT_NAME}_USE_Vulkan)
+    imstk_define_dependency(glfw)
+    imstk_define_dependency(glm)
+    imstk_define_dependency(gli)
+  endif()
+
   if(WIN32)
     imstk_define_dependency(PThreads)
     imstk_define_dependency(Libusb) #for VRPN
@@ -196,6 +204,20 @@ endif(Uncrustify_EXECUTABLE)
 find_package( Assimp REQUIRED )
 include_directories( ${Assimp_INCLUDE_DIRS} )
 
+if( ${PROJECT_NAME}_USE_Vulkan )
+  # glfw
+  find_package(glfw)
+  include_directories( ${glfw_INCLUDE_DIR} )
+
+  # glm
+  find_package(glm)
+  include_directories( ${glm_INCLUDE_DIR} )
+
+  # gli
+  find_package(gli)
+  include_directories( ${gli_INCLUDE_DIR} )
+endif()
+
 # g3log
 find_package( g3log REQUIRED )
 include_directories( ${g3log_INCLUDE_DIR} )
@@ -238,6 +260,15 @@ else()
     remove_definitions( -DiMSTK_USE_ODE )
 endif()
 
+# Vulkan
+if(${PROJECT_NAME}_USE_Vulkan)
+  find_package( VulkanSDK )
+  include_directories( ${VulkanSDK_INCLUDE_DIR} )
+  add_definitions( -DiMSTK_USE_Vulkan )
+else()
+  remove_definitions( -DiMSTK_USE_Vulkan )
+endif()
+
 # Google Test
 if(BUILD_TESTING)
   find_package( GoogleTest REQUIRED )
diff --git a/Examples/Sandbox/CMakeLists.txt b/Examples/Sandbox/CMakeLists.txt
index 2b6073a245c6ab7fc4873710a872b6c521c18fc7..716ed702d1003c15285f67a02cf38f3dd67b010c 100644
--- a/Examples/Sandbox/CMakeLists.txt
+++ b/Examples/Sandbox/CMakeLists.txt
@@ -58,7 +58,38 @@ imstk_add_data(${PROJECT_NAME} ${FILE_LIST})
 #-----------------------------------------------------------------------------
 # Shaders
 #-----------------------------------------------------------------------------
-file(COPY ${CMAKE_SOURCE_DIR}/Source/Rendering/VTKShaders
-  DESTINATION ${PROJECT_BINARY_DIR}/Shaders)
-file(COPY ${CMAKE_SOURCE_DIR}/Source/Rendering/VTKShaders
-  DESTINATION ${CMAKE_PROGRAM_PATH}/Shaders)
\ No newline at end of file
+function(compileShaders sourceShader binaryShader)
+  add_custom_command(
+    TARGET Sandbox
+    COMMAND glslangvalidator -V ${PROJECT_BINARY_DIR}/Shaders/VulkanShaders/${sourceShader} -o ${PROJECT_BINARY_DIR}/Shaders/VulkanShaders/${binaryShader})
+endfunction()
+
+if( iMSTK_USE_Vulkan )
+  file(COPY ${CMAKE_SOURCE_DIR}/Source/Rendering/VulkanRenderer/VulkanShaders
+    DESTINATION ${PROJECT_BINARY_DIR}/Shaders)
+
+  # Mesh shaders
+  compileShaders(Mesh/mesh_vert.vert Mesh/mesh_vert.spv)
+  compileShaders(Mesh/mesh_tesc.tesc Mesh/mesh_tesc.spv)
+  compileShaders(Mesh/mesh_tese.tese Mesh/mesh_tese.spv)
+  compileShaders(Mesh/mesh_frag.frag Mesh/mesh_frag.spv)
+
+  # Post processing shaders
+  compileShaders(PostProcessing/HDR_tonemap_frag.frag PostProcessing/HDR_tonemap_frag.spv)
+  compileShaders(PostProcessing/postprocess_vert.vert PostProcessing/postprocess_vert.spv)
+  compileShaders(PostProcessing/postprocess_frag.frag PostProcessing/postprocess_frag.spv)
+  compileShaders(PostProcessing/sss_frag.frag PostProcessing/sss_frag.spv)
+  compileShaders(PostProcessing/composite_frag.frag PostProcessing/composite_frag.spv)
+  compileShaders(PostProcessing/bloom_threshold_frag.frag PostProcessing/bloom_threshold_frag.spv)
+  compileShaders(PostProcessing/blur_horizontal_frag.frag PostProcessing/blur_horizontal_frag.spv)
+  compileShaders(PostProcessing/blur_vertical_frag.frag PostProcessing/blur_vertical_frag.spv)
+  compileShaders(PostProcessing/bloom_threshold_frag.frag PostProcessing/bloom_threshold_frag.spv)
+
+  file(COPY ${PROJECT_BINARY_DIR}/Shaders/VulkanShaders
+    DESTINATION ${CMAKE_PROGRAM_PATH}/Shaders)
+else( iMSTK_USE_Vulkan )
+  file(COPY ${CMAKE_SOURCE_DIR}/Source/Rendering/VTKRenderer/VTKShaders
+    DESTINATION ${PROJECT_BINARY_DIR}/Shaders)
+  file(COPY ${CMAKE_SOURCE_DIR}/Source/Rendering/VTKRenderer/VTKShaders
+    DESTINATION ${CMAKE_PROGRAM_PATH}/Shaders)
+endif()
diff --git a/Examples/Sandbox/main.cpp b/Examples/Sandbox/main.cpp
index 330dfb392fda4484399f330405e576866aba17c1..62f9a554a4fcf573c060ea3293e8a95e1b2bde44 100644
--- a/Examples/Sandbox/main.cpp
+++ b/Examples/Sandbox/main.cpp
@@ -218,6 +218,79 @@ void testMshAndVegaIO()
     sdk->startSimulation(true);
 }
 
+void testVTKTexture()
+{
+    // Parse command line arguments
+
+    std::string inputFilename = iMSTK_DATA_ROOT "/ETI/resources/OperatingRoom/cloth.obj";
+    std::string texturename = iMSTK_DATA_ROOT "/ETI/resources/TextureOR/cloth.jpg";
+
+    std::string inputFilename1 = iMSTK_DATA_ROOT "/ETI/resources/OperatingRoom/bed1.obj";
+    std::string texturename1 = iMSTK_DATA_ROOT "/ETI/resources/TextureOR/bed-1.jpg";
+
+    vtkSmartPointer<vtkOBJReader> reader =
+        vtkSmartPointer<vtkOBJReader>::New();
+    reader->SetFileName(inputFilename.c_str());
+    reader->Update();
+
+
+    vtkSmartPointer<vtkOBJReader> reader1 =
+        vtkSmartPointer<vtkOBJReader>::New();
+    reader1->SetFileName(inputFilename1.c_str());
+    reader1->Update();
+
+    // Visualize
+    vtkSmartPointer<vtkPolyDataMapper> mapper =
+        vtkSmartPointer<vtkPolyDataMapper>::New();
+    mapper->SetInputConnection(reader->GetOutputPort());
+
+    vtkSmartPointer<vtkPolyDataMapper> mapper1 =
+        vtkSmartPointer<vtkPolyDataMapper>::New();
+    mapper1->SetInputConnection(reader1->GetOutputPort());
+
+    vtkSmartPointer<vtkActor> actor =
+        vtkSmartPointer<vtkActor>::New();
+    actor->SetMapper(mapper);
+
+    vtkSmartPointer<vtkActor> actor1 =
+        vtkSmartPointer<vtkActor>::New();
+    actor1->SetMapper(mapper1);
+
+    vtkSmartPointer<vtkJPEGReader> jpgReader =
+        vtkSmartPointer<vtkJPEGReader>::New();
+    jpgReader->SetFileName(texturename.c_str());
+    jpgReader->Update();
+    vtkSmartPointer<vtkTexture> texture = vtkSmartPointer<vtkTexture>::New();
+    texture->SetInputConnection(jpgReader->GetOutputPort());
+    texture->InterpolateOn();
+    actor->SetTexture(texture);
+
+    vtkSmartPointer<vtkJPEGReader> jpgReader1 =
+        vtkSmartPointer<vtkJPEGReader>::New();
+    jpgReader1->SetFileName(texturename1.c_str());
+    jpgReader1->Update();
+    vtkSmartPointer<vtkTexture> texture1 = vtkSmartPointer<vtkTexture>::New();
+    texture1->SetInputConnection(jpgReader1->GetOutputPort());
+    texture1->InterpolateOn();
+    actor1->SetTexture(texture1);
+
+    vtkSmartPointer<vtkRenderer> renderer =
+        vtkSmartPointer<vtkRenderer>::New();
+    renderer->AddActor(actor);
+    renderer->AddActor(actor1);
+    renderer->SetBackground(.3, .6, .3); // Background color green
+
+    vtkSmartPointer<vtkRenderWindow> renderWindow =
+        vtkSmartPointer<vtkRenderWindow>::New();
+    renderWindow->AddRenderer(renderer);
+
+    vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
+        vtkSmartPointer<vtkRenderWindowInteractor>::New();
+    renderWindowInteractor->SetRenderWindow(renderWindow);
+
+    renderWindowInteractor->Start();
+}
+
 void testMultiObjectWithTextures()
 {
     // SDK and Scene
@@ -930,7 +1003,7 @@ void testIsometricMap()
 
     // Start simulation
     sdk->setActiveScene(geometryMapTest);
-    sdk->startSimulation(VTKRenderer::Mode::DEBUG);
+    sdk->startSimulation(imstk::Renderer::Mode::DEBUG);
 }
 
 void testTetraTriangleMap()
@@ -2499,14 +2572,19 @@ void testScreenShotUtility()
     cam1->setPosition(Vec3d(-5.5, 2.5, 32));
     cam1->setFocalPoint(Vec3d(1, 1, 0));
 
+#ifndef iMSTK_USE_Vulkan
+    auto viewer = std::dynamic_pointer_cast<VTKViewer>(sdk->getViewer());
+    auto screenShotUtility
+        = std::dynamic_pointer_cast<VTKScreenCaptureUtility>(viewer->getScreenCaptureUtility());
     // Set up for screen shot
     sdk->getViewer()->getScreenCaptureUtility()->setScreenShotPrefix("screenShot_");
     // Create a call back on key press of 'b' to take the screen shot
-    sdk->getViewer()->setOnCharFunction('b', [&](VTKInteractorStyle* c) -> bool
+    viewer->setOnCharFunction('b', [&](VTKInteractorStyle* c) -> bool
     {
-        sdk->getViewer()->getScreenCaptureUtility()->saveScreenShot();
+        screenShotUtility->saveScreenShot();
         return false;
     });
+#endif
 
     // Run
     sdk->setActiveScene(sceneTest);
@@ -3293,11 +3371,11 @@ int main()
     /*------------------
     Test physics
     ------------------*/
-    testPbdVolume();
+    //testPbdVolume();
     testPbdCloth();
     //testPbdCollision();
-    testPbdFluidBenchmarking();
-    testPbdFluid();
+    //testPbdFluidBenchmarking();
+    //testPbdFluid();
     //testDeformableBody();
     //testDeformableBodyCollision();
     //liverToolInteraction();
diff --git a/Source/Geometry/Reader/imstkAssimpMeshIO.cpp b/Source/Geometry/Reader/imstkAssimpMeshIO.cpp
index 72919ef5fd6852765847eb5b8b484c30adafa1e3..b3cfca9479f2ab8e0fc3f71d15615195a47b8a6a 100644
--- a/Source/Geometry/Reader/imstkAssimpMeshIO.cpp
+++ b/Source/Geometry/Reader/imstkAssimpMeshIO.cpp
@@ -80,6 +80,7 @@ AssimpMeshIO::readMeshData(const std::string& filePath)
 
     // Vertex positions
     StdVectorOfVec3d positions(numVertices);
+
     for (unsigned int i = 0; i < numVertices; i++)
     {
         auto positionX = importedMesh->mVertices[i].x;
@@ -90,6 +91,7 @@ AssimpMeshIO::readMeshData(const std::string& filePath)
 
     // Triangles
     std::vector<SurfaceMesh::TriangleArray> triangles(numTriangles);
+
     for (unsigned int i = 0; i < numTriangles; i++)
     {
         auto triangle = importedMesh->mFaces[i];
@@ -152,7 +154,6 @@ AssimpMeshIO::readMeshData(const std::string& filePath)
         mesh->setDefaultTCoords("tCoords");
         mesh->setPointDataArray("tCoords",UVs);
     }
-
     return mesh;
 }
 }
diff --git a/Source/Materials/imstkRenderMaterial.cpp b/Source/Materials/imstkRenderMaterial.cpp
index 672246021924614bc9856269a6cc3415770cfddf..0117bc21fa71f6d766f80442d9174e6f39bb1e6a 100644
--- a/Source/Materials/imstkRenderMaterial.cpp
+++ b/Source/Materials/imstkRenderMaterial.cpp
@@ -50,6 +50,24 @@ RenderMaterial::setDisplayMode(const DisplayMode displayMode)
     m_modified = true;
 }
 
+bool
+RenderMaterial::getTessellated() const
+{
+    return m_tessellated;
+}
+
+void
+RenderMaterial::setTessellated(const bool tessellated)
+{
+    if (tessellated == m_tessellated)
+    {
+        return;
+    }
+    m_tessellated = tessellated;
+    m_stateModified = true;
+    m_modified = true;
+}
+
 float
 RenderMaterial::getLineWidth() const
 {
diff --git a/Source/Materials/imstkRenderMaterial.h b/Source/Materials/imstkRenderMaterial.h
index ed8d22154f4f3dee7d1f4f05ae076d7e87b3ef67..68bfd4dfebf0e09a2c089d336ecd6fec26bc5bba 100644
--- a/Source/Materials/imstkRenderMaterial.h
+++ b/Source/Materials/imstkRenderMaterial.h
@@ -55,6 +55,12 @@ public:
     DisplayMode getDisplayMode() const;
     void setDisplayMode(const DisplayMode displayMode);
 
+    ///
+    /// \brief Get/Set tessellated
+    ///
+    bool getTessellated() const;
+    void setTessellated(const bool tessellated);
+
     ///
     /// \brief Get/Set line width or the wireframe
     ///
@@ -129,6 +135,7 @@ protected:
 
     // State
     DisplayMode m_displayMode = DisplayMode::SURFACE;
+    bool m_tessellated = false;
     float m_lineWidth = 1.0;
     float m_pointSize = 1.0;
     bool m_backfaceCulling = true; ///< For performance, uncommon for this to be false
diff --git a/Source/Materials/imstkTexture.cpp b/Source/Materials/imstkTexture.cpp
index 4e8a4dd46a8d576f16ee23a36353e77dc8530b0a..2065bb49a313b8651ee17ad86b891daccf8de89d 100644
--- a/Source/Materials/imstkTexture.cpp
+++ b/Source/Materials/imstkTexture.cpp
@@ -40,4 +40,10 @@ Texture::getPath() const
 {
     return m_path;
 }
+
+bool
+Texture::getMipmapsEnabled()
+{
+    return m_mipmapsEnabled;
+}
 }
\ No newline at end of file
diff --git a/Source/Materials/imstkTexture.h b/Source/Materials/imstkTexture.h
index 9484e8224273a73b50f538b1a4f5e55c51aa60ac..16b60bc0131b95e46c9c38b60d0f50538e807e45 100644
--- a/Source/Materials/imstkTexture.h
+++ b/Source/Materials/imstkTexture.h
@@ -30,9 +30,6 @@ namespace imstk
 ///
 /// \class Texture
 ///
-/// \brief iMSTK texture class. There are a few texture types that
-///     dictate how texture are to be treated.
-///
 class Texture
 {
 public:
@@ -46,9 +43,12 @@ public:
         SPECULAR,
         ROUGHNESS,
         METALNESS,
+        SUBSURFACE_SCATTERING,
         AMBIENT_OCCLUSION,
         CAVITY,
         CUBEMAP,
+        IRRADIANCE_CUBEMAP,
+        RADIANCE_CUBEMAP,
         NONE
     };
 
@@ -74,10 +74,17 @@ public:
     ///
     const std::string getPath() const;
 
-protected:
+    ///
+    /// \brief Get type
+    ///
+    bool getMipmapsEnabled();
 
+protected:
     Type m_type;            ///< Texture type
     std::string m_path;     ///< Texture file path
+
+    // Helps with texture aliasing (and a little with performance)
+    bool m_mipmapsEnabled = true;
 };
 }
 
diff --git a/Source/Rendering/CMakeLists.txt b/Source/Rendering/CMakeLists.txt
index b7f79ac025cc5fd0940a476f55aad641aa0db995..548319723db63a1fea32a47d669e3eafb8679397 100644
--- a/Source/Rendering/CMakeLists.txt
+++ b/Source/Rendering/CMakeLists.txt
@@ -1,46 +1,111 @@
 #-----------------------------------------------------------------------------
 # Create target
 #-----------------------------------------------------------------------------
+
+set(VTK_H_FILES
+    VTKRenderer/imstkVTKRenderer.h
+    VTKRenderer/imstkVTKTextureDelegate.h
+    VTKRenderer/imstkVTKCustomPolyDataMapper.h
+    VTKRenderer/RenderDelegate/imstkVTKCubeRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKLineMeshRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKPlaneRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKSphereRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKCapsuleRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKCylinderRenderDelegate.h
+    VTKRenderer/RenderDelegate/imstkVTKPointSetRenderDelegate.h)
+
+set(VTK_CPP_FILES
+    VTKRenderer/imstkVTKRenderer.cpp
+    VTKRenderer/imstkVTKTextureDelegate.cpp
+    VTKRenderer/imstkVTKCustomPolyDataMapper.cpp
+    VTKRenderer/RenderDelegate/imstkVTKCubeRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKLineMeshRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKPlaneRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKSphereRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKCapsuleRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKCylinderRenderDelegate.cpp
+    VTKRenderer/RenderDelegate/imstkVTKPointSetRenderDelegate.cpp)
+
+set(VULKAN_H_FILES
+    VulkanRenderer/imstkVulkanRenderer.h
+    VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.h
+    VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.h
+    VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.h
+    VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.h
+    VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.h
+    VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.h
+    VulkanRenderer/imstkVulkanMaterialDelegate.h
+    VulkanRenderer/imstkVulkanTextureDelegate.h
+    VulkanRenderer/imstkVulkanMemoryManager.h
+    VulkanRenderer/imstkVulkanValidation.h
+    VulkanRenderer/imstkVulkanBuffer.h
+    VulkanRenderer/imstkVulkanUniformBuffer.h
+    VulkanRenderer/imstkVulkanVertexBuffer.h
+    VulkanRenderer/imstkVulkanFramebuffer.h
+    VulkanRenderer/PostProcessing/imstkVulkanPostProcess.h
+    VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.h)
+
+set(VULKAN_CPP_FILES
+    VulkanRenderer/imstkVulkanRenderer.cpp
+    VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.cpp
+    VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.cpp
+    VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.cpp
+    VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp
+    VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.cpp
+    VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.cpp
+    VulkanRenderer/imstkVulkanMaterialDelegate.cpp
+    VulkanRenderer/imstkVulkanTextureDelegate.cpp
+    VulkanRenderer/imstkVulkanMemoryManager.cpp
+    VulkanRenderer/imstkVulkanUniformBuffer.cpp
+    VulkanRenderer/imstkVulkanVertexBuffer.cpp
+    VulkanRenderer/imstkVulkanFramebuffer.cpp
+    VulkanRenderer/PostProcessing/imstkVulkanPostProcess.cpp
+    VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.cpp)
+
+if( NOT iMSTK_USE_Vulkan )
+  set(RENDERING_H_FILES ${VTK_H_FILES})
+  set(RENDERING_CPP_FILES ${VTK_CPP_FILES})
+  set(RENDERING_SUBDIR
+    VTKRenderer
+    VTKRenderer/RenderDelegate)
+  set(RENDERING_DEPENDENCIES)
+else()
+  set(RENDERING_H_FILES ${VULKAN_H_FILES})
+  set(RENDERING_CPP_FILES ${VULKAN_CPP_FILES})
+  set(RENDERING_SUBDIR
+    VulkanRenderer
+    VulkanRenderer/RenderDelegate
+    VulkanRenderer/PostProcessing)
+  set(RENDERING_DEPENDENCIES
+    ${VulkanSDK_LIBRARIES}
+    glfw
+    glm
+    gli)
+endif()
+
 include(imstkAddLibrary)
 imstk_add_library( Rendering
   H_FILES
-    imstkVTKRenderer.h
-    imstkVTKTextureDelegate.h
-    imstkVTKCustomPolyDataMapper.h
-    RenderDelegate/imstkVTKCubeRenderDelegate.h
-    RenderDelegate/imstkVTKLineMeshRenderDelegate.h
-    RenderDelegate/imstkVTKPlaneRenderDelegate.h
-    RenderDelegate/imstkVTKCylinderRenderDelegate.h
-    RenderDelegate/imstkVTKCapsuleRenderDelegate.h
-    RenderDelegate/imstkVTKRenderDelegate.h
-    RenderDelegate/imstkVTKSphereRenderDelegate.h
-    RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.h
-    RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.h
-    RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.h
-    RenderDelegate/imstkVTKPointSetRenderDelegate.h
-    RenderDelegate/vtkCapsuleSource.h
-
+    imstkRenderer.h
+    ${RENDERING_H_FILES}
+    vtkCapsuleSource.h
   CPP_FILES
-    imstkVTKRenderer.cpp
-    imstkVTKTextureDelegate.cpp
-    imstkVTKCustomPolyDataMapper.cpp
-    RenderDelegate/imstkVTKCubeRenderDelegate.cpp
-    RenderDelegate/imstkVTKLineMeshRenderDelegate.cpp
-    RenderDelegate/imstkVTKPlaneRenderDelegate.cpp
-    RenderDelegate/imstkVTKCylinderRenderDelegate.cpp
-    RenderDelegate/imstkVTKCapsuleRenderDelegate.cpp
-    RenderDelegate/imstkVTKRenderDelegate.cpp
-    RenderDelegate/imstkVTKSphereRenderDelegate.cpp
-    RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.cpp
-    RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.cpp
-    RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.cpp
-    RenderDelegate/imstkVTKPointSetRenderDelegate.cpp
-    RenderDelegate/vtkCapsuleSource.cpp
-
+    imstkRenderer.cpp
+    ${RENDERING_CPP_FILES}
+    vtkCapsuleSource.cpp
   SUBDIR_LIST
-    RenderDelegate
+    ${RENDERING_SUBDIR}
   DEPENDS
     ${VTK_LIBRARIES}
+    ${RENDERING_DEPENDENCIES}
     Scene
   #VERBOSE
   )
diff --git a/Source/Rendering/RenderDelegate/imstkVTKCapsuleRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCapsuleRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKCapsuleRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCapsuleRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKCapsuleRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCapsuleRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKCapsuleRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCapsuleRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKCubeRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCubeRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKCubeRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCubeRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKCubeRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCubeRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKCubeRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCubeRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKCylinderRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCylinderRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKCylinderRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCylinderRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKCylinderRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCylinderRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKCylinderRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKCylinderRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKHexahedralMeshRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKLineMeshRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKLineMeshRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKLineMeshRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKLineMeshRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKLineMeshRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKLineMeshRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKLineMeshRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKLineMeshRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKPlaneRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPlaneRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKPlaneRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPlaneRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKPlaneRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPlaneRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKPlaneRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPlaneRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKPointSetRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPointSetRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKPointSetRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPointSetRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKPointSetRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPointSetRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKPointSetRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKPointSetRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKSphereRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSphereRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKSphereRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSphereRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKSphereRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSphereRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKSphereRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSphereRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKSurfaceMeshRenderDelegate.h
diff --git a/Source/Rendering/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.cpp b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.cpp
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.cpp
diff --git a/Source/Rendering/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.h b/Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.h
rename to Source/Rendering/VTKRenderer/RenderDelegate/imstkVTKTetrahedralMeshRenderDelegate.h
diff --git a/Source/Rendering/VTKShaders/mesh.frag b/Source/Rendering/VTKRenderer/VTKShaders/mesh.frag
similarity index 100%
rename from Source/Rendering/VTKShaders/mesh.frag
rename to Source/Rendering/VTKRenderer/VTKShaders/mesh.frag
diff --git a/Source/Rendering/VTKShaders/mesh.vert b/Source/Rendering/VTKRenderer/VTKShaders/mesh.vert
similarity index 100%
rename from Source/Rendering/VTKShaders/mesh.vert
rename to Source/Rendering/VTKRenderer/VTKShaders/mesh.vert
diff --git a/Source/Rendering/imstkVTKCustomPolyDataMapper.cpp b/Source/Rendering/VTKRenderer/imstkVTKCustomPolyDataMapper.cpp
similarity index 100%
rename from Source/Rendering/imstkVTKCustomPolyDataMapper.cpp
rename to Source/Rendering/VTKRenderer/imstkVTKCustomPolyDataMapper.cpp
diff --git a/Source/Rendering/imstkVTKCustomPolyDataMapper.h b/Source/Rendering/VTKRenderer/imstkVTKCustomPolyDataMapper.h
similarity index 100%
rename from Source/Rendering/imstkVTKCustomPolyDataMapper.h
rename to Source/Rendering/VTKRenderer/imstkVTKCustomPolyDataMapper.h
diff --git a/Source/Rendering/imstkVTKRenderer.cpp b/Source/Rendering/VTKRenderer/imstkVTKRenderer.cpp
similarity index 98%
rename from Source/Rendering/imstkVTKRenderer.cpp
rename to Source/Rendering/VTKRenderer/imstkVTKRenderer.cpp
index 9be6bbe06ad6138fd96d2dc3fa2f821abdfdeac5..93908e2dc61fc8dd36f36c6ee09c5cf4d217deab 100644
--- a/Source/Rendering/imstkVTKRenderer.cpp
+++ b/Source/Rendering/VTKRenderer/imstkVTKRenderer.cpp
@@ -122,7 +122,7 @@ VTKRenderer::getVtkRenderer() const
 }
 
 void
-VTKRenderer::setMode(Mode mode)
+VTKRenderer::setMode(Renderer::Mode mode)
 {
     if( mode == Mode::EMPTY && m_currentMode != Mode::EMPTY )
     {
@@ -174,12 +174,6 @@ VTKRenderer::setMode(Mode mode)
     m_currentMode = mode;
 }
 
-const VTKRenderer::Mode&
-VTKRenderer::getMode()
-{
-    return m_currentMode;
-}
-
 void
 VTKRenderer::updateSceneCamera(std::shared_ptr<Camera> imstkCam)
 {
diff --git a/Source/Rendering/imstkVTKRenderer.h b/Source/Rendering/VTKRenderer/imstkVTKRenderer.h
similarity index 87%
rename from Source/Rendering/imstkVTKRenderer.h
rename to Source/Rendering/VTKRenderer/imstkVTKRenderer.h
index b134f53589403bfa248a2b0d2c6fa84889513794..29d4424ff5c969f73e5b2bfff1577af972d10f55 100644
--- a/Source/Rendering/imstkVTKRenderer.h
+++ b/Source/Rendering/VTKRenderer/imstkVTKRenderer.h
@@ -28,6 +28,7 @@
 #include "imstkMath.h"
 #include "imstkTextureManager.h"
 #include "imstkVTKTextureDelegate.h"
+#include "imstkRenderer.h"
 
 #include "vtkSmartPointer.h"
 #include "vtkRenderer.h"
@@ -46,19 +47,9 @@ class VTKRenderDelegate;
 ///
 /// \brief
 ///
-class VTKRenderer
+class VTKRenderer : public Renderer
 {
 public:
-    ///
-    /// \brief Enumerations for the render mode
-    ///
-    enum Mode
-    {
-        EMPTY,
-        DEBUG,
-        SIMULATION
-    };
-
     ///
     /// \brief Constructor
     ///
@@ -73,8 +64,7 @@ public:
     /// \brief Set/Get the rendering mode which defined the
     /// visibility of the renderer actors and the default camera
     ///
-    void setMode(Mode mode);
-    const Mode& getMode();
+    virtual void setMode(Mode mode);
 
     ///
     /// \brief
@@ -94,7 +84,7 @@ public:
     ///
     /// \brief Update background colors
     ///
-    void updateBackground(const Vec3d color1, const Vec3d color2 = Vec3d::Zero(), const bool gradientBackground = false);
+    virtual void updateBackground(const Vec3d color1, const Vec3d color2 = Vec3d::Zero(), const bool gradientBackground = false);
 
 protected:
     ///
@@ -116,8 +106,6 @@ protected:
 
     std::vector<std::shared_ptr<VTKRenderDelegate>> m_renderDelegates;
 
-    Mode m_currentMode = Mode::EMPTY;
-
     TextureManager<VTKTextureDelegate> m_textureManager;
 };
 }
diff --git a/Source/Rendering/imstkVTKTextureDelegate.cpp b/Source/Rendering/VTKRenderer/imstkVTKTextureDelegate.cpp
similarity index 100%
rename from Source/Rendering/imstkVTKTextureDelegate.cpp
rename to Source/Rendering/VTKRenderer/imstkVTKTextureDelegate.cpp
diff --git a/Source/Rendering/imstkVTKTextureDelegate.h b/Source/Rendering/VTKRenderer/imstkVTKTextureDelegate.h
similarity index 100%
rename from Source/Rendering/imstkVTKTextureDelegate.h
rename to Source/Rendering/VTKRenderer/imstkVTKTextureDelegate.h
diff --git a/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcess.cpp b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcess.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a7c1dc4a60e3d7afa730155e2f144f1ef8bd4b04
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcess.cpp
@@ -0,0 +1,571 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanPostProcess.h"
+
+#include "imstkVulkanRenderer.h"
+
+namespace imstk
+{
+VulkanPostProcess::VulkanPostProcess(VulkanRenderer * renderer, unsigned int level, bool lastPass)
+{
+    m_lastPass = lastPass;
+    m_downsampleLevels = level;
+    this->createFramebuffer(renderer, level, lastPass);
+}
+
+void
+VulkanPostProcess::initialize(VulkanRenderer * renderer, std::string fragmentShaderPath)
+{
+    this->initializeFramebuffer(renderer);
+    this->createDescriptorSetLayouts(renderer);
+    this->createFullscreenQuad(renderer);
+    this->createPipeline(renderer, fragmentShaderPath);
+    this->createDescriptors(renderer);
+}
+
+void
+VulkanPostProcess::createPipeline(VulkanRenderer * renderer, std::string fragmentSource)
+{
+    // The vertex shader should be the same for every postprocess
+    std::string vertexShaderPath = "./Shaders/VulkanShaders/PostProcessing/postprocess_vert.spv";
+    std::ifstream vertexShaderDataStream(vertexShaderPath, std::ios_base::binary);
+    char vertexShaderData[16000];
+    vertexShaderDataStream.read(vertexShaderData, 15999);
+    vertexShaderData[(int)vertexShaderDataStream.gcount()] = '\0';
+
+    VkShaderModuleCreateInfo vertexShaderInfo;
+    vertexShaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    vertexShaderInfo.pNext = nullptr;
+    vertexShaderInfo.flags = 0;
+    vertexShaderInfo.codeSize = vertexShaderDataStream.gcount();
+    vertexShaderInfo.pCode = (uint32_t *)vertexShaderData;
+    if (vkCreateShaderModule(renderer->m_renderDevice, &vertexShaderInfo, nullptr, &m_pipelineComponents.vertexShader) != VK_SUCCESS)
+    {
+        LOG(FATAL) << "Unable to build vertex shader : " << vertexShaderPath;
+    }
+
+    std::string fragmentShaderPath = fragmentSource;
+    std::ifstream fragmentShaderDataStream(fragmentShaderPath, std::ios_base::binary);
+    char fragmentShaderData[16000];
+    fragmentShaderDataStream.read(fragmentShaderData, 15999);
+    fragmentShaderData[(int)fragmentShaderDataStream.gcount()] = '\0';
+
+    VkShaderModuleCreateInfo fragmentShaderInfo;
+    fragmentShaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    fragmentShaderInfo.pNext = nullptr;
+    fragmentShaderInfo.flags = 0;
+    fragmentShaderInfo.codeSize = fragmentShaderDataStream.gcount();
+    fragmentShaderInfo.pCode = (uint32_t *)fragmentShaderData;
+    if (vkCreateShaderModule(renderer->m_renderDevice, &fragmentShaderInfo, nullptr, &m_pipelineComponents.fragmentShader) != VK_SUCCESS)
+    {
+        LOG(FATAL) << "Unable to build fragment shader: " << fragmentShaderPath;
+    }
+
+    m_pipelineComponents.shaderInfo.resize(2);
+
+    m_pipelineComponents.fragmentSpecializationInfo.mapEntryCount = 0;
+    m_pipelineComponents.fragmentSpecializationInfo.pMapEntries = nullptr;
+    m_pipelineComponents.fragmentSpecializationInfo.dataSize = 0;
+    m_pipelineComponents.fragmentSpecializationInfo.pData = nullptr;
+
+    // Vertex Shader
+    m_pipelineComponents.shaderInfo[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    m_pipelineComponents.shaderInfo[0].pNext = nullptr;
+    m_pipelineComponents.shaderInfo[0].flags = 0;
+    m_pipelineComponents.shaderInfo[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+    m_pipelineComponents.shaderInfo[0].module = m_pipelineComponents.vertexShader;
+    m_pipelineComponents.shaderInfo[0].pName = "main";
+    m_pipelineComponents.shaderInfo[0].pSpecializationInfo = &m_pipelineComponents.fragmentSpecializationInfo;
+
+    // Fragment Shader
+    m_pipelineComponents.shaderInfo[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    m_pipelineComponents.shaderInfo[1].pNext = nullptr;
+    m_pipelineComponents.shaderInfo[1].flags = 0;
+    m_pipelineComponents.shaderInfo[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+    m_pipelineComponents.shaderInfo[1].module = m_pipelineComponents.fragmentShader;
+    m_pipelineComponents.shaderInfo[1].pName = "main";
+    m_pipelineComponents.shaderInfo[1].pSpecializationInfo = &m_pipelineComponents.fragmentSpecializationInfo;
+
+    // Vertex Attributes
+    m_pipelineComponents.vertexBindingDescription.resize(1);
+
+    m_pipelineComponents.vertexBindingDescription[0].binding = 0;
+    m_pipelineComponents.vertexBindingDescription[0].stride = 4 * 5;
+    m_pipelineComponents.vertexBindingDescription[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+    // Vertex Attributes
+    m_pipelineComponents.vertexAttributeDescription.resize(2);
+
+    m_pipelineComponents.vertexAttributeDescription[0].location = 0;
+    m_pipelineComponents.vertexAttributeDescription[0].binding = 0;
+    m_pipelineComponents.vertexAttributeDescription[0].format = VK_FORMAT_R32G32B32_SFLOAT;
+    m_pipelineComponents.vertexAttributeDescription[0].offset = 0;
+
+    m_pipelineComponents.vertexAttributeDescription[1].location = 1;
+    m_pipelineComponents.vertexAttributeDescription[1].binding = 0;
+    m_pipelineComponents.vertexAttributeDescription[1].format = VK_FORMAT_R32G32_SFLOAT;
+    m_pipelineComponents.vertexAttributeDescription[1].offset = 4 * 3;
+
+    // Pipeline stages
+    m_pipelineComponents.vertexInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+    m_pipelineComponents.vertexInfo.pNext = nullptr;
+    m_pipelineComponents.vertexInfo.flags = 0;
+    m_pipelineComponents.vertexInfo.vertexBindingDescriptionCount = (uint32_t)m_pipelineComponents.vertexBindingDescription.size();
+    m_pipelineComponents.vertexInfo.pVertexBindingDescriptions = &m_pipelineComponents.vertexBindingDescription[0];
+    m_pipelineComponents.vertexInfo.vertexAttributeDescriptionCount = (uint32_t)m_pipelineComponents.vertexAttributeDescription.size();
+    m_pipelineComponents.vertexInfo.pVertexAttributeDescriptions = &m_pipelineComponents.vertexAttributeDescription[0];
+
+    m_pipelineComponents.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+    m_pipelineComponents.inputAssemblyInfo.pNext = nullptr;
+    m_pipelineComponents.inputAssemblyInfo.flags = 0;
+    m_pipelineComponents.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+    m_pipelineComponents.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
+
+    m_pipelineComponents.tessellationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
+    m_pipelineComponents.tessellationInfo.pNext = nullptr;
+    m_pipelineComponents.tessellationInfo.flags = 0;
+    m_pipelineComponents.tessellationInfo.patchControlPoints = 1; // For now, this should be changed in the future
+
+    m_pipelineComponents.viewports.resize(1);
+
+    m_pipelineComponents.viewports[0].x = 0;
+    m_pipelineComponents.viewports[0].y = 0;
+    m_pipelineComponents.viewports[0].height = m_framebuffer->m_height;
+    m_pipelineComponents.viewports[0].width = m_framebuffer->m_width;
+    m_pipelineComponents.viewports[0].minDepth = 0.0;
+    m_pipelineComponents.viewports[0].maxDepth = 1.0;
+
+    m_pipelineComponents.scissors.resize(1);
+
+    m_pipelineComponents.scissors[0].offset = { 0, 0 };
+    m_pipelineComponents.scissors[0].extent = { m_framebuffer->m_width, m_framebuffer->m_height };
+
+    m_pipelineComponents.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+    m_pipelineComponents.viewportInfo.pNext = nullptr;
+    m_pipelineComponents.viewportInfo.flags = 0;
+    m_pipelineComponents.viewportInfo.viewportCount = (uint32_t)m_pipelineComponents.viewports.size();
+    m_pipelineComponents.viewportInfo.pViewports = &m_pipelineComponents.viewports[0];
+    m_pipelineComponents.viewportInfo.scissorCount = (uint32_t)m_pipelineComponents.scissors.size();
+    m_pipelineComponents.viewportInfo.pScissors = &m_pipelineComponents.scissors[0];
+
+    m_pipelineComponents.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+    m_pipelineComponents.rasterizationInfo.pNext = nullptr;
+    m_pipelineComponents.rasterizationInfo.flags = 0;
+    m_pipelineComponents.rasterizationInfo.depthClampEnable = VK_FALSE;
+    m_pipelineComponents.rasterizationInfo.rasterizerDiscardEnable = VK_FALSE;
+    m_pipelineComponents.rasterizationInfo.polygonMode = VK_POLYGON_MODE_FILL;
+    m_pipelineComponents.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
+    m_pipelineComponents.rasterizationInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+    m_pipelineComponents.rasterizationInfo.depthBiasEnable = VK_FALSE;
+    m_pipelineComponents.rasterizationInfo.depthBiasConstantFactor = 0.0;
+    m_pipelineComponents.rasterizationInfo.depthBiasClamp = VK_FALSE;
+    m_pipelineComponents.rasterizationInfo.depthBiasSlopeFactor = 0.0;
+    m_pipelineComponents.rasterizationInfo.lineWidth = 1.0;
+
+    m_pipelineComponents.multisampleInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+    m_pipelineComponents.multisampleInfo.pNext = nullptr;
+    m_pipelineComponents.multisampleInfo.flags = 0;
+    m_pipelineComponents.multisampleInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; // TODO: Enable multisampling
+    m_pipelineComponents.multisampleInfo.sampleShadingEnable = VK_FALSE;
+    m_pipelineComponents.multisampleInfo.minSampleShading = 0;
+    m_pipelineComponents.multisampleInfo.pSampleMask = nullptr;
+    m_pipelineComponents.multisampleInfo.alphaToCoverageEnable = VK_FALSE;
+    m_pipelineComponents.multisampleInfo.alphaToOneEnable = VK_FALSE;
+
+    VkStencilOpState states[2];
+    states[0].failOp = VK_STENCIL_OP_ZERO;
+    states[0].passOp = VK_STENCIL_OP_KEEP;
+    states[0].depthFailOp = VK_STENCIL_OP_ZERO;
+    states[0].compareOp = VK_COMPARE_OP_LESS;
+    states[0].compareMask = 0;
+    states[0].writeMask = 0;
+    states[0].reference = 0;
+
+    states[1].failOp = VK_STENCIL_OP_ZERO;
+    states[1].passOp = VK_STENCIL_OP_KEEP;
+    states[1].depthFailOp = VK_STENCIL_OP_ZERO;
+    states[1].compareOp = VK_COMPARE_OP_LESS;
+    states[1].compareMask = 0;
+    states[1].writeMask = 0;
+    states[1].reference = 0;
+
+    m_pipelineComponents.depthStencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+    m_pipelineComponents.depthStencilInfo.pNext = nullptr;
+    m_pipelineComponents.depthStencilInfo.flags = 0;
+    m_pipelineComponents.depthStencilInfo.depthTestEnable = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.depthWriteEnable = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
+    m_pipelineComponents.depthStencilInfo.depthBoundsTestEnable = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.stencilTestEnable = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.front = states[0];
+    m_pipelineComponents.depthStencilInfo.back = states[1];
+    m_pipelineComponents.depthStencilInfo.minDepthBounds = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.maxDepthBounds = VK_FALSE;
+
+    m_pipelineComponents.colorBlendAttachments.resize(m_colorAttachments.size());
+    for (int i = 0; i < m_pipelineComponents.colorBlendAttachments.size(); i++)
+    {
+        m_pipelineComponents.colorBlendAttachments[i].blendEnable = VK_TRUE;
+        m_pipelineComponents.colorBlendAttachments[i].srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+        m_pipelineComponents.colorBlendAttachments[i].dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+        m_pipelineComponents.colorBlendAttachments[i].colorBlendOp = VK_BLEND_OP_ADD;
+        m_pipelineComponents.colorBlendAttachments[i].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+        m_pipelineComponents.colorBlendAttachments[i].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+        m_pipelineComponents.colorBlendAttachments[i].alphaBlendOp = VK_BLEND_OP_ADD;
+        m_pipelineComponents.colorBlendAttachments[i].colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
+                                                                       VK_COLOR_COMPONENT_G_BIT |
+                                                                       VK_COLOR_COMPONENT_B_BIT |
+                                                                       VK_COLOR_COMPONENT_A_BIT;
+    }
+
+    m_pipelineComponents.colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+    m_pipelineComponents.colorBlendInfo.pNext = nullptr;
+    m_pipelineComponents.colorBlendInfo.flags = 0;
+    m_pipelineComponents.colorBlendInfo.logicOpEnable = VK_FALSE;
+    m_pipelineComponents.colorBlendInfo.logicOp = VK_LOGIC_OP_SET;
+    m_pipelineComponents.colorBlendInfo.attachmentCount = (uint32_t)m_pipelineComponents.colorBlendAttachments.size();
+    m_pipelineComponents.colorBlendInfo.pAttachments = &m_pipelineComponents.colorBlendAttachments[0];
+    m_pipelineComponents.colorBlendInfo.blendConstants[0] = 1.0;
+    m_pipelineComponents.colorBlendInfo.blendConstants[1] = 1.0;
+    m_pipelineComponents.colorBlendInfo.blendConstants[2] = 1.0;
+    m_pipelineComponents.colorBlendInfo.blendConstants[3] = 1.0;
+
+    VkPushConstantRange pushConstants;
+    pushConstants.offset = 0;
+    pushConstants.size = 128; // Minimum on all devices
+    pushConstants.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+    VkPipelineLayoutCreateInfo layoutInfo;
+    layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    layoutInfo.pNext = nullptr;
+    layoutInfo.flags = 0;
+    layoutInfo.setLayoutCount = (uint32_t)m_descriptorSetLayouts.size();
+    layoutInfo.pSetLayouts = &m_descriptorSetLayouts[0];
+    layoutInfo.pushConstantRangeCount = 1;
+    layoutInfo.pPushConstantRanges = &pushConstants;
+
+    vkCreatePipelineLayout(renderer->m_renderDevice, &layoutInfo, nullptr, &m_pipelineLayout);
+
+    m_graphicsPipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+    m_graphicsPipelineInfo.pNext = nullptr;
+    m_graphicsPipelineInfo.flags = VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT;
+    m_graphicsPipelineInfo.stageCount = (uint32_t)m_pipelineComponents.shaderInfo.size();
+    m_graphicsPipelineInfo.pStages = &m_pipelineComponents.shaderInfo[0];
+    m_graphicsPipelineInfo.pVertexInputState = &m_pipelineComponents.vertexInfo;
+    m_graphicsPipelineInfo.pInputAssemblyState = &m_pipelineComponents.inputAssemblyInfo;
+    m_graphicsPipelineInfo.pTessellationState = &m_pipelineComponents.tessellationInfo;
+    m_graphicsPipelineInfo.pViewportState = &m_pipelineComponents.viewportInfo;
+    m_graphicsPipelineInfo.pRasterizationState = &m_pipelineComponents.rasterizationInfo;
+    m_graphicsPipelineInfo.pMultisampleState = &m_pipelineComponents.multisampleInfo;
+    m_graphicsPipelineInfo.pDepthStencilState = &m_pipelineComponents.depthStencilInfo;
+    m_graphicsPipelineInfo.pColorBlendState = &m_pipelineComponents.colorBlendInfo;
+    m_graphicsPipelineInfo.pDynamicState = nullptr;
+    m_graphicsPipelineInfo.layout = m_pipelineLayout;
+    m_graphicsPipelineInfo.renderPass = m_renderPass;
+    m_graphicsPipelineInfo.subpass = 0;
+    m_graphicsPipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
+    m_graphicsPipelineInfo.basePipelineIndex = 0;
+}
+
+void
+VulkanPostProcess::createFullscreenQuad(VulkanRenderer * renderer)
+{
+    m_vertexBuffer = std::make_shared<VulkanVertexBuffer>(renderer->m_memoryManager, 4, 4 * 5, 2);
+
+    {
+        auto data = (float*)m_vertexBuffer->mapVertices();
+        data[0] = -1;
+        data[1] = -1;
+        data[2] = 0;
+        data[3] = 0;
+        data[4] = 0;
+
+        data[5] = -1;
+        data[6] = 1;
+        data[7] = 0;
+        data[8] = 0;
+        data[9] = 1;
+
+        data[10] = 1;
+        data[11] = -1;
+        data[12] = 0;
+        data[13] = 1;
+        data[14] = 0;
+
+        data[15] = 1;
+        data[16] = 1;
+        data[17] = 0;
+        data[18] = 1;
+        data[19] = 1;
+
+        m_vertexBuffer->unmapVertices();
+    }
+
+    {
+        auto data = (uint32_t*)m_vertexBuffer->mapTriangles();
+        data[0] = 0;
+        data[1] = 1;
+        data[2] = 2;
+        data[3] = 1;
+        data[4] = 2;
+        data[5] = 3;
+        m_vertexBuffer->unmapTriangles();
+    }
+
+    m_vertexBuffer->initializeBuffers(renderer->m_memoryManager);
+}
+
+
+void
+VulkanPostProcess::createDescriptors(VulkanRenderer * renderer)
+{
+    this->createDescriptorPool(renderer);
+    this->createDescriptorSets(renderer);
+}
+
+void
+VulkanPostProcess::createDescriptorSetLayouts(VulkanRenderer * renderer)
+{
+    m_descriptorSets.resize(1);
+    m_descriptorSetLayouts.resize(1);
+
+    std::vector<VkDescriptorSetLayoutBinding> fragmentDescriptorSetLayoutBindings;
+
+    // Diffuse texture
+    for (int i = 0; i < m_samplers.size(); i++)
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = i;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo[1];
+    descriptorSetLayoutInfo[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptorSetLayoutInfo[0].pNext = nullptr;
+    descriptorSetLayoutInfo[0].flags = 0;
+    descriptorSetLayoutInfo[0].bindingCount = (uint32_t)fragmentDescriptorSetLayoutBindings.size();
+    descriptorSetLayoutInfo[0].pBindings = &fragmentDescriptorSetLayoutBindings[0];
+
+    for (int i = 0; i < m_descriptorSetLayouts.size(); i++)
+    {
+        vkCreateDescriptorSetLayout(renderer->m_renderDevice, &descriptorSetLayoutInfo[i], nullptr, &m_descriptorSetLayouts[i]);
+    }
+}
+
+void
+VulkanPostProcess::createDescriptorPool(VulkanRenderer * renderer)
+{
+    std::vector<VkDescriptorPoolSize> descriptorPoolSizes;
+
+    // Fragment shader textures
+    VkDescriptorPoolSize poolSize;
+    poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    poolSize.descriptorCount = (uint32_t)m_samplers.size();
+    descriptorPoolSizes.push_back(poolSize);
+
+    VkDescriptorPoolCreateInfo descriptorPoolInfo;
+    descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    descriptorPoolInfo.pNext = nullptr;
+    descriptorPoolInfo.flags = 0;
+    descriptorPoolInfo.maxSets = (uint32_t)m_descriptorSets.size();
+    descriptorPoolInfo.poolSizeCount = (uint32_t)descriptorPoolSizes.size();
+    descriptorPoolInfo.pPoolSizes = &descriptorPoolSizes[0];
+
+    vkCreateDescriptorPool(renderer->m_renderDevice, &descriptorPoolInfo, nullptr, &m_descriptorPool);
+}
+
+void
+VulkanPostProcess::createDescriptorSets(VulkanRenderer * renderer)
+{
+    VkDescriptorSetAllocateInfo descriptorSetAllocationInfo[1];
+    descriptorSetAllocationInfo[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    descriptorSetAllocationInfo[0].pNext = nullptr;
+    descriptorSetAllocationInfo[0].descriptorPool = m_descriptorPool;
+    descriptorSetAllocationInfo[0].descriptorSetCount = (uint32_t)m_descriptorSetLayouts.size();
+    descriptorSetAllocationInfo[0].pSetLayouts = &m_descriptorSetLayouts[0];
+
+    VkDeviceSize size = { VK_WHOLE_SIZE };
+
+    std::vector<VkDescriptorImageInfo> fragmentTextureInfo;
+
+    // Textures
+    for (int i = 0; i < m_samplers.size(); i++)
+    {
+        VkDescriptorImageInfo textureInfo;
+        textureInfo.sampler = *m_samplers[i];
+        textureInfo.imageView = *m_imageViews[i];
+        textureInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+        fragmentTextureInfo.push_back(textureInfo);
+    }
+
+    vkAllocateDescriptorSets(renderer->m_renderDevice, descriptorSetAllocationInfo, &m_descriptorSets[0]);
+
+    m_writeDescriptorSets.resize(1);
+
+    m_writeDescriptorSets[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    m_writeDescriptorSets[0].pNext = nullptr;
+    m_writeDescriptorSets[0].dstBinding = 0;
+    m_writeDescriptorSets[0].dstArrayElement = 0;
+    m_writeDescriptorSets[0].dstSet = m_descriptorSets[0];
+    m_writeDescriptorSets[0].descriptorCount = (uint32_t)fragmentTextureInfo.size();
+    m_writeDescriptorSets[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    m_writeDescriptorSets[0].pBufferInfo = nullptr;
+    m_writeDescriptorSets[0].pImageInfo = &fragmentTextureInfo[0];
+    m_writeDescriptorSets[0].pTexelBufferView = nullptr;
+
+    vkUpdateDescriptorSets(renderer->m_renderDevice, (uint32_t)m_writeDescriptorSets.size(), &m_writeDescriptorSets[0], 0, nullptr);
+}
+
+void
+VulkanPostProcess::createRenderPass(VulkanRenderer * renderer)
+{
+    std::vector<VkAttachmentDescription> attachments;
+    bool depth = false;
+
+    // Color attachment
+    {
+        VkAttachmentDescription attachment;
+        attachment.flags = 0;
+        attachment.format = m_framebuffer->m_colorFormat;
+        attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+        attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+        attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+        attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+        attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+        attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        attachment.finalLayout =
+            m_lastPass ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+        attachments.push_back(attachment);
+    }
+
+    if (m_framebuffer->m_depthFormat != VK_FORMAT_UNDEFINED)
+    {
+        depth = true;
+
+        // Depth attachment
+        VkAttachmentDescription attachment;
+        attachment.flags = 0;
+        attachment.format = m_framebuffer->m_depthFormat;
+        attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+        attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+        attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+        attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+        attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+        attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+        attachments.push_back(attachment);
+    }
+
+    // Color attachment
+    VkAttachmentReference colorReference;
+    colorReference.attachment = 0;
+    colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    // Depth attachment
+    VkAttachmentReference depthReference;
+    depthReference.attachment = 1;
+    depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+    // Normal attachment
+    VkAttachmentReference normalReference;
+    normalReference.attachment = 2;
+    normalReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    // First pass: geometry
+    m_colorAttachments.push_back(colorReference);
+
+    // Render subpasses
+    VkSubpassDescription subpassInfo[1];
+    subpassInfo[0].flags = 0;
+    subpassInfo[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+    subpassInfo[0].inputAttachmentCount = 0;
+    subpassInfo[0].pInputAttachments = nullptr;
+    subpassInfo[0].colorAttachmentCount = (uint32_t)m_colorAttachments.size();
+    subpassInfo[0].pColorAttachments = &m_colorAttachments[0];
+    subpassInfo[0].pResolveAttachments = nullptr;
+    subpassInfo[0].pDepthStencilAttachment = depth ? &depthReference : nullptr;
+    subpassInfo[0].preserveAttachmentCount = 0;
+    subpassInfo[0].pPreserveAttachments = nullptr;
+
+    VkSubpassDependency dependencies[2];
+    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+    dependencies[0].dstSubpass = 0;
+    dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+    dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
+    dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+    dependencies[1].srcSubpass = 0;
+    dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+    dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
+    dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+    dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+    VkRenderPassCreateInfo renderPassInfo;
+    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+    renderPassInfo.pNext = nullptr;
+    renderPassInfo.flags = 0;
+    renderPassInfo.attachmentCount = (uint32_t)attachments.size();
+    renderPassInfo.pAttachments = &attachments[0];
+    renderPassInfo.subpassCount = 1;
+    renderPassInfo.pSubpasses = subpassInfo;
+    renderPassInfo.dependencyCount = 2;
+    renderPassInfo.pDependencies = dependencies;
+
+    vkCreateRenderPass(renderer->m_renderDevice, &renderPassInfo, nullptr, &m_renderPass);
+}
+
+void
+VulkanPostProcess::initializeFramebuffer(VulkanRenderer * renderer)
+{
+    this->createRenderPass(renderer);
+    m_framebuffer->initializeFramebuffer(&m_renderPass);
+}
+
+void
+VulkanPostProcess::createFramebuffer(VulkanRenderer * renderer,
+                                     unsigned int level,
+                                     bool lastPass)
+{
+    m_framebuffer = std::make_shared<VulkanFramebuffer>(
+        renderer->m_memoryManager,
+        renderer->m_width >> level,
+        renderer->m_height >> level,
+        lastPass);
+}
+
+void
+VulkanPostProcess::addInputImage(
+    VkSampler * sampler,
+    VkImageView * imageView)
+{
+    m_samplers.push_back(sampler);
+    m_imageViews.push_back(imageView);
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcess.h b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcess.h
new file mode 100644
index 0000000000000000000000000000000000000000..c1ebfe333c9573412a2beb53077fbc68d4ed133d
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcess.h
@@ -0,0 +1,108 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanPostProcess_h
+#define imstkVulkanPostProcess_h
+
+#include "vulkan/vulkan.h"
+#include "glm/glm.hpp"
+
+#include "imstkVulkanMaterialDelegate.h"
+#include "imstkVulkanVertexBuffer.h"
+#include "imstkVulkanFramebuffer.h"
+#include "imstkVulkanTextureDelegate.h"
+
+#include <memory>
+#include <vector>
+
+namespace imstk
+{
+class VulkanRenderer;
+
+class VulkanPostProcess
+{
+public:
+    ///
+    /// \brief Constructor
+    ///
+    VulkanPostProcess(VulkanRenderer * renderer, unsigned int level = 0, bool lastPass = false);
+
+    void addInputImage(
+        VkSampler * sampler,
+        VkImageView * imageView);
+
+    void generateMipmaps(VkCommandBuffer& commandBuffer,
+                         unsigned int levels,
+                         VkImage& image);
+
+protected:
+    friend class VulkanRenderer;
+    friend class VulkanPostProcessingChain;
+
+    ///
+    /// \brief Creates a parent pipeline object that gets inherited by other materials
+    ///
+    void createPipeline(VulkanRenderer * renderer, std::string fragmentSource);
+    void createRenderPass(VulkanRenderer * renderer);
+    void createFramebuffer(VulkanRenderer * renderer,
+                           unsigned int level = 0,
+                           bool lastPass = false);
+
+    void createFullscreenQuad(VulkanRenderer * renderer);
+
+    virtual void initialize(VulkanRenderer * renderer,
+                            std::string = "./Shaders/VulkanShaders/PostProcessing/postprocess_frag.spv");
+    void initializeFramebuffer(VulkanRenderer * renderer);
+
+
+    void createDescriptors(VulkanRenderer * renderer);
+    void createDescriptorSetLayouts(VulkanRenderer * renderer);
+    void createDescriptorPool(VulkanRenderer * renderer);
+    void createDescriptorSets(VulkanRenderer * renderer);
+
+    VkPipeline m_pipeline;
+    VkGraphicsPipelineCreateInfo m_graphicsPipelineInfo;
+    VkPipelineLayout m_pipelineLayout;
+    VulkanMaterialPipelineComponents m_pipelineComponents;
+
+    VkDescriptorPool m_descriptorPool;
+    std::vector<VkDescriptorSet> m_descriptorSets;
+    std::vector<VkDescriptorSetLayout> m_descriptorSetLayouts;
+    std::vector<VkWriteDescriptorSet> m_writeDescriptorSets;
+
+    std::shared_ptr<VulkanVertexBuffer> m_vertexBuffer;
+
+    std::shared_ptr<VulkanFramebuffer> m_framebuffer;
+
+    // Resources
+    std::vector<VkSampler *> m_samplers;
+    std::vector<VkImageView *> m_imageViews;
+    unsigned int m_downsampleLevels = 0;
+    unsigned int m_outputIndex = 0;
+    bool m_lastPass = false;
+
+    std::vector<VkAttachmentReference> m_colorAttachments;
+    VkRenderPass m_renderPass;
+    float m_pushConstantData[32];
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.cpp b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8621042f5402e329b4b70e2a551c095c68cabe03
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.cpp
@@ -0,0 +1,207 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanPostProcessingChain.h"
+
+#include "imstkVulkanRenderer.h"
+
+namespace imstk
+{
+VulkanPostProcessingChain::VulkanPostProcessingChain(VulkanRenderer * renderer)
+{
+    bool bloom = false;
+    bool sss = true;
+
+    // Subsurface scattering pass
+    // The buffer indices are hardcoded because it's before the accumulation composition pass
+    if (sss)
+    {
+        int sssSamples = 5;
+
+        auto sssHorizontalBlurPass = std::make_shared<VulkanPostProcess>(renderer, 0);
+        sssHorizontalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[0][0]);
+        sssHorizontalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_depthImageView[0]);
+        sssHorizontalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_normalImageView);
+        sssHorizontalBlurPass->m_framebuffer->setColor(&renderer->m_HDRImageView[2][0], VK_FORMAT_R16G16B16A16_SFLOAT);
+        sssHorizontalBlurPass->initialize(renderer, "./Shaders/VulkanShaders/PostProcessing/sss_frag.spv");
+        sssHorizontalBlurPass->m_pushConstantData[0] = 1.0;
+        sssHorizontalBlurPass->m_pushConstantData[1] = 0.0;
+        sssHorizontalBlurPass->m_pushConstantData[2] = renderer->m_fov;
+        sssHorizontalBlurPass->m_pushConstantData[3] = 3.0;
+        sssHorizontalBlurPass->m_pushConstantData[4] = renderer->m_nearPlane;
+        sssHorizontalBlurPass->m_pushConstantData[5] = renderer->m_farPlane;
+        sssHorizontalBlurPass->m_pushConstantData[6] = sssSamples;
+        this->calculateBlurValues(sssSamples,
+            &sssHorizontalBlurPass->m_pushConstantData[7],
+            renderer->m_nearPlane);
+        this->calculateBlurValues(sssSamples,
+            &sssHorizontalBlurPass->m_pushConstantData[17],
+            renderer->m_farPlane);
+        m_postProcesses.push_back(sssHorizontalBlurPass);
+
+        auto sssVerticalBlurPass = std::make_shared<VulkanPostProcess>(renderer, 0);
+        sssVerticalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[2][0]);
+        sssVerticalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_depthImageView[0]);
+        sssVerticalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_normalImageView);
+        sssVerticalBlurPass->m_framebuffer->setColor(&renderer->m_HDRImageView[0][0], VK_FORMAT_R16G16B16A16_SFLOAT);
+        sssVerticalBlurPass->initialize(renderer, "./Shaders/VulkanShaders/PostProcessing/sss_frag.spv");
+        sssVerticalBlurPass->m_pushConstantData[0] = 0.0;
+        sssVerticalBlurPass->m_pushConstantData[1] = 1.0;
+        sssVerticalBlurPass->m_pushConstantData[2] = renderer->m_fov;
+        sssVerticalBlurPass->m_pushConstantData[3] = 3.0;
+        sssVerticalBlurPass->m_pushConstantData[4] = renderer->m_nearPlane;
+        sssVerticalBlurPass->m_pushConstantData[5] = renderer->m_farPlane;
+        sssVerticalBlurPass->m_pushConstantData[6] = sssSamples;
+        this->calculateBlurValues(sssSamples,
+            &sssVerticalBlurPass->m_pushConstantData[7],
+            renderer->m_nearPlane);
+        this->calculateBlurValues(sssSamples,
+            &sssVerticalBlurPass->m_pushConstantData[17],
+            renderer->m_farPlane);
+        m_postProcesses.push_back(sssVerticalBlurPass);
+    }
+
+    // Accumulation composition pass
+    auto accumulationCompositePass = std::make_shared<VulkanPostProcess>(renderer);
+    accumulationCompositePass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[0][0]);
+    accumulationCompositePass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[1][0]);
+    accumulationCompositePass->m_framebuffer->setColor(&renderer->m_HDRImageView[2][0], VK_FORMAT_R16G16B16A16_SFLOAT);
+    accumulationCompositePass->initialize(renderer, "./Shaders/VulkanShaders/PostProcessing/composite_frag.spv");
+    m_postProcesses.push_back(accumulationCompositePass);
+
+    // Bloom pass
+    if (bloom)
+    {
+        int level = 1;
+        int bloomSamples = 5;
+
+        auto bloomThresholdPass = std::make_shared<VulkanPostProcess>(renderer, level);
+        bloomThresholdPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[m_lastOutput][0]);
+        bloomThresholdPass->m_framebuffer->setColor(&renderer->m_HDRImageView[m_lastInput][level], VK_FORMAT_R16G16B16A16_SFLOAT);
+        bloomThresholdPass->initialize(renderer, "./Shaders/VulkanShaders/PostProcessing/bloom_threshold_frag.spv");
+        m_postProcesses.push_back(bloomThresholdPass);
+
+        auto bloomHorizontalBlurPass = std::make_shared<VulkanPostProcess>(renderer, level);
+        bloomHorizontalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[m_lastInput][level]);
+        bloomHorizontalBlurPass->m_framebuffer->setColor(&renderer->m_HDRImageView[m_lastOutput][level], VK_FORMAT_R16G16B16A16_SFLOAT);
+        bloomHorizontalBlurPass->initialize(renderer, "./Shaders/VulkanShaders/PostProcessing/blur_horizontal_frag.spv");
+        bloomHorizontalBlurPass->m_pushConstantData[0] = std::max(renderer->m_width >> level, 1u);
+        bloomHorizontalBlurPass->m_pushConstantData[1] = std::max(renderer->m_height >> level, 1u);
+        bloomHorizontalBlurPass->m_pushConstantData[2] = bloomSamples;
+        this->calculateBlurValuesLinear(bloomSamples,
+            &bloomHorizontalBlurPass->m_pushConstantData[3],
+            &bloomHorizontalBlurPass->m_pushConstantData[13]);
+        m_postProcesses.push_back(bloomHorizontalBlurPass);
+
+        auto bloomVerticalBlurPass = std::make_shared<VulkanPostProcess>(renderer, level);
+        bloomVerticalBlurPass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[m_lastOutput][level]);
+        bloomVerticalBlurPass->m_framebuffer->setColor(&renderer->m_HDRImageView[m_lastInput][level], VK_FORMAT_R16G16B16A16_SFLOAT);
+        bloomVerticalBlurPass->initialize(renderer, "./Shaders/Vulkan/PostProcessing/blur_vertical_frag.spv");
+        bloomVerticalBlurPass->m_pushConstantData[0] = std::max(renderer->m_width >> level, 1u);
+        bloomVerticalBlurPass->m_pushConstantData[1] = std::max(renderer->m_height >> level, 1u);
+        bloomVerticalBlurPass->m_pushConstantData[2] = bloomSamples;
+        this->calculateBlurValuesLinear(bloomSamples,
+            &bloomVerticalBlurPass->m_pushConstantData[3],
+            &bloomVerticalBlurPass->m_pushConstantData[13]);
+        m_postProcesses.push_back(bloomVerticalBlurPass);
+
+        auto bloomCompositePass = std::make_shared<VulkanPostProcess>(renderer);
+        bloomCompositePass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[m_lastOutput][0]);
+        bloomCompositePass->addInputImage(&renderer->m_HDRImageSampler, &renderer->m_HDRImageView[m_lastInput][level]);
+        bloomCompositePass->m_framebuffer->setColor(&renderer->m_HDRImageView[m_lastInput][0], VK_FORMAT_R16G16B16A16_SFLOAT);
+        bloomCompositePass->initialize(renderer, "./Shaders/VulkanShaders/PostProcessing/composite_frag.spv");
+        m_postProcesses.push_back(bloomCompositePass);
+        this->incrementBufferNumbers();
+    }
+}
+
+std::vector<std::shared_ptr<VulkanPostProcess>>&
+VulkanPostProcessingChain::getPostProcesses()
+{
+    return m_postProcesses;
+}
+
+void
+VulkanPostProcessingChain::incrementBufferNumbers()
+{
+    m_lastInput = (m_lastInput + 1) % 3;
+    m_lastOutput = (m_lastOutput + 1) % 3;
+}
+
+void
+VulkanPostProcessingChain::calculateBlurValuesLinear(int samples, float * values, float * offsets)
+{
+    std::vector<float> intermediateValues(samples * 2 - 1);
+    float total = 0;
+
+    // Calculate normal distribution
+    for (int i = 0; i < intermediateValues.size(); i++)
+    {
+        float x = (i / (float)intermediateValues.size()) * 3;
+        intermediateValues[i] = 1.0 / std::sqrt(2 * PI) * std::pow(NLOG_E, -(x * x) / 2);
+        total += intermediateValues[i] * 2;
+    }
+
+    // Normalize
+    for (int i = 0; i < intermediateValues.size(); i++)
+    {
+        intermediateValues[i] /= total;
+    }
+
+    values[0] = intermediateValues[0];
+    offsets[0] = 0.0;
+
+    // Linear sampling optimization
+    for (int i = 1; i < samples; i++)
+    {
+        values[i] = intermediateValues[2 * i - 1] + intermediateValues[2 * i];
+        offsets[i] = (((2 * i - 1) * intermediateValues[2 * i - 1]) +
+                      ((2 * i) * intermediateValues[2 * i])) / values[i];
+    }
+}
+
+void
+VulkanPostProcessingChain::calculateBlurValues(int samples, float * values, float stdDev)
+{
+    float total = 0;
+
+    // Calculate normal distribution
+    for (int i = 0; i < samples; i++)
+    {
+        values[i] = 1.0 / std::sqrt(2 * PI * stdDev * stdDev) * std::pow(NLOG_E, -(i * i) / (2 * stdDev * stdDev));
+
+        if (i == 0)
+        {
+            total += values[i];
+        }
+        else
+        {
+            total += values[i] * 2;
+        }
+    }
+
+    // Normalize
+    for (int i = 0; i < samples; i++)
+    {
+        values[i] /= total;
+    }
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.h b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.h
new file mode 100644
index 0000000000000000000000000000000000000000..afd0c8eb9eb16c92998efa5130ead3542d4cd0c0
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/PostProcessing/imstkVulkanPostProcessingChain.h
@@ -0,0 +1,60 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanPostProcessingChain_h
+#define imstkVulkanPostProcessingChain_h
+
+#include "vulkan/vulkan.h"
+#include "glm/glm.hpp"
+
+#include "imstkVulkanPostProcess.h"
+
+#include <memory>
+#include <vector>
+
+namespace imstk
+{
+class VulkanRenderer;
+
+class VulkanPostProcessingChain
+{
+public:
+    ///
+    /// \brief Constructor
+    ///
+    VulkanPostProcessingChain(VulkanRenderer * renderer);
+
+    std::vector<std::shared_ptr<VulkanPostProcess>>& getPostProcesses();
+
+protected:
+    friend class VulkanRenderer;
+
+    std::vector<std::shared_ptr<VulkanPostProcess>> m_postProcesses;
+    void incrementBufferNumbers();
+    void calculateBlurValuesLinear(int samples, float * values, float * offsets);
+    void calculateBlurValues(int samples, float * values, float stdDev = 0.0);
+
+    unsigned int m_lastOutput = 2; ///< 2 by default because of accumulation composition
+    unsigned int m_lastInput = 0; ///< 0 by default because of accumulation composition
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..05be1fa866e0f5bfdbd77b02fd8fe1226a8c7fb5
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.cpp
@@ -0,0 +1,107 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanCapsuleRenderDelegate.h"
+
+namespace imstk
+{
+VulkanCapsuleRenderDelegate::VulkanCapsuleRenderDelegate(std::shared_ptr<Capsule> capsule, VulkanMemoryManager& memoryManager)
+    : m_geometry(capsule)
+{
+    auto source = vtkSmartPointer<vtkCapsuleSource>::New();
+    source->SetCenter(WORLD_ORIGIN[0], WORLD_ORIGIN[1], WORLD_ORIGIN[2]);
+    source->SetRadius(m_geometry->getRadius());
+    source->SetCylinderLength(m_geometry->getLength());
+    source->SetLatLongTessellation(20);
+    source->SetPhiResolution(20);
+    source->SetThetaResolution(20);
+    source->Update();
+
+    auto triangulate = vtkSmartPointer<vtkTriangleFilter>::New();
+    triangulate->SetInputConnection(source->GetOutputPort());
+
+    triangulate->Update();
+
+    auto sourceData = triangulate->GetOutput();
+
+    auto positions = sourceData->GetPoints();
+    auto normals = sourceData->GetPointData()->GetNormals();
+    auto triangles = sourceData->GetPolys();
+
+    for (int i = 0; i < sourceData->GetNumberOfPoints(); i++)
+    {
+        auto position = positions->GetPoint(i);
+        auto normal = normals->GetTuple(i);
+
+        VulkanBasicVertex capsuleVertex;
+
+        capsuleVertex.position[0] = position[0];
+        capsuleVertex.position[1] = position[1];
+        capsuleVertex.position[2] = position[2];
+
+        capsuleVertex.normal[0] = normal[0];
+        capsuleVertex.normal[1] = normal[1];
+        capsuleVertex.normal[2] = normal[2];
+
+        m_capsuleVertices.push_back(capsuleVertex);
+    }
+
+    triangles->InitTraversal();
+
+    for (int i = 0; i < triangles->GetNumberOfCells(); i++)
+    {
+        auto points = vtkSmartPointer<vtkIdList>::New();
+        auto triangle = triangles->GetNextCell(points);
+
+        std::array<uint32_t, 3> trianglePoints = {
+            (uint32_t)points->GetId(0),
+            (uint32_t)points->GetId(1),
+            (uint32_t)points->GetId(2)
+        };
+
+        m_capsuleTriangles.push_back(trianglePoints);
+    }
+
+    m_numVertices = (uint32_t)m_capsuleVertices.size();
+    m_numTriangles = (uint32_t)m_capsuleTriangles.size();
+    m_vertexSize = sizeof(VulkanBasicVertex);
+
+    this->initializeData(memoryManager);
+
+    m_vertexBuffer->updateVertexBuffer(&m_capsuleVertices, &m_capsuleTriangles);
+
+    this->update();
+}
+
+void
+VulkanCapsuleRenderDelegate::update()
+{
+    this->updateTransform(m_geometry);
+    m_vertexUniformBuffer->updateUniforms(sizeof(VulkanLocalVertexUniforms),
+        (void *)&m_localVertexUniforms);
+}
+
+std::shared_ptr<Geometry>
+VulkanCapsuleRenderDelegate::getGeometry() const
+{
+    return m_geometry;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.h b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..e548bbf562ca8f7d63536917eda7f90a271ce2ab
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCapsuleRenderDelegate.h
@@ -0,0 +1,67 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanCapsuleRenderDelegate_h
+#define imstkVulkanCapsuleRenderDelegate_h
+
+#include "imstkCapsule.h"
+
+#include "imstkVulkanRenderDelegate.h"
+
+#include "vtkCapsuleSource.h"
+#include "vtkPointData.h"
+#include "vtkTriangleFilter.h"
+
+namespace imstk
+{
+class VulkanCapsuleRenderDelegate : public VulkanRenderDelegate
+{
+public:
+
+    ///
+    /// \brief Default destructor
+    ///
+    ~VulkanCapsuleRenderDelegate() = default;
+
+    ///
+    /// \brief Default constructor
+    ///
+    VulkanCapsuleRenderDelegate(std::shared_ptr<Capsule> capsule, VulkanMemoryManager& memoryManager);
+
+    ///
+    /// \brief Update render geometry
+    ///
+    void update() override;
+
+    ///
+    /// \brief Get source geometry
+    ///
+    std::shared_ptr<Geometry> getGeometry() const override;
+
+protected:
+    std::shared_ptr<Capsule> m_geometry;
+
+    std::vector<std::array<uint32_t, 3>> m_capsuleTriangles;
+    std::vector<VulkanBasicVertex> m_capsuleVertices;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..79f5145139aa8d0e0c42ca0ed48ac9ae0004a812
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.cpp
@@ -0,0 +1,107 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanCubeRenderDelegate.h"
+
+namespace imstk
+{
+VulkanCubeRenderDelegate::VulkanCubeRenderDelegate(std::shared_ptr<Cube> cube, VulkanMemoryManager& memoryManager)
+    : m_geometry(cube)
+{
+    auto width = m_geometry->getWidth();
+
+    auto source = vtkSmartPointer<vtkCubeSource>::New();
+    source->SetCenter(WORLD_ORIGIN[0], WORLD_ORIGIN[1], WORLD_ORIGIN[2]);
+    source->SetXLength(width);
+    source->SetYLength(width);
+    source->SetZLength(width);
+    source->Update();
+
+    auto triangulate = vtkSmartPointer<vtkTriangleFilter>::New();
+    triangulate->SetInputConnection(source->GetOutputPort());
+
+    triangulate->Update();
+
+    auto sourceData = triangulate->GetOutput();
+
+    auto positions = sourceData->GetPoints();
+    auto normals = sourceData->GetPointData()->GetNormals();
+    auto triangles = sourceData->GetPolys();
+
+    for (int i = 0; i < sourceData->GetNumberOfPoints(); i++)
+    {
+        auto position = positions->GetPoint(i);
+        auto normal = normals->GetTuple(i);
+
+        VulkanBasicVertex cubeVertex;
+
+        cubeVertex.position[0] = position[0];
+        cubeVertex.position[1] = position[1];
+        cubeVertex.position[2] = position[2];
+
+        cubeVertex.normal[0] = normal[0];
+        cubeVertex.normal[1] = normal[1];
+        cubeVertex.normal[2] = normal[2];
+
+        m_cubeVertices.push_back(cubeVertex);
+    }
+
+    triangles->InitTraversal();
+
+    for (int i = 0; i < triangles->GetNumberOfCells(); i++)
+    {
+        auto points = vtkSmartPointer<vtkIdList>::New();
+        auto triangle = triangles->GetNextCell(points);
+
+        std::array<uint32_t, 3> trianglePoints = {
+            (uint32_t)points->GetId(0),
+            (uint32_t)points->GetId(1),
+            (uint32_t)points->GetId(2)
+        };
+
+        m_cubeTriangles.push_back(trianglePoints);
+    }
+
+    m_numVertices = (uint32_t)m_cubeVertices.size();
+    m_numTriangles = (uint32_t)m_cubeTriangles.size();
+    m_vertexSize = sizeof(VulkanBasicVertex);
+
+    this->initializeData(memoryManager);
+
+    m_vertexBuffer->updateVertexBuffer(&m_cubeVertices, &m_cubeTriangles);
+
+    this->update();
+}
+
+void
+VulkanCubeRenderDelegate::update()
+{
+    this->updateTransform(m_geometry);
+    m_vertexUniformBuffer->updateUniforms(sizeof(VulkanLocalVertexUniforms),
+        (void *)&m_localVertexUniforms);
+}
+
+std::shared_ptr<Geometry>
+VulkanCubeRenderDelegate::getGeometry() const
+{
+    return m_geometry;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.h b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd551f584e0b7c8bbd5adaa09acc9cb8072f86a3
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanCubeRenderDelegate.h
@@ -0,0 +1,67 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanCubeRenderDelegate_h
+#define imstkVulkanCubeRenderDelegate_h
+
+#include "imstkCube.h"
+
+#include "imstkVulkanRenderDelegate.h"
+
+#include "vtkCubeSource.h"
+#include "vtkPointData.h"
+#include "vtkTriangleFilter.h"
+
+namespace imstk
+{
+class VulkanCubeRenderDelegate : public VulkanRenderDelegate
+{
+public:
+
+    ///
+    /// \brief Default destructor
+    ///
+    ~VulkanCubeRenderDelegate() = default;
+
+    ///
+    /// \brief Default constructor
+    ///
+    VulkanCubeRenderDelegate(std::shared_ptr<Cube> cube, VulkanMemoryManager& memoryManager);
+
+    ///
+    /// \brief Update render geometry
+    ///
+    void update() override;
+
+    ///
+    /// \brief Get source geometry
+    ///
+    std::shared_ptr<Geometry> getGeometry() const override;
+
+protected:
+    std::shared_ptr<Cube> m_geometry;
+
+    std::vector<std::array<uint32_t, 3>> m_cubeTriangles;
+    std::vector<VulkanBasicVertex> m_cubeVertices;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b5dfcb2ea5cef5423bd9ab1a54cde2eee85f41af
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.cpp
@@ -0,0 +1,105 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanPlaneRenderDelegate.h"
+
+namespace imstk
+{
+VulkanPlaneRenderDelegate::VulkanPlaneRenderDelegate(std::shared_ptr<Plane> plane, VulkanMemoryManager& memoryManager)
+    : m_geometry(plane)
+{
+    auto width = m_geometry->getWidth();
+
+    auto source = vtkSmartPointer<vtkPlaneSource>::New();
+    source->SetCenter(WORLD_ORIGIN[0], WORLD_ORIGIN[1], WORLD_ORIGIN[2]);
+    source->SetNormal(UP_VECTOR[0], UP_VECTOR[1], UP_VECTOR[2]);
+    source->Update();
+
+    auto triangulate = vtkSmartPointer<vtkTriangleFilter>::New();
+    triangulate->SetInputConnection(source->GetOutputPort());
+
+    triangulate->Update();
+
+    auto sourceData = triangulate->GetOutput();
+
+    auto positions = sourceData->GetPoints();
+    auto normals = sourceData->GetPointData()->GetNormals();
+    auto triangles = sourceData->GetPolys();
+
+    for (int i = 0; i < sourceData->GetNumberOfPoints(); i++)
+    {
+        auto position = positions->GetPoint(i);
+        auto normal = normals->GetTuple(i);
+
+        VulkanBasicVertex planeVertex;
+
+        planeVertex.position[0] = position[0] * width;
+        planeVertex.position[1] = position[1] * width;
+        planeVertex.position[2] = position[2] * width;
+
+        planeVertex.normal[0] = normal[0];
+        planeVertex.normal[1] = normal[1];
+        planeVertex.normal[2] = normal[2];
+
+        m_planeVertices.push_back(planeVertex);
+    }
+
+    triangles->InitTraversal();
+
+    for (int i = 0; i < triangles->GetNumberOfCells(); i++)
+    {
+        auto points = vtkSmartPointer<vtkIdList>::New();
+        auto triangle = triangles->GetNextCell(points);
+
+        std::array<uint32_t, 3> trianglePoints = {
+            (uint32_t)points->GetId(0),
+            (uint32_t)points->GetId(1),
+            (uint32_t)points->GetId(2)
+        };
+
+        m_planeTriangles.push_back(trianglePoints);
+    }
+
+    m_numVertices = (uint32_t)m_planeVertices.size();
+    m_numTriangles = (uint32_t)m_planeTriangles.size();
+    m_vertexSize = sizeof(VulkanBasicVertex);
+
+    this->initializeData(memoryManager);
+
+    m_vertexBuffer->updateVertexBuffer(&m_planeVertices, &m_planeTriangles);
+
+    this->update();
+}
+
+void
+VulkanPlaneRenderDelegate::update()
+{
+    this->updateTransform(m_geometry);
+    m_vertexUniformBuffer->updateUniforms(sizeof(VulkanLocalVertexUniforms),
+        (void *)&m_localVertexUniforms);
+}
+
+std::shared_ptr<Geometry>
+VulkanPlaneRenderDelegate::getGeometry() const
+{
+    return m_geometry;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.h b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8593dcf6c7dc209da48602c8a982685bea38d3f
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanPlaneRenderDelegate.h
@@ -0,0 +1,68 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanPlaneRenderDelegate_h
+#define imstkVulkanPlaneRenderDelegate_h
+
+#include "imstkPlane.h"
+
+#include "imstkVulkanRenderDelegate.h"
+#include "vtkSphereSource.h"
+
+#include "vtkPlaneSource.h"
+#include "vtkPointData.h"
+#include "vtkTriangleFilter.h"
+
+namespace imstk
+{
+class VulkanPlaneRenderDelegate : public VulkanRenderDelegate
+{
+public:
+
+    ///
+    /// \brief Default destructor
+    ///
+    ~VulkanPlaneRenderDelegate() = default;
+
+    ///
+    /// \brief Default constructor
+    ///
+    VulkanPlaneRenderDelegate(std::shared_ptr<Plane> plane, VulkanMemoryManager& memoryManager);
+
+    ///
+    /// \brief Update render geometry
+    ///
+    void update() override;
+
+    ///
+    /// \brief Get source geometry
+    ///
+    std::shared_ptr<Geometry> getGeometry() const override;
+
+protected:
+    std::shared_ptr<Plane> m_geometry;
+
+    std::vector<std::array<uint32_t, 3>> m_planeTriangles;
+    std::vector<VulkanBasicVertex> m_planeVertices;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dfb8560d9c37f0643fc4d41e88f2b95f2f4587df
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp
@@ -0,0 +1,144 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanRenderDelegate.h"
+
+#include "g3log/g3log.hpp"
+
+#include "imstkPlane.h"
+#include "imstkSphere.h"
+#include "imstkCube.h"
+#include "imstkCapsule.h"
+#include "imstkSurfaceMesh.h"
+#include "imstkTetrahedralMesh.h"
+
+#include "imstkVulkanPlaneRenderDelegate.h"
+#include "imstkVulkanSphereRenderDelegate.h"
+#include "imstkVulkanCubeRenderDelegate.h"
+#include "imstkVulkanCapsuleRenderDelegate.h"
+#include "imstkVulkanSurfaceMeshRenderDelegate.h"
+
+namespace imstk
+{
+std::shared_ptr<VulkanRenderDelegate>
+VulkanRenderDelegate::make_delegate(std::shared_ptr<Geometry> geom, VulkanMemoryManager& memoryManager)
+{
+    switch (geom->getType())
+    {
+    case Geometry::Type::Plane:
+    {
+        auto plane = std::dynamic_pointer_cast<Plane>(geom);
+        return std::make_shared<VulkanPlaneRenderDelegate>(plane, memoryManager);
+    }
+    case Geometry::Type::Sphere:
+    {
+        auto sphere = std::dynamic_pointer_cast<Sphere>(geom);
+        return std::make_shared<VulkanSphereRenderDelegate>(sphere, memoryManager);
+    }
+    case Geometry::Type::Cube:
+    {
+        auto cube = std::dynamic_pointer_cast<Cube>(geom);
+        return std::make_shared<VulkanCubeRenderDelegate>(cube, memoryManager);
+    }
+    case Geometry::Type::Capsule:
+    {
+        auto capsule = std::dynamic_pointer_cast<Capsule>(geom);
+        return std::make_shared<VulkanCapsuleRenderDelegate>(capsule, memoryManager);
+    }
+    case Geometry::Type::SurfaceMesh:
+    {
+        auto surfaceMesh = std::dynamic_pointer_cast<SurfaceMesh>(geom);
+        return std::make_shared<VulkanSurfaceMeshRenderDelegate>(surfaceMesh, memoryManager);
+    }
+    /*case Geometry::Type::TetrahedralMesh:
+    {
+        auto tetrahedralMesh = std::dynamic_pointer_cast<TetrahedralMesh>(geom);
+        return std::make_shared<VulkanTetrahedralMeshRenderDelegate>(tetrahedralMesh);
+    }
+    case Geometry::Type::LineMesh:
+    {
+        LOG(WARNING) << "RenderDelegate::make_delegate error: LineMeshRenderDelegate not yet implemented";
+        return nullptr;
+    }
+    case Geometry::Type::HexahedralMesh:
+    {
+        LOG(WARNING) << "RenderDelegate::make_delegate error: HexahedralMeshRenderDelegate not yet implemented";
+        return nullptr;
+    }*/
+    default:
+    {
+        LOG(WARNING) << "RenderDelegate::make_delegate error: Geometry type incorrect.";
+        return nullptr;
+    }
+    }
+}
+
+vtkSmartPointer<vtkPolyDataMapper>
+VulkanRenderDelegate::setUpMapper(vtkAlgorithmOutput * source)
+{
+    vtkSmartPointer<vtkPolyDataMapper> mapper;
+    mapper->SetInputConnection(source);
+    mapper->Update();
+    return mapper;
+}
+
+std::shared_ptr<VulkanVertexBuffer>
+VulkanRenderDelegate::getBuffer()
+{
+    return m_vertexBuffer;
+}
+
+void
+VulkanRenderDelegate::initializeData(VulkanMemoryManager& memoryManager, std::shared_ptr<RenderMaterial> material)
+{
+    m_vertexUniformBuffer = std::make_shared<VulkanUniformBuffer>(memoryManager, (uint32_t)sizeof(VulkanLocalVertexUniforms));
+    m_fragmentUniformBuffer = std::make_shared<VulkanUniformBuffer>(memoryManager, (uint32_t)sizeof(VulkanLocalFragmentUniforms));
+
+    m_material = std::make_shared<VulkanMaterialDelegate>(m_vertexUniformBuffer,
+        m_fragmentUniformBuffer,
+        material,
+        memoryManager);
+
+    m_vertexBuffer = std::make_shared<VulkanVertexBuffer>(memoryManager, m_numVertices, m_vertexSize, m_numTriangles);
+}
+
+void
+VulkanRenderDelegate::updateTransform(std::shared_ptr<Geometry> geometry)
+{
+    glm::mat4 transform;
+
+    glm::vec3 scale(geometry->getScaling());
+    transform = glm::scale(transform, scale);
+
+    auto rotation = geometry->getRotation();
+    glm::mat3 rotationMatrix(rotation(0, 0), rotation(0, 1), rotation(0, 2),
+                             rotation(1, 0), rotation(1, 1), rotation(1, 2),
+                             rotation(2, 0), rotation(2, 1), rotation(2, 2));
+
+    transform = glm::mat4(rotationMatrix) * transform;
+
+    transform[3][0] = geometry->getTranslation().x();
+    transform[3][1] = geometry->getTranslation().y();
+    transform[3][2] = geometry->getTranslation().z();
+
+    m_localVertexUniforms.transform = transform;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.h b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..2ba54e6ecb53f47c59d9054d2f76c7edcb444abe
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.h
@@ -0,0 +1,120 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanRenderDelegate_h
+#define imstkVulkanRenderDelegate_h
+
+#include "glm/glm.hpp"
+#include "glm/gtc/matrix_transform.hpp"
+#include "glm/gtc/quaternion.hpp"
+
+#include <array>
+
+#include "imstkGeometry.h"
+#include "imstkVulkanVertexBuffer.h"
+#include "imstkVulkanUniformBuffer.h"
+#include "imstkVulkanMaterialDelegate.h"
+
+#include "vtkPolyDataMapper.h"
+
+namespace imstk
+{
+class VulkanRenderDelegate
+{
+public:
+
+    ///
+    /// \brief Default destructor
+    ///
+    ~VulkanRenderDelegate() = default;
+
+    ///
+    /// \brief Creates a render delegate from geometry
+    ///
+    static std::shared_ptr<VulkanRenderDelegate> make_delegate(
+        std::shared_ptr<Geometry> geom,
+        VulkanMemoryManager& details);
+
+    ///
+    /// \brief Get source geometry
+    ///
+    virtual std::shared_ptr<Geometry> getGeometry() const = 0;
+
+    ///
+    /// \brief Update render geometry. This is implemented a little differently
+    ///        in that memory is directly mapped from the Geometry to the
+    ///        RenderDelegate.
+    ///
+    virtual void update(){};
+
+    ///
+    /// \brief Initialize memory backing
+    ///
+    void initialize(VkDevice &device, uint32_t memoryIndex, std::shared_ptr<Geometry> geometry);
+
+    ///
+    /// \brief Get vertex buffer
+    ///
+    std::shared_ptr<VulkanVertexBuffer> getBuffer();
+
+    ///
+    /// \brief Get vertex buffer
+    ///
+    vtkSmartPointer<vtkPolyDataMapper> setUpMapper(vtkAlgorithmOutput * source);
+
+    ///
+    /// \brief Initialize data
+    ///
+    void initializeData(VulkanMemoryManager& memoryManager,
+                        std::shared_ptr<RenderMaterial> material = nullptr);
+
+    ///
+    /// \brief Initialize data
+    ///
+    void updateTransform(std::shared_ptr<Geometry> geometry);
+
+protected:
+    friend class VulkanVertexBuffer;
+    friend class VulkanRenderer;
+    friend class VulkanUniformBuffer;
+
+    unsigned int m_numTriangles;
+    unsigned int m_numVertices;
+    unsigned int m_vertexSize;
+
+    ///
+    /// \brief Default constructor (protected)
+    ///
+    VulkanRenderDelegate(){};
+
+    std::shared_ptr<VulkanVertexBuffer> m_vertexBuffer;
+
+    std::shared_ptr<VulkanUniformBuffer> m_vertexUniformBuffer;
+    std::shared_ptr<VulkanUniformBuffer> m_fragmentUniformBuffer;
+
+    std::shared_ptr<VulkanMaterialDelegate> m_material;
+
+    VulkanLocalVertexUniforms m_localVertexUniforms;
+    VulkanLocalFragmentUniforms m_localFragmentUniforms;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..41efdd7f3511cda6e5954ecd2710babf52b47b5b
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.cpp
@@ -0,0 +1,107 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanSphereRenderDelegate.h"
+
+namespace imstk
+{
+VulkanSphereRenderDelegate::VulkanSphereRenderDelegate(std::shared_ptr<Sphere> sphere, VulkanMemoryManager& memoryManager)
+    : m_geometry(sphere)
+{
+    auto radius = m_geometry->getRadius();
+
+    auto source = vtkSmartPointer<vtkSphereSource>::New();
+    source->SetCenter(WORLD_ORIGIN[0], WORLD_ORIGIN[1], WORLD_ORIGIN[2]);
+    source->SetPhiResolution(20);
+    source->SetThetaResolution(20);
+    source->SetRadius(radius);
+    source->Update();
+
+    auto triangulate = vtkSmartPointer<vtkTriangleFilter>::New();
+    triangulate->SetInputConnection(source->GetOutputPort());
+
+    triangulate->Update();
+
+    auto sourceData = triangulate->GetOutput();
+
+    auto positions = sourceData->GetPoints();
+    auto normals = sourceData->GetPointData()->GetNormals();
+    auto triangles = sourceData->GetPolys();
+
+    for (int i = 0; i < sourceData->GetNumberOfPoints(); i++)
+    {
+        auto position = positions->GetPoint(i);
+        auto normal = normals->GetTuple(i);
+
+        VulkanBasicVertex sphereVertex;
+
+        sphereVertex.position[0] = position[0];
+        sphereVertex.position[1] = position[1];
+        sphereVertex.position[2] = position[2];
+
+        sphereVertex.normal[0] = normal[0];
+        sphereVertex.normal[1] = normal[1];
+        sphereVertex.normal[2] = normal[2];
+
+        m_sphereVertices.push_back(sphereVertex);
+    }
+
+    triangles->InitTraversal();
+
+    for (int i = 0; i < triangles->GetNumberOfCells(); i++)
+    {
+        auto points = vtkSmartPointer<vtkIdList>::New();
+        auto triangle = triangles->GetNextCell(points);
+
+        std::array<uint32_t, 3> trianglePoints = {
+            (uint32_t)points->GetId(0),
+            (uint32_t)points->GetId(1),
+            (uint32_t)points->GetId(2)
+        };
+
+        m_sphereTriangles.push_back(trianglePoints);
+    }
+
+    m_numVertices = (uint32_t)m_sphereVertices.size();
+    m_numTriangles = (uint32_t)m_sphereTriangles.size();
+    m_vertexSize = sizeof(VulkanBasicVertex);
+
+    this->initializeData(memoryManager);
+
+    m_vertexBuffer->updateVertexBuffer(&m_sphereVertices, &m_sphereTriangles);
+
+    this->update();
+}
+
+void
+VulkanSphereRenderDelegate::update()
+{
+    this->updateTransform(m_geometry);
+    m_vertexUniformBuffer->updateUniforms(sizeof(VulkanLocalVertexUniforms),
+        (void *)&m_localVertexUniforms);
+}
+
+std::shared_ptr<Geometry>
+VulkanSphereRenderDelegate::getGeometry() const
+{
+    return m_geometry;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.h b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..2f42035c20f52ff1b933085957c9c54c1bef481e
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSphereRenderDelegate.h
@@ -0,0 +1,67 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanSphereRenderDelegate_h
+#define imstkVulkanSphereRenderDelegate_h
+
+#include "imstkSphere.h"
+
+#include "imstkVulkanRenderDelegate.h"
+
+#include "vtkSphereSource.h"
+#include "vtkPointData.h"
+#include "vtkTriangleFilter.h"
+
+namespace imstk
+{
+class VulkanSphereRenderDelegate : public VulkanRenderDelegate
+{
+public:
+
+    ///
+    /// \brief Default destructor
+    ///
+    ~VulkanSphereRenderDelegate() = default;
+
+    ///
+    /// \brief Default constructor
+    ///
+    VulkanSphereRenderDelegate(std::shared_ptr<Sphere> sphere, VulkanMemoryManager& memoryManager);
+
+    ///
+    /// \brief Update render geometry
+    ///
+    void update() override;
+
+    ///
+    /// \brief Get source geometry
+    ///
+    std::shared_ptr<Geometry> getGeometry() const override;
+
+protected:
+    std::shared_ptr<Sphere> m_geometry;
+
+    std::vector<std::array<uint32_t, 3>> m_sphereTriangles;
+    std::vector<VulkanBasicVertex> m_sphereVertices;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2c8bf4986e31573440adab37bdcb35783fbd01b6
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.cpp
@@ -0,0 +1,125 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanSurfaceMeshRenderDelegate.h"
+
+namespace imstk
+{
+VulkanSurfaceMeshRenderDelegate::VulkanSurfaceMeshRenderDelegate(std::shared_ptr<SurfaceMesh> surfaceMesh, VulkanMemoryManager& memoryManager)
+    : m_geometry(surfaceMesh)
+{
+    m_numVertices = (uint32_t)m_geometry->getNumVertices();
+    m_numTriangles = (uint32_t)m_geometry->getNumTriangles();
+    m_vertexSize = sizeof(VulkanBasicVertex);
+
+    this->initializeData(memoryManager, m_geometry->getRenderMaterial());
+
+    this->updateVertexBuffer();
+
+    this->update();
+}
+
+void
+VulkanSurfaceMeshRenderDelegate::updateVertexBuffer()
+{
+    auto vertices = (VulkanBasicVertex *)m_vertexBuffer->mapVertices();
+
+    auto normals = m_geometry->getVertexNormals();
+    auto tangents = m_geometry->getVertexTangents();
+    auto bitangents = m_geometry->getVertexBitangents();
+    const StdVectorOfVectorf* UVs;
+
+    if (m_geometry->getDefaultTCoords() != "")
+    {
+        UVs = m_geometry->getPointDataArray(m_geometry->getDefaultTCoords());
+    }
+    else
+    {
+        UVs = nullptr;
+    }
+
+    for (unsigned i = 0; i < m_geometry->getNumVertices(); i++)
+    {
+        vertices[i].position = glm::vec3(
+            m_geometry->getVertexPosition(i)[0],
+            m_geometry->getVertexPosition(i)[1],
+            m_geometry->getVertexPosition(i)[2]);
+
+        if (normals.size() == m_geometry->getNumVertices())
+        {
+            vertices[i].normal = glm::vec3(
+                normals[i][0],
+                normals[i][1],
+                normals[i][2]);
+        }
+
+        if (tangents.size() == m_geometry->getNumVertices())
+        {
+            vertices[i].tangent = glm::vec3(
+                tangents[i][0],
+                tangents[i][1],
+                tangents[i][2]);
+
+            vertices[i].bitangent = glm::vec3(
+                bitangents[i][0],
+                bitangents[i][1],
+                bitangents[i][2]);
+        }
+
+        if (UVs && UVs->size() == m_geometry->getNumVertices())
+        {
+            vertices[i].uv = glm::vec2(
+                (*UVs)[i][0],
+                (*UVs)[i][1]);
+        }
+    }
+
+    m_vertexBuffer->unmapVertices();
+
+    auto triangles = (std::array<uint32_t, 3> *)m_vertexBuffer->mapTriangles();
+
+    for (unsigned i = 0; i < m_geometry->getNumTriangles(); i++)
+    {
+        triangles[i][0] = (uint32_t)m_geometry->getTrianglesVertices()[i][0];
+        triangles[i][1] = (uint32_t)m_geometry->getTrianglesVertices()[i][1];
+        triangles[i][2] = (uint32_t)m_geometry->getTrianglesVertices()[i][2];
+    }
+    m_vertexBuffer->unmapTriangles();
+}
+
+void
+VulkanSurfaceMeshRenderDelegate::update()
+{
+    this->updateTransform(m_geometry);
+    m_vertexUniformBuffer->updateUniforms(sizeof(VulkanLocalVertexUniforms),
+        (void *)&m_localVertexUniforms);
+
+    m_geometry->computeVertexNormals(); // This method needs to be rewritten
+    // TODO: only update if deformable
+    this->updateVertexBuffer();
+}
+
+std::shared_ptr<Geometry>
+VulkanSurfaceMeshRenderDelegate::getGeometry() const
+{
+    return m_geometry;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.h b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..fe8e44a9646386c9eeac6034f77e755338a3cc98
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanSurfaceMeshRenderDelegate.h
@@ -0,0 +1,65 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanSurfaceMeshRenderDelegate_h
+#define imstkVulkanSurfaceMeshRenderDelegate_h
+
+#include "imstkSurfaceMesh.h"
+
+#include "imstkVulkanRenderDelegate.h"
+
+namespace imstk
+{
+class VulkanSurfaceMeshRenderDelegate : public VulkanRenderDelegate {
+public:
+
+    ///
+    /// \brief Default destructor
+    ///
+    ~VulkanSurfaceMeshRenderDelegate() = default;
+
+    ///
+    /// \brief Default constructor
+    ///
+    VulkanSurfaceMeshRenderDelegate(std::shared_ptr<SurfaceMesh> surfaceMesh, VulkanMemoryManager& memoryManager);
+
+    ///
+    /// \brief Update render geometry
+    ///
+    void update() override;
+
+    ///
+    /// \brief Get source geometry
+    ///
+    std::shared_ptr<Geometry> getGeometry() const override;
+
+
+    ///
+    /// \brief Fill vertex buffer
+    ///
+    void updateVertexBuffer();
+
+protected:
+    std::shared_ptr<SurfaceMesh> m_geometry;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/HDR_tonemap_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/HDR_tonemap_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4452b3bab53a4c6066f208c6e09c81169ad03654
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/HDR_tonemap_frag.frag
@@ -0,0 +1,48 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform sampler2D colorTexture;
+
+layout (location = 0) out vec4 finalColor;
+
+layout (location = 3) in vertexData{
+    vec2 uv;
+}vertex;
+
+// Reinhard tonemapping
+vec3 tonemap(vec3 color)
+{
+    vec3 tempColor = (color / (color + 1));
+    return tempColor;
+}
+
+// John Hable's filmic tonemapping
+vec3 tonemapTwoHelper(vec3 color)
+{
+    float shoulderValue = 0.22;
+    float linearValue = 0.3;
+    float linearDir = 0.1;
+    float toeValue = 0.2;
+    float toeUpper = 0.001;
+    float toeLower = 0.3;
+
+    vec3 x = color * (color * shoulderValue + linearValue * linearDir) + toeValue * toeUpper;
+    vec3 y = color * (color * shoulderValue + linearValue) + toeValue * toeLower;
+    float z = toeUpper/toeLower;
+
+    return (x / y) - z;
+}
+
+vec3 tonemapTwo(vec3 color)
+{
+    vec3 whiteColor = vec3(1.0);
+    vec3 tempColor = tonemapTwoHelper(color);
+    vec3 normalizedColor = tonemapTwoHelper(whiteColor);
+
+    return tempColor / whiteColor;
+}
+
+void main(void)
+{
+    vec4 inputColor = texture(colorTexture, vertex.uv);
+    finalColor = vec4(tonemapTwo(inputColor.rgb), inputColor.a);
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/bloom_threshold_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/bloom_threshold_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2b45532cb30bc5cf11d76f94f228c0fb890bda43
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/bloom_threshold_frag.frag
@@ -0,0 +1,15 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform sampler2D colorTexture;
+
+layout (location = 0) out vec4 finalColor;
+
+layout (location = 3) in vertexData{
+    vec2 uv;
+}vertex;
+
+void main(void)
+{
+    vec3 inputColor = texture(colorTexture, vertex.uv).rgb;
+    finalColor = vec4(inputColor.rgb * vec3(0.1), 1);
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/blur_horizontal_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/blur_horizontal_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..648286ed042d8e8049aa5b17f75399407b61c612
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/blur_horizontal_frag.frag
@@ -0,0 +1,34 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform sampler2D colorTexture;
+
+layout (location = 0) out vec4 finalColor;
+
+layout (location = 3) in vertexData{
+    vec2 uv;
+}vertex;
+
+layout (push_constant) uniform pushConstants
+{
+    float width;
+    float height;
+    float numSamples;
+    float values[10];
+    float offsets[10];
+}constants;
+
+void main(void)
+{
+    vec3 inputColor = vec3(0);
+    inputColor += texture(colorTexture, vertex.uv).rgb * constants.values[0];
+
+    for (int i = 1; i < constants.numSamples; i++)
+    {
+        vec3 color = texture(colorTexture, vertex.uv + vec2(constants.offsets[i] / constants.width, 0) ).rgb;
+        inputColor += color * constants.values[i];
+        color = texture(colorTexture, vertex.uv - vec2(constants.offsets[i] / constants.width, 0) ).rgb;
+        inputColor += color * constants.values[i];
+    }
+
+    finalColor = vec4(inputColor, 1);
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/blur_vertical_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/blur_vertical_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5a9c1f8b01cf6e2c922fea2d47b11de9c414b7ce
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/blur_vertical_frag.frag
@@ -0,0 +1,34 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform sampler2D colorTexture;
+
+layout (location = 0) out vec4 finalColor;
+
+layout (location = 3) in vertexData{
+    vec2 uv;
+}vertex;
+
+layout (push_constant) uniform pushConstants
+{
+    float width;
+    float height;
+    float numSamples;
+    float values[10];
+    float offsets[10];
+}constants;
+
+void main(void)
+{
+    vec3 inputColor = vec3(0);
+    inputColor += texture(colorTexture, vertex.uv).rgb * constants.values[0];
+
+    for (int i = 1; i < constants.numSamples; i++)
+    {
+        vec3 color = texture(colorTexture, vertex.uv + vec2(0, constants.offsets[i] / constants.height) ).rgb;
+        inputColor += color * constants.values[i];
+        color = texture(colorTexture, vertex.uv - vec2(0, constants.offsets[i] / constants.height) ).rgb;
+        inputColor += color * constants.values[i];
+    }
+
+    finalColor = vec4(inputColor, 1);
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/composite_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/composite_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3b9bda38788fffb6125a9ea8d7119589a969570f
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/composite_frag.frag
@@ -0,0 +1,17 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform sampler2D colorTexture1;
+layout (set = 0, binding = 1) uniform sampler2D colorTexture2;
+
+layout (location = 0) out vec4 finalColor;
+
+layout (location = 3) in vertexData{
+    vec2 uv;
+}vertex;
+
+void main(void)
+{
+    vec4 color1 = texture(colorTexture1, vertex.uv);
+    vec4 color2 = texture(colorTexture2, vertex.uv);
+    finalColor = vec4(color1.rgb + color2.rgb, color1.a);
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/postprocess_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/postprocess_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4b55c9ba9a3e662c9e48cfae272b60870482a92c
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/postprocess_frag.frag
@@ -0,0 +1,15 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform sampler2D colorTexture;
+
+layout (location = 0) out vec4 finalColor;
+
+layout (location = 3) in vertexData{
+    vec2 uv;
+}vertex;
+
+void main(void)
+{
+    vec4 inputColor = texture(colorTexture, vertex.uv);
+    finalColor = inputColor * vec4(1,0,0,1);
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/postprocess_vert.vert b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/postprocess_vert.vert
new file mode 100644
index 0000000000000000000000000000000000000000..90996d0141e9e814756bd67aebccc86a93974b22
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/postprocess_vert.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) in vec3 vertexPosition;
+layout (location = 1) in vec2 vertexUV;
+
+layout (location = 3) out vertexData{
+    vec2 uv;
+}vertex;
+
+void main(void)
+{
+    vertex.uv = vertexUV;
+    gl_Position = vec4(vertexPosition, 1.0);
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/sss_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/sss_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..eb06949aedf0b977d786317db952c0beb121084a
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/PostProcessing/sss_frag.frag
@@ -0,0 +1,109 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform sampler2D colorTexture;
+layout (set = 0, binding = 1) uniform sampler2D depthTexture;
+layout (set = 0, binding = 2) uniform sampler2D normalTexture;
+
+layout (location = 0) out vec4 finalColor;
+
+layout (location = 3) in vertexData{
+    vec2 uv;
+}vertex;
+
+layout (push_constant) uniform pushConstants
+{
+    vec2 direction;
+    float fov;
+    float kernelWidth;
+    float nearPlane;
+    float farPlane;
+    float numSamples;
+    float nearValues[10];
+    float farValues[10];
+}constants;
+
+const float e = 2.7183;
+const float pi = 3.1415;
+
+const vec3 near = vec3(0.25, 0.08, 0.02);
+const vec3 far = vec3(1.0, 0.3, 0.05);
+const float w = 0.6;
+
+vec3 maxValue = vec3(0.0);
+
+float getLinearDepth(float depth);
+vec3 separableKernel(float x, float w, vec3 near, vec3 far, float depth);
+vec3 calculateGaussian(float x, vec3 stdDev);
+
+void main(void)
+{
+    vec3 inputColor = vec3(0);
+
+    vec4 normal = texture(normalTexture, vertex.uv);
+    float sss = normal.a;
+
+
+    if (sss >= 0.001)
+    {
+        float depth = getLinearDepth(texture(depthTexture, vertex.uv).r);
+        float dx = 0.001 / (tan(constants.fov / 2.0) * depth);
+        dx = dx * normal.a / (constants.kernelWidth);
+
+        maxValue = separableKernel(0, w, near, far, depth) * sss;
+
+        for (int i = 1; i < constants.numSamples; i++)
+        {
+            vec2 offset = vec2(i * dx) * constants.direction;
+            float partOfMeshPos = texture(normalTexture, vertex.uv + offset).a;
+            float partOfMeshNeg = texture(normalTexture, vertex.uv - offset).a;
+
+            depth = getLinearDepth(texture(depthTexture, vertex.uv + offset).r);
+            maxValue += separableKernel(i/(constants.numSamples), w, near, far, depth) * partOfMeshPos;
+
+            depth = getLinearDepth(texture(depthTexture, vertex.uv - offset).r);
+            maxValue += separableKernel(-i/(constants.numSamples), w, near, far, depth) * partOfMeshNeg;
+        }
+
+        inputColor += texture(colorTexture, vertex.uv).rgb * separableKernel(0, w, near, far, depth) * sss;
+
+        for (int i = 1; i < constants.numSamples; i++)
+        {
+            vec2 offset = vec2(i * dx) * constants.direction;
+            float partOfMeshPos = texture(normalTexture, vertex.uv + offset).a;
+            float partOfMeshNeg = texture(normalTexture, vertex.uv - offset).a;
+
+            vec3 color = texture(colorTexture, vertex.uv + offset).rgb;
+            depth = getLinearDepth(texture(depthTexture, vertex.uv + offset).r);
+            inputColor += color * separableKernel(i/(constants.numSamples), w, near, far, depth) * partOfMeshPos;
+
+            color = texture(colorTexture, vertex.uv - offset).rgb;
+            depth = getLinearDepth(texture(depthTexture, vertex.uv - offset).r);
+            inputColor += color * separableKernel(-i/(constants.numSamples), w, near, far, depth) * partOfMeshNeg;
+        }
+        inputColor = inputColor / maxValue;
+    }
+    else
+    {
+        inputColor = texture(colorTexture, vertex.uv).rgb;
+    }
+
+    finalColor = vec4(inputColor, 1);
+}
+
+float getLinearDepth(float depth)
+{
+    return (2 * constants.nearPlane * constants.farPlane) /
+        (constants.nearPlane + constants.farPlane - (depth * (constants.farPlane - constants.nearPlane)));
+}
+
+vec3 separableKernel(float x, float w, vec3 near, vec3 far, float depth)
+{
+    vec3 depthCompensation = vec3(1.0);//pow(vec3(e), (- depth * depth) / (2 * max(near, far)));
+    vec3 separableKernelValue = w * calculateGaussian(x, near) + (1 - w) * calculateGaussian(x, far);
+    return depthCompensation * separableKernelValue;
+}
+
+vec3 calculateGaussian(float x, vec3 stdDev)
+{
+    return (1 / sqrt(2 * pi * stdDev * stdDev)) * pow(vec3(e), -(x * x) / (2 * stdDev * stdDev));
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_frag.frag b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_frag.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0bcc11b6196ddf0355ea5de94908bbe64013736d
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_frag.frag
@@ -0,0 +1,196 @@
+#version 450
+
+layout (location = 0) out vec4 outputColor;
+layout (location = 1) out vec4 outputNormal;
+layout (location = 2) out vec4 outputSpecular;
+
+layout (constant_id = 0) const uint numLights = 0;
+layout (constant_id = 1) const bool tessellation = false;
+layout (constant_id = 2) const bool hasDiffuseTexture = false;
+layout (constant_id = 3) const bool hasNormalTexture = false;
+layout (constant_id = 4) const bool hasSpecularTexture = false;
+layout (constant_id = 5) const bool hasRoughnessTexture = false;
+layout (constant_id = 6) const bool hasMetalnessTexture = false;
+layout (constant_id = 7) const bool hasSubsurfaceScatteringTexture = false;
+
+struct light
+{
+    vec3 lightVector;
+    float lightAngle;
+    vec3 lightColor;
+    float lightIntensity;
+};
+
+layout (set = 1, binding = 0) uniform globalUniforms
+{
+    light lights[16];
+} globals;
+
+layout (location = 0) in vertexData{
+    vec3 position;
+    vec3 normal;
+    vec2 uv;
+    mat3 TBN;
+    vec3 cameraPosition;
+}vertex;
+
+layout (set = 1, binding = 2) uniform sampler2D diffuseTexture;
+layout (set = 1, binding = 3) uniform sampler2D normalTexture;
+layout (set = 1, binding = 4) uniform sampler2D roughnessTexture;
+layout (set = 1, binding = 5) uniform sampler2D metalnessTexture;
+layout (set = 1, binding = 6) uniform sampler2D subsurfaceScatteringTexture;
+//layout (set = 1, binding = 6) uniform samplerCube irradianceCubemapTexture;
+
+// Constants
+const float PI = 3.1415;
+
+// Global variables
+vec3 finalDiffuse = vec3(0);
+vec3 finalSpecular = vec3(0);
+vec3 diffuseColor = vec3(1, 1, 1);
+vec3 normal = vec3(0, 0, 1);
+float specularValue = 20;
+vec3 cameraDirection = vec3(0, 0, 1);
+float specularPow = 0;
+vec3 specularColor = vec3(1, 1, 1);
+float roughness = 1.0;
+float metalness = 0.0;
+float subsurfaceScattering = 0.0;
+vec3 diffuseIndirect = vec3(0, 0, 0);
+
+// Functions
+void readTextures();
+void calculateIndirectLighting();
+void calculateClassicalLighting(vec3 lightDirection, vec3 lightColor);
+void calculatePBRLighting(vec3 lightDirection, vec3 lightColor);
+float geometryTerm(vec3 vector);
+float squared(float x);
+
+void main(void)
+{
+    readTextures();
+
+    // If it's 0, then there's a divide by zero error
+    roughness = max(roughness * roughness, 0.0001);
+
+    cameraDirection = normalize(vertex.cameraPosition - vertex.position);
+
+    //calculateIndirectLighting();
+
+    for (int i = 0; i < numLights; i++)
+    {
+        calculatePBRLighting(normalize(globals.lights[i].lightVector), globals.lights[i].lightColor * 5);
+    }
+
+    finalDiffuse *= diffuseColor;
+    outputColor = vec4(finalDiffuse, 1);
+    outputSpecular = vec4(finalSpecular, 1);
+
+    outputNormal = vec4(normal, subsurfaceScattering);
+}
+
+void readTextures()
+{
+    float mipLevel = textureQueryLod(diffuseTexture, vertex.uv).x;
+
+    if (hasDiffuseTexture)
+    {
+        diffuseColor = texture(diffuseTexture, vertex.uv, mipLevel).rgb;
+    }
+
+    if (hasNormalTexture)
+    {
+        normal = vertex.TBN * normalize((2.0 * texture(normalTexture, vertex.uv, mipLevel).rgb) - 1.0);
+    }
+    else
+    {
+        normal = vertex.TBN * normal;
+    }
+
+    if (hasRoughnessTexture)
+    {
+        roughness = texture(roughnessTexture, vertex.uv, mipLevel).r;
+    }
+
+    if (hasMetalnessTexture)
+    {
+        metalness = texture(metalnessTexture, vertex.uv, mipLevel).r;
+    }
+
+    if (hasSubsurfaceScatteringTexture)
+    {
+        subsurfaceScattering = texture(subsurfaceScatteringTexture, vertex.uv, mipLevel).r;
+    }
+}
+
+void calculateIndirectLighting()
+{
+    // Fresnel term: Schlick's approximation
+    vec3 F_0 = mix(vec3(0.04), diffuseColor, metalness);
+    vec3 F = (F_0) + (1.0 - F_0) * pow(1.0 - max(dot(cameraDirection, normal), 0), 5);
+
+    // Energy conservation
+    vec3 k_s = F;
+    vec3 k_d = (1 - k_s) * (1.0 - metalness);
+
+    finalDiffuse += diffuseIndirect * k_d;
+}
+
+void calculateClassicalLighting(vec3 lightDirection, vec3 lightColor)
+{
+    // Lambert BRDF
+    float l_dot_n = max(dot(normal, lightDirection), 0);
+
+    vec3 halfway = normalize(lightDirection + cameraDirection);
+
+    // Blinn-Phong BRDF
+    float specularity = max(dot(normal, halfway), 0);
+    specularPow = pow(specularity, specularValue);
+		
+    finalDiffuse += lightColor * l_dot_n;
+    finalSpecular += specularPow * specularColor * l_dot_n;
+}
+
+void calculatePBRLighting(vec3 lightDirection, vec3 lightColor)
+{
+    // Lambert BRDF
+    float diffusePow = 1.0 / PI;
+    float l_dot_n = max(dot(normal, lightDirection), 0);
+
+    vec3 halfway = normalize(normalize(lightDirection) + cameraDirection);
+
+    // Cook-Torrance BRDF
+
+    // Distribution term: Trowbridge-Reitz
+    float roughness_squared = roughness * roughness;
+    float D = roughness_squared / (PI * squared(squared(max(dot(halfway, normal), 0)) * (roughness_squared - 1) + 1));
+
+    // Fresnel term: Schlick's approximation
+    vec3 F_0 = mix(vec3(0.04), diffuseColor, metalness);
+    vec3 F = (F_0) + (1.0 - F_0) * pow(1.0 - max(dot(cameraDirection, halfway), 0), 5);
+
+    // Geometry term: Schlick's GGX
+    float G = geometryTerm(cameraDirection) * geometryTerm(lightDirection);
+
+    vec3 specularPow = (D * F * G) /
+        (4 * max(dot(normal, cameraDirection), 0.01) * max(dot(normal, lightDirection), 0.01));
+
+    // Energy conservation
+    vec3 k_s = F;
+    vec3 k_d = (1 - k_s) * (1.0 - metalness);
+
+    finalDiffuse += k_d * diffusePow * lightColor * l_dot_n;
+    finalSpecular += specularPow * specularColor * lightColor * l_dot_n;
+}
+
+float geometryTerm(vec3 vector)
+{
+    float k = roughness / 2;
+    float vector_dot_n = max(dot(vector, normal), 0);
+    return vector_dot_n / (vector_dot_n * (1 - k) + k);
+}
+
+float squared(float x)
+{
+    return x * x;
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_tesc.tesc b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_tesc.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..c88f1747864447812cc57bf49be15465d054b1fe
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_tesc.tesc
@@ -0,0 +1,138 @@
+#version 450
+
+// PN triangles implementation
+
+layout (constant_id = 0) const uint numLights = 0;
+layout (constant_id = 3) const bool hasNormalTexture = false;
+
+layout (vertices = 3) out;
+
+struct light
+{
+    vec3 lightVector;
+    float lightAngle;
+    vec3 lightColor;
+    float lightIntensity;
+};
+
+layout (set = 0, binding = 0) uniform globalUniforms
+{
+    mat4 projectionMatrix;
+    mat4 viewMatrix;
+    vec4 cameraPosition;
+    light lights[16];
+} globals;
+
+layout (set = 0, binding = 1) uniform localUniforms
+{
+    mat4 transform;
+} locals;
+
+layout(location = 0) in vertexData{
+    vec3 position;
+    vec3 normal;
+    vec2 uv;
+    mat3 TBN;
+    vec3 cameraPosition;
+}vertex[];
+
+layout (location = 0) out vertexDataTessellation{
+    vec3 position;
+    vec3 normal;
+    vec2 uv;
+    mat3 TBN;
+    vec3 cameraPosition;
+}vertexTessellation[];
+
+patch out TrianglePatch
+{
+    vec3 b300;
+    vec3 b030;
+    vec3 b003;
+    vec3 b012;
+    vec3 b021;
+    vec3 b102;
+    vec3 b120;
+    vec3 b201;
+    vec3 b210;
+    vec3 b111;
+    vec3 n200;
+    vec3 n020;
+    vec3 n002;
+    vec3 n110;
+    vec3 n011;
+    vec3 n101;
+} trianglePatch;
+
+float projectPosition(vec3 point, vec3 planePoint, vec3 planeNormal)
+{
+    return dot(point - planePoint, planeNormal);
+}
+
+float projectNormal(vec3 point1, vec3 point2, vec3 normal1, vec3 normal2)
+{
+    vec3 difference = point2 - point1;
+    return dot(difference, normal1 + normal2) / dot(difference, difference);
+}
+
+vec3 computeEdgeControlPosition(int index1, int index2)
+{
+    vec3 point1 = vertex[index1].position;
+    vec3 point2 = vertex[index2].position;
+    vec3 normal1 = vertex[index1].normal;
+    return (2 * point1 + point2 - projectPosition(point2, point1, normal1) * normal1) / 3;
+}
+
+vec3 computeEdgeControlNormal(int index1, int index2)
+{
+    vec3 point1 = vertex[index1].position;
+    vec3 point2 = vertex[index2].position;
+    vec3 normal1 = vertex[index1].normal;
+    vec3 normal2 = vertex[index2].normal;
+    return normalize(normal1 + normal2 - projectNormal(point1, point2, normal1, normal2) * (point2 - point1));
+}
+
+void main(void)
+{
+    // Pass through vertex values
+    vertexTessellation[gl_InvocationID].position = vertex[gl_InvocationID].position;
+    vertexTessellation[gl_InvocationID].normal = vertex[gl_InvocationID].normal;
+    vertexTessellation[gl_InvocationID].uv = vertex[gl_InvocationID].uv;
+    vertexTessellation[gl_InvocationID].TBN = vertex[gl_InvocationID].TBN;
+    vertexTessellation[gl_InvocationID].cameraPosition = vertex[gl_InvocationID].cameraPosition;
+
+    // Calculate patch control point positions
+    trianglePatch.b300 = vertex[0].position;
+    trianglePatch.b030 = vertex[1].position;
+    trianglePatch.b003 = vertex[2].position;
+    trianglePatch.b012 = computeEdgeControlPosition(2, 1);
+    trianglePatch.b021 = computeEdgeControlPosition(1, 2);
+    trianglePatch.b102 = computeEdgeControlPosition(2, 0);
+    trianglePatch.b120 = computeEdgeControlPosition(1, 0);
+    trianglePatch.b201 = computeEdgeControlPosition(0, 2);
+    trianglePatch.b210 = computeEdgeControlPosition(0, 1);
+    vec3 averageEdge = (trianglePatch.b012 + trianglePatch.b021
+        + trianglePatch.b102 + trianglePatch.b120
+        + trianglePatch.b201 + trianglePatch.b210) / 6;
+    vec3 averageVertex = (trianglePatch.b300 + trianglePatch.b030 + trianglePatch.b003) / 3;
+    trianglePatch.b111 = averageEdge + (averageEdge - averageVertex) / 2;
+
+    // Calculate patch control point normals
+    trianglePatch.n200 = vertex[0].normal;
+    trianglePatch.n020 = vertex[1].normal;
+    trianglePatch.n002 = vertex[2].normal;
+    trianglePatch.n110 = computeEdgeControlNormal(0, 1);
+    trianglePatch.n011 = computeEdgeControlNormal(1, 2);
+    trianglePatch.n101 = computeEdgeControlNormal(2, 1);
+
+    // Determines how much to tessellate
+    if (gl_InvocationID == 0)
+    {
+        gl_TessLevelOuter[0] = 3;
+        gl_TessLevelOuter[1] = 3;
+        gl_TessLevelOuter[2] = 3;
+        gl_TessLevelInner[0] = 3;
+    }
+
+    //vertexTessellation[0] = vertex[0];
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_tese.tese b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_tese.tese
new file mode 100644
index 0000000000000000000000000000000000000000..6d6e1091670441a437576fac6acbe17d6b4b0b88
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_tese.tese
@@ -0,0 +1,111 @@
+#version 450
+
+// PN triangles implementation
+
+layout (constant_id = 0) const uint numLights = 0;
+layout (constant_id = 3) const bool hasNormalTexture = false;
+
+layout (triangles, equal_spacing, cw) in;
+
+struct light
+{
+    vec3 lightVector;
+    float lightAngle;
+    vec3 lightColor;
+    float lightIntensity;
+};
+
+layout (set = 0, binding = 0) uniform globalUniforms
+{
+    mat4 projectionMatrix;
+    mat4 viewMatrix;
+    vec4 cameraPosition;
+    light lights[16];
+} globals;
+
+layout (set = 0, binding = 1) uniform localUniforms
+{
+    mat4 transform;
+} locals;
+
+layout (location = 0) out vertexData{
+    vec3 position;
+    vec3 normal;
+    vec2 uv;
+    mat3 TBN;
+    vec3 cameraPosition;
+}vertex;
+
+layout (location = 0) in vertexDataTessellation{
+    vec3 position;
+    vec3 normal;
+    vec2 uv;
+    mat3 TBN;
+    vec3 cameraPosition;
+}vertexTessellation[];
+
+patch in TrianglePatch
+{
+    vec3 b300;
+    vec3 b030;
+    vec3 b003;
+    vec3 b012;
+    vec3 b021;
+    vec3 b102;
+    vec3 b120;
+    vec3 b201;
+    vec3 b210;
+    vec3 b111;
+    vec3 n200;
+    vec3 n020;
+    vec3 n002;
+    vec3 n110;
+    vec3 n011;
+    vec3 n101;
+} trianglePatch;
+
+void main(void)
+{
+    // Calculate normal
+    vec3 normal = vec3(1);
+    normal.xyz = trianglePatch.n200 * pow(gl_TessCoord.z, 2)
+        + trianglePatch.n020 * pow(gl_TessCoord.x, 2)
+        + trianglePatch.n002 * pow(gl_TessCoord.y, 2)
+        + trianglePatch.n011 * gl_TessCoord.x * gl_TessCoord.y
+        + trianglePatch.n101 * gl_TessCoord.z * gl_TessCoord.y
+        + trianglePatch.n110 * gl_TessCoord.z * gl_TessCoord.x;
+
+    // Calculate position
+    vec4 position = vec4(1);
+    position.xyz = trianglePatch.b300 * pow(gl_TessCoord.z, 3)
+        + trianglePatch.b030 * pow(gl_TessCoord.x, 3)
+        + trianglePatch.b003 * pow(gl_TessCoord.y, 3)
+        + 3 * trianglePatch.b012 * pow(gl_TessCoord.y, 2) * gl_TessCoord.x
+        + 3 * trianglePatch.b021 * pow(gl_TessCoord.x, 2) * gl_TessCoord.y
+        + 3 * trianglePatch.b102 * pow(gl_TessCoord.y, 2) * gl_TessCoord.z
+        + 3 * trianglePatch.b120 * pow(gl_TessCoord.x, 2) * gl_TessCoord.z
+        + 3 * trianglePatch.b201 * pow(gl_TessCoord.z, 2) * gl_TessCoord.y
+        + 3 * trianglePatch.b210 * pow(gl_TessCoord.z, 2) * gl_TessCoord.x
+        + 6 * trianglePatch.b111 * gl_TessCoord.x * gl_TessCoord.y * gl_TessCoord.z;
+
+    // Interpolate vertex
+    vertex.normal = normalize(normal);
+    vertex.uv = vertexTessellation[0].uv * gl_TessCoord.z
+        + vertexTessellation[1].uv * gl_TessCoord.x
+        + vertexTessellation[2].uv * gl_TessCoord.y;
+    vertex.TBN[0] = normalize(vertexTessellation[0].TBN[0] * gl_TessCoord.z
+        + vertexTessellation[1].TBN[0] * gl_TessCoord.x
+        + vertexTessellation[2].TBN[0] * gl_TessCoord.y);
+    vertex.TBN[1] = normalize(vertexTessellation[0].TBN[1] * gl_TessCoord.z
+        + vertexTessellation[1].TBN[1] * gl_TessCoord.x
+        + vertexTessellation[2].TBN[1] * gl_TessCoord.y);
+    //vertex.TBN[1] = normalize(cross(vertex.normal, vertex.TBN[0]));
+    //vertex.TBN[0] = normalize(cross(vertex.TBN[1], vertex.normal));
+    vertex.TBN[2] = vertex.normal;
+    vertex.cameraPosition = vertexTessellation[0].cameraPosition * gl_TessCoord.z
+        + vertexTessellation[1].cameraPosition * gl_TessCoord.x
+        + vertexTessellation[2].cameraPosition * gl_TessCoord.y;
+    vertex.position = position.xyz;
+
+    gl_Position = globals.projectionMatrix * globals.viewMatrix * position;
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_vert.vert b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_vert.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c88f4c781d6998667b3916978c66f42cad58099b
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/VulkanShaders/mesh/mesh_vert.vert
@@ -0,0 +1,59 @@
+#version 450
+
+layout (constant_id = 0) const uint numLights = 0;
+layout (constant_id = 1) const bool tessellation = false;
+layout (constant_id = 3) const bool hasNormalTexture = false;
+
+layout (location = 0) in vec3 vertexPosition;
+layout (location = 1) in vec3 vertexNormal;
+layout (location = 2) in vec3 vertexTangent;
+layout (location = 3) in vec3 vertexBitangent;
+layout (location = 4) in vec2 vertexUV;
+
+struct light
+{
+    vec3 lightVector;
+    float lightAngle;
+    vec3 lightColor;
+    float lightIntensity;
+};
+
+layout (set = 0, binding = 0) uniform globalUniforms
+{
+    mat4 projectionMatrix;
+    mat4 viewMatrix;
+    vec4 cameraPosition;
+    light lights[16];
+} globals;
+
+layout (set = 0, binding = 1) uniform localUniforms
+{
+    mat4 transform;
+} locals;
+
+layout (location = 0) out vertexData{
+    vec3 position;
+    vec3 normal;
+    vec2 uv;
+    mat3 TBN;
+    vec3 cameraPosition;
+}vertex;
+
+void main(void)
+{
+    vec4 position = locals.transform * vec4(vertexPosition, 1.0);
+    vec4 normal = normalize(locals.transform * vec4(normalize(vertexNormal), 0.0));
+    vec4 tangent = normalize(locals.transform * vec4(normalize(vertexTangent), 0.0));
+    vec4 bitangent = normalize(locals.transform * vec4(normalize(vertexBitangent), 0.0));
+
+    bitangent = vec4(cross(normal.xyz, tangent.xyz), 0.0);
+
+    vertex.TBN = mat3(tangent.xyz, bitangent.xyz, normal.xyz);
+
+    vertex.cameraPosition = globals.cameraPosition.xyz;
+    vertex.position = position.xyz;
+    vertex.normal = normalize(normal.xyz);
+    vertex.uv = vertexUV;
+
+    gl_Position = globals.projectionMatrix * globals.viewMatrix * position;
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanBuffer.h b/Source/Rendering/VulkanRenderer/imstkVulkanBuffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..9b0dd783dde274fd8e47ef120754cde8140747c4
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanBuffer.h
@@ -0,0 +1,48 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanBuffer_h
+#define imstkVulkanBuffer_h
+
+#include "vulkan/vulkan.h"
+
+#include "glm/glm.hpp"
+
+#include <vector>
+
+namespace imstk
+{
+///
+/// \class VulkanBuffer
+///
+/// \brief Abstract class for buffers
+///
+class VulkanBuffer
+{
+public:
+    ///
+    /// \brief Binds the buffer to memory
+    ///
+    virtual void bind(){};
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanFramebuffer.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanFramebuffer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e0ea0be738182002660e44cee22353363b22918
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanFramebuffer.cpp
@@ -0,0 +1,156 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanFramebuffer.h"
+
+namespace imstk
+{
+VulkanFramebuffer::VulkanFramebuffer(
+    VulkanMemoryManager& memoryManager,
+    uint32_t width,
+    uint32_t height,
+    bool lastPass,
+    VkSampleCountFlagBits samples)
+{
+    m_renderDevice = memoryManager.m_device;
+    m_width = width;
+    m_height = height;
+    m_lastPass = lastPass;
+    m_samples = samples;
+}
+
+void
+VulkanFramebuffer::initializeFramebuffer(VkRenderPass * renderPass)
+{
+    m_renderPass = renderPass;
+    std::vector<VkImageView> framebufferAttachments;
+
+    // Color attachment
+    if (m_colorFormat != VK_FORMAT_UNDEFINED)
+    {
+        VkAttachmentDescription attachment;
+        attachment.flags = 0;
+        attachment.format = m_colorFormat;
+        attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+        attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+        attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+        attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+        attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+        attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        attachment.finalLayout =
+            m_lastPass ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+        m_attachments.push_back(attachment);
+        framebufferAttachments.push_back(*m_colorImageView);
+    }
+
+    // Depth attachment
+    if (m_depthFormat != VK_FORMAT_UNDEFINED)
+    {
+        VkAttachmentDescription attachment;
+        attachment.flags = 0;
+        attachment.format = m_depthFormat;
+        attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+        attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+        attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+        attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+        attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+        attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+        m_attachments.push_back(attachment);
+        framebufferAttachments.push_back(*m_depthImageView);
+    }
+
+    // Normal attachment
+    if (m_normalFormat != VK_FORMAT_UNDEFINED)
+    {
+        VkAttachmentDescription attachment;
+        attachment.flags = 0;
+        attachment.format = m_normalFormat;
+        attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+        attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+        attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+        attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+        attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+        attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+        m_attachments.push_back(attachment);
+        framebufferAttachments.push_back(*m_normalImageView);
+    }
+
+    // Normal attachment
+    if (m_specularFormat != VK_FORMAT_UNDEFINED)
+    {
+        VkAttachmentDescription attachment;
+        attachment.flags = 0;
+        attachment.format = m_specularFormat;
+        attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+        attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+        attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+        attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+        attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+        attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+        m_attachments.push_back(attachment);
+        framebufferAttachments.push_back(*m_specularImageView);
+    }
+
+    VkFramebufferCreateInfo framebufferInfo;
+    framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+    framebufferInfo.pNext = nullptr;
+    framebufferInfo.flags = 0;
+    framebufferInfo.renderPass = *m_renderPass;
+    framebufferInfo.attachmentCount = (uint32_t)framebufferAttachments.size();
+    framebufferInfo.pAttachments = &framebufferAttachments[0];
+    framebufferInfo.height = m_height;
+    framebufferInfo.width = m_width;
+    framebufferInfo.layers = 1;
+
+    vkCreateFramebuffer(m_renderDevice, &framebufferInfo, nullptr, &m_framebuffer);
+}
+
+void
+VulkanFramebuffer::setColor(VkImageView * color, VkFormat format)
+{
+    m_colorImageView = color;
+    m_colorFormat = format;
+}
+
+void
+VulkanFramebuffer::setDepth(VkImageView * depth, VkFormat format)
+{
+    m_depthImageView = depth;
+    m_depthFormat = format;
+}
+
+void
+VulkanFramebuffer::setNormal(VkImageView * normal, VkFormat format)
+{
+    m_normalImageView = normal;
+    m_normalFormat = format;
+}
+
+void
+VulkanFramebuffer::setSpecular(VkImageView * specular, VkFormat format)
+{
+    m_specularImageView = specular;
+    m_specularFormat = format;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanFramebuffer.h b/Source/Rendering/VulkanRenderer/imstkVulkanFramebuffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..43ebff2a88c0547c8fc6659d4397d669078a9aab
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanFramebuffer.h
@@ -0,0 +1,101 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanFramebuffer_h
+#define imstkVulkanFramebuffer_h
+
+#include "vulkan/vulkan.h"
+
+#include "imstkVulkanMemoryManager.h"
+
+#include <array>
+#include <vector>
+
+namespace imstk
+{
+class VulkanFramebuffer
+{
+public:
+    VulkanFramebuffer(
+        VulkanMemoryManager& memoryManager,
+        unsigned int width,
+        unsigned int height,
+        bool lastPass = false,
+        VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT);
+
+    void setColor(VkImageView * imageView, VkFormat format);
+
+    void setSpecular(VkImageView * imageView, VkFormat format);
+
+    void setDepth(VkImageView * depthImage, VkFormat format);
+
+    void setNormal(VkImageView * normalImage, VkFormat format);
+
+    void initializeFramebuffer(VkRenderPass * renderPass);
+
+    ~VulkanFramebuffer(){};
+
+    void changeImageLayout(VkCommandBuffer& commandBuffer,
+                           VkImage& image,
+                           VkImageLayout layout1,
+                           VkImageLayout layout2,
+                           VkAccessFlags sourceFlags,
+                           VkAccessFlags destinationFlags,
+                           VkImageSubresourceRange range);
+
+private:
+    friend class VulkanRenderer;
+    friend class VulkanPostProcess;
+    friend class VulkanPostProcessingChain;
+
+    uint32_t m_width;
+    uint32_t m_height;
+    bool m_lastPass = false;
+    VkSampleCountFlagBits m_samples;
+    VkDevice m_renderDevice;
+
+    // Depth buffer
+    VkImageView * m_depthImageView;
+    VkFormat m_depthFormat = VK_FORMAT_UNDEFINED;
+
+    // Normal buffer
+    VkImageView * m_normalImageView;
+    VkFormat m_normalFormat = VK_FORMAT_UNDEFINED;
+
+    // Color accumulation buffer
+    VkImageView * m_colorImageView;
+    VkFormat m_colorFormat = VK_FORMAT_UNDEFINED;
+
+    // Specular accumulation buffer
+    VkImageView * m_specularImageView;
+    VkFormat m_specularFormat = VK_FORMAT_UNDEFINED;
+
+    // Attachments
+    std::vector<VkAttachmentDescription> m_attachments;
+
+    VkFramebuffer m_framebuffer;
+
+    // Renderpass
+    VkRenderPass * m_renderPass;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanMaterialDelegate.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanMaterialDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf87985118032ae4cd31d725a4d271ff86e362db
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanMaterialDelegate.cpp
@@ -0,0 +1,801 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanMaterialDelegate.h"
+
+#include "imstkVulkanRenderer.h"
+
+namespace imstk
+{
+VulkanMaterialDelegate::VulkanMaterialDelegate(
+    std::shared_ptr<VulkanUniformBuffer> vertexUniformBuffer,
+    std::shared_ptr<VulkanUniformBuffer> fragmentUniformBuffer,
+    std::shared_ptr<RenderMaterial> material,
+    VulkanMemoryManager& memoryManager)
+{
+    m_vertexUniformBuffer = vertexUniformBuffer;
+    m_fragmentUniformBuffer = fragmentUniformBuffer;
+
+    m_memoryManager = &memoryManager;
+
+    if (material)
+    {
+        m_material = material;
+    }
+    else
+    {
+        m_material = std::make_shared<RenderMaterial>();
+    }
+}
+
+void
+VulkanMaterialDelegate::initialize(VulkanRenderer * renderer)
+{
+    this->createDescriptorSetLayouts(renderer);
+    this->createPipeline(renderer);
+    this->initializeTextures(renderer);
+    this->createDescriptors(renderer);
+}
+
+void
+VulkanMaterialDelegate::createPipeline(VulkanRenderer * renderer)
+{
+    m_memoryManager->m_device = renderer->m_renderDevice;
+    m_memoryManager->m_queueFamilyIndex = renderer->m_renderQueueFamily;
+
+    std::string vertexShaderPath = "./Shaders/VulkanShaders/Mesh/mesh_vert.spv";
+    std::ifstream vertexShaderDataStream(vertexShaderPath, std::ios_base::binary);
+    char vertexShaderData[16000];
+    vertexShaderDataStream.read(vertexShaderData, 15999);
+    vertexShaderData[(int)vertexShaderDataStream.gcount()] = '\0';
+
+    VkShaderModuleCreateInfo vertexShaderInfo;
+    vertexShaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    vertexShaderInfo.pNext = nullptr;
+    vertexShaderInfo.flags = 0;
+    vertexShaderInfo.codeSize = vertexShaderDataStream.gcount();
+    vertexShaderInfo.pCode = (uint32_t *)vertexShaderData;
+    if (vkCreateShaderModule(renderer->m_renderDevice, &vertexShaderInfo, nullptr, &m_pipelineComponents.vertexShader) != VK_SUCCESS)
+    {
+        LOG(FATAL) << "Unable to build vertex shader : " << vertexShaderPath;
+    }
+
+    if (m_material->getTessellated())
+    {
+        std::string tessellationControlShaderPath = "./Shaders/VulkanShaders/Mesh/mesh_tesc.spv";
+        std::ifstream tessellationControlShaderDataStream(tessellationControlShaderPath, std::ios_base::binary);
+        char tessellationControlShaderData[16000];
+        tessellationControlShaderDataStream.read(tessellationControlShaderData, 15999);
+        tessellationControlShaderData[(int)tessellationControlShaderDataStream.gcount()] = '\0';
+
+        VkShaderModuleCreateInfo tessellationControlShaderInfo;
+        tessellationControlShaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+        tessellationControlShaderInfo.pNext = nullptr;
+        tessellationControlShaderInfo.flags = 0;
+        tessellationControlShaderInfo.codeSize = tessellationControlShaderDataStream.gcount();
+        tessellationControlShaderInfo.pCode = (uint32_t *)tessellationControlShaderData;
+        if (vkCreateShaderModule(renderer->m_renderDevice, &tessellationControlShaderInfo, nullptr, &m_pipelineComponents.tessellationControlShader) != VK_SUCCESS)
+        {
+            LOG(FATAL) << "Unable to build fragment shader: " << tessellationControlShaderPath;
+        }
+
+        std::string tessellatedEvaluationShaderPath = "./Shaders/VulkanShaders/Mesh/mesh_tese.spv";
+        std::ifstream tessellationEvaluationShaderDataStream(tessellatedEvaluationShaderPath, std::ios_base::binary);
+        char tessellationEvaluationShaderData[16000];
+        tessellationEvaluationShaderDataStream.read(tessellationEvaluationShaderData, 15999);
+        tessellationEvaluationShaderData[(int)tessellationEvaluationShaderDataStream.gcount()] = '\0';
+
+        VkShaderModuleCreateInfo tessellationEvaluationShaderInfo;
+        tessellationEvaluationShaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+        tessellationEvaluationShaderInfo.pNext = nullptr;
+        tessellationEvaluationShaderInfo.flags = 0;
+        tessellationEvaluationShaderInfo.codeSize = tessellationEvaluationShaderDataStream.gcount();
+        tessellationEvaluationShaderInfo.pCode = (uint32_t *)tessellationEvaluationShaderData;
+        if (vkCreateShaderModule(renderer->m_renderDevice, &tessellationEvaluationShaderInfo, nullptr, &m_pipelineComponents.tessellationEvaluationShader) != VK_SUCCESS)
+        {
+            LOG(FATAL) << "Unable to build fragment shader: " << tessellatedEvaluationShaderPath;
+        }
+    }
+
+    std::string fragmentShaderPath = "./Shaders/VulkanShaders/Mesh/mesh_frag.spv";
+    std::ifstream fragmentShaderDataStream(fragmentShaderPath, std::ios_base::binary);
+    char fragmentShaderData[16000];
+    fragmentShaderDataStream.read(fragmentShaderData, 15999);
+    fragmentShaderData[(int)fragmentShaderDataStream.gcount()] = '\0';
+
+    VkShaderModuleCreateInfo fragmentShaderInfo;
+    fragmentShaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    fragmentShaderInfo.pNext = nullptr;
+    fragmentShaderInfo.flags = 0;
+    fragmentShaderInfo.codeSize = fragmentShaderDataStream.gcount();
+    fragmentShaderInfo.pCode = (uint32_t *)fragmentShaderData;
+    if (vkCreateShaderModule(renderer->m_renderDevice, &fragmentShaderInfo, nullptr, &m_pipelineComponents.fragmentShader) != VK_SUCCESS)
+    {
+        LOG(FATAL) << "Unable to build fragment shader: " << fragmentShaderPath;
+    }
+
+    // Copy renderer constants to material constants
+    renderer->m_constants.numLights = (uint32_t)renderer->m_scene->getLights().size();
+
+    m_constants.numLights = renderer->m_constants.numLights;
+    m_constants.tessellation = m_material->getTessellated();
+    m_constants.diffuseTexture = (m_material->getTexture(Texture::Type::DIFFUSE)->getPath() != "");
+    m_constants.normalTexture = (m_material->getTexture(Texture::Type::NORMAL)->getPath() != "");
+    m_constants.specularTexture = (m_material->getTexture(Texture::Type::SPECULAR)->getPath() != "");
+    m_constants.roughnessTexture = (m_material->getTexture(Texture::Type::ROUGHNESS)->getPath() != "");
+    m_constants.metalnessTexture = (m_material->getTexture(Texture::Type::METALNESS)->getPath() != "");
+    m_constants.subsurfaceScatteringTexture = (m_material->getTexture(Texture::Type::SUBSURFACE_SCATTERING)->getPath() != "");
+    m_constants.irradianceCubemapTexture = (m_material->getTexture(Texture::Type::IRRADIANCE_CUBEMAP)->getPath() != "");
+
+    this->addSpecializationConstant(sizeof(m_constants.numLights),
+        offsetof(VulkanMaterialConstants, numLights));
+    this->addSpecializationConstant(sizeof(m_constants.tessellation),
+        offsetof(VulkanMaterialConstants, tessellation));
+    this->addSpecializationConstant(sizeof(m_constants.diffuseTexture),
+        offsetof(VulkanMaterialConstants, diffuseTexture));
+    this->addSpecializationConstant(sizeof(m_constants.normalTexture),
+        offsetof(VulkanMaterialConstants, normalTexture));
+    this->addSpecializationConstant(sizeof(m_constants.specularTexture),
+        offsetof(VulkanMaterialConstants, specularTexture));
+    this->addSpecializationConstant(sizeof(m_constants.roughnessTexture),
+        offsetof(VulkanMaterialConstants, roughnessTexture));
+    this->addSpecializationConstant(sizeof(m_constants.metalnessTexture),
+        offsetof(VulkanMaterialConstants, metalnessTexture));
+    this->addSpecializationConstant(sizeof(m_constants.subsurfaceScatteringTexture),
+        offsetof(VulkanMaterialConstants, subsurfaceScatteringTexture));
+    this->addSpecializationConstant(sizeof(m_constants.irradianceCubemapTexture),
+        offsetof(VulkanMaterialConstants, irradianceCubemapTexture));
+
+    m_pipelineComponents.fragmentSpecializationInfo.mapEntryCount = m_numConstants;
+    m_pipelineComponents.fragmentSpecializationInfo.pMapEntries = &m_pipelineComponents.fragmentMapEntries[0];
+    m_pipelineComponents.fragmentSpecializationInfo.dataSize = sizeof(m_constants);
+    m_pipelineComponents.fragmentSpecializationInfo.pData = (void *)(&m_constants);
+
+    m_pipelineComponents.shaderInfo.clear();
+
+    // Vertex Shader
+    {
+        VkPipelineShaderStageCreateInfo shaderInfo;
+        shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+        shaderInfo.pNext = nullptr;
+        shaderInfo.flags = 0;
+        shaderInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
+        shaderInfo.module = m_pipelineComponents.vertexShader;
+        shaderInfo.pName = "main";
+        shaderInfo.pSpecializationInfo = &m_pipelineComponents.fragmentSpecializationInfo;
+        m_pipelineComponents.shaderInfo.push_back(shaderInfo);
+    }
+
+    // Tessellation Shaders
+    if (m_material->getTessellated())
+    {
+        {
+            VkPipelineShaderStageCreateInfo shaderInfo;
+            shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+            shaderInfo.pNext = nullptr;
+            shaderInfo.flags = 0;
+            shaderInfo.stage = VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
+            shaderInfo.module = m_pipelineComponents.tessellationControlShader;
+            shaderInfo.pName = "main";
+            shaderInfo.pSpecializationInfo = &m_pipelineComponents.fragmentSpecializationInfo;
+            m_pipelineComponents.shaderInfo.push_back(shaderInfo);
+        }
+
+        {
+            VkPipelineShaderStageCreateInfo shaderInfo;
+            shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+            shaderInfo.pNext = nullptr;
+            shaderInfo.flags = 0;
+            shaderInfo.stage = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
+            shaderInfo.module = m_pipelineComponents.tessellationEvaluationShader;
+            shaderInfo.pName = "main";
+            shaderInfo.pSpecializationInfo = &m_pipelineComponents.fragmentSpecializationInfo;
+            m_pipelineComponents.shaderInfo.push_back(shaderInfo);
+        }
+    }
+
+    // Fragment Shader
+    {
+        VkPipelineShaderStageCreateInfo shaderInfo;
+        shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+        shaderInfo.pNext = nullptr;
+        shaderInfo.flags = 0;
+        shaderInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+        shaderInfo.module = m_pipelineComponents.fragmentShader;
+        shaderInfo.pName = "main";
+        shaderInfo.pSpecializationInfo = &m_pipelineComponents.fragmentSpecializationInfo;
+        m_pipelineComponents.shaderInfo.push_back(shaderInfo);
+    }
+
+    // Vertex Attributes
+    m_pipelineComponents.vertexBindingDescription.resize(1);
+
+    m_pipelineComponents.vertexBindingDescription[0].binding = 0;
+    m_pipelineComponents.vertexBindingDescription[0].stride = sizeof(VulkanBasicVertex);
+    m_pipelineComponents.vertexBindingDescription[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+    // Vertex Attributes
+    m_pipelineComponents.vertexAttributeDescription.resize(5);
+
+    m_pipelineComponents.vertexAttributeDescription[0].location = 0;
+    m_pipelineComponents.vertexAttributeDescription[0].binding = 0;
+    m_pipelineComponents.vertexAttributeDescription[0].format = VK_FORMAT_R32G32B32_SFLOAT;
+    m_pipelineComponents.vertexAttributeDescription[0].offset = offsetof(VulkanBasicVertex, position);
+
+    m_pipelineComponents.vertexAttributeDescription[1].location = 1;
+    m_pipelineComponents.vertexAttributeDescription[1].binding = 0;
+    m_pipelineComponents.vertexAttributeDescription[1].format = VK_FORMAT_R32G32B32_SFLOAT;
+    m_pipelineComponents.vertexAttributeDescription[1].offset = offsetof(VulkanBasicVertex, normal);
+
+    m_pipelineComponents.vertexAttributeDescription[2].location = 2;
+    m_pipelineComponents.vertexAttributeDescription[2].binding = 0;
+    m_pipelineComponents.vertexAttributeDescription[2].format = VK_FORMAT_R32G32B32_SFLOAT;
+    m_pipelineComponents.vertexAttributeDescription[2].offset = offsetof(VulkanBasicVertex, tangent);
+
+    m_pipelineComponents.vertexAttributeDescription[3].location = 3;
+    m_pipelineComponents.vertexAttributeDescription[3].binding = 0;
+    m_pipelineComponents.vertexAttributeDescription[3].format = VK_FORMAT_R32G32B32_SFLOAT;
+    m_pipelineComponents.vertexAttributeDescription[3].offset = offsetof(VulkanBasicVertex, bitangent);
+
+    m_pipelineComponents.vertexAttributeDescription[4].location = 4;
+    m_pipelineComponents.vertexAttributeDescription[4].binding = 0;
+    m_pipelineComponents.vertexAttributeDescription[4].format = VK_FORMAT_R32G32_SFLOAT;
+    m_pipelineComponents.vertexAttributeDescription[4].offset = offsetof(VulkanBasicVertex, uv);
+
+    // Pipeline stages
+    m_pipelineComponents.vertexInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+    m_pipelineComponents.vertexInfo.pNext = nullptr;
+    m_pipelineComponents.vertexInfo.flags = 0;
+    m_pipelineComponents.vertexInfo.vertexBindingDescriptionCount = (uint32_t)m_pipelineComponents.vertexBindingDescription.size();
+    m_pipelineComponents.vertexInfo.pVertexBindingDescriptions = &m_pipelineComponents.vertexBindingDescription[0];
+    m_pipelineComponents.vertexInfo.vertexAttributeDescriptionCount = (uint32_t)m_pipelineComponents.vertexAttributeDescription.size();
+    m_pipelineComponents.vertexInfo.pVertexAttributeDescriptions = &m_pipelineComponents.vertexAttributeDescription[0];
+
+    m_pipelineComponents.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+    m_pipelineComponents.inputAssemblyInfo.pNext = nullptr;
+    m_pipelineComponents.inputAssemblyInfo.flags = 0;
+
+    if (m_material->getTessellated())
+    {
+        m_pipelineComponents.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
+    }
+    else
+    {
+        m_pipelineComponents.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+    }
+
+    m_pipelineComponents.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
+
+    m_pipelineComponents.tessellationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
+    m_pipelineComponents.tessellationInfo.pNext = nullptr;
+    m_pipelineComponents.tessellationInfo.flags = 0;
+    if (m_material->getTessellated())
+    {
+        m_pipelineComponents.tessellationInfo.patchControlPoints =
+            3;//renderer->m_deviceLimits.maxTessellationPatchSize;
+    }
+    else
+    {
+        m_pipelineComponents.tessellationInfo.patchControlPoints = 1;
+    }
+
+    m_pipelineComponents.viewports.resize(1);
+
+    m_pipelineComponents.viewports[0].x = 0;
+    m_pipelineComponents.viewports[0].y = 0;
+    m_pipelineComponents.viewports[0].height = renderer->m_height;
+    m_pipelineComponents.viewports[0].width = renderer->m_width;
+    m_pipelineComponents.viewports[0].minDepth = 0.0;
+    m_pipelineComponents.viewports[0].maxDepth = 1.0;
+
+    m_pipelineComponents.scissors.resize(1);
+
+    m_pipelineComponents.scissors[0].offset = { 0, 0 };
+    m_pipelineComponents.scissors[0].extent = { renderer->m_width, renderer->m_height };
+
+    m_pipelineComponents.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+    m_pipelineComponents.viewportInfo.pNext = nullptr;
+    m_pipelineComponents.viewportInfo.flags = 0;
+    m_pipelineComponents.viewportInfo.viewportCount = (uint32_t)m_pipelineComponents.viewports.size();
+    m_pipelineComponents.viewportInfo.pViewports = &m_pipelineComponents.viewports[0];
+    m_pipelineComponents.viewportInfo.scissorCount = (uint32_t)m_pipelineComponents.scissors.size();
+    m_pipelineComponents.viewportInfo.pScissors = &m_pipelineComponents.scissors[0];
+
+    m_pipelineComponents.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+    m_pipelineComponents.rasterizationInfo.pNext = nullptr;
+    m_pipelineComponents.rasterizationInfo.flags = 0;
+    m_pipelineComponents.rasterizationInfo.depthClampEnable = VK_FALSE;
+    m_pipelineComponents.rasterizationInfo.rasterizerDiscardEnable = VK_FALSE; // Might be enabled later
+
+    if (m_material->getDisplayMode() == RenderMaterial::DisplayMode::WIREFRAME)
+    {
+        m_pipelineComponents.rasterizationInfo.polygonMode = VK_POLYGON_MODE_LINE;
+    }
+    else
+    {
+        m_pipelineComponents.rasterizationInfo.polygonMode = VK_POLYGON_MODE_FILL;
+    }
+
+    m_pipelineComponents.rasterizationInfo.cullMode = VK_CULL_MODE_NONE; // TODO: Allow backface culling
+    m_pipelineComponents.rasterizationInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+    m_pipelineComponents.rasterizationInfo.depthBiasEnable = VK_FALSE;
+    m_pipelineComponents.rasterizationInfo.depthBiasConstantFactor = 0.0;
+    m_pipelineComponents.rasterizationInfo.depthBiasClamp = VK_FALSE;
+    m_pipelineComponents.rasterizationInfo.depthBiasSlopeFactor = 0.0;
+    m_pipelineComponents.rasterizationInfo.lineWidth = 1.0;
+
+    m_pipelineComponents.multisampleInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+    m_pipelineComponents.multisampleInfo.pNext = nullptr;
+    m_pipelineComponents.multisampleInfo.flags = 0;
+    m_pipelineComponents.multisampleInfo.rasterizationSamples = renderer->m_samples; // TODO: Enable multisampling
+    m_pipelineComponents.multisampleInfo.sampleShadingEnable = VK_FALSE;
+    m_pipelineComponents.multisampleInfo.minSampleShading = 0;
+    m_pipelineComponents.multisampleInfo.pSampleMask = nullptr;
+    m_pipelineComponents.multisampleInfo.alphaToCoverageEnable = VK_FALSE;
+    m_pipelineComponents.multisampleInfo.alphaToOneEnable = VK_FALSE;
+
+    VkStencilOpState states[2];
+    states[0].failOp = VK_STENCIL_OP_ZERO;
+    states[0].passOp = VK_STENCIL_OP_KEEP;
+    states[0].depthFailOp = VK_STENCIL_OP_ZERO;
+    states[0].compareOp = VK_COMPARE_OP_LESS;
+    states[0].compareMask = 0;
+    states[0].writeMask = 0;
+    states[0].reference = 0;
+
+    states[1].failOp = VK_STENCIL_OP_ZERO;
+    states[1].passOp = VK_STENCIL_OP_KEEP;
+    states[1].depthFailOp = VK_STENCIL_OP_ZERO;
+    states[1].compareOp = VK_COMPARE_OP_LESS;
+    states[1].compareMask = 0;
+    states[1].writeMask = 0;
+    states[1].reference = 0;
+
+    m_pipelineComponents.depthStencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+    m_pipelineComponents.depthStencilInfo.pNext = nullptr;
+    m_pipelineComponents.depthStencilInfo.flags = 0;
+    m_pipelineComponents.depthStencilInfo.depthTestEnable = VK_TRUE;
+    m_pipelineComponents.depthStencilInfo.depthWriteEnable = VK_TRUE;
+    m_pipelineComponents.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
+    m_pipelineComponents.depthStencilInfo.depthBoundsTestEnable = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.stencilTestEnable = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.front = states[0];
+    m_pipelineComponents.depthStencilInfo.back = states[1];
+    m_pipelineComponents.depthStencilInfo.minDepthBounds = VK_FALSE;
+    m_pipelineComponents.depthStencilInfo.maxDepthBounds = VK_FALSE;
+
+    m_pipelineComponents.colorBlendAttachments.resize(3);
+    for (int i = 0; i < m_pipelineComponents.colorBlendAttachments.size(); i++)
+    {
+        m_pipelineComponents.colorBlendAttachments[i].blendEnable = VK_FALSE;
+        m_pipelineComponents.colorBlendAttachments[i].srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+        m_pipelineComponents.colorBlendAttachments[i].dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+        m_pipelineComponents.colorBlendAttachments[i].colorBlendOp = VK_BLEND_OP_ADD;
+        m_pipelineComponents.colorBlendAttachments[i].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+        m_pipelineComponents.colorBlendAttachments[i].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+        m_pipelineComponents.colorBlendAttachments[i].alphaBlendOp = VK_BLEND_OP_ADD;
+        m_pipelineComponents.colorBlendAttachments[i].colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
+                                                                       VK_COLOR_COMPONENT_G_BIT |
+                                                                       VK_COLOR_COMPONENT_B_BIT |
+                                                                       VK_COLOR_COMPONENT_A_BIT;
+    }
+
+    m_pipelineComponents.colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+    m_pipelineComponents.colorBlendInfo.pNext = nullptr;
+    m_pipelineComponents.colorBlendInfo.flags = 0;
+    m_pipelineComponents.colorBlendInfo.logicOpEnable = VK_FALSE;
+    m_pipelineComponents.colorBlendInfo.logicOp = VK_LOGIC_OP_SET;
+    m_pipelineComponents.colorBlendInfo.attachmentCount = (uint32_t)m_pipelineComponents.colorBlendAttachments.size();
+    m_pipelineComponents.colorBlendInfo.pAttachments = &m_pipelineComponents.colorBlendAttachments[0];
+    m_pipelineComponents.colorBlendInfo.blendConstants[0] = 1.0;
+    m_pipelineComponents.colorBlendInfo.blendConstants[1] = 1.0;
+    m_pipelineComponents.colorBlendInfo.blendConstants[2] = 1.0;
+    m_pipelineComponents.colorBlendInfo.blendConstants[3] = 1.0;
+
+    VkPipelineLayoutCreateInfo layoutInfo;
+    layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    layoutInfo.pNext = nullptr;
+    layoutInfo.flags = 0;
+    layoutInfo.setLayoutCount = (uint32_t)m_descriptorSetLayouts.size();
+    layoutInfo.pSetLayouts = &m_descriptorSetLayouts[0];
+    layoutInfo.pushConstantRangeCount = 0;
+    layoutInfo.pPushConstantRanges = nullptr;
+
+    vkCreatePipelineLayout(renderer->m_renderDevice, &layoutInfo, nullptr, &m_pipelineLayout);
+
+    m_graphicsPipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+    m_graphicsPipelineInfo.pNext = nullptr;
+    m_graphicsPipelineInfo.flags = VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT;
+    m_graphicsPipelineInfo.stageCount = (uint32_t)m_pipelineComponents.shaderInfo.size();
+    m_graphicsPipelineInfo.pStages = &m_pipelineComponents.shaderInfo[0];
+    m_graphicsPipelineInfo.pVertexInputState = &m_pipelineComponents.vertexInfo;
+    m_graphicsPipelineInfo.pInputAssemblyState = &m_pipelineComponents.inputAssemblyInfo;
+    m_graphicsPipelineInfo.pTessellationState = &m_pipelineComponents.tessellationInfo;
+    m_graphicsPipelineInfo.pViewportState = &m_pipelineComponents.viewportInfo;
+    m_graphicsPipelineInfo.pRasterizationState = &m_pipelineComponents.rasterizationInfo;
+    m_graphicsPipelineInfo.pMultisampleState = &m_pipelineComponents.multisampleInfo;
+    m_graphicsPipelineInfo.pDepthStencilState = &m_pipelineComponents.depthStencilInfo;
+    m_graphicsPipelineInfo.pColorBlendState = &m_pipelineComponents.colorBlendInfo;
+    m_graphicsPipelineInfo.pDynamicState = nullptr;
+    m_graphicsPipelineInfo.layout = m_pipelineLayout;
+    m_graphicsPipelineInfo.renderPass = renderer->m_renderPasses[0];
+    m_graphicsPipelineInfo.subpass = 0;
+    m_graphicsPipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
+    m_graphicsPipelineInfo.basePipelineIndex = 0;
+}
+
+void
+VulkanMaterialDelegate::initializeTextures(VulkanRenderer * renderer)
+{
+    auto defaultTexture = std::make_shared<Texture>("");
+    auto defaultCubemap = std::make_shared<Texture>("", Texture::Type::IRRADIANCE_CUBEMAP);
+
+    if (m_material->getTexture(Texture::Type::DIFFUSE))
+    {
+        m_diffuseTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, m_material->getTexture(Texture::Type::DIFFUSE));
+    }
+    else
+    {
+        m_diffuseTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, defaultTexture);
+    }
+
+    if (m_material->getTexture(Texture::Type::NORMAL))
+    {
+        m_normalTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, m_material->getTexture(Texture::Type::NORMAL));
+    }
+    else
+    {
+        m_normalTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, defaultTexture);
+    }
+
+    if (m_material->getTexture(Texture::Type::ROUGHNESS))
+    {
+        m_roughnessTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, m_material->getTexture(Texture::Type::ROUGHNESS));
+    }
+    else
+    {
+        m_roughnessTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, defaultTexture);
+    }
+
+    if (m_material->getTexture(Texture::Type::METALNESS))
+    {
+        m_metalnessTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, m_material->getTexture(Texture::Type::METALNESS));
+    }
+    else
+    {
+        m_metalnessTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, defaultTexture);
+    }
+
+    if (m_material->getTexture(Texture::Type::SUBSURFACE_SCATTERING))
+    {
+        m_subsurfaceScatteringTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, m_material->getTexture(Texture::Type::SUBSURFACE_SCATTERING));
+    }
+    else
+    {
+        m_subsurfaceScatteringTexture =
+            std::make_shared<VulkanTextureDelegate>(*m_memoryManager, defaultCubemap);
+    }
+}
+
+void
+VulkanMaterialDelegate::addSpecializationConstant(uint32_t size, uint32_t offset)
+{
+    m_pipelineComponents.fragmentMapEntries.push_back(VkSpecializationMapEntry());
+
+    m_pipelineComponents.fragmentMapEntries[m_numConstants].constantID = m_numConstants;
+    m_pipelineComponents.fragmentMapEntries[m_numConstants].offset = offset;
+    m_pipelineComponents.fragmentMapEntries[m_numConstants].size = size;
+
+    m_numConstants++;
+}
+
+void
+VulkanMaterialDelegate::createDescriptors(VulkanRenderer * renderer)
+{
+    this->createDescriptorPool(renderer);
+    this->createDescriptorSets(renderer);
+}
+
+void
+VulkanMaterialDelegate::createDescriptorSetLayouts(VulkanRenderer * renderer)
+{
+    m_descriptorSets.resize(2);
+    m_descriptorSetLayouts.resize(2);
+
+    // Descriptor Sets
+    VkDescriptorSetLayoutBinding vertexDescriptorSetLayoutBindings[2];
+    vertexDescriptorSetLayoutBindings[0].binding = 0;
+    vertexDescriptorSetLayoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    vertexDescriptorSetLayoutBindings[0].descriptorCount = 1;
+    vertexDescriptorSetLayoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT
+                                                      | VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT
+                                                      | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
+    vertexDescriptorSetLayoutBindings[0].pImmutableSamplers = nullptr;
+
+    vertexDescriptorSetLayoutBindings[1].binding = 1;
+    vertexDescriptorSetLayoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    vertexDescriptorSetLayoutBindings[1].descriptorCount = 1;
+    vertexDescriptorSetLayoutBindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT
+                                                      | VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT
+                                                      | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
+    vertexDescriptorSetLayoutBindings[1].pImmutableSamplers = nullptr;
+
+    std::vector<VkDescriptorSetLayoutBinding> fragmentDescriptorSetLayoutBindings;
+
+    // Global uniform buffer
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = 0;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    // Local uniform buffer
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = 1;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    // Diffuse texture
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = 2;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    // Normal texture
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = 3;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    // Roughness texture
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = 4;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    // Metalness texture
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = 5;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    // Subsurface scattering texture
+    {
+        VkDescriptorSetLayoutBinding fragmentLayoutBinding;
+        fragmentLayoutBinding.binding = 6;
+        fragmentLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        fragmentLayoutBinding.descriptorCount = 1;
+        fragmentLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+        fragmentLayoutBinding.pImmutableSamplers = nullptr;
+        fragmentDescriptorSetLayoutBindings.push_back(fragmentLayoutBinding);
+    }
+
+    VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo[2];
+    descriptorSetLayoutInfo[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptorSetLayoutInfo[0].pNext = nullptr;
+    descriptorSetLayoutInfo[0].flags = 0;
+    descriptorSetLayoutInfo[0].bindingCount = 2;
+    descriptorSetLayoutInfo[0].pBindings = vertexDescriptorSetLayoutBindings;
+
+    descriptorSetLayoutInfo[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptorSetLayoutInfo[1].pNext = nullptr;
+    descriptorSetLayoutInfo[1].flags = 0;
+    descriptorSetLayoutInfo[1].bindingCount = (uint32_t)fragmentDescriptorSetLayoutBindings.size();
+    descriptorSetLayoutInfo[1].pBindings = &fragmentDescriptorSetLayoutBindings[0];
+
+    for (int i = 0; i < m_descriptorSetLayouts.size(); i++)
+    {
+        vkCreateDescriptorSetLayout(renderer->m_renderDevice, &descriptorSetLayoutInfo[i], nullptr, &m_descriptorSetLayouts[i]);
+    }
+}
+
+void
+VulkanMaterialDelegate::createDescriptorPool(VulkanRenderer * renderer)
+{
+    std::vector<VkDescriptorPoolSize> descriptorPoolSizes;
+
+    // Vertex shader uniform buffers
+    {
+        VkDescriptorPoolSize poolSize;
+        poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+        poolSize.descriptorCount = 2;
+        descriptorPoolSizes.push_back(poolSize);
+    }
+
+    // Fragment shader uniform buffers
+    {
+        VkDescriptorPoolSize poolSize;
+        poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+        poolSize.descriptorCount = 2;
+        descriptorPoolSizes.push_back(poolSize);
+    }
+
+    // Fragment shader textures
+    {
+        VkDescriptorPoolSize poolSize;
+        poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        poolSize.descriptorCount = 5; // Number of textures
+        descriptorPoolSizes.push_back(poolSize);
+    }
+
+    VkDescriptorPoolCreateInfo descriptorPoolInfo;
+    descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    descriptorPoolInfo.pNext = nullptr;
+    descriptorPoolInfo.flags = 0;
+    descriptorPoolInfo.maxSets = (uint32_t)m_descriptorSets.size();
+    descriptorPoolInfo.poolSizeCount = (uint32_t)descriptorPoolSizes.size();
+    descriptorPoolInfo.pPoolSizes = &descriptorPoolSizes[0];
+
+    vkCreateDescriptorPool(renderer->m_renderDevice, &descriptorPoolInfo, nullptr, &m_descriptorPool);
+}
+
+void
+VulkanMaterialDelegate::createDescriptorSets(VulkanRenderer * renderer)
+{
+    VkDescriptorSetAllocateInfo descriptorSetAllocationInfo[1];
+    descriptorSetAllocationInfo[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    descriptorSetAllocationInfo[0].pNext = nullptr;
+    descriptorSetAllocationInfo[0].descriptorPool = m_descriptorPool;
+    descriptorSetAllocationInfo[0].descriptorSetCount = (uint32_t)m_descriptorSetLayouts.size();
+    descriptorSetAllocationInfo[0].pSetLayouts = &m_descriptorSetLayouts[0];
+
+    VkDeviceSize size = { VK_WHOLE_SIZE };
+
+    // Global Buffers
+    std::vector<VkDescriptorBufferInfo> vertexBufferInfo(2);
+    vertexBufferInfo[0].offset = 0;
+    vertexBufferInfo[0].range = size;
+    vertexBufferInfo[0].buffer = *(renderer->m_globalVertexUniformBuffer->getUniformBuffer());
+
+    vertexBufferInfo[1].offset = 0;
+    vertexBufferInfo[1].range = size;
+    vertexBufferInfo[1].buffer = *(m_vertexUniformBuffer->getUniformBuffer());
+
+    // Global buffers
+    std::vector<VkDescriptorBufferInfo> fragmentBufferInfo(2);
+    fragmentBufferInfo[0].offset = 0;
+    fragmentBufferInfo[0].range = size;
+    fragmentBufferInfo[0].buffer = *(renderer->m_globalFragmentUniformBuffer->getUniformBuffer());
+
+    fragmentBufferInfo[1].offset = 0;
+    fragmentBufferInfo[1].range = size;
+    fragmentBufferInfo[1].buffer = *(m_fragmentUniformBuffer->getUniformBuffer());
+
+    std::vector<VkDescriptorImageInfo> fragmentTextureInfo;
+
+    // Textures
+    {
+        VkDescriptorImageInfo textureInfo;
+        textureInfo.sampler = m_diffuseTexture->m_sampler;
+        textureInfo.imageView = m_diffuseTexture->m_imageView;
+        textureInfo.imageLayout = m_diffuseTexture->m_layout;
+        fragmentTextureInfo.push_back(textureInfo);
+    }
+
+    {
+        VkDescriptorImageInfo textureInfo;
+        textureInfo.sampler = m_normalTexture->m_sampler;
+        textureInfo.imageView = m_normalTexture->m_imageView;
+        textureInfo.imageLayout = m_normalTexture->m_layout;
+        fragmentTextureInfo.push_back(textureInfo);
+    }
+
+    {
+        VkDescriptorImageInfo textureInfo;
+        textureInfo.sampler = m_roughnessTexture->m_sampler;
+        textureInfo.imageView = m_roughnessTexture->m_imageView;
+        textureInfo.imageLayout = m_roughnessTexture->m_layout;
+        fragmentTextureInfo.push_back(textureInfo);
+    }
+
+    {
+        VkDescriptorImageInfo textureInfo;
+        textureInfo.sampler = m_metalnessTexture->m_sampler;
+        textureInfo.imageView = m_metalnessTexture->m_imageView;
+        textureInfo.imageLayout = m_metalnessTexture->m_layout;
+        fragmentTextureInfo.push_back(textureInfo);
+    }
+
+    {
+        VkDescriptorImageInfo textureInfo;
+        textureInfo.sampler = m_subsurfaceScatteringTexture->m_sampler;
+        textureInfo.imageView = m_subsurfaceScatteringTexture->m_imageView;
+        textureInfo.imageLayout = m_subsurfaceScatteringTexture->m_layout;
+        fragmentTextureInfo.push_back(textureInfo);
+    }
+
+    vkAllocateDescriptorSets(renderer->m_renderDevice, descriptorSetAllocationInfo, &m_descriptorSets[0]);
+
+    m_writeDescriptorSets.resize(2);
+    VkWriteDescriptorSet set;
+    m_writeDescriptorSets.push_back(set);
+
+    for (int i = 0; i < m_writeDescriptorSets.size(); i++)
+    {
+        m_writeDescriptorSets[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+        m_writeDescriptorSets[i].pNext = nullptr;
+        m_writeDescriptorSets[i].dstBinding = 0;
+        m_writeDescriptorSets[i].dstArrayElement = 0;
+        m_writeDescriptorSets[i].descriptorCount = 0;
+        m_writeDescriptorSets[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+        m_writeDescriptorSets[i].pBufferInfo = &vertexBufferInfo[0];
+        m_writeDescriptorSets[i].pImageInfo = nullptr;
+        m_writeDescriptorSets[i].pTexelBufferView = nullptr;
+    }
+
+    // Vertex descriptor set
+    m_writeDescriptorSets[0].descriptorCount = 2;
+    m_writeDescriptorSets[0].dstSet = m_descriptorSets[0];
+    m_writeDescriptorSets[0].pBufferInfo = &vertexBufferInfo[0];
+
+    // Fragment descriptor set
+    m_writeDescriptorSets[1].descriptorCount = 2;
+    m_writeDescriptorSets[1].dstSet = m_descriptorSets[1];
+    m_writeDescriptorSets[1].pBufferInfo = &fragmentBufferInfo[0];
+
+    // Fragment texture descriptor set
+    m_writeDescriptorSets[2].descriptorCount = (uint32_t)fragmentTextureInfo.size();
+    m_writeDescriptorSets[2].dstBinding = 2;
+    m_writeDescriptorSets[2].dstSet = m_descriptorSets[1];
+    m_writeDescriptorSets[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    m_writeDescriptorSets[2].pImageInfo = &fragmentTextureInfo[0];
+
+    vkUpdateDescriptorSets(renderer->m_renderDevice, (uint32_t)m_writeDescriptorSets.size(), &m_writeDescriptorSets[0], 0, nullptr);
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanMaterialDelegate.h b/Source/Rendering/VulkanRenderer/imstkVulkanMaterialDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..65b80110fb74625e4edc527be92b52419f20a4f5
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanMaterialDelegate.h
@@ -0,0 +1,139 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanMaterial_h
+#define imstkVulkanMaterial_h
+
+#include "vulkan/vulkan.h"
+#include "glm/glm.hpp"
+
+#include "imstkRenderMaterial.h"
+#include "imstkVulkanUniformBuffer.h"
+#include "imstkVulkanTextureDelegate.h"
+
+#include <memory>
+#include <vector>
+
+namespace imstk
+{
+class VulkanRenderer;
+
+// Large struct to contain pipeline components for later pipeline creation
+struct VulkanMaterialPipelineComponents
+{
+    VkShaderModule fragmentShader;
+    VkShaderModule tessellationControlShader;
+    VkShaderModule tessellationEvaluationShader;
+    VkShaderModule vertexShader;
+    std::vector<VkPipelineShaderStageCreateInfo> shaderInfo;
+    std::vector<VkSpecializationMapEntry> fragmentMapEntries;
+    VkSpecializationInfo fragmentSpecializationInfo;
+    std::vector<VkVertexInputBindingDescription> vertexBindingDescription;
+    std::vector<VkVertexInputAttributeDescription> vertexAttributeDescription;
+    VkPipelineVertexInputStateCreateInfo vertexInfo;
+    VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
+    VkPipelineTessellationStateCreateInfo tessellationInfo;
+    std::vector<VkViewport> viewports;
+    std::vector<VkRect2D> scissors;
+    VkPipelineViewportStateCreateInfo viewportInfo;
+    VkPipelineRasterizationStateCreateInfo rasterizationInfo;
+    VkPipelineMultisampleStateCreateInfo multisampleInfo;
+    VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
+    std::vector<VkPipelineColorBlendAttachmentState> colorBlendAttachments;
+    VkPipelineColorBlendStateCreateInfo colorBlendInfo;
+};
+
+struct VulkanMaterialConstants
+{
+    unsigned int numLights;
+    bool tessellation;
+    bool diffuseTexture;
+    bool normalTexture;
+    bool specularTexture;
+    bool roughnessTexture;
+    bool metalnessTexture;
+    bool subsurfaceScatteringTexture;
+    bool irradianceCubemapTexture;
+};
+
+class VulkanMaterialDelegate
+{
+public:
+    ///
+    /// \brief Constructor
+    ///
+    VulkanMaterialDelegate(
+        std::shared_ptr<VulkanUniformBuffer> vertexUniformBuffer,
+        std::shared_ptr<VulkanUniformBuffer> fragmentUniformBuffer,
+        std::shared_ptr<RenderMaterial> material,
+        VulkanMemoryManager& memoryManager);
+
+protected:
+    friend class VulkanRenderer;
+
+    ///
+    /// \brief Creates a parent pipeline object that gets inherited by other materials
+    ///
+    void createPipeline(VulkanRenderer * renderer);
+
+    void initializeTextures(VulkanRenderer * renderer);
+
+    void initialize(VulkanRenderer * renderer);
+
+    void addSpecializationConstant(uint32_t size, uint32_t offset);
+    uint32_t m_numConstants = 0;
+    VulkanMaterialConstants m_constants;
+
+    uint32_t m_numTextures = 0;
+
+    void createDescriptors(VulkanRenderer * renderer);
+    void createDescriptorSetLayouts(VulkanRenderer * renderer);
+    void createDescriptorPool(VulkanRenderer * renderer);
+    void createDescriptorSets(VulkanRenderer * renderer);
+
+    std::shared_ptr<RenderMaterial> m_material;
+
+    VkPipeline m_pipeline;
+    VkGraphicsPipelineCreateInfo m_graphicsPipelineInfo;
+    VkPipelineLayout m_pipelineLayout;
+    VulkanMaterialPipelineComponents m_pipelineComponents;
+
+    VkDescriptorPool m_descriptorPool;
+    std::vector<VkDescriptorSet> m_descriptorSets;
+    std::vector<VkDescriptorSetLayout> m_descriptorSetLayouts;
+    std::vector<VkWriteDescriptorSet> m_writeDescriptorSets;
+
+    std::shared_ptr<VulkanUniformBuffer> m_vertexUniformBuffer;
+    std::shared_ptr<VulkanUniformBuffer> m_fragmentUniformBuffer;
+
+    std::shared_ptr<VulkanTextureDelegate> m_diffuseTexture;
+    std::shared_ptr<VulkanTextureDelegate> m_normalTexture;
+    std::shared_ptr<VulkanTextureDelegate> m_roughnessTexture;
+    std::shared_ptr<VulkanTextureDelegate> m_metalnessTexture;
+    std::shared_ptr<VulkanTextureDelegate> m_subsurfaceScatteringTexture;
+    std::shared_ptr<VulkanTextureDelegate> m_irradianceCubemapTexture;
+    std::shared_ptr<VulkanTextureDelegate> m_radianceCubemapTexture;
+
+    VulkanMemoryManager * m_memoryManager;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanMemoryManager.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanMemoryManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..606c1367d31ad88ed1135dc52d89505eb2092652
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanMemoryManager.cpp
@@ -0,0 +1,85 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanMemoryManager.h"
+
+namespace imstk
+{
+VulkanMemoryManager::VulkanMemoryManager()
+{
+}
+
+VkDeviceMemory *
+VulkanMemoryManager::allocateMemory(
+    const VkMemoryRequirements& memoryRequirements,
+    uint32_t location)
+{
+    auto memory = new VkDeviceMemory();
+
+    // Get device memory types
+    VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
+    vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &deviceMemoryProperties);
+
+    uint32_t memoryIndex = 0;
+    bool foundIndex = false;
+
+    for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++)
+    {
+        // Determine if the memory type is valid for this resource
+        if ((1 << i) & memoryRequirements.memoryTypeBits)
+        {
+            // Determine if the memory type has the correct properties
+            if (deviceMemoryProperties.memoryTypes[i].propertyFlags & location)
+            {
+                memoryIndex = i;
+                foundIndex = true;
+                break;
+            }
+        }
+    }
+
+    if (!foundIndex)
+    {
+        LOG(FATAL) << "Couldn't find correct memory object";
+    }
+
+    VkMemoryAllocateInfo memoryInfo;
+    memoryInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    memoryInfo.pNext = nullptr;
+    memoryInfo.allocationSize = memoryRequirements.size;
+    memoryInfo.memoryTypeIndex = memoryIndex;
+
+    vkAllocateMemory(m_device, &memoryInfo, nullptr, memory);
+
+    m_memoryAllocations.push_back(memory);
+
+    return memory;
+}
+
+void
+VulkanMemoryManager::clear()
+{
+    for (unsigned int i = 0; i < m_memoryAllocations.size(); i++)
+    {
+        vkFreeMemory(m_device, *m_memoryAllocations[i], nullptr);
+    }
+}
+};
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanMemoryManager.h b/Source/Rendering/VulkanRenderer/imstkVulkanMemoryManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..a11cc9408e4a544a30cfed662ca04c6306488a54
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanMemoryManager.h
@@ -0,0 +1,54 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanMemoryManager_h
+#define imstkVulkanMemoryManager_h
+
+#include "vulkan/vulkan.h"
+
+#include <vector>
+
+#include "g3log/g3log.hpp"
+
+namespace imstk
+{
+class VulkanMemoryManager
+{
+public:
+    VulkanMemoryManager();
+    void clear();
+
+    VkDeviceMemory * allocateMemory(
+        const VkMemoryRequirements& memoryRequirements,
+        uint32_t location);
+
+    VkPhysicalDevice m_physicalDevice;
+    VkDevice m_device;
+    uint32_t m_queueFamilyIndex;
+    VkCommandBuffer * m_transferCommandBuffer;
+    VkQueue * m_transferQueue;
+
+protected:
+    std::vector<VkDeviceMemory*> m_memoryAllocations;
+};
+};
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5377667f5c4ecf936422eed4a5ae5df9358f60c5
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.cpp
@@ -0,0 +1,1099 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanRenderer.h"
+
+namespace imstk {
+VulkanRenderer::VulkanRenderer(std::shared_ptr<Scene> scene)
+{
+    m_scene = scene;
+}
+
+void
+VulkanRenderer::initialize()
+{
+    // If debug mode, enable validation layer (slower performance)
+#ifndef NDEBUG
+    m_layers.push_back(VulkanValidation::getValidationLayer());
+    m_extensions.push_back(VulkanValidation::getValidationExtension());
+#endif
+
+    // Instance of a Vulkan application
+    VkInstanceCreateInfo m_creationInfo;
+    m_creationInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+    m_creationInfo.pNext = nullptr;
+    m_creationInfo.flags = 0;
+    m_creationInfo.pApplicationInfo = nullptr;
+    m_creationInfo.enabledLayerCount = (uint32_t)m_layers.size();
+    m_creationInfo.ppEnabledLayerNames = &m_layers[0];
+    m_creationInfo.enabledExtensionCount = (uint32_t)m_extensions.size();
+    m_creationInfo.ppEnabledExtensionNames = &m_extensions[0];
+
+    for (int i = 0; i < m_extensions.size(); i++)
+    {
+        std::cout << "Enabled extension: " << m_creationInfo.ppEnabledExtensionNames[i] << std::endl;
+    }
+
+    m_instance = new VkInstance();
+
+    vkCreateInstance(&m_creationInfo, nullptr, m_instance);
+
+#ifndef NDEBUG
+    VkDebugReportCallbackCreateInfoEXT debugReportInfo;
+    debugReportInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
+    debugReportInfo.pNext = nullptr;
+    debugReportInfo.flags =
+        VK_DEBUG_REPORT_WARNING_BIT_EXT |
+        VK_DEBUG_REPORT_ERROR_BIT_EXT;
+    debugReportInfo.pfnCallback = &(VulkanValidation::debugReportCallback);
+    debugReportInfo.pUserData = nullptr;
+
+    VkDebugReportCallbackEXT debugReportCallback;
+    PFN_vkCreateDebugReportCallbackEXT createCallback =
+        (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(*m_instance, "vkCreateDebugReportCallbackEXT");
+    createCallback(*m_instance, &debugReportInfo, nullptr, &debugReportCallback);
+#endif
+
+    auto camera = m_scene->getCamera();
+    m_fov = (float)glm::radians(camera->getViewAngle());
+
+    // Setup logical devices
+    this->setupGPUs();
+    this->printGPUs();
+
+    // Setup command pool(s) - right now we just have one
+    this->setupCommandPools();
+    this->buildCommandBuffer();
+    this->setupRenderPasses();
+    this->setupSynchronization();
+    this->setupMemoryManager();
+    this->createGlobalUniformBuffers();
+
+    std::vector<VkPipeline> graphicsPipelines;
+    std::vector<VkGraphicsPipelineCreateInfo> graphicsPipelinesInfo;
+
+    VkPhysicalDeviceProperties deviceProperties;
+    vkGetPhysicalDeviceProperties(m_renderPhysicalDevice, &deviceProperties);
+    m_deviceLimits = deviceProperties.limits;
+
+    VkPipelineCacheCreateInfo pipelineCacheCreateInfo;
+    pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+    pipelineCacheCreateInfo.pNext = nullptr;
+    pipelineCacheCreateInfo.flags = 0;
+    pipelineCacheCreateInfo.initialDataSize = 0;
+    pipelineCacheCreateInfo.pInitialData = nullptr;
+
+    vkCreatePipelineCache(m_renderDevice, &pipelineCacheCreateInfo, nullptr, &m_pipelineCache);
+
+    // Geometry pipeline creation
+    for (int i = 0; i < m_scene->getSceneObjects().size(); i++)
+    {
+        std::shared_ptr<Geometry> geometry = m_scene->getSceneObjects()[i]->getVisualGeometry();
+        auto renderDelegate = loadGeometry(geometry);
+
+        if (renderDelegate)
+        {
+            auto material = renderDelegate->m_material;
+            material->initialize(this);
+
+            graphicsPipelines.push_back(material->m_pipeline);
+            graphicsPipelinesInfo.push_back(material->m_graphicsPipelineInfo);
+        }
+    }
+
+    if (graphicsPipelines.size() > 0)
+    {
+        vkCreateGraphicsPipelines(m_renderDevice,
+            m_pipelineCache,
+            (uint32_t)graphicsPipelines.size(),
+            &graphicsPipelinesInfo[0],
+            nullptr,
+            &graphicsPipelines[0]);
+    }
+
+    for (int i = 0; i < m_renderDelegates.size(); i++)
+    {
+        m_renderDelegates[i]->m_material->m_pipeline = graphicsPipelines[i];
+    }
+}
+
+void
+VulkanRenderer::setupGPUs()
+{
+    // Prevent devices from being set up multiple times
+    if (m_physicalDeviceCount != 0)
+    {
+        return;
+    }
+
+    // Setup physical devices
+    vkEnumeratePhysicalDevices(*m_instance, &m_physicalDeviceCount, nullptr);
+    m_physicalDevices = new VkPhysicalDevice[(int)m_physicalDeviceCount]();
+    vkEnumeratePhysicalDevices(*m_instance, &m_physicalDeviceCount, m_physicalDevices);
+    m_renderPhysicalDevice = m_physicalDevices[0];
+
+    // Get render queue family
+    vkGetPhysicalDeviceQueueFamilyProperties(m_renderPhysicalDevice, &m_queueFamilyPropertiesCount, nullptr);
+    m_queueFamilyProperties = new VkQueueFamilyProperties[(int)m_queueFamilyPropertiesCount]();
+    vkGetPhysicalDeviceQueueFamilyProperties(m_renderPhysicalDevice, &m_queueFamilyPropertiesCount, m_queueFamilyProperties);
+
+    m_renderQueueFamily = 0;
+
+    for (int i = 0; i < (int)(m_queueFamilyPropertiesCount); i++)
+    {
+        if (m_queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
+        {
+            m_renderQueueFamily = i;
+            break;
+        }
+    }
+
+    std::vector<float> priorities(m_queueFamilyProperties[m_renderQueueFamily].queueCount);
+    std::fill(priorities.begin(), priorities.end(), 0.0f);
+    priorities[m_renderQueueFamily] = 1.0;
+
+    //Setup logical devices
+    m_deviceCount = m_physicalDeviceCount;
+    m_devices = new VkDevice[(int)(m_deviceCount)]();
+
+    VkDeviceQueueCreateInfo queueInfo;
+    queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+    queueInfo.pNext = nullptr;
+    queueInfo.flags = 0;
+    queueInfo.queueFamilyIndex = m_renderQueueFamily;
+    queueInfo.queueCount = m_queueFamilyProperties[m_renderQueueFamily].queueCount;
+    queueInfo.pQueuePriorities = &priorities[0];
+
+    // The display system isn't part of the Vulkan core
+    char * deviceExtensions[1];
+    deviceExtensions[0] = "VK_KHR_swapchain";
+
+    // Enabling optional Vulkan features
+    VkPhysicalDeviceFeatures features = {VK_FALSE};
+    features.fillModeNonSolid = VK_TRUE;
+    features.tessellationShader = VK_TRUE;
+
+    VkDeviceCreateInfo deviceInfo;
+    deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+    deviceInfo.pNext = nullptr;
+    deviceInfo.flags = 0;
+    deviceInfo.queueCreateInfoCount = 1;
+    deviceInfo.pQueueCreateInfos = &queueInfo;
+#ifndef NDEBUG
+    deviceInfo.enabledLayerCount = (uint32_t)m_layers.size();
+    deviceInfo.ppEnabledLayerNames = &m_layers[0];
+#else
+    deviceInfo.enabledLayerCount = 0;
+    deviceInfo.ppEnabledLayerNames = nullptr;
+#endif
+    deviceInfo.enabledExtensionCount = 1;
+    deviceInfo.ppEnabledExtensionNames = deviceExtensions;
+    deviceInfo.pEnabledFeatures = &features;
+
+    for (int i = 0; i < (int)(m_physicalDeviceCount); i++)
+    {
+        vkCreateDevice(m_physicalDevices[i], &deviceInfo, nullptr, &m_devices[i]);
+    }
+
+    // This decision needs some work, may pick weaker device
+    m_renderDevice = m_devices[0];
+
+    // Get the first render-capable queue
+    vkGetDeviceQueue(m_renderDevice, m_renderQueueFamily, 0, &m_renderQueue);
+}
+
+void
+VulkanRenderer::printGPUs()
+{
+    this->setupGPUs();
+
+    VkPhysicalDeviceProperties properties;
+
+    std::cout << "Devices:" << std::endl;
+
+    for (int i = 0; i < (int)(m_physicalDeviceCount); i++)
+    {
+        vkGetPhysicalDeviceProperties(m_physicalDevices[i], &properties);
+        properties.limits.maxMemoryAllocationCount;
+        std::cout << (i + 1) << ". " << properties.deviceName << std::endl;
+    }
+}
+
+void
+VulkanRenderer::setupCommandPools()
+{
+    // Create command pools (only one for now)
+    VkCommandPoolCreateInfo commandPoolInfo;
+    commandPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+    commandPoolInfo.pNext = nullptr;
+    commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+    commandPoolInfo.queueFamilyIndex = 0;
+
+    vkCreateCommandPool(m_renderDevice, &commandPoolInfo, nullptr, &m_renderCommandPool);
+}
+
+void
+VulkanRenderer::buildCommandBuffer()
+{
+    // Build command buffer
+    VkCommandBufferAllocateInfo commandBufferInfo;
+    commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    commandBufferInfo.pNext = nullptr;
+    commandBufferInfo.commandPool = m_renderCommandPool;
+    commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    commandBufferInfo.commandBufferCount = 1; // TODO: increase this for double/triple buffering
+
+    vkAllocateCommandBuffers(m_renderDevice, &commandBufferInfo, &m_renderCommandBuffer);
+
+    vkAllocateCommandBuffers(m_renderDevice, &commandBufferInfo, &m_postProcessingCommandBuffer);
+}
+
+void
+VulkanRenderer::setupRenderPasses()
+{
+    // Number of geometry passes
+    m_renderPasses.resize(1);
+
+    VkAttachmentDescription attachments[4];
+
+    // Color attachment
+    attachments[0].flags = 0;
+    attachments[0].format = VK_FORMAT_R16G16B16A16_SFLOAT;
+    attachments[0].samples = m_samples;
+    attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+    attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    attachments[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    // Depth attachment
+    attachments[1].flags = 0;
+    attachments[1].format = VK_FORMAT_D32_SFLOAT;
+    attachments[1].samples = m_samples;
+    attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+    attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
+
+    // Normal attachment
+    attachments[2].flags = 0;
+    attachments[2].format = VK_FORMAT_R8G8B8A8_SNORM;
+    attachments[2].samples = m_samples;
+    attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+    attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    attachments[2].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    // Specular attachment
+    attachments[3].flags = 0;
+    attachments[3].format = VK_FORMAT_R16G16B16A16_SFLOAT;
+    attachments[3].samples = m_samples;
+    attachments[3].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+    attachments[3].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    attachments[3].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    attachments[3].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    attachments[3].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    attachments[3].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    // Color attachment
+    VkAttachmentReference colorReference;
+    colorReference.attachment = 0;
+    colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    // Depth attachment
+    VkAttachmentReference depthReference;
+    depthReference.attachment = 1;
+    depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+    // Normal attachment
+    VkAttachmentReference normalReference;
+    normalReference.attachment = 2;
+    normalReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    // Specular attachment
+    VkAttachmentReference specularReference;
+    specularReference.attachment = 3;
+    specularReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    // Render subpasses
+    VkSubpassDescription subpassInfo[1];
+
+    // First pass: geometry
+    VkAttachmentReference colorAttachments[] = { colorReference, normalReference, specularReference };
+    subpassInfo[0].flags = 0;
+    subpassInfo[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+    subpassInfo[0].inputAttachmentCount = 0;
+    subpassInfo[0].pInputAttachments = nullptr;
+    subpassInfo[0].colorAttachmentCount = 3;
+    subpassInfo[0].pColorAttachments = colorAttachments;
+    subpassInfo[0].pResolveAttachments = nullptr;
+    subpassInfo[0].pDepthStencilAttachment = &depthReference;
+    subpassInfo[0].preserveAttachmentCount = 0;
+    subpassInfo[0].pPreserveAttachments = nullptr;
+
+    VkSubpassDependency dependencies[2];
+    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+    dependencies[0].dstSubpass = 0;
+    dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+    dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
+    dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+    dependencies[1].srcSubpass = 0;
+    dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+    dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
+    dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+    dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+    VkRenderPassCreateInfo renderPassInfo;
+    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+    renderPassInfo.pNext = nullptr;
+    renderPassInfo.flags = 0;
+    renderPassInfo.attachmentCount = 4;
+    renderPassInfo.pAttachments = attachments;
+    renderPassInfo.subpassCount = 1;
+    renderPassInfo.pSubpasses = subpassInfo;
+    renderPassInfo.dependencyCount = 2;
+    renderPassInfo.pDependencies = dependencies;
+
+    vkCreateRenderPass(m_renderDevice, &renderPassInfo, nullptr, &m_renderPasses[0]);
+}
+
+void
+VulkanRenderer::resizeFramebuffers(VkSwapchainKHR * swapchain, int width, int height)
+{
+    m_width = width;
+    m_height = height;
+
+    std::vector<VkPipeline> pipelines;
+    std::vector<VkGraphicsPipelineCreateInfo> pipelineInfos;
+
+    for (int i = 0; i < m_renderDelegates.size(); i++)
+    {
+        auto material = m_renderDelegates[i]->m_material;
+        vkDestroyPipeline(m_renderDevice, material->m_pipeline, nullptr);
+        material->createPipeline(this);
+        pipelines.push_back(material->m_pipeline);
+        pipelineInfos.push_back(material->m_graphicsPipelineInfo);
+    }
+    if (pipelines.size() > 0)
+    {
+        vkCreateGraphicsPipelines(m_renderDevice,
+            m_pipelineCache,
+            (uint32_t)m_renderDelegates.size(),
+            &pipelineInfos[0],
+            nullptr,
+            &pipelines[0]);
+    }
+
+    for (int i = 0; i < m_renderDelegates.size(); i++)
+    {
+        m_renderDelegates[i]->m_material->m_pipeline = pipelines[i];
+    }
+
+    this->deleteFramebuffers();
+
+    this->initializeFramebuffers(swapchain);
+}
+
+void
+VulkanRenderer::initializeFramebuffers(VkSwapchainKHR * swapchain)
+{
+    m_mipLevels = std::log2(std::max(m_width, m_height)) + 1;
+
+    // Get images from surface (color images)
+    m_swapchain = swapchain;
+    vkGetSwapchainImagesKHR(m_renderDevice, *m_swapchain, &m_swapchainImageCount, nullptr);
+    m_swapchainImages.resize(m_swapchainImageCount);
+    vkGetSwapchainImagesKHR(m_renderDevice, *m_swapchain, &m_swapchainImageCount, &m_swapchainImages[0]);
+
+    // Depth image
+    VkImageCreateInfo depthImageInfo;
+    depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    depthImageInfo.pNext = nullptr;
+    depthImageInfo.flags = 0;
+    depthImageInfo.imageType = VK_IMAGE_TYPE_2D;
+    depthImageInfo.format = VK_FORMAT_D32_SFLOAT;
+    depthImageInfo.extent = { m_width, m_height, 1 };
+    depthImageInfo.mipLevels = 1;
+    depthImageInfo.arrayLayers = 1;
+    depthImageInfo.samples = m_samples;
+    depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+    depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
+                           | VK_IMAGE_USAGE_SAMPLED_BIT;
+    depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    depthImageInfo.queueFamilyIndexCount = 0;
+    depthImageInfo.pQueueFamilyIndices = nullptr;
+    depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+    m_depthImage.resize(m_mipLevels);
+    for (uint32_t i = 0; i < m_mipLevels; i++)
+    {
+        depthImageInfo.extent = { std::max(m_width >> i, 1u), std::max(m_height >> i, 1u), 1 };
+        vkCreateImage(m_renderDevice, &depthImageInfo, nullptr, &m_depthImage[i]);
+    }
+
+    // Normal image
+    auto normalImageInfo = depthImageInfo;
+    normalImageInfo.extent = { m_width, m_height, 1 };
+    normalImageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+                            VK_IMAGE_USAGE_SAMPLED_BIT;
+    normalImageInfo.format = VK_FORMAT_R8G8B8A8_SNORM;
+
+    vkCreateImage(m_renderDevice, &normalImageInfo, nullptr, &m_normalImage);
+
+    // HDR image
+    auto HDRImageInfo = depthImageInfo;
+    HDRImageInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+    HDRImageInfo.mipLevels = 1;
+    HDRImageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+                         | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+    m_HDRImage[0].resize(m_mipLevels);
+    m_HDRImage[1].resize(m_mipLevels);
+    m_HDRImage[2].resize(m_mipLevels);
+    for (uint32_t i = 0; i < m_mipLevels; i++)
+    {
+        HDRImageInfo.extent = { std::max(m_width >> i, 1u), std::max(m_height >> i, 1u), 1 };
+        vkCreateImage(m_renderDevice, &HDRImageInfo, nullptr, &m_HDRImage[0][i]);
+        vkCreateImage(m_renderDevice, &HDRImageInfo, nullptr, &m_HDRImage[1][i]);
+        vkCreateImage(m_renderDevice, &HDRImageInfo, nullptr, &m_HDRImage[2][i]);
+    }
+
+    for (uint32_t i = 0; i < m_swapchainImageCount; i++)
+    {
+        m_HDRTonemaps.resize(m_swapchainImageCount);
+    }
+
+    // Create image views
+    m_swapchainImageViews.resize(m_swapchainImageCount);
+    m_depthImageView.resize(m_mipLevels);
+    m_depthImageMemory.resize(m_mipLevels);
+
+    for (uint32_t i = 0; i < m_mipLevels; i++)
+    {
+        VkMemoryRequirements memReqs;
+        vkGetImageMemoryRequirements(m_renderDevice, m_depthImage[i], &memReqs);
+
+        m_depthImageMemory[i] = m_memoryManager.allocateMemory(memReqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+        vkBindImageMemory(m_renderDevice, m_depthImage[i], *m_depthImageMemory[i], 0);
+
+        VkComponentMapping componentMap;
+        componentMap.r = VK_COMPONENT_SWIZZLE_R;
+        componentMap.g = VK_COMPONENT_SWIZZLE_G;
+        componentMap.b = VK_COMPONENT_SWIZZLE_B;
+        componentMap.a = VK_COMPONENT_SWIZZLE_A;
+
+        VkImageSubresourceRange subresourceRange;
+        subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+        subresourceRange.baseMipLevel = 0;
+        subresourceRange.levelCount = 1;
+        subresourceRange.baseArrayLayer = 0;
+        subresourceRange.layerCount = 1;
+
+        VkImageViewCreateInfo imageViewInfo;
+        imageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+        imageViewInfo.pNext = nullptr;
+        imageViewInfo.flags = 0;
+        imageViewInfo.image = m_depthImage[i];
+        imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+        imageViewInfo.format = VK_FORMAT_D32_SFLOAT;
+        imageViewInfo.components = componentMap;
+        imageViewInfo.subresourceRange = subresourceRange;
+
+        vkCreateImageView(m_renderDevice, &imageViewInfo, nullptr, &m_depthImageView[i]);
+    }
+
+    {
+        VkMemoryRequirements memReqs;
+        vkGetImageMemoryRequirements(m_renderDevice, m_normalImage, &memReqs);
+
+        m_normalImageMemory = m_memoryManager.allocateMemory(memReqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+        vkBindImageMemory(m_renderDevice, m_normalImage, *m_normalImageMemory, 0);
+
+        VkComponentMapping componentMap;
+        componentMap.r = VK_COMPONENT_SWIZZLE_R;
+        componentMap.g = VK_COMPONENT_SWIZZLE_G;
+        componentMap.b = VK_COMPONENT_SWIZZLE_B;
+        componentMap.a = VK_COMPONENT_SWIZZLE_A;
+
+        VkImageSubresourceRange subresourceRange;
+        subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        subresourceRange.baseMipLevel = 0;
+        subresourceRange.levelCount = 1;
+        subresourceRange.baseArrayLayer = 0;
+        subresourceRange.layerCount = 1;
+
+        VkImageViewCreateInfo imageViewInfo;
+        imageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+        imageViewInfo.pNext = nullptr;
+        imageViewInfo.flags = 0;
+        imageViewInfo.image = m_normalImage;
+        imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+        imageViewInfo.format = VK_FORMAT_R8G8B8A8_SNORM;
+        imageViewInfo.components = componentMap;
+        imageViewInfo.subresourceRange = subresourceRange;
+
+        vkCreateImageView(m_renderDevice, &imageViewInfo, nullptr, &m_normalImageView);
+    }
+
+    for (int i = 0; i < 3; i++)
+    {
+        m_HDRImageView[i].resize(m_mipLevels);
+        m_HDRImageMemory[i].resize(m_mipLevels);
+
+        VkSamplerCreateInfo samplerInfo;
+        samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+        samplerInfo.pNext = nullptr;
+        samplerInfo.flags = 0;
+        samplerInfo.magFilter = VK_FILTER_LINEAR;
+        samplerInfo.minFilter = VK_FILTER_LINEAR;
+        samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; // Trilinear interpolation
+        samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+        samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+        samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+        samplerInfo.mipLodBias = 0.0;
+        samplerInfo.anisotropyEnable = VK_FALSE; // TODO:: add option to enable
+        samplerInfo.maxAnisotropy = 1.0;
+        samplerInfo.compareEnable = VK_FALSE;
+        samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+        samplerInfo.minLod = 0;
+        samplerInfo.maxLod = 0;
+        samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+        samplerInfo.unnormalizedCoordinates = VK_FALSE;
+
+        vkCreateSampler(m_renderDevice, &samplerInfo, nullptr, &m_HDRImageSampler);
+
+        for (uint32_t j = 0; j < m_mipLevels; j++)
+        {
+            VkMemoryRequirements memReqs;
+            vkGetImageMemoryRequirements(m_renderDevice, m_HDRImage[i][j], &memReqs);
+
+            m_HDRImageMemory[i][j] = m_memoryManager.allocateMemory(memReqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+            vkBindImageMemory(m_renderDevice, m_HDRImage[i][j], *m_HDRImageMemory[i][j], 0);
+
+            VkComponentMapping componentMap;
+            componentMap.r = VK_COMPONENT_SWIZZLE_R;
+            componentMap.g = VK_COMPONENT_SWIZZLE_G;
+            componentMap.b = VK_COMPONENT_SWIZZLE_B;
+            componentMap.a = VK_COMPONENT_SWIZZLE_A;
+
+            VkImageSubresourceRange subresourceRange;
+            subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+            subresourceRange.baseMipLevel = 0;
+            subresourceRange.levelCount = 1;
+            subresourceRange.baseArrayLayer = 0;
+            subresourceRange.layerCount = 1;
+
+            VkImageViewCreateInfo imageViewInfo;
+            imageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+            imageViewInfo.pNext = nullptr;
+            imageViewInfo.flags = 0;
+            imageViewInfo.image = m_HDRImage[i][j];
+            imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+            imageViewInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+            imageViewInfo.components = componentMap;
+            imageViewInfo.subresourceRange = subresourceRange;
+
+            vkCreateImageView(m_renderDevice, &imageViewInfo, nullptr, &m_HDRImageView[i][j]);
+        }
+    }
+
+    m_swapchainImageSamplers.resize(m_swapchainImageCount);
+    for (uint32_t i = 0; i < m_swapchainImageCount; i++)
+    {
+        VkComponentMapping componentMap;
+        componentMap.r = VK_COMPONENT_SWIZZLE_R;
+        componentMap.g = VK_COMPONENT_SWIZZLE_G;
+        componentMap.b = VK_COMPONENT_SWIZZLE_B;
+        componentMap.a = VK_COMPONENT_SWIZZLE_A;
+
+        VkImageSubresourceRange subresourceRange;
+        subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        subresourceRange.baseMipLevel = 0;
+        subresourceRange.levelCount = 1;
+        subresourceRange.baseArrayLayer = 0;
+        subresourceRange.layerCount = 1;
+
+        VkImageViewCreateInfo imageViewInfo;
+        imageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+        imageViewInfo.pNext = nullptr;
+        imageViewInfo.flags = 0;
+        imageViewInfo.image = m_swapchainImages[i];
+        imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+        imageViewInfo.format = VK_FORMAT_B8G8R8A8_SRGB;
+        imageViewInfo.components = componentMap;
+        imageViewInfo.subresourceRange = subresourceRange;
+
+        vkCreateImageView(m_renderDevice, &imageViewInfo, nullptr, &m_swapchainImageViews[i]);
+
+        VkSamplerCreateInfo samplerInfo;
+        samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+        samplerInfo.pNext = nullptr;
+        samplerInfo.flags = 0;
+        samplerInfo.magFilter = VK_FILTER_LINEAR;
+        samplerInfo.minFilter = VK_FILTER_LINEAR;
+        samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; // Trilinear interpolation
+        samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+        samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+        samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+        samplerInfo.mipLodBias = 0.0;
+        samplerInfo.anisotropyEnable = VK_FALSE; // TODO:: add option to enable
+        samplerInfo.maxAnisotropy = 1.0;
+        samplerInfo.compareEnable = VK_FALSE;
+        samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+        samplerInfo.minLod = 0;
+        samplerInfo.maxLod = 0;
+        samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+        samplerInfo.unnormalizedCoordinates = VK_FALSE;
+
+        vkCreateSampler(m_renderDevice, &samplerInfo, nullptr, &m_swapchainImageSamplers[i]);
+    }
+
+    this->initializePostProcesses();
+
+    m_drawingFramebuffers.clear();
+    m_drawingFramebuffers.push_back(
+        std::make_shared<VulkanFramebuffer>(m_memoryManager, m_width, m_height, false, m_samples));
+    m_drawingFramebuffers[0]->setColor(&m_HDRImageView[0][0], VK_FORMAT_R16G16B16A16_SFLOAT);
+    m_drawingFramebuffers[0]->setSpecular(&m_HDRImageView[1][0], VK_FORMAT_R16G16B16A16_SFLOAT);
+    m_drawingFramebuffers[0]->setDepth(&m_depthImageView[0], VK_FORMAT_D32_SFLOAT);
+    m_drawingFramebuffers[0]->setNormal(&m_normalImageView, VK_FORMAT_R8G8B8A8_SNORM);
+    m_drawingFramebuffers[0]->initializeFramebuffer(&m_renderPasses[0]);
+}
+
+void
+VulkanRenderer::deleteFramebuffers()
+{
+    // The framebuffers/command buffers may still be in use
+    vkDeviceWaitIdle(m_renderDevice);
+
+    // Depth buffer
+    for (uint32_t i = 0; i < m_mipLevels; i++)
+    {
+        vkDestroyImage(m_renderDevice, m_depthImage[i], nullptr);
+        vkDestroyImageView(m_renderDevice, m_depthImageView[i], nullptr);
+    }
+
+
+    // HDR buffers
+    for (int i = 0; i < 3; i++)
+    {
+        for (int j = 0; j < m_HDRImageView[i].size(); j++)
+        {
+            vkDestroyImage(m_renderDevice, m_HDRImage[i][j], nullptr);
+            vkDestroyImageView(m_renderDevice, m_HDRImageView[i][j], nullptr);
+        }
+    }
+
+    // Normal buffer
+    vkDestroyImage(m_renderDevice, m_normalImage, nullptr);
+    vkDestroyImageView(m_renderDevice, m_normalImageView, nullptr);
+
+    for (uint32_t i = 0; i < m_swapchainImageCount; i++)
+    {
+        vkDestroyImageView(m_renderDevice, m_swapchainImageViews[i], nullptr);
+    }
+
+    // Delete all post processing resources
+    for (int i = 0; i < m_postProcessingChain->m_postProcesses.size(); i++)
+    {
+        vkDestroyFramebuffer(m_renderDevice, m_postProcessingChain->m_postProcesses[i]->m_framebuffer->m_framebuffer, nullptr);
+    }
+
+    // Delete all HDR resources
+    for (int i = 0; i < m_HDRTonemaps.size(); i++)
+    {
+        vkDestroyFramebuffer(m_renderDevice, m_HDRTonemaps[i]->m_framebuffer->m_framebuffer, nullptr);
+    }
+
+    // Delete all drawing resources
+    for (int i = 0; i < m_drawingFramebuffers.size() - 1; i++)
+    {
+        vkDestroyFramebuffer(m_renderDevice, m_drawingFramebuffers[i]->m_framebuffer, nullptr);
+    }
+}
+
+void
+VulkanRenderer::renderFrame()
+{
+    m_frameNumber++;
+
+    // Update global uniforms
+    this->updateGlobalUniforms();
+
+    // Update local uniforms
+    for (unsigned int renderDelegateIndex = 0; renderDelegateIndex < m_renderDelegates.size(); renderDelegateIndex++)
+    {
+        m_renderDelegates[renderDelegateIndex]->update();
+    }
+
+    // The swapchain contains multiple buffers, so get one that is available (i.e., not currently being written to)
+    uint32_t nextImageIndex;
+    vkAcquireNextImageKHR(m_renderDevice, *m_swapchain, UINT64_MAX, m_readyToRender, VK_NULL_HANDLE, &nextImageIndex);
+
+    VkCommandBufferBeginInfo commandBufferBeginInfo;
+    commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    commandBufferBeginInfo.pNext = nullptr;
+    commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+    commandBufferBeginInfo.pInheritanceInfo = nullptr;
+
+    vkBeginCommandBuffer(m_renderCommandBuffer, &commandBufferBeginInfo);
+
+    VkRect2D renderArea;
+    renderArea.offset = { 0, 0 };
+    renderArea.extent = { m_width, m_height };
+
+    std::array<VkClearValue, 4> clearValues;
+    clearValues[0].color = { { 0.5, 0.5, 0.5, 1 } }; // Color
+    clearValues[1].depthStencil = { { 1.0 }, { 0 } }; // Depth
+    clearValues[2].color = { { 0, 0, 0, 0 } }; // Normal
+    clearValues[3].color = { { 0, 0, 0, 0 } }; // Specular
+
+    VkRenderPassBeginInfo renderPassBeginInfo;
+    renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+    renderPassBeginInfo.pNext = nullptr;
+    renderPassBeginInfo.renderPass = m_renderPasses[0];
+    renderPassBeginInfo.framebuffer = m_drawingFramebuffers[0]->m_framebuffer;
+    renderPassBeginInfo.renderArea = renderArea;
+    renderPassBeginInfo.clearValueCount = (uint32_t)clearValues.size();
+    renderPassBeginInfo.pClearValues = &clearValues[0];
+
+    // Do buffer transfers
+    for (unsigned int renderDelegateIndex = 0; renderDelegateIndex < m_renderDelegates.size(); renderDelegateIndex++)
+    {
+        auto buffers = m_renderDelegates[renderDelegateIndex]->getBuffer().get();
+        buffers->uploadBuffers(m_renderCommandBuffer);
+    }
+
+    vkCmdBeginRenderPass(m_renderCommandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+    VkDeviceSize deviceSize = { 0 };
+
+    // Pass 1: Render all geometry
+    for (unsigned int renderDelegateIndex = 0; renderDelegateIndex < m_renderDelegates.size(); renderDelegateIndex++)
+    {
+        auto material = m_renderDelegates[renderDelegateIndex]->m_material;
+        vkCmdBindPipeline(m_renderCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material->m_pipeline);
+
+        vkCmdBindDescriptorSets(m_renderCommandBuffer,
+            VK_PIPELINE_BIND_POINT_GRAPHICS,
+            material->m_pipelineLayout, 0, (uint32_t)material->m_descriptorSets.size(),
+            &material->m_descriptorSets[0], 0, &m_dynamicOffsets);
+
+        auto buffers = m_renderDelegates[renderDelegateIndex]->getBuffer().get();
+
+        vkCmdBindVertexBuffers(m_renderCommandBuffer, 0, 1, &buffers->m_vertexBuffer, &deviceSize);
+        vkCmdBindIndexBuffer(m_renderCommandBuffer, buffers->m_indexBuffer, deviceSize, VK_INDEX_TYPE_UINT32);
+        vkCmdDrawIndexed(m_renderCommandBuffer, buffers->m_numIndices, 1, 0, 0, 0);
+    }
+
+    vkCmdEndRenderPass(m_renderCommandBuffer);
+    vkEndCommandBuffer(m_renderCommandBuffer);
+
+    vkBeginCommandBuffer(m_postProcessingCommandBuffer, &commandBufferBeginInfo);
+
+    // Pass 2 to N - 1: Post processing
+    for (unsigned int postProcessIndex = 0; postProcessIndex < m_postProcessingChain->m_postProcesses.size(); postProcessIndex++)
+    {
+        clearValues[0].color = { { 1.0, 0.0, 0.0, 1 } }; // Color
+
+        auto postProcess = m_postProcessingChain->m_postProcesses[postProcessIndex];
+
+        auto postProcessRenderPassBeginInfo = renderPassBeginInfo;
+        auto framebuffer = postProcess->m_framebuffer;
+        postProcessRenderPassBeginInfo.renderPass =
+            postProcess->m_renderPass;
+        postProcessRenderPassBeginInfo.framebuffer = framebuffer->m_framebuffer;
+        postProcessRenderPassBeginInfo.clearValueCount = (uint32_t)framebuffer->m_attachments.size();
+        postProcessRenderPassBeginInfo.renderArea.extent = { framebuffer->m_width, framebuffer->m_height };
+
+        vkCmdBeginRenderPass(m_postProcessingCommandBuffer, &postProcessRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+        vkCmdPushConstants(m_postProcessingCommandBuffer, postProcess->m_pipelineLayout,
+            VK_SHADER_STAGE_FRAGMENT_BIT, 0, 128, (void*)postProcess->m_pushConstantData);
+
+        vkCmdBindPipeline(m_postProcessingCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+            postProcess->m_pipeline);
+
+        vkCmdBindDescriptorSets(m_postProcessingCommandBuffer,
+            VK_PIPELINE_BIND_POINT_GRAPHICS,
+            postProcess->m_pipelineLayout, 0,
+            (uint32_t)postProcess->m_descriptorSets.size(),
+            &postProcess->m_descriptorSets[0], 0, &m_dynamicOffsets);
+
+        auto buffers = postProcess->m_vertexBuffer;
+        vkCmdBindVertexBuffers(m_postProcessingCommandBuffer, 0, 1, &buffers->m_vertexBuffer, &deviceSize);
+        vkCmdBindIndexBuffer(m_postProcessingCommandBuffer, buffers->m_indexBuffer, deviceSize, VK_INDEX_TYPE_UINT32);
+        vkCmdDrawIndexed(m_postProcessingCommandBuffer, buffers->m_numIndices, 1, 0, 0, 0);
+
+        vkCmdEndRenderPass(m_postProcessingCommandBuffer);
+    }
+
+    // Pass N: HDR tonemap (this is special because of the swapchain)
+    {
+        auto postProcessRenderPassBeginInfo = renderPassBeginInfo;
+        postProcessRenderPassBeginInfo.renderPass = m_HDRTonemaps[nextImageIndex]->m_renderPass;
+        postProcessRenderPassBeginInfo.framebuffer = m_HDRTonemaps[nextImageIndex]->m_framebuffer->m_framebuffer;
+        postProcessRenderPassBeginInfo.clearValueCount = (uint32_t)m_HDRTonemaps[nextImageIndex]->m_framebuffer->m_attachments.size();
+
+        vkCmdBeginRenderPass(m_postProcessingCommandBuffer, &postProcessRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+        vkCmdBindPipeline(m_postProcessingCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_HDRTonemaps[nextImageIndex]->m_pipeline);
+
+        vkCmdBindDescriptorSets(m_postProcessingCommandBuffer,
+            VK_PIPELINE_BIND_POINT_GRAPHICS,
+            m_HDRTonemaps[nextImageIndex]->m_pipelineLayout, 0, (uint32_t)m_HDRTonemaps[nextImageIndex]->m_descriptorSets.size(),
+            &m_HDRTonemaps[nextImageIndex]->m_descriptorSets[0], 0, &m_dynamicOffsets);
+
+        auto buffers = m_HDRTonemaps[nextImageIndex]->m_vertexBuffer;
+        vkCmdBindVertexBuffers(m_postProcessingCommandBuffer, 0, 1, &buffers->m_vertexBuffer, &deviceSize);
+        vkCmdBindIndexBuffer(m_postProcessingCommandBuffer, buffers->m_indexBuffer, deviceSize, VK_INDEX_TYPE_UINT32);
+        vkCmdDrawIndexed(m_postProcessingCommandBuffer, buffers->m_numIndices, 1, 0, 0, 0);
+
+        vkCmdEndRenderPass(m_postProcessingCommandBuffer);
+    }
+
+    vkEndCommandBuffer(m_postProcessingCommandBuffer);
+
+    VkCommandBuffer commandBuffers[2];
+    commandBuffers[0] = m_renderCommandBuffer;
+    commandBuffers[1] = m_postProcessingCommandBuffer;
+
+    VkPipelineStageFlags stageWaitFlags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    VkSubmitInfo submitInfo[2];
+    submitInfo[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submitInfo[0].pNext = nullptr;
+    submitInfo[0].waitSemaphoreCount = 1;
+    submitInfo[0].pWaitSemaphores = &m_readyToRender;
+    submitInfo[0].pWaitDstStageMask = &stageWaitFlags;
+    submitInfo[0].commandBufferCount = 1;
+    submitInfo[0].pCommandBuffers = &commandBuffers[0];
+    submitInfo[0].signalSemaphoreCount = 1;
+    submitInfo[0].pSignalSemaphores = &m_drawingComplete;
+
+    submitInfo[1] = submitInfo[0];
+    submitInfo[1].pWaitSemaphores = &m_drawingComplete;
+    submitInfo[1].pWaitDstStageMask = &stageWaitFlags;
+    submitInfo[1].pCommandBuffers = &commandBuffers[1];
+    submitInfo[1].pSignalSemaphores = &m_presentImages;
+
+    // Submit command buffers
+    vkQueueSubmit(m_renderQueue, 2, submitInfo, m_commandBufferSubmit);
+
+    VkSwapchainKHR swapchains[] = {*m_swapchain};
+
+    VkPresentInfoKHR presentInfo;
+    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+    presentInfo.pNext = nullptr;
+    presentInfo.waitSemaphoreCount = 1;
+    presentInfo.pWaitSemaphores = &m_presentImages;
+    presentInfo.swapchainCount = 1;
+    presentInfo.pSwapchains = swapchains;
+    presentInfo.pImageIndices = &nextImageIndex; // change this in the future for multi-screen rendering
+    presentInfo.pResults = nullptr;
+
+    // Display backbuffer
+    vkQueuePresentKHR(m_renderQueue, &presentInfo);
+
+    // Wait until command buffer is done so that we can write to it again
+    vkWaitForFences(m_renderDevice, 1, &m_commandBufferSubmit, VK_TRUE, UINT64_MAX);
+    vkResetFences(m_renderDevice, 1, &m_commandBufferSubmit);
+}
+
+void
+VulkanRenderer::setupSynchronization()
+{
+    VkSemaphoreCreateInfo semaphoreInfo;
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    semaphoreInfo.pNext = nullptr;
+    semaphoreInfo.flags = 0;
+
+    vkCreateSemaphore(m_renderDevice, &semaphoreInfo, nullptr, &m_readyToRender);
+
+    vkCreateSemaphore(m_renderDevice, &semaphoreInfo, nullptr, &m_presentImages);
+
+    vkCreateSemaphore(m_renderDevice, &semaphoreInfo, nullptr, &m_drawingComplete);
+
+    VkFenceCreateInfo fenceInfo;
+    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+    fenceInfo.pNext = nullptr;
+    fenceInfo.flags = 0;
+
+    vkCreateFence(m_renderDevice, &fenceInfo, nullptr, &m_commandBufferSubmit);
+}
+
+std::shared_ptr<VulkanRenderDelegate>
+VulkanRenderer::loadGeometry(std::shared_ptr<Geometry> geometry)
+{
+    auto renderDelegate = VulkanRenderDelegate::make_delegate(geometry, m_memoryManager);
+    if (renderDelegate != nullptr)
+    {
+        m_renderDelegates.push_back(renderDelegate);
+        renderDelegate->getBuffer()->initializeBuffers(m_memoryManager);
+    }
+    return renderDelegate;
+}
+
+void
+VulkanRenderer::setupMemoryManager()
+{
+    m_memoryManager.m_device = m_renderDevice;
+    m_memoryManager.m_physicalDevice = m_renderPhysicalDevice;
+    m_memoryManager.m_queueFamilyIndex = m_renderQueueFamily;
+    m_memoryManager.m_transferCommandBuffer = &m_renderCommandBuffer;
+    m_memoryManager.m_transferQueue = &m_renderQueue;
+}
+
+void
+VulkanRenderer::createGlobalUniformBuffers()
+{
+    m_globalVertexUniformBuffer = std::make_shared<VulkanUniformBuffer>(m_memoryManager, (uint32_t)sizeof(VulkanGlobalVertexUniforms));
+    m_globalFragmentUniformBuffer = std::make_shared<VulkanUniformBuffer>(m_memoryManager, (uint32_t)sizeof(VulkanGlobalFragmentUniforms));
+}
+
+void
+VulkanRenderer::initializePostProcesses()
+{
+    std::vector<VkPipeline> graphicsPipelines;
+    std::vector<VkGraphicsPipelineCreateInfo> graphicsPipelinesInfo;
+    m_postProcessingChain = std::make_shared<VulkanPostProcessingChain>(this);
+
+    // HDR pipeline creation
+    for (uint32_t i = 0; i < m_swapchainImageCount; i++)
+    {
+        m_HDRTonemaps[i] = std::make_shared<VulkanPostProcess>(this, 0, true);
+        m_HDRTonemaps[i]->addInputImage(&m_swapchainImageSamplers[i], &m_HDRImageView[m_postProcessingChain->m_lastOutput][0]);
+        m_HDRTonemaps[i]->m_framebuffer->setColor(&m_swapchainImageViews[i], VK_FORMAT_B8G8R8A8_SRGB);
+        m_HDRTonemaps[i]->initialize(this, "./Shaders/VulkanShaders/PostProcessing/HDR_tonemap_frag.spv");
+
+        graphicsPipelines.push_back(m_HDRTonemaps[i]->m_pipeline);
+        graphicsPipelinesInfo.push_back(m_HDRTonemaps[i]->m_graphicsPipelineInfo);
+    }
+
+    // Post processing pipeline creation
+    for (int i = 0; i < m_postProcessingChain->m_postProcesses.size(); i++)
+    {
+        graphicsPipelines.push_back(m_postProcessingChain->m_postProcesses[i]->m_pipeline);
+        graphicsPipelinesInfo.push_back(m_postProcessingChain->m_postProcesses[i]->m_graphicsPipelineInfo);
+    }
+
+    vkCreateGraphicsPipelines(m_renderDevice,
+        m_pipelineCache,
+        (uint32_t)graphicsPipelines.size(),
+        &graphicsPipelinesInfo[0],
+        nullptr,
+        &graphicsPipelines[0]);
+
+    for (uint32_t i = 0; i < m_swapchainImageCount; i++)
+    {
+        m_HDRTonemaps[i]->m_pipeline = graphicsPipelines[i];
+    }
+
+    for (int i = m_swapchainImageCount; i < m_postProcessingChain->m_postProcesses.size() + m_swapchainImageCount; i++)
+    {
+        m_postProcessingChain->m_postProcesses[i - m_swapchainImageCount]->m_pipeline = graphicsPipelines[i];
+    }
+}
+
+void
+VulkanRenderer::updateGlobalUniforms()
+{
+    // Vertex uniforms
+    {
+        // Projection matrix
+        auto camera = m_scene->getCamera();
+        m_fov = (float)glm::radians(camera->getViewAngle());
+        m_globalVertexUniforms.projectionMatrix = glm::perspective(m_fov, (float)(m_width) / (float)(m_height), m_nearPlane, m_farPlane);
+        m_globalVertexUniforms.projectionMatrix[1][1] *= -1; // Must do this for Vulkan
+
+        // View matrix
+
+        auto eye = glm::tvec3<float>(camera->getPosition().x(), camera->getPosition().y(), camera->getPosition().z());
+        auto center = glm::tvec3<float>(camera->getFocalPoint().x(), camera->getFocalPoint().y(), camera->getFocalPoint().z());
+        auto up = glm::tvec3<float>(camera->getViewUp().x(), camera->getViewUp().y(), camera->getViewUp().z());
+        m_globalVertexUniforms.cameraPosition = glm::vec4(camera->getPosition().x(), camera->getPosition().y(), camera->getPosition().z(), 0.0);
+
+        m_globalVertexUniforms.viewMatrix = glm::lookAt(eye, center, up);
+    }
+
+    // Lights uniforms
+    {
+        // Update lights - need to optimize
+        auto lights = m_scene->getLights();
+        for (int i = 0; i < lights.size(); i++)
+        {
+            // Only supports directional lights right now
+            auto focalPoint = lights[i]->getFocalPoint();
+            auto position = Vec3d(0,0,0);
+
+            m_globalFragmentUniforms.lights[i].lightVector.x = position.x() - focalPoint.x();
+            m_globalFragmentUniforms.lights[i].lightVector.y = position.y() - focalPoint.y();
+            m_globalFragmentUniforms.lights[i].lightVector.z = position.z() - focalPoint.z();
+
+            m_globalFragmentUniforms.lights[i].lightVector = glm::normalize(m_globalFragmentUniforms.lights[i].lightVector);
+
+            Color lightColor = lights[i]->getColor();
+            m_globalFragmentUniforms.lights[i].lightColor.r = lightColor.r;
+            m_globalFragmentUniforms.lights[i].lightColor.g = lightColor.g;
+            m_globalFragmentUniforms.lights[i].lightColor.b = lightColor.b;
+
+            memcpy(&m_globalVertexUniforms.lights, &m_globalFragmentUniforms.lights, sizeof(m_globalFragmentUniforms.lights));
+        }
+    }
+
+    m_globalVertexUniformBuffer->updateUniforms(sizeof(VulkanGlobalVertexUniforms), &m_globalVertexUniforms);
+    m_globalFragmentUniformBuffer->updateUniforms(sizeof(VulkanGlobalFragmentUniforms), &m_globalFragmentUniforms);
+}
+
+VulkanRenderer::~VulkanRenderer()
+{
+    // Delete devices
+    for (int i = 0; i < (int)(m_deviceCount); i++)
+    {
+        // Important: must wait for device to be finished
+        vkDeviceWaitIdle(m_devices[i]);
+        vkDestroySemaphore(m_renderDevice, m_readyToRender, nullptr);
+        vkDestroySemaphore(m_renderDevice, m_drawingComplete, nullptr);
+
+        // Clear all memory
+        m_memoryManager.clear();
+
+        for (int x = 0; x < m_renderDelegates.size(); x++)
+        {
+            // TODO: Clear all render delegates
+        }
+
+        vkDestroyDevice(m_devices[i], nullptr);
+    }
+
+    vkDestroyInstance(*m_instance, nullptr);
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.h b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.h
new file mode 100644
index 0000000000000000000000000000000000000000..772cd3ff89f8166e2d1b1d66444a804f5408a6f5
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.h
@@ -0,0 +1,244 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanRenderer_h
+#define imstkVulkanRenderer_h
+
+#include <iostream>
+#include <memory>
+#include <vector>
+#include <fstream>
+
+#include "vulkan/vulkan.h"
+
+#include "glm/glm.hpp"
+#include "glm/gtc/matrix_transform.hpp"
+
+#include "imstkScene.h"
+#include "imstkRenderer.h"
+
+#include "imstkVulkanValidation.h"
+#include "imstkVulkanVertexBuffer.h"
+#include "imstkVulkanUniformBuffer.h"
+#include "imstkVulkanRenderDelegate.h"
+#include "imstkVulkanMaterialDelegate.h"
+
+#include "imstkVulkanPostProcess.h"
+#include "imstkVulkanPostProcessingChain.h"
+
+#include "imstkVulkanMemoryManager.h"
+
+#include "imstkVulkanFramebuffer.h"
+
+namespace imstk
+{
+struct VulkanRendererConstants
+{
+    unsigned int numLights;
+};
+
+class VulkanRenderer : public Renderer {
+public:
+    VulkanRenderer(std::shared_ptr<Scene> scene);
+    ~VulkanRenderer();
+
+    ///
+    /// \brief Populates the device fields for the rendering class (both physical and logical devices)
+    ///
+    void setupGPUs();
+
+    ///
+    /// \brief Prints the physical device name
+    ///
+    void printGPUs();
+
+    ///
+    /// \brief Sets up command pools
+    ///
+    void setupCommandPools();
+
+    ///
+    /// \brief Sets up command pools
+    ///
+    void buildCommandBuffer();
+
+    ///
+    /// \brief Sets up render pass
+    ///
+    void setupRenderPasses();
+
+    ///
+    /// \brief Sets up swapchain
+    ///
+    void setupSwapchain();
+
+    ///
+    /// \brief Initializes the framebuffer
+    ///
+    void initializeFramebuffers(VkSwapchainKHR * swapchain);
+
+    ///
+    /// \brief Deletes the framebuffer
+    ///
+    void deleteFramebuffers();
+
+    ///
+    /// \brief Initializes the framebuffer
+    ///
+    void resizeFramebuffers(VkSwapchainKHR * swapchain, int width, int height);
+
+    ///
+    /// \brief Renders the frame
+    ///
+    void renderFrame();
+
+    ///
+    /// \brief Setups semaphores/fences
+    ///
+    void setupSynchronization();
+
+    ///
+    /// \brief Get device memory properties
+    ///
+    void setupMemoryManager();
+
+    ///
+    /// \brief Create global uniforms
+    ///
+    void createGlobalUniformBuffers();
+
+    ///
+    /// \brief Initialize the post processing effects
+    ///
+    void initializePostProcesses();
+
+    ///
+    /// \brief Update global uniforms
+    ///
+    void updateGlobalUniforms();
+
+protected:
+    friend class VulkanViewer;
+    friend class VulkanMaterialDelegate;
+    friend class VulkanPostProcess;
+    friend class VulkanPostProcessingChain;
+
+    void initialize();
+    std::shared_ptr<VulkanRenderDelegate> loadGeometry(std::shared_ptr<Geometry> geometry);
+
+    unsigned int m_width = 1000;
+    unsigned int m_height = 800;
+    float m_fov = PI;
+    float m_nearPlane = 0.01;
+    float m_farPlane = 1000;
+
+    VulkanRendererConstants m_constants;
+
+    std::vector<char *> m_extensions;
+    std::vector<char *> m_layers;
+
+    std::shared_ptr<Scene> m_scene = nullptr;
+
+    VkInstance * m_instance = nullptr;
+
+    uint32_t m_physicalDeviceCount = 0;
+    VkPhysicalDevice * m_physicalDevices = nullptr;
+    VkPhysicalDevice m_renderPhysicalDevice;
+
+    uint32_t m_deviceCount = 0;
+    VkDevice * m_devices = nullptr;
+    VkPhysicalDeviceLimits m_deviceLimits;
+    VkDevice m_renderDevice;
+
+    VkPipelineCache m_pipelineCache;
+
+    uint32_t m_queueFamilyPropertiesCount = 0;
+    VkQueueFamilyProperties * m_queueFamilyProperties = nullptr;
+    VkQueue m_renderQueue;
+
+    VkCommandPool m_renderCommandPool;
+    VkCommandBuffer m_renderCommandBuffer;
+    VkCommandBuffer m_postProcessingCommandBuffer;
+
+    uint32_t m_dynamicOffsets = {0};
+
+    VulkanMemoryManager m_memoryManager;
+
+    std::shared_ptr<VulkanUniformBuffer> m_globalVertexUniformBuffer;
+    std::shared_ptr<VulkanUniformBuffer> m_globalFragmentUniformBuffer;
+    VulkanGlobalVertexUniforms m_globalVertexUniforms;
+    VulkanGlobalFragmentUniforms m_globalFragmentUniforms;
+
+    VkDescriptorPool m_globalDescriptorPool;
+    std::vector<VkDescriptorSet> m_globalDescriptorSets;
+    std::vector<VkDescriptorSetLayout> m_globalDescriptorSetLayouts;
+    std::vector<VkWriteDescriptorSet> m_globalWriteDescriptorSets;
+
+    std::vector<VkRenderPass> m_renderPasses;
+
+    // Framebuffers
+    VkSwapchainKHR * m_swapchain = nullptr;
+    uint32_t m_swapchainImageCount = 0;
+    std::vector<VkImage> m_swapchainImages;
+    std::vector<VkImageView> m_swapchainImageViews;
+    std::vector<VkSampler> m_swapchainImageSamplers;
+
+    // Depth buffer
+    std::vector<VkImage> m_depthImage;
+    std::vector<VkImageView> m_depthImageView;
+    std::vector<VkDeviceMemory*> m_depthImageMemory;
+
+    // Normal buffer
+    VkImage m_normalImage;
+    VkImageView m_normalImageView;
+    VkDeviceMemory * m_normalImageMemory;
+
+    // Color buffers
+    std::vector<VkImage> m_HDRImage[3];
+    VkSampler m_HDRImageSampler;
+    std::vector<VkImageView> m_HDRImageView[3];
+    std::vector<VkDeviceMemory*> m_HDRImageMemory[3];
+    uint32_t m_mipLevels = 1;
+
+    std::vector<std::shared_ptr<VulkanFramebuffer>> m_drawingFramebuffers;
+    std::vector<std::shared_ptr<VulkanPostProcess>> m_HDRTonemaps;
+    std::shared_ptr<VulkanPostProcessingChain> m_postProcessingChain;
+
+    VkSemaphore m_readyToRender;
+    VkSemaphore m_drawingComplete;
+    VkSemaphore m_presentImages;
+
+    unsigned int m_buffering = 3;
+
+    int m_frameNumber = 0;
+    VkSampleCountFlagBits m_samples = VK_SAMPLE_COUNT_1_BIT;
+
+    VkFence m_commandBufferSubmit;
+
+    glm::mat4 m_projectionMatrix;
+
+    std::vector<std::shared_ptr<VulkanRenderDelegate>> m_renderDelegates;
+
+    uint32_t m_renderQueueFamily = 0;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanTextureDelegate.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanTextureDelegate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ad41d71fd985cc6178e7649ce6cddc262ba0a4ba
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanTextureDelegate.cpp
@@ -0,0 +1,531 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanTextureDelegate.h"
+
+namespace imstk
+{
+VulkanTextureDelegate::VulkanTextureDelegate(
+    VulkanMemoryManager& memoryManager,
+    std::shared_ptr<Texture> texture)
+{
+    m_path = texture->getPath();
+    m_type = texture->getType();
+
+    // Load textures and get texture information
+    if ((texture->getType() != Texture::Type::IRRADIANCE_CUBEMAP)
+        && (texture->getType() != Texture::Type::RADIANCE_CUBEMAP))
+    {
+        m_arrayLayers = 1;
+        this->loadTexture(memoryManager);
+        m_imageInfo.flags = 0;
+    }
+    else
+    {
+        m_arrayLayers = 6;
+        this->loadCubemapTexture(memoryManager);
+        m_imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+        m_isCubemap = true;
+    }
+
+    // Determine number of mipmaps
+    if (m_mipLevels < 1)
+    {
+        if (!texture->getMipmapsEnabled())
+        {
+            m_mipLevels = 1;
+        }
+        else
+        {
+            m_mipLevels = std::log2(std::max(m_width, m_height)) + 1;
+        }
+    }
+
+    m_layout = VK_IMAGE_LAYOUT_GENERAL;
+
+    m_imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    m_imageInfo.pNext = nullptr;
+
+    if (m_type == Texture::Type::DIFFUSE)
+    {
+        m_imageInfo.format = VK_FORMAT_B8G8R8A8_SRGB;
+    }
+    else
+    {
+        m_imageInfo.format = VK_FORMAT_B8G8R8A8_UNORM;
+    }
+
+    m_imageInfo.imageType = VK_IMAGE_TYPE_2D;
+    m_imageInfo.extent = { m_width, m_height, 1 };
+    m_imageInfo.mipLevels = m_mipLevels;
+    m_imageInfo.arrayLayers = m_arrayLayers;
+    m_imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+    m_imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+    m_imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+    m_imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    m_imageInfo.queueFamilyIndexCount = 1;
+    m_imageInfo.pQueueFamilyIndices = &memoryManager.m_queueFamilyIndex;
+    m_imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+
+    vkCreateImage(memoryManager.m_device, &m_imageInfo, nullptr, &m_image);
+
+    m_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    m_range.baseMipLevel = 0;
+    m_range.levelCount = m_mipLevels;
+    m_range.baseArrayLayer = 0;
+    m_range.layerCount = m_arrayLayers;
+
+    if (m_isCubemap)
+    {
+        this->uploadCubemapTexture(memoryManager);
+    }
+    else
+    {
+        this->uploadTexture(memoryManager);
+    }
+
+    VkComponentMapping mapping;
+    mapping.r = VK_COMPONENT_SWIZZLE_R;
+    mapping.g = VK_COMPONENT_SWIZZLE_G;
+    mapping.b = VK_COMPONENT_SWIZZLE_B;
+    mapping.a = VK_COMPONENT_SWIZZLE_A;
+
+    VkImageViewCreateInfo imageViewInfo;
+    imageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    imageViewInfo.pNext = nullptr;
+    imageViewInfo.flags = 0;
+    imageViewInfo.image = m_image;
+
+    if (!m_isCubemap)
+    {
+        imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    }
+    else
+    {
+        imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+    }
+
+    imageViewInfo.format = m_imageInfo.format;
+    imageViewInfo.components = mapping;
+    imageViewInfo.subresourceRange = m_range;
+
+    vkCreateImageView(memoryManager.m_device, &imageViewInfo, nullptr, &m_imageView);
+
+    VkSamplerCreateInfo samplerInfo;
+    samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+    samplerInfo.pNext = nullptr;
+    samplerInfo.flags = 0;
+    samplerInfo.magFilter = VK_FILTER_LINEAR;
+    samplerInfo.minFilter = VK_FILTER_LINEAR;
+    samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; // Trilinear interpolation
+    samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    samplerInfo.mipLodBias = 0.0;
+    samplerInfo.anisotropyEnable = VK_FALSE; // TODO:: add option to enable
+    samplerInfo.maxAnisotropy = 1.0;
+    samplerInfo.compareEnable = VK_FALSE;
+    samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+    samplerInfo.minLod = 0;
+    samplerInfo.maxLod = 0;
+    samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+    samplerInfo.unnormalizedCoordinates = VK_FALSE;
+
+    vkCreateSampler(memoryManager.m_device, &samplerInfo, nullptr, &m_sampler);
+}
+
+void
+VulkanTextureDelegate::loadTexture(VulkanMemoryManager& memoryManager)
+{
+    if (m_path != "")
+    {
+        auto readerGenerator = vtkSmartPointer<vtkImageReader2Factory>::New();
+        auto reader = readerGenerator->CreateImageReader2(m_path.c_str());
+
+        reader->SetFileName(m_path.c_str());
+        reader->Update();
+
+        auto data = reader->GetOutput();
+        m_width = data->GetDimensions()[0];
+        m_height = data->GetDimensions()[1];
+        m_channels = reader->GetNumberOfScalarComponents();
+        m_data = (unsigned char *)data->GetScalarPointer();
+    }
+    else
+    {
+        std::vector<unsigned char> data(4);
+        data[0] = '\255';
+        data[1] = '\255';
+        data[2] = '\255';
+        data[3] = '\255';
+        m_width = 1;
+        m_height = 1;
+        m_data = &data[0];
+    }
+}
+
+void
+VulkanTextureDelegate::loadCubemapTexture(VulkanMemoryManager& memoryManager)
+{
+    if (m_path != "")
+    {
+        m_cubemap = gli::texture_cube(gli::load(m_path));
+
+        m_width = m_cubemap.extent().x;
+        m_height = m_cubemap.extent().y;
+        m_mipLevels = (uint32_t)m_cubemap.levels();
+    }
+    else
+    {
+        m_cubemap = gli::texture_cube(gli::format::FORMAT_BGRA8_UNORM_PACK8, gli::extent2d(1,1), 1);
+        m_width = 1;
+        m_height = 1;
+        m_mipLevels = 1;
+    }
+}
+
+void
+VulkanTextureDelegate::uploadTexture(VulkanMemoryManager& memoryManager)
+{
+    uint32_t imageSize = m_width * m_height * 4;
+
+    // Staging image
+    VkBufferCreateInfo stagingBufferInfo;
+    stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    stagingBufferInfo.pNext = nullptr;
+    stagingBufferInfo.flags = 0;
+    stagingBufferInfo.size = imageSize;
+    stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+    stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    stagingBufferInfo.queueFamilyIndexCount = 0;
+    stagingBufferInfo.pQueueFamilyIndices = nullptr;
+
+    vkCreateBuffer(memoryManager.m_device, &stagingBufferInfo, nullptr, &m_stagingBuffer);
+
+    VkMemoryRequirements memoryRequirementsStagingBuffer;
+    vkGetBufferMemoryRequirements(memoryManager.m_device, m_stagingBuffer, &memoryRequirementsStagingBuffer);
+    m_stagingBufferMemory = *memoryManager.allocateMemory(memoryRequirementsStagingBuffer,
+        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+        VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+    VkMemoryRequirements memoryRequirementsImage;
+    vkGetImageMemoryRequirements(memoryManager.m_device, m_image, &memoryRequirementsImage);
+    m_imageMemory = *memoryManager.allocateMemory(memoryRequirementsImage,
+        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+    void * imageData;
+    vkMapMemory(memoryManager.m_device, m_stagingBufferMemory, 0, imageSize, 0, &imageData);
+
+    auto imageEditData = (unsigned char*)imageData;
+
+    unsigned int y_offset = 0;
+    unsigned int colorChannels = std::min(m_channels, 3u);
+
+    for (unsigned int y = 0; y < m_height; y++)
+    {
+        y_offset = y * m_width;
+        for (unsigned int x = 0; x < m_width; x++)
+        {
+            // Fill in image data
+            for (unsigned int z = 0; z < colorChannels; z++)
+            {
+                imageEditData[4 * (y_offset + x) + z] =
+                    m_data[m_channels * (y_offset + x) + (colorChannels - z - 1)];
+            }
+
+            // Fill in the rest of the memory
+            memset(&imageEditData[4 * (y_offset + x) + colorChannels],
+                (unsigned char)255,
+                (4 - colorChannels) * sizeof(unsigned char));
+        }
+    }
+
+    vkUnmapMemory(memoryManager.m_device, m_stagingBufferMemory);
+
+    // Start transfer commands
+    VkCommandBufferBeginInfo commandBufferBeginInfo;
+    commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    commandBufferBeginInfo.pNext = nullptr;
+    commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+    commandBufferBeginInfo.pInheritanceInfo = nullptr;
+
+    vkBeginCommandBuffer(*memoryManager.m_transferCommandBuffer, &commandBufferBeginInfo);
+    vkBindBufferMemory(memoryManager.m_device, m_stagingBuffer, m_stagingBufferMemory, 0);
+    vkBindImageMemory(memoryManager.m_device, m_image, m_imageMemory, 0);
+
+    VkImageSubresourceLayers layersDestination;
+    layersDestination.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    layersDestination.mipLevel = 0;
+    layersDestination.baseArrayLayer = 0;
+    layersDestination.layerCount = 1;
+
+    VkBufferImageCopy copyInfo;
+    copyInfo.bufferOffset = 0;
+    copyInfo.bufferRowLength = m_width;
+    copyInfo.bufferImageHeight = m_height;
+    copyInfo.imageSubresource = layersDestination;
+    copyInfo.imageOffset = { 0, 0, 0 };
+    copyInfo.imageExtent = { m_width, m_height, 1 };
+
+    VkImageSubresourceRange baseRange = m_range;
+    baseRange.levelCount = 1;
+
+    this->changeImageLayout(*memoryManager.m_transferCommandBuffer, m_image,
+        VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+        VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, m_range);
+    vkCmdCopyBufferToImage(*memoryManager.m_transferCommandBuffer, m_stagingBuffer,
+        m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyInfo);
+
+    if (m_mipLevels != 1)
+    {
+        this->generateMipmaps(*memoryManager.m_transferCommandBuffer);
+    }
+
+    this->changeImageLayout(*memoryManager.m_transferCommandBuffer, m_image,
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+        VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, m_range);
+
+    vkEndCommandBuffer(*memoryManager.m_transferCommandBuffer);
+
+    VkCommandBuffer commandBuffers[] = { *memoryManager.m_transferCommandBuffer };
+
+    VkPipelineStageFlags stageWaitFlags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    VkSubmitInfo submitInfo[1];
+    submitInfo[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submitInfo[0].pNext = nullptr;
+    submitInfo[0].waitSemaphoreCount = 0;
+    submitInfo[0].pWaitSemaphores = nullptr;
+    submitInfo[0].pWaitDstStageMask = &stageWaitFlags;
+    submitInfo[0].commandBufferCount = 1;
+    submitInfo[0].pCommandBuffers = commandBuffers;
+    submitInfo[0].signalSemaphoreCount = 0;
+    submitInfo[0].pSignalSemaphores = nullptr;
+
+    vkQueueSubmit(*memoryManager.m_transferQueue, 1, submitInfo, nullptr);
+    vkDeviceWaitIdle(memoryManager.m_device);
+}
+
+void
+VulkanTextureDelegate::uploadCubemapTexture(VulkanMemoryManager& memoryManager)
+{
+    uint32_t imageSize = (uint32_t)m_cubemap.size();
+
+    // Staging image
+    VkBufferCreateInfo stagingBufferInfo;
+    stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    stagingBufferInfo.pNext = nullptr;
+    stagingBufferInfo.flags = 0;
+    stagingBufferInfo.size = imageSize;
+    stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+    stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    stagingBufferInfo.queueFamilyIndexCount = 0;
+    stagingBufferInfo.pQueueFamilyIndices = nullptr;
+
+    vkCreateBuffer(memoryManager.m_device, &stagingBufferInfo, nullptr, &m_stagingBuffer);
+
+    VkMemoryRequirements memoryRequirementsStagingBuffer;
+    vkGetBufferMemoryRequirements(memoryManager.m_device, m_stagingBuffer, &memoryRequirementsStagingBuffer);
+    m_stagingBufferMemory = *memoryManager.allocateMemory(memoryRequirementsStagingBuffer,
+        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+        VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+    VkMemoryRequirements memoryRequirementsImage;
+    vkGetImageMemoryRequirements(memoryManager.m_device, m_image, &memoryRequirementsImage);
+    m_imageMemory = *memoryManager.allocateMemory(memoryRequirementsImage,
+        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+    void * imageData;
+    vkMapMemory(memoryManager.m_device, m_stagingBufferMemory, 0, imageSize, 0, &imageData);
+
+    memcpy(imageData, m_cubemap.data(), m_cubemap.size());
+
+    vkUnmapMemory(memoryManager.m_device, m_stagingBufferMemory);
+
+    // Start transfer commands
+    VkCommandBufferBeginInfo commandBufferBeginInfo;
+    commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    commandBufferBeginInfo.pNext = nullptr;
+    commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+    commandBufferBeginInfo.pInheritanceInfo = nullptr;
+
+    vkBeginCommandBuffer(*memoryManager.m_transferCommandBuffer, &commandBufferBeginInfo);
+    vkBindBufferMemory(memoryManager.m_device, m_stagingBuffer, m_stagingBufferMemory, 0);
+    vkBindImageMemory(memoryManager.m_device, m_image, m_imageMemory, 0);
+
+    std::vector<VkBufferImageCopy> copyInfos(m_mipLevels * m_arrayLayers);
+
+    unsigned int currentOffset = 0;
+    for (unsigned int layer = 0; layer < m_arrayLayers; layer++)
+    {
+        for (unsigned int level = 0; level < m_mipLevels; level++)
+        {
+            VkImageSubresourceLayers layersDestination;
+            layersDestination.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+            layersDestination.mipLevel = level;
+            layersDestination.baseArrayLayer = layer;
+            layersDestination.layerCount = 1;
+
+            unsigned int currentRegion = layer * m_mipLevels + level;
+
+            copyInfos[currentRegion].bufferOffset = currentOffset;
+            copyInfos[currentRegion].bufferRowLength = m_cubemap[layer][level].extent().x;
+            copyInfos[currentRegion].bufferImageHeight = m_cubemap[layer][level].extent().y;
+            copyInfos[currentRegion].imageSubresource = layersDestination;
+            copyInfos[currentRegion].imageOffset = { 0, 0, 0 };
+            copyInfos[currentRegion].imageExtent = {
+                m_cubemap[layer][level].extent().x,
+                m_cubemap[layer][level].extent().y,
+                1
+            };
+            currentOffset += (unsigned int)m_cubemap[layer][level].size();
+        }
+    }
+
+    this->changeImageLayout(*memoryManager.m_transferCommandBuffer, m_image,
+        VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+        VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, m_range);
+
+    vkCmdCopyBufferToImage(*memoryManager.m_transferCommandBuffer, m_stagingBuffer,
+        m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)copyInfos.size(), &copyInfos[0]);
+
+    this->changeImageLayout(*memoryManager.m_transferCommandBuffer, m_image,
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+        VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, m_range);
+
+    vkEndCommandBuffer(*memoryManager.m_transferCommandBuffer);
+
+    VkCommandBuffer commandBuffers[] = { *memoryManager.m_transferCommandBuffer };
+
+    VkPipelineStageFlags stageWaitFlags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    VkSubmitInfo submitInfo[1];
+    submitInfo[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submitInfo[0].pNext = nullptr;
+    submitInfo[0].waitSemaphoreCount = 0;
+    submitInfo[0].pWaitSemaphores = nullptr;
+    submitInfo[0].pWaitDstStageMask = &stageWaitFlags;
+    submitInfo[0].commandBufferCount = 1;
+    submitInfo[0].pCommandBuffers = commandBuffers;
+    submitInfo[0].signalSemaphoreCount = 0;
+    submitInfo[0].pSignalSemaphores = nullptr;
+
+    vkQueueSubmit(*memoryManager.m_transferQueue, 1, submitInfo, nullptr);
+    vkDeviceWaitIdle(memoryManager.m_device);
+}
+
+void
+VulkanTextureDelegate::changeImageLayout(VkCommandBuffer& commandBuffer,
+                                         VkImage& image,
+                                         VkImageLayout layout1,
+                                         VkImageLayout layout2,
+                                         VkAccessFlags sourceFlags,
+                                         VkAccessFlags destinationFlags,
+                                         VkImageSubresourceRange range)
+{
+    VkImageMemoryBarrier layoutChange;
+    layoutChange.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    layoutChange.pNext = nullptr;
+    layoutChange.srcAccessMask = sourceFlags;
+    layoutChange.dstAccessMask = destinationFlags;
+    layoutChange.oldLayout = layout1;
+    layoutChange.newLayout = layout2;
+    layoutChange.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    layoutChange.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    layoutChange.image = image;
+    layoutChange.subresourceRange = range;
+
+    vkCmdPipelineBarrier(commandBuffer,
+        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+        0,
+        0,
+        nullptr,
+        0,
+        nullptr,
+        1,
+        &layoutChange);
+}
+
+void
+VulkanTextureDelegate::generateMipmaps(VkCommandBuffer& commandBuffer)
+{
+    for (uint32_t i = 0; i < m_mipLevels - 1; i++)
+    {
+        VkImageSubresourceLayers sourceLayers;
+        sourceLayers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        sourceLayers.mipLevel = i;
+        sourceLayers.baseArrayLayer = 0;
+        sourceLayers.layerCount = 1;
+
+        VkImageSubresourceLayers destinationLayers;
+        destinationLayers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        destinationLayers.mipLevel = i + 1;
+        destinationLayers.baseArrayLayer = 0;
+        destinationLayers.layerCount = 1;
+
+        VkOffset3D sourceOffsets[2];
+        sourceOffsets[0].x = 0;
+        sourceOffsets[0].y = 0;
+        sourceOffsets[0].z = 0;
+
+        sourceOffsets[1].x = m_width / (1 << i);
+        sourceOffsets[1].y = m_height / (1 << i);
+        sourceOffsets[1].z = 1;
+
+        VkOffset3D destinationOffsets[2];
+        destinationOffsets[0].x = 0;
+        destinationOffsets[0].y = 0;
+        destinationOffsets[0].z = 0;
+
+        destinationOffsets[1].x = m_width / (1 << (i + 1));
+        destinationOffsets[1].y = m_height / (1 << (i + 1));
+        destinationOffsets[1].z = 1;
+
+        VkImageBlit mipFormat;
+        mipFormat.srcSubresource = sourceLayers;
+        mipFormat.srcOffsets[0] = sourceOffsets[0];
+        mipFormat.srcOffsets[1] = sourceOffsets[1];
+        mipFormat.dstSubresource = destinationLayers;
+        mipFormat.dstOffsets[0] = destinationOffsets[0];
+        mipFormat.dstOffsets[1] = destinationOffsets[1];
+
+        VkImageSubresourceRange mipHighRange = m_range;
+        mipHighRange.baseMipLevel = i;
+        mipHighRange.levelCount = 1;
+
+        VkImageSubresourceRange mipLowRange = m_range;
+        mipLowRange.baseMipLevel = i + 1;
+        mipLowRange.levelCount = 1;
+
+        this->changeImageLayout(commandBuffer, m_image,
+            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+            VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, m_range);
+
+        vkCmdBlitImage(commandBuffer,
+            m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+            m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            1, &mipFormat, VK_FILTER_LINEAR);
+
+        this->changeImageLayout(commandBuffer, m_image,
+            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, m_range);
+    }
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanTextureDelegate.h b/Source/Rendering/VulkanRenderer/imstkVulkanTextureDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..b6f4dc2399fc084c0b217bd80e4ead8948972bfa
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanTextureDelegate.h
@@ -0,0 +1,105 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanTexture_h
+#define imstkVulkanTexture_h
+
+#include "imstkTexture.h"
+#include "imstkVulkanMemoryManager.h"
+
+#include "vulkan/vulkan.h"
+
+#include "gli/gli.hpp"
+
+#include <vtkImageReader2.h>
+#include <vtkSmartPointer.h>
+#include <vtkImageData.h>
+#include <vtkImageReader2Factory.h>
+
+#include <string>
+#include <memory>
+#include <vector>
+
+namespace imstk
+{
+///
+/// \class VulkanTexture
+///
+/// \brief Vulkan texture implementation.
+///
+class VulkanTextureDelegate
+{
+public:
+    ///
+    /// \brief Constructor
+    /// \param path Path to the texture source file
+    /// \param type Type of texture
+    ///
+    VulkanTextureDelegate(VulkanMemoryManager& memoryManager,
+                          std::shared_ptr<Texture> texture);
+
+    void loadTexture(VulkanMemoryManager& memoryManager);
+    void loadCubemapTexture(VulkanMemoryManager& memoryManager);
+    void uploadTexture(VulkanMemoryManager& memoryManager);
+    void uploadCubemapTexture(VulkanMemoryManager& memoryManager);
+    static void changeImageLayout(VkCommandBuffer& commandBuffer,
+                                  VkImage& image,
+                                  VkImageLayout layout1,
+                                  VkImageLayout layout2,
+                                  VkAccessFlags sourceFlags,
+                                  VkAccessFlags destinationFlags,
+                                  VkImageSubresourceRange range);
+
+    void generateMipmaps(VkCommandBuffer& commandBuffer);
+protected:
+    friend class VulkanMaterialDelegate;
+
+    VkImage m_image;
+
+    VkImageView m_imageView;
+    VkSampler m_sampler;
+    VkImageLayout m_layout;
+    VkImageCreateInfo m_imageInfo;
+    VkDeviceMemory m_imageMemory;
+
+    VkImageSubresourceRange m_range;
+
+    VkBuffer m_stagingBuffer;
+    VkDeviceMemory m_stagingBufferMemory;
+
+    std::string m_path;
+    Texture::Type m_type;
+    unsigned int m_mipLevels = 0;
+    unsigned int m_arrayLayers = 1;
+
+    unsigned int m_width = 0;
+    unsigned int m_height = 0;
+    unsigned int m_channels = 0;
+
+    unsigned char * m_data;
+    gli::texture_cube m_cubemap; ///> Only used for cubemaps
+    bool m_isCubemap = false;
+
+    VulkanMemoryManager * m_memoryManager;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanUniformBuffer.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanUniformBuffer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9ae19902979e27acdcf24faf19a05ffda64af7e1
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanUniformBuffer.cpp
@@ -0,0 +1,90 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanUniformBuffer.h"
+
+namespace imstk
+{
+VulkanUniformBuffer::VulkanUniformBuffer(VulkanMemoryManager& memoryManager, uint32_t uniformSize)
+{
+    m_renderDevice = memoryManager.m_device;
+
+    // Uniform buffer
+    VkBufferCreateInfo uniformBufferInfo;
+    uniformBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    uniformBufferInfo.pNext = nullptr;
+    uniformBufferInfo.flags = 0;
+    uniformBufferInfo.size = uniformSize;
+    uniformBufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+    uniformBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    uniformBufferInfo.queueFamilyIndexCount = 0;
+    uniformBufferInfo.pQueueFamilyIndices = nullptr;
+
+    vkCreateBuffer(m_renderDevice, &uniformBufferInfo, nullptr, &m_uniformBuffer);
+
+    VkMemoryRequirements memoryRequirements;
+    vkGetBufferMemoryRequirements(m_renderDevice, m_uniformBuffer, &memoryRequirements);
+
+    // Allocate vertex memory
+    m_uniformBufferSize = uniformSize;
+
+    m_uniformMemory = *memoryManager.allocateMemory(memoryRequirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
+
+    this->Bind();
+}
+
+void
+VulkanUniformBuffer::updateUniforms(uint32_t uniformSize, void * uniformData)
+{
+    auto uniformMemory = this->mapUniforms();
+
+    memcpy(uniformMemory, uniformData, uniformSize);
+
+    this->unmapUniforms();
+}
+
+VkBuffer *
+VulkanUniformBuffer::getUniformBuffer()
+{
+    return &m_uniformBuffer;
+}
+
+void *
+VulkanUniformBuffer::mapUniforms()
+{
+    void * uniformData;
+    vkMapMemory(m_renderDevice, m_uniformMemory, 0, m_uniformBufferSize, 0, &uniformData);
+
+    return uniformData;
+}
+
+void
+VulkanUniformBuffer::unmapUniforms()
+{
+    vkUnmapMemory(m_renderDevice, m_uniformMemory);
+}
+
+void
+VulkanUniformBuffer::Bind()
+{
+    vkBindBufferMemory(m_renderDevice, m_uniformBuffer, m_uniformMemory, 0);
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanUniformBuffer.h b/Source/Rendering/VulkanRenderer/imstkVulkanUniformBuffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..f4d7da4fce428a67571f763e0e47487525c430c1
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanUniformBuffer.h
@@ -0,0 +1,106 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanUniformBuffer_h
+#define imstkVulkanUniformBuffer_h
+
+#include "imstkVulkanBuffer.h"
+#include "imstkVulkanMemoryManager.h"
+
+#include "vulkan/vulkan.h"
+
+#include "glm/glm.hpp"
+
+#include <vector>
+
+namespace imstk
+{
+struct VulkanLocalVertexUniforms
+{
+    glm::mat4 transform;
+};
+
+struct VulkanLocalFragmentUniforms
+{
+    glm::mat4 transform;
+};
+
+struct VulkanLight
+{
+    glm::vec3 lightVector;
+    float lightAngle;
+    glm::vec3 lightColor;
+    float lightIntensity;
+};
+
+struct VulkanGlobalVertexUniforms
+{
+    glm::mat4 projectionMatrix;
+    glm::mat4 viewMatrix;
+    glm::vec4 cameraPosition;
+    VulkanLight lights[16];
+};
+
+struct VulkanGlobalFragmentUniforms
+{
+    VulkanLight lights[16];
+};
+
+
+class VulkanUniformBuffer : public VulkanBuffer
+{
+public:
+    VulkanUniformBuffer(VulkanMemoryManager& memoryManager, uint32_t uniformSize);
+
+    void updateUniforms(uint32_t uniformSize, void * uniformData);
+
+    ~VulkanUniformBuffer() = default;
+
+    ///
+    /// \brief Binds the vertex buffer to memory
+    ///
+    void Bind();
+
+protected:
+    friend class VulkanRenderer;
+    friend class VulkanMaterialDelegate;
+
+    VkBuffer * getUniformBuffer();
+
+    void * mapUniforms();
+    void unmapUniforms();
+
+    VkBuffer m_uniformBuffer;
+    VkDeviceMemory m_uniformMemory;
+
+    VkDevice m_renderDevice;
+    uint32_t m_bufferMemoryIndex;
+
+    VkDeviceSize m_uniformBufferSize;
+
+    static const uint32_t maxBufferSize = 1024 * 1024;
+
+private:
+    VulkanUniformBuffer() = delete;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanValidation.h b/Source/Rendering/VulkanRenderer/imstkVulkanValidation.h
new file mode 100644
index 0000000000000000000000000000000000000000..41a581fb67acda6e38a6a8b3f746194f83abc425
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanValidation.h
@@ -0,0 +1,57 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanValidation_h
+#define imstkVulkanValidation_h
+
+#include "g3log/g3log.hpp"
+
+namespace imstk
+{
+class VulkanValidation
+{
+public:
+    static char * getValidationLayer()
+    {
+        return "VK_LAYER_LUNARG_standard_validation";
+    };
+
+    static char * getValidationExtension()
+    {
+        return "VK_EXT_debug_report";
+    };
+
+    static VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT debugReportFlags,
+                                                              VkDebugReportObjectTypeEXT debugReportObjectType,
+                                                              uint64_t callbackObject,
+                                                              size_t level,
+                                                              int32_t code,
+                                                              const char * prefix,
+                                                              const char * message,
+                                                              void * data)
+    {
+        LOG(WARNING) << prefix << ": " << message;
+        return VK_FALSE;
+    };
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanVertexBuffer.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanVertexBuffer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e12e527fe5e08caca5220349a45b5235e0dfeef2
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanVertexBuffer.cpp
@@ -0,0 +1,230 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanVertexBuffer.h"
+
+namespace imstk
+{
+VulkanVertexBuffer::VulkanVertexBuffer(VulkanMemoryManager& memoryManager,
+                                       unsigned int numVertices,
+                                       unsigned int vertexSize,
+                                       unsigned int numTriangles,
+                                       VulkanVertexBufferMode mode)
+{
+    m_mode = mode;
+    m_renderDevice = memoryManager.m_device;
+    m_vertexBufferSize = numVertices * vertexSize;
+    m_numIndices = numTriangles;
+    m_indexBufferSize = m_numIndices * 3 * sizeof(uint32_t);
+
+    // Vertex buffer
+    {
+        VkBufferCreateInfo vertexBufferInfo;
+        vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+        vertexBufferInfo.pNext = nullptr;
+        vertexBufferInfo.flags = 0;
+        vertexBufferInfo.size = m_vertexBufferSize;
+        vertexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+        vertexBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        vertexBufferInfo.queueFamilyIndexCount = 0;
+        vertexBufferInfo.pQueueFamilyIndices = nullptr;
+
+        auto vertexStagingBufferInfo = vertexBufferInfo;
+        vertexStagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+
+        vkCreateBuffer(m_renderDevice, &vertexBufferInfo, nullptr, &m_vertexBuffer);
+        vkCreateBuffer(m_renderDevice, &vertexStagingBufferInfo, nullptr, &m_vertexStagingBuffer);
+    }
+
+    // Index buffer
+    {
+        VkBufferCreateInfo indexBufferInfo;
+        indexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+        indexBufferInfo.pNext = nullptr;
+        indexBufferInfo.flags = 0;
+        indexBufferInfo.size = m_indexBufferSize;
+        indexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+        indexBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        indexBufferInfo.queueFamilyIndexCount = 0;
+        indexBufferInfo.pQueueFamilyIndices = nullptr;
+
+        auto indexStagingBufferInfo = indexBufferInfo;
+        indexStagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+
+        vkCreateBuffer(m_renderDevice, &indexBufferInfo, nullptr, &m_indexBuffer);
+        vkCreateBuffer(m_renderDevice, &indexStagingBufferInfo, nullptr, &m_indexStagingBuffer);
+    }
+
+    // Allocate vertex memory
+    {
+        VkMemoryRequirements memoryRequirements;
+        vkGetBufferMemoryRequirements(m_renderDevice, m_vertexStagingBuffer, &memoryRequirements);
+        m_vertexStagingMemory = *memoryManager.allocateMemory(memoryRequirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
+
+        vkGetBufferMemoryRequirements(m_renderDevice, m_vertexBuffer, &memoryRequirements);
+        m_vertexMemory = *memoryManager.allocateMemory(memoryRequirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+    }
+
+    // Allocate index memory
+    {
+        VkMemoryRequirements memoryRequirements;
+        m_numIndices = numTriangles * 3;
+        vkGetBufferMemoryRequirements(m_renderDevice, m_indexStagingBuffer, &memoryRequirements);
+        m_indexStagingMemory = *memoryManager.allocateMemory(memoryRequirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
+
+        vkGetBufferMemoryRequirements(m_renderDevice, m_indexBuffer, &memoryRequirements);
+        m_indexMemory = *memoryManager.allocateMemory(memoryRequirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+    }
+}
+
+void *
+VulkanVertexBuffer::mapVertices()
+{
+    void * vertexData;
+    vkMapMemory(m_renderDevice, m_vertexStagingMemory, 0, m_vertexBufferSize, 0, &vertexData);
+
+    return vertexData;
+}
+
+void
+VulkanVertexBuffer::unmapVertices()
+{
+    m_vertexBufferModified = true;
+    vkUnmapMemory(m_renderDevice, m_vertexStagingMemory);
+}
+
+void *
+VulkanVertexBuffer::mapTriangles()
+{
+    void * indexData;
+    vkMapMemory(m_renderDevice, m_indexStagingMemory, 0, m_indexBufferSize, 0, &indexData);
+    return indexData;
+}
+
+void
+VulkanVertexBuffer::unmapTriangles()
+{
+    m_indexBufferModified = true;
+    vkUnmapMemory(m_renderDevice, m_indexStagingMemory);
+}
+
+void
+VulkanVertexBuffer::bind()
+{
+    // Testing
+}
+
+void
+VulkanVertexBuffer::updateVertexBuffer(std::vector<VulkanBasicVertex> * vertices,
+                                       std::vector<std::array<uint32_t, 3>> * triangles)
+{
+    auto local_vertices = (VulkanBasicVertex *)this->mapVertices();
+
+    for (unsigned i = 0; i < vertices->size(); i++)
+    {
+        local_vertices[i].position = glm::vec3(
+            (*vertices)[i].position.x,
+            (*vertices)[i].position.y,
+            (*vertices)[i].position.z);
+
+        local_vertices[i].normal = glm::vec3(
+            (*vertices)[i].normal.x,
+            (*vertices)[i].normal.y,
+            (*vertices)[i].normal.z);
+    }
+
+    this->unmapVertices();
+
+    if (triangles != nullptr)
+    {
+        auto local_triangles = (std::array<uint32_t, 3> *) this->mapTriangles();
+
+        for (unsigned i = 0; i < triangles->size(); i++)
+        {
+            local_triangles[i][0] = (*triangles)[i][0];
+            local_triangles[i][1] = (*triangles)[i][1];
+            local_triangles[i][2] = (*triangles)[i][2];
+        }
+
+        this->unmapTriangles();
+    }
+}
+
+void
+VulkanVertexBuffer::uploadBuffers(VkCommandBuffer& commandBuffer)
+{
+    if (m_vertexBufferModified)
+    {
+        VkBufferCopy copyInfo;
+        copyInfo.size = m_vertexBufferSize;
+        copyInfo.srcOffset = 0;
+        copyInfo.dstOffset = 0;
+
+        vkCmdCopyBuffer(commandBuffer, m_vertexStagingBuffer, m_vertexBuffer, 1, &copyInfo);
+    }
+    if (m_indexBufferModified)
+    {
+        VkBufferCopy copyInfo;
+        copyInfo.size = m_indexBufferSize;
+        copyInfo.srcOffset = 0;
+        copyInfo.dstOffset = 0;
+
+        vkCmdCopyBuffer(commandBuffer, m_indexStagingBuffer, m_indexBuffer, 1, &copyInfo);
+    }
+}
+
+void
+VulkanVertexBuffer::initializeBuffers(VulkanMemoryManager& memoryManager)
+{
+    vkBindBufferMemory(m_renderDevice, m_vertexBuffer, m_vertexMemory, 0);
+    vkBindBufferMemory(m_renderDevice, m_indexBuffer, m_indexMemory, 0);
+    vkBindBufferMemory(m_renderDevice, m_vertexStagingBuffer, m_vertexStagingMemory, 0);
+    vkBindBufferMemory(m_renderDevice, m_indexStagingBuffer, m_indexStagingMemory, 0);
+
+    // Start transfer commands
+    VkCommandBufferBeginInfo commandBufferBeginInfo;
+    commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    commandBufferBeginInfo.pNext = nullptr;
+    commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+    commandBufferBeginInfo.pInheritanceInfo = nullptr;
+
+    vkBeginCommandBuffer(*memoryManager.m_transferCommandBuffer, &commandBufferBeginInfo);
+    this->uploadBuffers(*memoryManager.m_transferCommandBuffer);
+    vkEndCommandBuffer(*memoryManager.m_transferCommandBuffer);
+
+    VkCommandBuffer commandBuffers[] = { *memoryManager.m_transferCommandBuffer };
+
+    VkPipelineStageFlags stageWaitFlags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    VkSubmitInfo submitInfo[1];
+    submitInfo[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submitInfo[0].pNext = nullptr;
+    submitInfo[0].waitSemaphoreCount = 0;
+    submitInfo[0].pWaitSemaphores = nullptr;
+    submitInfo[0].pWaitDstStageMask = &stageWaitFlags;
+    submitInfo[0].commandBufferCount = 1;
+    submitInfo[0].pCommandBuffers = commandBuffers;
+    submitInfo[0].signalSemaphoreCount = 0;
+    submitInfo[0].pSignalSemaphores = nullptr;
+
+    vkQueueSubmit(*memoryManager.m_transferQueue, 1, submitInfo, nullptr);
+    vkDeviceWaitIdle(memoryManager.m_device);
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanVertexBuffer.h b/Source/Rendering/VulkanRenderer/imstkVulkanVertexBuffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..0647d91c9ecafadac0897f395ddb2711a0535e41
--- /dev/null
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanVertexBuffer.h
@@ -0,0 +1,112 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanVertexBuffer_h
+#define imstkVulkanVertexBuffer_h
+
+#include "vulkan/vulkan.h"
+
+#include "imstkVulkanBuffer.h"
+#include "imstkVulkanMemoryManager.h"
+
+#include <array>
+#include <vector>
+
+namespace imstk
+{
+enum VulkanVertexBufferMode
+{
+    VERTEX_BUFFER_STATIC = 0,
+    VERTEX_BUFFER_DYNAMIC,
+    VERTEX_BUFFER_DYNAMIC_RESIZEABLE
+};
+
+struct VulkanBasicVertex
+{
+    glm::vec3 position;
+    glm::vec3 normal;
+    glm::vec3 tangent;
+    glm::vec3 bitangent;
+    glm::vec2 uv;
+};
+
+class VulkanVertexBuffer : public VulkanBuffer
+{
+public:
+    VulkanVertexBuffer(VulkanMemoryManager& memoryManager,
+                       unsigned int numVertices,
+                       unsigned int vertexSize,
+                       unsigned int numTriangles,
+                       VulkanVertexBufferMode mode = VERTEX_BUFFER_STATIC);
+
+    void * mapVertices();
+    void unmapVertices();
+
+    void * mapTriangles();
+    void unmapTriangles();
+
+    ~VulkanVertexBuffer() = default;
+
+    ///
+    /// \brief Utility function to update buffers
+    ///
+    void updateVertexBuffer(std::vector<VulkanBasicVertex> * vertices,
+                            std::vector<std::array<uint32_t, 3>> * triangles);
+
+    ///
+    /// \brief Binds the vertex buffer to memory
+    ///
+    void bind();
+
+    void uploadBuffers(VkCommandBuffer& commandBuffer);
+
+    void initializeBuffers(VulkanMemoryManager& memoryManager);
+
+private:
+    friend class VulkanRenderer;
+
+    VkBuffer m_vertexBuffer;
+    VkBuffer m_vertexStagingBuffer;
+    VkDeviceMemory m_vertexMemory;
+    VkDeviceMemory m_vertexStagingMemory;
+
+    uint32_t m_numIndices;
+
+    VkBuffer m_indexBuffer;
+    VkBuffer m_indexStagingBuffer;
+    VkDeviceMemory m_indexMemory;
+    VkDeviceMemory m_indexStagingMemory;
+
+    VkDevice m_renderDevice;
+    uint32_t m_bufferMemoryIndex;
+
+    VulkanVertexBufferMode m_mode;
+
+    uint32_t m_vertexBufferSize = 0;
+    uint32_t m_indexBufferSize = 0;
+    bool m_vertexBufferModified = true;
+    bool m_indexBufferModified = true;
+
+    static const uint32_t maxBufferSize = 1024 * 1024;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/imstkRenderer.cpp b/Source/Rendering/imstkRenderer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..93a5ae1d7abf16ff9b3a8e1c09349a017812f051
--- /dev/null
+++ b/Source/Rendering/imstkRenderer.cpp
@@ -0,0 +1,43 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkRenderer.h"
+
+namespace imstk
+{
+std::shared_ptr<Renderer>
+Renderer::getRenderer()
+{
+    return nullptr;
+}
+
+void
+Renderer::setMode(Renderer::Mode mode)
+{
+    m_currentMode = mode;
+}
+
+const Renderer::Mode&
+Renderer::getMode()
+{
+    return m_currentMode;
+}
+}
\ No newline at end of file
diff --git a/Source/Rendering/imstkRenderer.h b/Source/Rendering/imstkRenderer.h
new file mode 100644
index 0000000000000000000000000000000000000000..f0fec4d2f5205b6d45eb849e21bca7af81b60ed0
--- /dev/null
+++ b/Source/Rendering/imstkRenderer.h
@@ -0,0 +1,70 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkRenderer_h
+#define imstkRenderer_h
+
+#include "imstkTextureManager.h"
+#include "imstkMath.h"
+
+#include <memory>
+
+namespace imstk
+{
+class Renderer
+{
+public:
+    ///
+    /// \brief Enumerations for the render mode
+    ///
+    enum Mode
+    {
+        EMPTY,
+        DEBUG,
+        SIMULATION
+    };
+
+    ///
+    /// \brief Get renderer
+    ///
+    virtual std::shared_ptr<Renderer> getRenderer();
+
+    ///
+    /// \brief Set rendering mode
+    ///
+    virtual void setMode(Renderer::Mode mode);
+
+    ///
+    /// \brief Get rendering mode
+    ///
+    virtual const Renderer::Mode& getMode();
+
+    ///
+    /// \brief Update background colors
+    ///
+    virtual void updateBackground(const Vec3d color1, const Vec3d color2 = Vec3d::Zero(), const bool gradientBackground = false){};
+
+protected:
+    Renderer::Mode m_currentMode = Renderer::Mode::EMPTY;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Rendering/RenderDelegate/vtkCapsuleSource.cpp b/Source/Rendering/vtkCapsuleSource.cpp
similarity index 100%
rename from Source/Rendering/RenderDelegate/vtkCapsuleSource.cpp
rename to Source/Rendering/vtkCapsuleSource.cpp
diff --git a/Source/Rendering/RenderDelegate/vtkCapsuleSource.h b/Source/Rendering/vtkCapsuleSource.h
similarity index 100%
rename from Source/Rendering/RenderDelegate/vtkCapsuleSource.h
rename to Source/Rendering/vtkCapsuleSource.h
diff --git a/Source/SimulationManager/CMakeLists.txt b/Source/SimulationManager/CMakeLists.txt
index 12a1943c05a5444a7500c1dc9b467ba05dddeefb..9ed3ab9850fdb61705512fa3ab789c359663e200 100644
--- a/Source/SimulationManager/CMakeLists.txt
+++ b/Source/SimulationManager/CMakeLists.txt
@@ -1,8 +1,57 @@
 #-----------------------------------------------------------------------------
 # Create target
 #-----------------------------------------------------------------------------
+
+set(VTK_H_FILES
+    VTKRenderer/imstkVTKInteractorStyle.h
+    VTKRenderer/imstkVTKScreenCaptureUtility.h
+    VTKRenderer/imstkVTKViewer.h)
+
+set(VTK_CPP_FILES
+    VTKRenderer/imstkVTKInteractorStyle.cpp
+    VTKRenderer/imstkVTKScreenCaptureUtility.cpp
+    VTKRenderer/imstkVTKViewer.cpp)
+
+set(VULKAN_H_FILES
+    VulkanRenderer/imstkVulkanInteractorStyle.h
+    VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.h
+    VulkanRenderer/imstkVulkanScreenCaptureUtility.h
+    VulkanRenderer/imstkVulkanViewer.h)
+
+set(VULKAN_CPP_FILES
+    VulkanRenderer/imstkVulkanInteractorStyle.cpp
+    VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.cpp
+    VulkanRenderer/imstkVulkanScreenCaptureUtility.cpp
+    VulkanRenderer/imstkVulkanViewer.cpp)
+
+if( NOT iMSTK_USE_Vulkan )
+  set(SIMULATIONMANAGER_H_FILES ${VTK_H_FILES})
+  set(SIMULATIONMANAGER_CPP_FILES ${VTK_CPP_FILES})
+  set(SIMULATIONMANAGER_SUBDIR
+    VTKRenderer)
+else()
+  set(SIMULATIONMANAGER_H_FILES ${VULKAN_H_FILES})
+  set(SIMULATIONMANAGER_CPP_FILES ${VULKAN_CPP_FILES})
+  set(SIMULATIONMANAGER_SUBDIR
+    VulkanRenderer)
+endif()
+
 include(imstkAddLibrary)
 imstk_add_library( SimulationManager
+  H_FILES
+    imstkSceneManager.h
+    imstkSimulationManager.h
+    imstkViewer.h
+    imstkScreenCaptureUtility.h
+    ${SIMULATIONMANAGER_H_FILES}
+  CPP_FILES
+    imstkSceneManager.cpp
+    imstkSimulationManager.cpp
+    imstkViewer.cpp
+    imstkScreenCaptureUtility.cpp
+    ${SIMULATIONMANAGER_CPP_FILES}
+  SUBDIR_LIST
+    ${SIMULATIONMANAGER_SUBDIR}
   DEPENDS
     Rendering
     Devices
diff --git a/Source/SimulationManager/imstkVTKInteractorStyle.cpp b/Source/SimulationManager/VTKRenderer/imstkVTKInteractorStyle.cpp
similarity index 87%
rename from Source/SimulationManager/imstkVTKInteractorStyle.cpp
rename to Source/SimulationManager/VTKRenderer/imstkVTKInteractorStyle.cpp
index 80b055beef41edc393d426ada28145e199fef472..4d66696f1fb88061579af89b42167b856ea59527 100644
--- a/Source/SimulationManager/imstkVTKInteractorStyle.cpp
+++ b/Source/SimulationManager/VTKRenderer/imstkVTKInteractorStyle.cpp
@@ -71,11 +71,13 @@ VTKInteractorStyle::SetCurrentRenderer(vtkRenderer* ren)
 void
 VTKInteractorStyle::OnTimer()
 {
+    auto renderer = std::static_pointer_cast<VTKRenderer>(m_simManager->getViewer()->getActiveRenderer());
+
     // Update Camera
-    m_simManager->getViewer()->getActiveRenderer()->updateSceneCamera(m_simManager->getActiveScene()->getCamera());
+    renderer->updateSceneCamera(m_simManager->getActiveScene()->getCamera());
 
     // Update render delegates
-    m_simManager->getViewer()->getActiveRenderer()->updateRenderDelegates();
+    renderer->updateRenderDelegates();
 
     // Reset camera clipping range
     this->CurrentRenderer->ResetCameraClippingRange();
@@ -160,13 +162,13 @@ VTKInteractorStyle::OnChar()
     }
     else if (key == 'd' || key == 'D') // switch rendering mode
     {
-        if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::SIMULATION)
+        if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::SIMULATION)
         {
-            m_simManager->getViewer()->setRenderingMode(VTKRenderer::Mode::SIMULATION);
+            m_simManager->getViewer()->setRenderingMode(Renderer::Mode::SIMULATION);
         }
         else
         {
-            m_simManager->getViewer()->setRenderingMode(VTKRenderer::Mode::DEBUG);
+            m_simManager->getViewer()->setRenderingMode(Renderer::Mode::DEBUG);
         }
     }
     else if (key == '\u001B') // quit viewer
@@ -197,7 +199,7 @@ VTKInteractorStyle::OnMouseMove()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -218,7 +220,7 @@ VTKInteractorStyle::OnLeftButtonDown()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -239,7 +241,7 @@ VTKInteractorStyle::OnLeftButtonUp()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -260,7 +262,7 @@ VTKInteractorStyle::OnMiddleButtonDown()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -281,7 +283,7 @@ VTKInteractorStyle::OnMiddleButtonUp()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -302,7 +304,7 @@ VTKInteractorStyle::OnRightButtonDown()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -323,7 +325,7 @@ VTKInteractorStyle::OnRightButtonUp()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -344,7 +346,7 @@ VTKInteractorStyle::OnMouseWheelForward()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
@@ -365,7 +367,7 @@ VTKInteractorStyle::OnMouseWheelBackward()
     }
 
     // Default behavior : ignore mouse if simulation active
-    if (m_simManager->getViewer()->getRenderingMode() != VTKRenderer::Mode::DEBUG)
+    if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::DEBUG)
     {
         return;
     }
diff --git a/Source/SimulationManager/imstkVTKInteractorStyle.h b/Source/SimulationManager/VTKRenderer/imstkVTKInteractorStyle.h
similarity index 100%
rename from Source/SimulationManager/imstkVTKInteractorStyle.h
rename to Source/SimulationManager/VTKRenderer/imstkVTKInteractorStyle.h
diff --git a/Source/SimulationManager/imstkVTKScreenCaptureUtility.cpp b/Source/SimulationManager/VTKRenderer/imstkVTKScreenCaptureUtility.cpp
similarity index 98%
rename from Source/SimulationManager/imstkVTKScreenCaptureUtility.cpp
rename to Source/SimulationManager/VTKRenderer/imstkVTKScreenCaptureUtility.cpp
index 278980732ce1031652223690d15d6ae6965198f0..b0658e016793a199ffe9f97edbf6924cae114282 100644
--- a/Source/SimulationManager/imstkVTKScreenCaptureUtility.cpp
+++ b/Source/SimulationManager/VTKRenderer/imstkVTKScreenCaptureUtility.cpp
@@ -26,8 +26,8 @@
 namespace imstk
 {
 VTKScreenCaptureUtility::VTKScreenCaptureUtility(vtkRenderWindow* const rw, const std::string prefix /*= "Screenshot-"*/)
-    : m_screenShotNumber(0)
 {
+    m_screenShotNumber = 0;
     m_screenShotPrefix = prefix;
     if (rw != nullptr)
     {
@@ -35,7 +35,6 @@ VTKScreenCaptureUtility::VTKScreenCaptureUtility(vtkRenderWindow* const rw, cons
     }
 }
 
-
 void
 VTKScreenCaptureUtility::saveScreenShot()
 {
@@ -70,7 +69,6 @@ VTKScreenCaptureUtility::saveScreenShot()
     m_screenShotNumber++;
 }
 
-
 unsigned int
 VTKScreenCaptureUtility::getScreenShotNumber() const
 {
diff --git a/Source/SimulationManager/imstkVTKScreenCaptureUtility.h b/Source/SimulationManager/VTKRenderer/imstkVTKScreenCaptureUtility.h
similarity index 74%
rename from Source/SimulationManager/imstkVTKScreenCaptureUtility.h
rename to Source/SimulationManager/VTKRenderer/imstkVTKScreenCaptureUtility.h
index 189d412397b265b5e7561b94c9f9e051b96301d1..f2e797bb2f48de6101952c80cc4f855f4391b650 100644
--- a/Source/SimulationManager/imstkVTKScreenCaptureUtility.h
+++ b/Source/SimulationManager/VTKRenderer/imstkVTKScreenCaptureUtility.h
@@ -17,10 +17,12 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 
-=========================================================================*/
+   =========================================================================*/
 
-#ifndef imstkScreenCaptureUtility_h
-#define imstkScreenCaptureUtility_h
+#ifndef imstkVTKScreenCaptureUtility_h
+#define imstkVTKScreenCaptureUtility_h
+
+#include "imstkScreenCaptureUtility.h"
 
 #include "vtkNew.h"
 #include "vtkRenderWindow.h"
@@ -36,7 +38,7 @@ namespace imstk
 ///
 /// \brief Utility class to manage screen capture through VTK
 ///
-class VTKScreenCaptureUtility
+class VTKScreenCaptureUtility : public ScreenCaptureUtility
 {
 public:
     ///
@@ -52,27 +54,18 @@ public:
     ///
     /// \brief Saves the screenshot as a png file
     ///
-    void saveScreenShot();
+    virtual void saveScreenShot();
 
-    ///
-    /// \brief Returns the number of the screenshot
-    ///
     unsigned int getScreenShotNumber() const;
 
-    ///
-    /// \brief set/reset the prefix amd the count numbers
     void setScreenShotPrefix(const std::string newPrefix);
 
-    ///
-    /// \brief reset the screenshot number indicator
     void resetScreenShotNumber();
 
 protected:
     vtkNew<vtkWindowToImageFilter> m_windowToImageFilter;
     vtkNew<vtkPNGWriter> m_pngWriter; //> using vtk's png writer to save the screenshots
     vtkRenderWindow* m_renderWindow; //> render window whose screen shot will be taken
-    unsigned int m_screenShotNumber; //> screen shot number is added to the file prefix, and incremented everytime a screen shot is taken
-    std::string m_screenShotPrefix; //> the prefix for the screenshots to be saved
 };
 } // imstk
 
diff --git a/Source/SimulationManager/imstkVTKViewer.cpp b/Source/SimulationManager/VTKRenderer/imstkVTKViewer.cpp
similarity index 82%
rename from Source/SimulationManager/imstkVTKViewer.cpp
rename to Source/SimulationManager/VTKRenderer/imstkVTKViewer.cpp
index 281fe38c85288d5a4f56826d58f0e421c8eb6e5d..58ac64f9d42684a2afc33ac734a004d1b5990ca9 100644
--- a/Source/SimulationManager/imstkVTKViewer.cpp
+++ b/Source/SimulationManager/VTKRenderer/imstkVTKViewer.cpp
@@ -21,33 +21,25 @@
 
 #include "imstkVTKViewer.h"
 
-#include "g3log/g3log.hpp"
-
 #include "imstkVTKRenderDelegate.h"
 
 namespace imstk
 {
-std::shared_ptr<Scene>
-VTKViewer::getActiveScene() const
-{
-    return m_activeScene;
-}
-
 void
-VTKViewer::setActiveScene(std::shared_ptr<Scene> scene)
+VTKViewer::setActiveScene(std::shared_ptr<Scene>scene)
 {
     // If already current scene
-    if( scene == m_activeScene )
+    if (scene == m_activeScene)
     {
         LOG(WARNING) << scene->getName() << " already is the viewer current scene.";
         return;
     }
 
     // If the current scene has a renderer, remove it
-    if( m_activeScene )
+    if (m_activeScene)
     {
-        auto vtkRenderer = this->getActiveRenderer()->getVtkRenderer();
-        if(m_vtkRenderWindow->HasRenderer(vtkRenderer))
+        auto vtkRenderer = std::dynamic_pointer_cast<VTKRenderer>(this->getActiveRenderer())->getVtkRenderer();
+        if (m_vtkRenderWindow->HasRenderer(vtkRenderer))
         {
             m_vtkRenderWindow->RemoveRenderer(vtkRenderer);
         }
@@ -62,26 +54,23 @@ VTKViewer::setActiveScene(std::shared_ptr<Scene> scene)
         m_rendererMap[m_activeScene] = std::make_shared<VTKRenderer>(m_activeScene);
     }
 
+    // Cast to VTK renderer
+    auto vtkRenderer = std::dynamic_pointer_cast<VTKRenderer>(this->getActiveRenderer())->getVtkRenderer();
+
     // Set renderer to renderWindow
-    m_vtkRenderWindow->AddRenderer(this->getActiveRenderer()->getVtkRenderer());
+    m_vtkRenderWindow->AddRenderer(vtkRenderer);
 
     // Set renderer to interactorStyle
-    m_interactorStyle->SetCurrentRenderer(this->getActiveRenderer()->getVtkRenderer());
+    m_interactorStyle->SetCurrentRenderer(vtkRenderer);
 
     // Set name to renderWindow
     m_vtkRenderWindow->SetWindowName(m_activeScene->getName().data());
 }
 
-std::shared_ptr<VTKRenderer>
-VTKViewer::getActiveRenderer() const
-{
-    return m_rendererMap.at(m_activeScene);
-}
-
 void
-VTKViewer::setRenderingMode(const VTKRenderer::Mode mode)
+VTKViewer::setRenderingMode(Renderer::Mode mode)
 {
-    if( !m_activeScene )
+    if (!m_activeScene)
     {
         LOG(WARNING) << "Missing scene, can not set rendering mode.\n"
                      << "Use Viewer::setCurrentScene to setup scene.";
@@ -90,7 +79,7 @@ VTKViewer::setRenderingMode(const VTKRenderer::Mode mode)
 
     // Setup renderer
     this->getActiveRenderer()->setMode(mode);
-    if( !m_running )
+    if (!m_running)
     {
         return;
     }
@@ -99,8 +88,9 @@ VTKViewer::setRenderingMode(const VTKRenderer::Mode mode)
     m_vtkRenderWindow->Render();
 
     // Setup render window
-    if (mode == VTKRenderer::Mode::SIMULATION)
+    if (mode == Renderer::Mode::SIMULATION)
     {
+        m_interactorStyle->HighlightProp(nullptr);
         m_vtkRenderWindow->HideCursor();
         //m_vtkRenderWindow->BordersOff();
         //m_vtkRenderWindow->FullScreenOn(1);
@@ -113,7 +103,7 @@ VTKViewer::setRenderingMode(const VTKRenderer::Mode mode)
     }
 }
 
-const VTKRenderer::Mode&
+const Renderer::Mode
 VTKViewer::getRenderingMode()
 {
     return this->getActiveRenderer()->getMode();
@@ -123,11 +113,10 @@ void
 VTKViewer::startRenderingLoop()
 {
     m_running = true;
-    auto interactor = m_vtkRenderWindow->GetInteractor();
-    interactor->Initialize();
-    interactor->CreateOneShotTimer(0);
-    interactor->Start();
-    interactor->DestroyTimer();
+    m_vtkRenderWindow->GetInteractor()->Initialize();
+    m_vtkRenderWindow->GetInteractor()->CreateOneShotTimer(0);
+    m_vtkRenderWindow->GetInteractor()->Start();
+    m_vtkRenderWindow->GetInteractor()->DestroyTimer();
     m_running = false;
 }
 
@@ -147,12 +136,6 @@ VTKViewer::getVtkRenderWindow() const
     return m_vtkRenderWindow;
 }
 
-bool
-VTKViewer::isRendering() const
-{
-    return m_running;
-}
-
 double
 VTKViewer::getTargetFrameRate() const
 {
@@ -166,7 +149,7 @@ VTKViewer::getTargetFrameRate() const
 }
 
 void
-VTKViewer::setTargetFrameRate(const double fps)
+VTKViewer::setTargetFrameRate(const double& fps)
 {
     if(fps < 0)
     {
@@ -252,7 +235,7 @@ VTKViewer::setOnTimerFunction(VTKEventHandlerFunction func)
 std::shared_ptr<VTKScreenCaptureUtility>
 VTKViewer::getScreenCaptureUtility() const
 {
-    return m_screenCapturer;
+    return std::static_pointer_cast<VTKScreenCaptureUtility>(m_screenCapturer);
 }
 
 void
diff --git a/Source/SimulationManager/imstkVTKViewer.h b/Source/SimulationManager/VTKRenderer/imstkVTKViewer.h
similarity index 73%
rename from Source/SimulationManager/imstkVTKViewer.h
rename to Source/SimulationManager/VTKRenderer/imstkVTKViewer.h
index 76dbd4fb7f571b354862ccd3186ea6594fd8e2b8..79f5958a0671ad0d93483e82d754c9bb76afb0be 100644
--- a/Source/SimulationManager/imstkVTKViewer.h
+++ b/Source/SimulationManager/VTKRenderer/imstkVTKViewer.h
@@ -25,16 +25,19 @@
 #include <memory>
 #include <unordered_map>
 
+#include "g3log/g3log.hpp"
+
 #include "imstkScene.h"
 #include "imstkVTKRenderer.h"
 #include "imstkVTKInteractorStyle.h"
 #include "imstkVTKScreenCaptureUtility.h"
-
+#include "imstkViewer.h"
+#include "imstkVTKRenderDelegate.h"
 #include "vtkSmartPointer.h"
 #include "vtkRenderWindow.h"
 #include "vtkRenderWindowInteractor.h"
 
-// Screenshot utility
+//Screenshot utility
 #include "imstkVTKScreenCaptureUtility.h"
 
 namespace imstk
@@ -46,7 +49,7 @@ class SimulationManager;
 ///
 /// \brief Viewer
 ///
-class VTKViewer
+class VTKViewer : public Viewer
 {
 public:
     ///
@@ -56,61 +59,45 @@ public:
     {
         m_interactorStyle->m_simManager = manager;
         m_vtkRenderWindow->SetInteractor(m_vtkRenderWindow->MakeRenderWindowInteractor());
-        m_vtkRenderWindow->GetInteractor()->SetInteractorStyle(m_interactorStyle);
-        m_vtkRenderWindow->SetSize(1000, 800);
+        m_vtkRenderWindow->GetInteractor()->SetInteractorStyle( m_interactorStyle );
+        m_vtkRenderWindow->SetSize(1000,800);
         m_screenCapturer = std::make_shared<VTKScreenCaptureUtility>(m_vtkRenderWindow);
     }
 
     ///
     /// \brief Destructor
     ///
-    ~VTKViewer() = default;
+    virtual void setRenderingMode(Renderer::Mode mode);
 
     ///
-    /// \brief Get scene currently being rendered
+    /// \brief Destructor
     ///
-    std::shared_ptr<Scene> getActiveScene() const;
+    ~VTKViewer() = default;
 
     ///
     /// \brief Set scene to be rendered
     ///
-    void setActiveScene(std::shared_ptr<Scene> scene);
-
-    ///
-    /// \brief Retrieve the renderer associated with the current scene
-    ///
-    std::shared_ptr<VTKRenderer> getActiveRenderer() const;
-
-    ///
-    /// \brief Setup the current renderer to render what's needed
-    /// based on the mode chosen
-    ///
-    void setRenderingMode(const VTKRenderer::Mode mode);
+    virtual void setActiveScene(std::shared_ptr<Scene>scene);
 
     ///
     /// \brief Get the current renderer mode
     ///
-    const VTKRenderer::Mode& getRenderingMode();
+    virtual const Renderer::Mode getRenderingMode();
 
     ///
     /// \brief Start rendering
     ///
-    void startRenderingLoop();
+    virtual void startRenderingLoop();
 
     ///
     /// \brief Terminate rendering
     ///
-    void endRenderingLoop();
+    virtual void endRenderingLoop();
 
     ///
     /// \brief Get pointer to the vtkRenderWindow rendering
     ///
-    vtkSmartPointer<vtkRenderWindow> getVtkRenderWindow() const;
-
-    ///
-    /// \brief Returns true if the Viewer is rendering
-    ///
-    bool isRendering() const;
+    vtkSmartPointer<vtkRenderWindow>getVtkRenderWindow() const;
 
     ///
     /// \brief Get the target FPS for rendering
@@ -120,7 +107,7 @@ public:
     ///
     /// \brief Set the target FPS for rendering
     ///
-    void setTargetFrameRate(const double fps);
+    void setTargetFrameRate(const double& fps);
 
     ///
     /// \brief Set custom event handlers on interactor style
@@ -151,16 +138,12 @@ public:
     /// \brief Set the coloring of the screen background
     /// If 'gradientBackground' is false or not supplied color1 will fill the entire background
     ///
-    void setBackgroundColors(const Vec3d color1, const Vec3d color2 = Vec3d::Zero(), const bool gradientBackground = false);
+    virtual void setBackgroundColors(const Vec3d color1, const Vec3d color2 = Vec3d::Zero(), const bool gradientBackground = false);
 
 protected:
 
     vtkSmartPointer<vtkRenderWindow> m_vtkRenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
     vtkSmartPointer<VTKInteractorStyle> m_interactorStyle = vtkSmartPointer<VTKInteractorStyle>::New();
-    std::shared_ptr<Scene> m_activeScene;
-    std::unordered_map<std::shared_ptr<Scene>, std::shared_ptr<VTKRenderer>> m_rendererMap;
-    std::shared_ptr<VTKScreenCaptureUtility> m_screenCapturer; ///> Screen shot utility
-    bool m_running = false;
 };
 } // imstk
 
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyle.cpp b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyle.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cd499e53033afee2a48b248b3841d58c1d7f95b6
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyle.cpp
@@ -0,0 +1,248 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanInteractorStyle.h"
+
+#include "imstkVulkanViewer.h"
+#include "imstkSimulationManager.h"
+
+namespace imstk
+{
+VulkanInteractorStyle::VulkanInteractorStyle()
+{
+}
+
+void
+VulkanInteractorStyle::OnTimer()
+{
+}
+
+void
+VulkanInteractorStyle::OnChar(int keyID, int type)
+{
+    char key = (char)keyID;
+
+    if (type != GLFW_PRESS)
+    {
+        return;
+    }
+
+    // Call custom function if exists, and return
+    // if it returned `override=true`
+    if(m_onCharFunctionMap.count(key) &&
+       m_onCharFunctionMap.at(key) &&
+       m_onCharFunctionMap.at(key)(this))
+    {
+        return;
+    }
+
+    SimulationStatus status = m_simManager->getStatus();
+
+    if (key == ' ')
+    {
+        // pause simulation
+        if (status == SimulationStatus::RUNNING)
+        {
+            m_simManager->pauseSimulation();
+        }
+        // play simulation
+        else if (status == SimulationStatus::PAUSED)
+        {
+            m_simManager->runSimulation();
+        }
+        // Launch simulation if inactive
+        if (status == SimulationStatus::INACTIVE)
+        {
+            m_simManager->launchSimulation();
+        }
+    }
+    else if (status != SimulationStatus::INACTIVE &&
+             (key == 'q' || key == 'Q' || key == 'e' || key == 'E')) // end Simulation
+    {
+        m_simManager->endSimulation();
+    }
+    else if (key == 'd' || key == 'D') // switch rendering mode
+    {
+        if (m_simManager->getViewer()->getRenderingMode() != Renderer::Mode::SIMULATION)
+        {
+            m_simManager->getViewer()->setRenderingMode(Renderer::Mode::SIMULATION);
+        }
+        else
+        {
+            m_simManager->getViewer()->setRenderingMode(Renderer::Mode::DEBUG);
+        }
+    }
+    else if (key == '\u001B') // quit viewer
+    {
+        m_simManager->getViewer()->endRenderingLoop();
+    }
+    else if (key == 'p' || key == 'P') // switch framerate display
+    {
+    }
+    else if (key == 'r' || key == 'R')
+    {
+        m_simManager->resetSimulation();
+    }
+}
+
+void
+VulkanInteractorStyle::OnMouseMove(double x, double y)
+{
+    if (m_onMouseMoveFunction && m_onMouseMoveFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnMouseMove(x, y);
+}
+
+void
+VulkanInteractorStyle::OnLeftButtonDown()
+{
+    if (m_onLeftButtonDownFunction && m_onLeftButtonDownFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnLeftButtonDown();
+}
+
+void
+VulkanInteractorStyle::OnLeftButtonUp()
+{
+    if (m_onLeftButtonUpFunction && m_onLeftButtonUpFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnLeftButtonUp();
+}
+
+void
+VulkanInteractorStyle::OnMiddleButtonDown()
+{
+    if (m_onMiddleButtonDownFunction && m_onMiddleButtonDownFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnMiddleButtonDown();
+}
+
+void VulkanInteractorStyle::OnMiddleButtonUp()
+{
+    if (m_onMiddleButtonUpFunction && m_onMiddleButtonUpFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnMiddleButtonUp();
+}
+
+void
+VulkanInteractorStyle::OnRightButtonDown()
+{
+    if (m_onRightButtonDownFunction && m_onRightButtonDownFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnRightButtonDown();
+}
+
+void
+VulkanInteractorStyle::OnRightButtonUp()
+{
+    if (m_onRightButtonUpFunction && m_onRightButtonUpFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnRightButtonUp();
+}
+
+void
+VulkanInteractorStyle::OnMouseWheelForward(double y)
+{
+    if (m_onMouseWheelForwardFunction && m_onMouseWheelForwardFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnMouseWheelForward(y);
+}
+
+void
+VulkanInteractorStyle::OnMouseWheelBackward(double y)
+{
+    if (m_onMouseWheelBackwardFunction && m_onMouseWheelBackwardFunction(this))
+    {
+        return;
+    }
+
+    if (m_simManager->getStatus() != SimulationStatus::INACTIVE)
+    {
+        return;
+    }
+
+    VulkanBaseInteractorStyle::OnMouseWheelBackward(y);
+}
+}
\ No newline at end of file
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyle.h b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyle.h
new file mode 100644
index 0000000000000000000000000000000000000000..be765a5fd525247ee60f4e6fd492ffef28915ea9
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyle.h
@@ -0,0 +1,76 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanInteractorStyle_h
+#define imstkVulkanInteractorStyle_h
+
+#include "GLFW/glfw3.h"
+
+#include "imstkVulkanInteractorStyleTrackballCamera.h"
+
+#include <iostream>
+#include <unordered_map>
+#include <functional>
+
+namespace imstk
+{
+class VulkanInteractorStyle;
+class VulkanViewer;
+
+using VulkanBaseInteractorStyle = VulkanInteractorStyleTrackballCamera;
+
+using VulkanEventHandlerFunction = std::function< bool(VulkanInteractorStyle * iStyle) >;
+
+class VulkanInteractorStyle : public VulkanBaseInteractorStyle
+{
+public:
+    VulkanInteractorStyle();
+    ~VulkanInteractorStyle(){};
+
+    virtual void OnTimer();
+    virtual void OnChar(int keyID, int type);
+    virtual void OnMouseMove(double x, double y);
+    virtual void OnLeftButtonDown();
+    virtual void OnLeftButtonUp();
+    virtual void OnMiddleButtonDown();
+    virtual void OnMiddleButtonUp();
+    virtual void OnRightButtonDown();
+    virtual void OnRightButtonUp();
+    virtual void OnMouseWheelForward(double y);
+    virtual void OnMouseWheelBackward(double y);
+
+private:
+    friend class VulkanViewer;
+
+    std::unordered_map<char, VulkanEventHandlerFunction> m_onCharFunctionMap;
+    VulkanEventHandlerFunction m_onMouseMoveFunction;
+    VulkanEventHandlerFunction m_onLeftButtonDownFunction;
+    VulkanEventHandlerFunction m_onLeftButtonUpFunction;
+    VulkanEventHandlerFunction m_onMiddleButtonDownFunction;
+    VulkanEventHandlerFunction m_onMiddleButtonUpFunction;
+    VulkanEventHandlerFunction m_onRightButtonDownFunction;
+    VulkanEventHandlerFunction m_onRightButtonUpFunction;
+    VulkanEventHandlerFunction m_onMouseWheelForwardFunction;
+    VulkanEventHandlerFunction m_onMouseWheelBackwardFunction;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.cpp b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..62e3988ea8d08d56ccf759ae295a5012c9e5254e
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.cpp
@@ -0,0 +1,259 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanInteractorStyleTrackballCamera.h"
+
+#include "imstkVulkanViewer.h"
+#include "imstkSimulationManager.h"
+
+namespace imstk
+{
+VulkanInteractorStyleTrackballCamera::VulkanInteractorStyleTrackballCamera()
+{
+}
+
+void
+VulkanInteractorStyleTrackballCamera::setWindow(GLFWwindow * window, VulkanViewer * viewer)
+{
+    m_window = window;
+    m_viewer = viewer;
+
+    glfwSetWindowUserPointer(window, (void *)this);
+
+    glfwSetKeyCallback(m_window, VulkanInteractorStyleTrackballCamera::OnCharInterface);
+    glfwSetMouseButtonCallback(m_window, VulkanInteractorStyleTrackballCamera::OnMouseButtonInterface);
+    glfwSetCursorPosCallback(m_window, VulkanInteractorStyleTrackballCamera::OnMouseMoveInterface);
+    glfwSetScrollCallback(m_window, VulkanInteractorStyleTrackballCamera::OnMouseWheelInterface);
+    glfwSetWindowSizeCallback(m_window, VulkanInteractorStyleTrackballCamera::OnWindowResizeInterface);
+    glfwSetFramebufferSizeCallback(m_window, VulkanInteractorStyleTrackballCamera::OnFramebuffersResizeInterface);
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnTimer()
+{
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnCharInterface(GLFWwindow * window, int keyID, int code, int type, int extra)
+{
+    VulkanInteractorStyleTrackballCamera * style = (VulkanInteractorStyleTrackballCamera *)glfwGetWindowUserPointer(window);
+    style->OnChar(keyID, type);
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnMouseButtonInterface(GLFWwindow * window, int buttonID, int type, int extra)
+{
+    VulkanInteractorStyleTrackballCamera * style = (VulkanInteractorStyleTrackballCamera *)glfwGetWindowUserPointer(window);
+
+    switch (buttonID)
+    {
+    case GLFW_MOUSE_BUTTON_LEFT:
+        if (type == GLFW_PRESS)
+        {
+            style->OnLeftButtonDown();
+        }
+        else if (type == GLFW_RELEASE)
+        {
+            style->OnLeftButtonUp();
+        }
+        break;
+    case GLFW_MOUSE_BUTTON_RIGHT:
+        if (type == GLFW_PRESS)
+        {
+            style->OnRightButtonDown();
+        }
+        else if (type == GLFW_RELEASE)
+        {
+            style->OnRightButtonUp();
+        }
+        break;
+    case GLFW_MOUSE_BUTTON_MIDDLE:
+        if (type == GLFW_PRESS)
+        {
+            style->OnMiddleButtonDown();
+        }
+        else if (type == GLFW_RELEASE)
+        {
+            style->OnMiddleButtonUp();
+        }
+        break;
+    }
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnMouseMoveInterface(GLFWwindow * window, double x, double y)
+{
+    VulkanInteractorStyleTrackballCamera * style = (VulkanInteractorStyleTrackballCamera *)glfwGetWindowUserPointer(window);
+    style->OnMouseMove(x, y);
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnMouseWheelInterface(GLFWwindow * window, double x, double y)
+{
+    VulkanInteractorStyleTrackballCamera * style = (VulkanInteractorStyleTrackballCamera *)glfwGetWindowUserPointer(window);
+    if (y < 0)
+    {
+        style->OnMouseWheelBackward(y);
+    }
+    else
+    {
+        style->OnMouseWheelForward(y);
+    }
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnChar(int keyID, int type)
+{
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnMouseMove(double x, double y)
+{
+    m_mouseX = (x - m_viewer->m_width / 2) / m_viewer->m_width;
+    m_mouseY = (y - m_viewer->m_height / 2) / m_viewer->m_height;
+    auto camera = m_simManager->getActiveScene()->getCamera();
+    auto offset = camera->getPosition() - camera->getFocalPoint();
+    auto dx = m_mouseX - m_lastMouseX;
+    auto dy = m_mouseY - m_lastMouseY;
+
+    if (m_state & VulkanInteractorStyleTrackballCamera::LEFT_MOUSE_DOWN)
+    {
+        double strength = distance(m_mouseX, m_mouseY);
+
+        auto convertedOffset = glm::vec3(offset.x(), offset.y(), offset.z());
+        glm::vec3 rotationAxis(0,1,0);
+        glm::mat4 rotation;
+        rotation = glm::rotate(rotation, -(float)dx, rotationAxis);
+        auto new_position = rotation * glm::vec4(offset.x(), offset.y(), offset.z(), 1);
+        imstk::Vec3d position(new_position[0], new_position[1], new_position[2]);
+        camera->setPosition(camera->getFocalPoint() + position);
+    }
+    else if (m_state & VulkanInteractorStyleTrackballCamera::MIDDLE_MOUSE_DOWN)
+    {
+        auto camera = m_simManager->getActiveScene()->getCamera();
+        auto eye = glm::tvec3<float>(camera->getPosition().x(), camera->getPosition().y(), camera->getPosition().z());
+        auto center = glm::tvec3<float>(camera->getFocalPoint().x(), camera->getFocalPoint().y(), camera->getFocalPoint().z());
+        auto up = glm::tvec3<float>(camera->getViewUp().x(), camera->getViewUp().y(), camera->getViewUp().z());
+
+        glm::mat4 cameraTranslationMatrix(1);
+        cameraTranslationMatrix = glm::translate(cameraTranslationMatrix, glm::tvec3<float>(-dx * 10, dy * 10, 0));
+        auto cameraMatrix = glm::inverse(glm::lookAt(eye, center, up));
+
+        auto new_position = cameraMatrix * cameraTranslationMatrix;
+        auto new_focal_point_offset = glm::mat3(cameraMatrix) * glm::vec3(cameraTranslationMatrix[3]);
+        imstk::Vec3d position(new_position[3][0], new_position[3][1], new_position[3][2]);
+        camera->setPosition(position);
+        auto new_focal_point = center + new_focal_point_offset;
+        camera->setFocalPoint(Vec3d(new_focal_point[0], new_focal_point[1], new_focal_point[2]));
+    }
+
+    m_lastMouseX = m_mouseX;
+    m_lastMouseY = m_mouseY;
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnLeftButtonDown()
+{
+    m_state |= VulkanInteractorStyleTrackballCamera::LEFT_MOUSE_DOWN;
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnLeftButtonUp()
+{
+    m_state &= ~VulkanInteractorStyleTrackballCamera::LEFT_MOUSE_DOWN;
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnMiddleButtonDown()
+{
+    m_state |= VulkanInteractorStyleTrackballCamera::MIDDLE_MOUSE_DOWN;
+}
+
+void VulkanInteractorStyleTrackballCamera::OnMiddleButtonUp()
+{
+    m_state &= ~VulkanInteractorStyleTrackballCamera::MIDDLE_MOUSE_DOWN;
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnRightButtonDown()
+{
+    m_state |= VulkanInteractorStyleTrackballCamera::RIGHT_MOUSE_DOWN;
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnRightButtonUp()
+{
+    m_state &= ~VulkanInteractorStyleTrackballCamera::RIGHT_MOUSE_DOWN;
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnMouseWheelForward(double y)
+{
+    auto camera = m_simManager->getActiveScene()->getCamera();
+
+    auto offset = camera->getPosition() - camera->getFocalPoint();
+
+    auto offsetx = -0.01 * offset.x() * y + camera->getPosition().x();
+    auto offsety = -0.01 * offset.y() * y + camera->getPosition().y();
+    auto offsetz = -0.01 * offset.z() * y + camera->getPosition().z();
+
+    camera->setPosition(Vec3d(offsetx, offsety, offsetz));
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnMouseWheelBackward(double y)
+{
+    auto camera = m_simManager->getActiveScene()->getCamera();
+
+    auto offset = camera->getPosition() - camera->getFocalPoint();
+
+    auto offsetx = -0.01 * offset.x() * y + camera->getPosition().x();
+    auto offsety = -0.01 * offset.y() * y + camera->getPosition().y();
+    auto offsetz = -0.01 * offset.z() * y + camera->getPosition().z();
+
+    camera->setPosition(Vec3d(offsetx, offsety, offsetz));
+}
+
+inline double
+VulkanInteractorStyleTrackballCamera::distance(double x, double y)
+{
+    return sqrt(x * x + y * y);
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnWindowResizeInterface(GLFWwindow * window, int width, int height)
+{
+    VulkanInteractorStyleTrackballCamera * style = (VulkanInteractorStyleTrackballCamera *)glfwGetWindowUserPointer(window);
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnFramebuffersResizeInterface(GLFWwindow * window, int width, int height)
+{
+    VulkanInteractorStyleTrackballCamera * style = (VulkanInteractorStyleTrackballCamera *)glfwGetWindowUserPointer(window);
+    style->OnWindowResize(width, height);
+}
+
+void
+VulkanInteractorStyleTrackballCamera::OnWindowResize(int width, int height)
+{
+    m_viewer->resizeWindow(width, height);
+}
+}
\ No newline at end of file
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.h b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.h
new file mode 100644
index 0000000000000000000000000000000000000000..3171fb7a1f9d6163daa701735a06b8f63fe68060
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanInteractorStyleTrackballCamera.h
@@ -0,0 +1,94 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanInteractorStyleTrackballCamera_h
+#define imstkVulkanInteractorStyleTrackballCamera_h
+
+#include "GLFW/glfw3.h"
+
+#include <iostream>
+#include <unordered_map>
+#include <functional>
+
+namespace imstk
+{
+class VulkanViewer;
+class SimulationManager;
+
+class VulkanInteractorStyleTrackballCamera
+{
+public:
+    VulkanInteractorStyleTrackballCamera();
+    ~VulkanInteractorStyleTrackballCamera(){};
+
+    void setWindow(GLFWwindow * window, VulkanViewer * viewer);
+
+    virtual void OnTimer();
+    virtual void OnChar(int keyID, int type);
+    virtual void OnMouseMove(double x, double y);
+    virtual void OnLeftButtonDown();
+    virtual void OnLeftButtonUp();
+    virtual void OnMiddleButtonDown();
+    virtual void OnMiddleButtonUp();
+    virtual void OnRightButtonDown();
+    virtual void OnRightButtonUp();
+    virtual void OnMouseWheelForward(double y);
+    virtual void OnMouseWheelBackward(double y);
+    void OnWindowResize(int width, int height);
+
+    // Dispatch function
+    static void OnCharInterface(GLFWwindow * window, int a, int b, int c, int d);
+    static void OnMouseButtonInterface(GLFWwindow * window, int a, int b, int c);
+    static void OnMouseMoveInterface(GLFWwindow * window, double x, double y);
+    static void OnMouseWheelInterface(GLFWwindow * window, double x, double y);
+
+    static void OnWindowResizeInterface(GLFWwindow * window, int width, int height);
+    static void OnFramebuffersResizeInterface(GLFWwindow * window, int width, int height);
+    static void OnFrame();
+
+protected:
+    friend class VulkanViewer;
+
+    GLFWwindow * m_window;
+    SimulationManager * m_simManager;
+    VulkanViewer * m_viewer;
+
+    double distance(double x, double y);
+
+    // States
+    enum
+    {
+        LEFT_MOUSE_DOWN = 0x1,
+        MIDDLE_MOUSE_DOWN = 0x2,
+        RIGHT_MOUSE_DOWN = 0x4
+    };
+
+    double m_lastMouseX = 0;
+    double m_lastMouseY = 0;
+
+    double m_mouseX = 0;
+    double m_mouseY = 0;
+
+    unsigned int m_state = 0;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanScreenCaptureUtility.cpp b/Source/SimulationManager/VulkanRenderer/imstkVulkanScreenCaptureUtility.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..971fb5e87fcc07b1610b5e46b6c283d3abbbe00d
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanScreenCaptureUtility.cpp
@@ -0,0 +1,41 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanScreenCaptureUtility.h"
+
+#include "g3log/g3log.hpp"
+
+namespace imstk
+{
+VulkanScreenCaptureUtility::VulkanScreenCaptureUtility(const std::string prefix /*= "Screenshot-"*/)
+{
+    m_screenShotNumber = 0;
+    m_screenShotPrefix = prefix;
+}
+
+void
+VulkanScreenCaptureUtility::saveScreenShot()
+{
+    LOG(WARNING) << "Screen capture not supported\n";
+
+    m_screenShotNumber++;
+}
+} // imstk
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanScreenCaptureUtility.h b/Source/SimulationManager/VulkanRenderer/imstkVulkanScreenCaptureUtility.h
new file mode 100644
index 0000000000000000000000000000000000000000..c500bb16e817bb57ca28f77359794c7c1d61f70f
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanScreenCaptureUtility.h
@@ -0,0 +1,58 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanScreenCaptureUtility_h
+#define imstkVulkanScreenCaptureUtility_h
+
+#include "imstkScreenCaptureUtility.h"
+
+#include <string>
+
+namespace imstk
+{
+///
+/// \class VulkanScreenCaptureUtility
+///
+/// \brief Utility class to manage screen capture through Vulkan
+///
+class VulkanScreenCaptureUtility : public ScreenCaptureUtility
+{
+public:
+    ///
+    /// \brief Constructor
+    ///
+    VulkanScreenCaptureUtility(const std::string prefix = "Screenshot-");
+
+    ///
+    /// \brief Destructor
+    ///
+    ~VulkanScreenCaptureUtility() = default;
+
+    ///
+    /// \brief Saves the screenshot as a png file
+    ///
+    virtual void saveScreenShot();
+
+protected:
+};
+} // imstk
+
+#endif // ifndef imstkScreenCaptureUtility_h
\ No newline at end of file
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanViewer.cpp b/Source/SimulationManager/VulkanRenderer/imstkVulkanViewer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a1f63731432932e4119022033323e1bfb5f60e08
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanViewer.cpp
@@ -0,0 +1,167 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkVulkanViewer.h"
+
+namespace imstk
+{
+VulkanViewer::VulkanViewer(SimulationManager * manager)
+{
+    m_simManager = manager;
+}
+
+void
+VulkanViewer::setActiveScene(std::shared_ptr<Scene>scene)
+{
+    m_renderer = std::make_shared<VulkanRenderer>(scene);
+}
+
+void
+VulkanViewer::startRenderingLoop()
+{
+    m_running = true;
+    this->setupWindow();
+    m_renderer->initialize();
+    this->createWindow();
+
+    this->setupSwapchain();
+    m_renderer->initializeFramebuffers(&m_swapchain);
+
+    while (!glfwWindowShouldClose(m_window))
+    {
+        m_renderer->renderFrame();
+        glfwPollEvents();
+        m_interactorStyle->OnTimer();
+    }
+
+    m_running = false;
+}
+
+void
+VulkanViewer::endRenderingLoop()
+{
+}
+
+void
+VulkanViewer::setupWindow()
+{
+    if (!glfwInit())
+    {
+        LOG(FATAL) << "GLFW failed to initialize";
+        return;
+    }
+
+    if (!glfwVulkanSupported())
+    {
+        LOG(FATAL) << "Vulkan is not supported";
+        return;
+    }
+
+    uint32_t tempCount;
+    const char ** tempExtensions = glfwGetRequiredInstanceExtensions(&tempCount);
+    for (int i = 0; i < (int)tempCount; i++)
+    {
+        m_renderer->m_extensions.push_back((char*)tempExtensions[i]);
+    }
+}
+
+void
+VulkanViewer::createWindow()
+{
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+    m_window = glfwCreateWindow(m_width, m_height, "iMSTK", nullptr, nullptr);
+    VkResult status = glfwCreateWindowSurface(*m_renderer->m_instance, m_window, nullptr, &m_surface);
+    std::cout << status << std::endl;
+
+    m_interactorStyle = std::make_shared<VulkanInteractorStyle>();
+
+    m_interactorStyle->setWindow(m_window, this);
+    m_interactorStyle->m_simManager = m_simManager;
+}
+
+void
+VulkanViewer::resizeWindow(int width, int height)
+{
+    m_width = width;
+    m_height = height;
+    vkDeviceWaitIdle(m_renderer->m_renderDevice);
+    vkDestroySwapchainKHR(m_renderer->m_renderDevice, m_swapchain, nullptr);
+    this->setupSwapchain();
+
+    m_renderer->resizeFramebuffers(&m_swapchain, width, height);
+}
+
+void
+VulkanViewer::setupSwapchain()
+{
+    // Build swapchain
+    VkExtent2D extent;
+    extent.height = m_height;
+    extent.width = m_width;
+
+    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_renderer->m_physicalDevices[0], m_surface, &m_physicalCapabilities);
+
+    vkGetPhysicalDeviceSurfaceFormatsKHR(m_renderer->m_physicalDevices[0], m_surface, &m_physicalFormatsCount, nullptr);
+    m_physicalFormats = new VkSurfaceFormatKHR[(int)m_physicalFormatsCount]();
+    vkGetPhysicalDeviceSurfaceFormatsKHR(m_renderer->m_physicalDevices[0], m_surface, &m_physicalFormatsCount, m_physicalFormats);
+
+    VkBool32 supported;
+    vkGetPhysicalDeviceSurfaceSupportKHR(m_renderer->m_physicalDevices[0], 0, m_surface, &supported);
+
+    bool linearColorSpaceSupported = false;
+
+    // Right now linear colorspace is a requirement
+    for (int i = 0; i < (int)m_physicalFormatsCount; i++)
+    {
+        if (m_physicalFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB)
+        {
+            linearColorSpaceSupported = true;
+        }
+    }
+
+    if (!linearColorSpaceSupported)
+    {
+        LOG(FATAL) << "Linear color space not supported";
+    }
+
+    VkSwapchainCreateInfoKHR swapchainInfo;
+    swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+    swapchainInfo.pNext = nullptr;
+    swapchainInfo.flags = 0;
+    swapchainInfo.surface = m_surface;
+    swapchainInfo.minImageCount = 3; // triple buffering
+    swapchainInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB;
+    swapchainInfo.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
+    swapchainInfo.imageExtent = extent;
+    swapchainInfo.imageArrayLayers = 1;
+    swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+    swapchainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    swapchainInfo.queueFamilyIndexCount = 0;
+    swapchainInfo.pQueueFamilyIndices = nullptr;
+    swapchainInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+    swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+    swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
+    swapchainInfo.clipped = VK_TRUE;
+    swapchainInfo.oldSwapchain = VK_NULL_HANDLE;
+
+    vkCreateSwapchainKHR(m_renderer->m_renderDevice, &swapchainInfo, nullptr, &m_swapchain);
+}
+}
\ No newline at end of file
diff --git a/Source/SimulationManager/VulkanRenderer/imstkVulkanViewer.h b/Source/SimulationManager/VulkanRenderer/imstkVulkanViewer.h
new file mode 100644
index 0000000000000000000000000000000000000000..da292c8fdd2cacd2cf100c98019ef3b02ccb8639
--- /dev/null
+++ b/Source/SimulationManager/VulkanRenderer/imstkVulkanViewer.h
@@ -0,0 +1,79 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkVulkanViewer_h
+#define imstkVulkanViewer_h
+
+#include "vulkan/vulkan.h"
+#include "GLFW/glfw3.h"
+#include "g3log/g3log.hpp"
+
+#include "imstkVulkanRenderer.h"
+#include "imstkViewer.h"
+#include "imstkTimer.h"
+#include "imstkVulkanInteractorStyle.h"
+
+namespace imstk
+{
+class VulkanInteractorStyle;
+
+class VulkanViewer : public Viewer {
+public:
+    VulkanViewer(SimulationManager * manager = nullptr);
+
+    virtual void setActiveScene(std::shared_ptr<Scene> scene);
+
+    virtual void startRenderingLoop();
+
+    virtual void endRenderingLoop();
+
+    ///
+    /// \brief Setups up the swapchain
+    ///
+    /// A swapchain is basically a queue of backbuffers
+    ///
+    void setupSwapchain();
+
+protected:
+    friend class VulkanInteractorStyle;
+    friend class VulkanInteractorStyleTrackballCamera;
+
+    void setupWindow();
+    void createWindow();
+    void resizeWindow(int width, int height);
+
+    unsigned int m_width = 1000;
+    unsigned int m_height = 800;
+
+    std::shared_ptr<VulkanRenderer> m_renderer;
+    VkSurfaceKHR m_surface;
+    GLFWwindow * m_window;
+    SimulationManager * m_simManager;
+    VkSwapchainKHR m_swapchain;
+
+    VkSurfaceCapabilitiesKHR m_physicalCapabilities;
+
+    uint32_t m_physicalFormatsCount;
+    VkSurfaceFormatKHR * m_physicalFormats;
+    std::shared_ptr<VulkanInteractorStyle> m_interactorStyle;
+};
+}
+#endif
\ No newline at end of file
diff --git a/Source/SimulationManager/imstkScreenCaptureUtility.cpp b/Source/SimulationManager/imstkScreenCaptureUtility.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..893f73222578d9b6da57cbf42be32e0be7a8e371
--- /dev/null
+++ b/Source/SimulationManager/imstkScreenCaptureUtility.cpp
@@ -0,0 +1,49 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+   =========================================================================*/
+
+#include "imstkScreenCaptureUtility.h"
+
+#include "g3log/g3log.hpp"
+
+namespace imstk
+{
+unsigned int
+ScreenCaptureUtility::getScreenShotNumber() const
+{
+    return m_screenShotNumber;
+}
+
+void
+ScreenCaptureUtility::setScreenShotPrefix(const std::string newPrefix)
+{
+    if (m_screenShotPrefix.compare(newPrefix) != 0)
+    {
+        m_screenShotPrefix = newPrefix;
+        m_screenShotNumber = 0;
+    }
+}
+
+void
+ScreenCaptureUtility::resetScreenShotNumber()
+{
+    m_screenShotNumber = 0;
+}
+} // imstk
diff --git a/Source/SimulationManager/imstkScreenCaptureUtility.h b/Source/SimulationManager/imstkScreenCaptureUtility.h
new file mode 100644
index 0000000000000000000000000000000000000000..219fa69488369d9b73bff8c41994aa1456e3c116
--- /dev/null
+++ b/Source/SimulationManager/imstkScreenCaptureUtility.h
@@ -0,0 +1,71 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkScreenCaptureUtility_h
+#define imstkScreenCaptureUtility_h
+
+#include <string>
+
+namespace imstk
+{
+///
+/// \class ScreenCaptureUtility
+///
+/// \brief Utility class to manage screen capture
+///
+class ScreenCaptureUtility
+{
+public:
+    ///
+    /// \brief Saves the screenshot as a png file
+    ///
+    virtual void saveScreenShot(){};
+
+    ///
+    /// \brief Returns the number of the screenshot
+    ///
+    unsigned int getScreenShotNumber() const;
+
+    ///
+    /// \brief set/reset the prefix amd the count numbers
+    void setScreenShotPrefix(const std::string newPrefix);
+
+    ///
+    /// \brief reset the screenshot number indicator
+    void resetScreenShotNumber();
+
+protected:
+    ///
+    /// \brief Constructor
+    ///
+    ScreenCaptureUtility(){};
+
+    ///
+    /// \brief Destructor
+    ///
+    ~ScreenCaptureUtility() = default;
+
+    unsigned int m_screenShotNumber; //> screen shot number is added to the file prefix, and incremented everytime a screen shot is taken
+    std::string m_screenShotPrefix; //> the prefix for the screenshots to be saved
+};
+} // imstk
+
+#endif // ifndef imstkScreenCaptureUtility_h
\ No newline at end of file
diff --git a/Source/SimulationManager/imstkSimulationManager.cpp b/Source/SimulationManager/imstkSimulationManager.cpp
index 49e38684a758a405abc7d562db2acbbe18190b94..b3622dbe139854e1bc5b552650e9b7f8953c21a1 100644
--- a/Source/SimulationManager/imstkSimulationManager.cpp
+++ b/Source/SimulationManager/imstkSimulationManager.cpp
@@ -27,8 +27,20 @@
 
 namespace imstk
 {
-const SimulationStatus&
-SimulationManager::getStatus() const
+SimulationManager::SimulationManager()
+{
+    // Init g3logger
+    m_logUtil->createLogger("simulation", "./");
+
+#ifdef iMSTK_USE_Vulkan
+    m_viewer = std::make_shared<VulkanViewer>(this);
+#else
+    m_viewer = std::make_shared<VTKViewer>(this);
+#endif
+}
+
+const
+SimulationStatus& SimulationManager::getStatus() const
 {
     return m_status;
 }
@@ -198,7 +210,7 @@ SimulationManager::removeModule(const std::string& moduleName)
     LOG(INFO) << "Module removed: " << moduleName;
 }
 
-std::shared_ptr<VTKViewer>
+std::shared_ptr<Viewer>
 SimulationManager::getViewer() const
 {
     return m_viewer;
@@ -244,14 +256,14 @@ SimulationManager::setActiveScene(const std::string& newSceneName,
     // render scene in debug, update current scene, and return
     if (m_status == SimulationStatus::INACTIVE)
     {
-        m_viewer->setRenderingMode(VTKRenderer::Mode::DEBUG);
+        m_viewer->setRenderingMode(Renderer::Mode::DEBUG);
         m_activeSceneName = newSceneName;
         return;
     }
 
     // If rendering and simulation active:
     // render scene in simulation mode, and update simulation
-    m_viewer->setRenderingMode(VTKRenderer::Mode::SIMULATION);
+    m_viewer->setRenderingMode(Renderer::Mode::SIMULATION);
 
     // Stop/Pause running scene
     auto oldSceneManager = m_sceneManagerMap.at(m_activeSceneName);
@@ -363,7 +375,7 @@ SimulationManager::startSimulation(const bool startSimulationPaused /*= true*/,
 void
 SimulationManager::startViewer(const bool debug /*= true*/)
 {
-    m_viewer->setRenderingMode(debug ? VTKRenderer::Mode::DEBUG : VTKRenderer::Mode::SIMULATION);
+    m_viewer->setRenderingMode(debug ? Renderer::Mode::DEBUG : Renderer::Mode::SIMULATION);
 
     // Start Rendering
     if (!m_viewer->isRendering())
@@ -476,7 +488,7 @@ SimulationManager::endSimulation()
     }
 
     // Update Renderer
-    m_viewer->setRenderingMode(VTKRenderer::Mode::DEBUG);
+    m_viewer->setRenderingMode(Renderer::Mode::DEBUG);
 
     // End modules
     for(const auto& pair : m_modulesMap)
diff --git a/Source/SimulationManager/imstkSimulationManager.h b/Source/SimulationManager/imstkSimulationManager.h
index 02187b6801c8c9fa129a7dfdbe498971eb1c15a9..a888fe06484df35a0017214e019bb19765194d54 100644
--- a/Source/SimulationManager/imstkSimulationManager.h
+++ b/Source/SimulationManager/imstkSimulationManager.h
@@ -27,12 +27,17 @@
 #include <thread>
 #include <memory>
 
-#include "imstkVTKRenderer.h"
 #include "imstkScene.h"
 #include "imstkModule.h"
 #include "imstkSceneManager.h"
-#include "imstkVTKViewer.h"
 #include "imstkLogUtility.h"
+#include "imstkViewer.h"
+
+#ifdef iMSTK_USE_Vulkan
+#include "imstkVulkanViewer.h"
+#else
+#include "imstkVTKViewer.h"
+#endif
 
 namespace imstk
 {
@@ -49,11 +54,7 @@ public:
     ///
     /// \brief Constructor
     ///
-    SimulationManager()
-    {
-        // Init g3logger
-        m_logUtil->createLogger("simulation", "./");
-    }
+    SimulationManager();
 
     ///
     /// \brief Default destructor
@@ -131,10 +132,8 @@ public:
     ///
     void removeModule(const std::string& moduleName);
 
-    ///
-    /// \brief Returns the vtk viewer
-    ///
-    std::shared_ptr<VTKViewer> getViewer() const;
+    // Viewer
+    std::shared_ptr<Viewer> getViewer() const;
 
     // Simulation
 
@@ -202,7 +201,7 @@ private:
 
     std::unordered_map<std::string, std::thread> m_threadMap;
 
-    std::shared_ptr<VTKViewer> m_viewer = std::make_shared<VTKViewer>(this);
+    std::shared_ptr<Viewer> m_viewer;
     std::shared_ptr<LogUtility> m_logUtil = std::make_shared<LogUtility>();
 };
 } // imstk
diff --git a/Source/SimulationManager/imstkViewer.cpp b/Source/SimulationManager/imstkViewer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..24230e4a2e2d4f4eca0abdf1ef3ed84b4dc44f21
--- /dev/null
+++ b/Source/SimulationManager/imstkViewer.cpp
@@ -0,0 +1,49 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkViewer.h"
+
+namespace imstk
+{
+std::shared_ptr<Scene>
+Viewer::getActiveScene() const
+{
+    return m_activeScene;
+}
+
+std::shared_ptr<Renderer>
+Viewer::getActiveRenderer() const
+{
+    return m_rendererMap.at(m_activeScene);
+}
+
+const bool&
+Viewer::isRendering() const
+{
+    return m_running;
+}
+
+std::shared_ptr<ScreenCaptureUtility>
+Viewer::getScreenCaptureUtility() const
+{
+    return m_screenCapturer;
+}
+}
\ No newline at end of file
diff --git a/Source/SimulationManager/imstkViewer.h b/Source/SimulationManager/imstkViewer.h
new file mode 100644
index 0000000000000000000000000000000000000000..5874874264ee59b1434470d3d67647b8c28bf4ce
--- /dev/null
+++ b/Source/SimulationManager/imstkViewer.h
@@ -0,0 +1,103 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#ifndef imstkViewer_h
+#define imstkViewer_h
+
+#include <memory>
+#include "imstkScene.h"
+#include "imstkRenderer.h"
+#include "imstkScreenCaptureUtility.h"
+
+namespace imstk
+{
+class SimulationManager;
+
+class Viewer
+{
+public:
+
+    Viewer(){};
+
+    Viewer(SimulationManager * manager){};
+
+    ///
+    /// \brief Get scene currently being rendered
+    ///
+    std::shared_ptr<Scene> getActiveScene() const;
+
+    ///
+    /// \brief Set scene to be rendered
+    ///
+    virtual void setActiveScene(std::shared_ptr<Scene>scene){};
+
+    ///
+    /// \brief Start rendering
+    ///
+    virtual void startRenderingLoop(){};
+
+    ///
+    /// \brief Terminate rendering
+    ///
+    virtual void endRenderingLoop(){};
+
+    ///
+    /// \brief Setup the current renderer to render what's needed
+    /// based on the mode chosen
+    ///
+    virtual void setRenderingMode(Renderer::Mode mode){};
+
+    ///
+    /// \brief Get the current renderer's mode
+    ///
+    virtual const Renderer::Mode getRenderingMode(){ return Renderer::Mode::EMPTY; };
+
+    ///
+    /// \brief Returns true if the Viewer is rendering
+    ///
+    const bool& isRendering() const;
+
+    ///
+    /// \brief Retrieve the renderer associated with the current scene
+    ///
+    std::shared_ptr<Renderer> getActiveRenderer() const;
+
+    ///
+    /// \brief access screen shot utility
+    ///
+    std::shared_ptr<ScreenCaptureUtility> getScreenCaptureUtility() const;
+
+    ///
+    /// \brief Set the coloring of the screen background
+    /// If 'gradientBackground' is false or not supplied color1 will fill the entire background
+    ///
+    virtual void setBackgroundColors(const Vec3d color1, const Vec3d color2 = Vec3d::Zero(), const bool gradientBackground = false){};
+
+protected:
+    std::shared_ptr<Scene> m_activeScene;
+
+    std::unordered_map<std::shared_ptr<Scene>, std::shared_ptr<Renderer>> m_rendererMap;
+    bool m_running = false;
+    std::shared_ptr<ScreenCaptureUtility> m_screenCapturer; ///> Screen shot utility
+};
+}
+
+#endif
\ No newline at end of file