diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6cb60d3926d30ee89edf8b98d5480d1383ff950d..7082c8a8da8c0f696c1ec532f4e083303846914a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,19 @@ if (LibArchive_FOUND)
 	set(HAVE_LIBARCHIVE true)
 endif()
 
+## OpenVR API path
+find_library(OPENVR_LIBRARIES
+  NAMES
+    openvr_api
+)
+set(OPENVR_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../headers)
+
+if (OPENVR_LIBRARIES)
+	message(STATUS "Found OpenVR: ${OPENVR_LIBRARIES}")
+	set(HAVE_OPENVR true)
+endif()
+
+
 if (WITH_FIXSTARS)
 	find_package( LibSGM )
 	if (LibSGM_FOUND)
diff --git a/applications/gui/CMakeLists.txt b/applications/gui/CMakeLists.txt
index baffb9bdd645c85cbfc593d81e14687965b9bf4c..b8970d7b91c47aa780c4ee9692b92779b1bc2fd6 100644
--- a/applications/gui/CMakeLists.txt
+++ b/applications/gui/CMakeLists.txt
@@ -27,6 +27,6 @@ target_include_directories(ftl-gui PUBLIC
 #endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} glog::glog ftlnet ftlrender nanogui GL)
+target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet ftlrender nanogui GL)
 
 
diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index 4ee248dc306c47784b9284f3975d9caee8d267cc..8fdad63282a9a9f54020cb0268e366794104bf83 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -242,6 +242,17 @@ void ftl::gui::Camera::setChannel(ftl::rgbd::channel_t c) {
 	}
 }
 
+static Eigen::Matrix4d ConvertSteamVRMatrixToMatrix4( const vr::HmdMatrix34_t &matPose )
+{
+	Eigen::Matrix4d matrixObj;
+	matrixObj <<
+		matPose.m[0][0], matPose.m[1][0], matPose.m[2][0], 0.0,
+		matPose.m[0][1], matPose.m[1][1], matPose.m[2][1], 0.0,
+		matPose.m[0][2], matPose.m[1][2], matPose.m[2][2], 0.0,
+		matPose.m[0][3], matPose.m[1][3], matPose.m[2][3], 1.0f;
+	return matrixObj;
+}
+
 const GLTexture &ftl::gui::Camera::captureFrame() {
 	float now = (float)glfwGetTime();
 	delta_ = now - ftime_;
@@ -250,16 +261,32 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 	if (src_ && src_->isReady()) {
 		cv::Mat rgb, depth;
 
-		// Lerp the Eye
-		eye_[0] += (neye_[0] - eye_[0]) * lerpSpeed_ * delta_;
-		eye_[1] += (neye_[1] - eye_[1]) * lerpSpeed_ * delta_;
-		eye_[2] += (neye_[2] - eye_[2]) * lerpSpeed_ * delta_;
-
-		Eigen::Translation3d trans(eye_);
-		Eigen::Affine3d t(trans);
-		Eigen::Matrix4d viewPose = t.matrix() * rotmat_;
+		if (screen_->hasVR()) {
+			#ifdef HAVE_OPENVR
+			vr::VRCompositor()->WaitGetPoses(rTrackedDevicePose_, vr::k_unMaxTrackedDeviceCount, NULL, 0 );
+
+			if ( rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid )
+			{
+				auto pose = ConvertSteamVRMatrixToMatrix4( rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking );
+				pose.inverse();
+				if (src_->hasCapabilities(ftl::rgbd::kCapMovable)) src_->setPose(pose);
+			} else {
+				LOG(ERROR) << "No VR Pose";
+			}
+			#endif
+		} else {
+			// Lerp the Eye
+			eye_[0] += (neye_[0] - eye_[0]) * lerpSpeed_ * delta_;
+			eye_[1] += (neye_[1] - eye_[1]) * lerpSpeed_ * delta_;
+			eye_[2] += (neye_[2] - eye_[2]) * lerpSpeed_ * delta_;
+
+			Eigen::Translation3d trans(eye_);
+			Eigen::Affine3d t(trans);
+			Eigen::Matrix4d viewPose = t.matrix() * rotmat_;
+
+			if (src_->hasCapabilities(ftl::rgbd::kCapMovable)) src_->setPose(viewPose);
+		}
 
-		if (src_->hasCapabilities(ftl::rgbd::kCapMovable)) src_->setPose(viewPose);
 		src_->grab();
 		src_->getFrames(rgb, depth);
 
diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp
index 8d48e2c0648caa37ad3064f195db47329e0bf82c..162bad2f0e73879e31efacaa1d20f66c35184b15 100644
--- a/applications/gui/src/camera.hpp
+++ b/applications/gui/src/camera.hpp
@@ -6,6 +6,10 @@
 
 #include <string>
 
+#ifdef HAVE_OPENVR
+#include <openvr/openvr.h>
+#endif
+
 class StatisticsImage;
 
 namespace ftl {
@@ -39,6 +43,7 @@ class Camera {
 	const std::vector<ftl::rgbd::channel_t> &availableChannels();
 
 	const GLTexture &captureFrame();
+	const GLTexture &getLeft() const { return texture_; }
 
 	nlohmann::json getMetaData();
 
@@ -62,6 +67,10 @@ class Camera {
 	bool pause_;
 	ftl::rgbd::channel_t channel_;
 	std::vector<ftl::rgbd::channel_t> channels_;
+
+	#ifdef HAVE_OPENVR
+	vr::TrackedDevicePose_t rTrackedDevicePose_[ vr::k_unMaxTrackedDeviceCount ];
+	#endif
 };
 
 }
diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp
index 56624ce61d9524c43e6dffe00948e807528ae595..e944c76b1c5f9eed7200b370097d883675e5085a 100644
--- a/applications/gui/src/screen.cpp
+++ b/applications/gui/src/screen.cpp
@@ -244,10 +244,31 @@ ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl
 
 	setVisible(true);
 	performLayout();
+
+
+	#ifdef HAVE_OPENVR
+	if (vr::VR_IsHmdPresent()) {
+		// Loading the SteamVR Runtime
+		vr::EVRInitError eError = vr::VRInitError_None;
+		HMD_ = vr::VR_Init( &eError, vr::VRApplication_Scene );
+
+		if ( eError != vr::VRInitError_None )
+		{
+			HMD_ = nullptr;
+			LOG(ERROR) << "Unable to init VR runtime: " << vr::VR_GetVRInitErrorAsEnglishDescription( eError );
+		}
+	} else {
+		HMD_ = nullptr;
+	}
+	#endif
 }
 
 ftl::gui::Screen::~Screen() {
 	mShader.free();
+
+	#ifdef HAVE_OPENVR
+	vr::VR_Shutdown();
+	#endif
 }
 
 void ftl::gui::Screen::setActiveCamera(ftl::gui::Camera *cam) {
@@ -337,6 +358,17 @@ void ftl::gui::Screen::draw(NVGcontext *ctx) {
 		imageSize = {camera_->width(), camera_->height()};
 
 		mImageID = camera_->captureFrame().texture();
+		leftEye_ = mImageID;
+		rightEye_ = mImageID;
+
+		#ifdef HAVE_OPENVR
+		if (hasVR() && (mImageID < std::numeric_limits<unsigned int>::max() && imageSize[0] > 0) && camera_->getLeft().isValid()) {
+			vr::Texture_t leftEyeTexture = {(void*)(uintptr_t)leftEye_, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
+			vr::VRCompositor()->Submit(vr::Eye_Left, &leftEyeTexture );
+			vr::Texture_t rightEyeTexture = {(void*)(uintptr_t)rightEye_, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
+			vr::VRCompositor()->Submit(vr::Eye_Right, &rightEyeTexture );
+		}
+		#endif
 
 		if (mImageID < std::numeric_limits<unsigned int>::max() && imageSize[0] > 0) {
 			auto mScale = (screenSize.cwiseQuotient(imageSize).minCoeff());
diff --git a/applications/gui/src/screen.hpp b/applications/gui/src/screen.hpp
index 8bfce830a034e024ec42037dbc43539a62d50101..d51cec2bf2c34afc7c589b7e6114f503379afe30 100644
--- a/applications/gui/src/screen.hpp
+++ b/applications/gui/src/screen.hpp
@@ -11,6 +11,10 @@
 #include "src_window.hpp"
 #include "gltexture.hpp"
 
+#ifdef HAVE_OPENVR
+#include <openvr/openvr.h>
+#endif
+
 class StatisticsImageNSamples;
 
 namespace ftl {
@@ -39,6 +43,12 @@ class Screen : public nanogui::Screen {
 	void setActiveCamera(ftl::gui::Camera*);
 	ftl::gui::Camera *activeCamera() { return camera_; }
 
+	#ifdef HAVE_OPENVR
+	bool hasVR() const { return HMD_ != nullptr; }
+	#else
+	bool hasVR() const { return false; }
+	#endif
+
 	nanogui::Theme *windowtheme;
 	nanogui::Theme *specialtheme;
 	nanogui::Theme *mediatheme;
@@ -68,6 +78,13 @@ class Screen : public nanogui::Screen {
 	ftl::Configurable *root_;
 	std::string status_;
 	ftl::gui::Camera *camera_;
+
+	GLuint leftEye_;
+	GLuint rightEye_;
+
+	#ifdef HAVE_OPENVR
+	vr::IVRSystem *HMD_;
+	#endif
 };
 
 }
diff --git a/components/common/cpp/include/ftl/config.h.in b/components/common/cpp/include/ftl/config.h.in
index 025ad576f9878019a41e0aa46269645c442780cd..ff965cbb011c5a52d9e7aed4b45c9e73774e847a 100644
--- a/components/common/cpp/include/ftl/config.h.in
+++ b/components/common/cpp/include/ftl/config.h.in
@@ -22,6 +22,7 @@
 #cmakedefine HAVE_REALSENSE
 #cmakedefine HAVE_NANOGUI
 #cmakedefine HAVE_LIBARCHIVE
+#cmakedefine HAVE_OPENVR
 
 extern const char *FTL_BRANCH;
 extern const char *FTL_VERSION_LONG;