From 0fa26d451d5f3f92797287a871f39057f8a4d351 Mon Sep 17 00:00:00 2001
From: Alexander Gauggel <agauggel@uni-koblenz.de>
Date: Tue, 3 Aug 2021 19:00:46 +0200
Subject: [PATCH] [#106] Add motion vectors

---
 .../resources/shaders/gammaCorrection.comp    |  4 ++
 .../resources/shaders/prepass.frag            | 18 +++++
 .../resources/shaders/prepass.vert            | 19 +++++
 projects/indirect_dispatch/src/App.cpp        | 71 ++++++++++++++-----
 projects/indirect_dispatch/src/App.hpp        |  2 +-
 projects/indirect_dispatch/src/AppConfig.hpp  |  1 +
 projects/indirect_dispatch/src/AppSetup.cpp   | 53 +++++++++++---
 projects/indirect_dispatch/src/AppSetup.hpp   |  3 +
 8 files changed, 142 insertions(+), 29 deletions(-)
 create mode 100644 projects/indirect_dispatch/resources/shaders/prepass.frag
 create mode 100644 projects/indirect_dispatch/resources/shaders/prepass.vert

diff --git a/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp b/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp
index d3d1ee72..59540806 100644
--- a/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp
+++ b/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp
@@ -17,6 +17,10 @@ void main(){
     vec2    uv          = vec2(coord) / textureRes;
 
     vec3 linearColor    = texture(sampler2D(inTexture, textureSampler), uv).rgb;
+    
+    // in case of motion vector visualisation negative values are possible
+    linearColor         = abs(linearColor); 
+    
     vec3 gammaCorrected = pow(linearColor, vec3(1 / 2.2));
 
     imageStore(outImage, coord, vec4(gammaCorrected, 0.f));
diff --git a/projects/indirect_dispatch/resources/shaders/prepass.frag b/projects/indirect_dispatch/resources/shaders/prepass.frag
new file mode 100644
index 00000000..e9030883
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/prepass.frag
@@ -0,0 +1,18 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec4 passNDC;
+layout(location = 1) in vec4 passNDCPrevious;
+
+layout(location = 0) out vec2 outMotion;
+
+void main()	{
+
+    vec2 ndc            = passNDC.xy            / passNDC.w;
+    vec2 ndcPrevious    = passNDCPrevious.xy    / passNDCPrevious.w;
+
+    vec2 uv         = ndc           * 0.5 + 0.5;
+    vec2 uvPrevious = ndcPrevious   * 0.5 + 0.5;
+
+	outMotion = uvPrevious - uv;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/prepass.vert b/projects/indirect_dispatch/resources/shaders/prepass.vert
new file mode 100644
index 00000000..00b47280
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/prepass.vert
@@ -0,0 +1,19 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+
+layout(location = 0) out vec4 passNDC;
+layout(location = 1) out vec4 passNDCPrevious;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    mat4 mvpPrevious;
+};
+
+void main()	{
+	gl_Position     = mvp * vec4(inPosition, 1.0);
+	passNDC         = gl_Position;
+    passNDCPrevious = mvpPrevious * vec4(inPosition, 1.0);
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp
index e53c86ef..c74ffe2b 100644
--- a/projects/indirect_dispatch/src/App.cpp
+++ b/projects/indirect_dispatch/src/App.cpp
@@ -1,6 +1,7 @@
 #include "App.hpp"
 #include "AppConfig.hpp"
 #include <chrono>
+#include <vkcv/gui/GUI.hpp>
 
 App::App() : 
 	m_applicationName("Indirect Dispatch"),
@@ -18,10 +19,7 @@ App::App() :
 		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
 		{},
 		{ "VK_KHR_swapchain" })),
-	m_cameraManager(m_window){
-
-	m_isInitialized = false;
-}
+	m_cameraManager(m_window){}
 
 bool App::initialize() {
 
@@ -31,6 +29,9 @@ bool App::initialize() {
 	if (!loadSkyPass(m_core, &m_skyPassHandles))
 		return false;
 
+	if (!loadPrePass(m_core, &m_prePassHandles))
+		false;
+
 	if (!loadComputePass(m_core, "resources/shaders/gammaCorrection.comp", &m_gammaCorrectionPass))
 		return false;
 
@@ -50,24 +51,20 @@ bool App::initialize() {
 
 	const int cameraIndex = m_cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 	m_cameraManager.getCamera(cameraIndex).setPosition(glm::vec3(0, 0, -3));
-
-	m_isInitialized = true;
 }
 
 void App::run() {
 
-	if (!m_isInitialized) {
-		vkcv_log(vkcv::LogLevel::WARNING, "Application is not initialized, app should be initialized explicitly to check for errors");
-		if (!initialize()) {
-			vkcv_log(vkcv::LogLevel::ERROR, "Emergency initialization failed, exiting");
-			return;
-		}
-	}
-
 	auto                        frameStartTime = std::chrono::system_clock::now();
 	const vkcv::ImageHandle     swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 	const vkcv::DrawcallInfo    sphereDrawcall(m_sphereMesh.mesh, {}, 1);
-    const vkcv::DrawcallInfo    cubeDrawcall(m_cubeMesh.mesh, {}, 1);
+	const vkcv::DrawcallInfo    cubeDrawcall(m_cubeMesh.mesh, {}, 1);
+
+	vkcv::gui::GUI gui(m_core, m_window);
+
+	bool drawMotionVectors = false;
+
+	glm::mat4 previousFrameViewProjection = m_cameraManager.getActiveCamera().getMVP();
 
 	while (m_window.isWindowOpen()) {
 		vkcv::Window::pollEvents();
@@ -94,12 +91,32 @@ void App::run() {
 		m_cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
 		const glm::mat4 viewProjection = m_cameraManager.getActiveCamera().getMVP();
 
+		const vkcv::CommandStreamHandle cmdStream = m_core.createCommandStream(vkcv::QueueType::Graphics);
+
+		// prepass
+		glm::mat4 prepassMatrices[2] = {
+			viewProjection,
+			previousFrameViewProjection };
+		vkcv::PushConstants prepassPushConstants(sizeof(glm::mat4)*2);
+		prepassPushConstants.appendDrawcall(prepassMatrices);
+
+		const std::vector<vkcv::ImageHandle> prepassRenderTargets = {
+			m_renderTargets.motionBuffer,
+			m_renderTargets.depthBuffer };
+
+		m_core.recordDrawcallsToCmdStream(
+			cmdStream,
+			m_prePassHandles.renderPass,
+			m_prePassHandles.pipeline,
+			prepassPushConstants,
+			{ sphereDrawcall },
+			prepassRenderTargets);
+
+		// main pass
 		const std::vector<vkcv::ImageHandle> renderTargets   = { 
 			m_renderTargets.colorBuffer, 
 			m_renderTargets.depthBuffer };
 
-		const vkcv::CommandStreamHandle cmdStream = m_core.createCommandStream(vkcv::QueueType::Graphics);
-
 		vkcv::PushConstants meshPushConstants(sizeof(glm::mat4));
 		meshPushConstants.appendDrawcall(viewProjection);
 
@@ -111,6 +128,7 @@ void App::run() {
 			{ sphereDrawcall },
 			renderTargets);
 
+		// sky
 		vkcv::PushConstants skyPushConstants(sizeof(glm::mat4));
 		skyPushConstants.appendDrawcall(viewProjection);
 
@@ -122,9 +140,15 @@ void App::run() {
 			{ cubeDrawcall },
 			renderTargets);
 
+		// gamma correction
+		vkcv::ImageHandle gammaCorrectionInput = m_renderTargets.colorBuffer;
+		if (drawMotionVectors) {
+			gammaCorrectionInput = m_renderTargets.motionBuffer;
+		}
+
 		vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
 		gammaCorrectionDescriptorWrites.sampledImageWrites = {
-			vkcv::SampledImageDescriptorWrite(0, m_renderTargets.colorBuffer) };
+			vkcv::SampledImageDescriptorWrite(0, gammaCorrectionInput) };
 		gammaCorrectionDescriptorWrites.samplerWrites = {
 			vkcv::SamplerDescriptorWrite(1, m_linearSampler) };
 		gammaCorrectionDescriptorWrites.storageImageWrites = {
@@ -132,7 +156,7 @@ void App::run() {
 
 		m_core.writeDescriptorSet(m_gammaCorrectionPass.descriptorSet, gammaCorrectionDescriptorWrites);
 
-		m_core.prepareImageForSampling(cmdStream, m_renderTargets.colorBuffer);
+		m_core.prepareImageForSampling(cmdStream, gammaCorrectionInput);
 		m_core.prepareImageForStorage (cmdStream, swapchainInput);
 
 		uint32_t gammaCorrectionDispatch[3] = {
@@ -149,6 +173,15 @@ void App::run() {
 
 		m_core.prepareSwapchainImageForPresent(cmdStream);
 		m_core.submitCommandStream(cmdStream);
+
+		gui.beginGUI();
+		ImGui::Begin("Settings");
+		ImGui::Checkbox("View motion vectors", &drawMotionVectors);
+		ImGui::End();
+		gui.endGUI();
+
 		m_core.endFrame();
+
+		previousFrameViewProjection = viewProjection;
 	}
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.hpp b/projects/indirect_dispatch/src/App.hpp
index ea889108..78fe382b 100644
--- a/projects/indirect_dispatch/src/App.hpp
+++ b/projects/indirect_dispatch/src/App.hpp
@@ -10,7 +10,6 @@ public:
 	void run();
 private:
 	const char* m_applicationName;
-	bool        m_isInitialized;
 
 	int m_windowWidth;
 	int m_windowHeight;
@@ -24,6 +23,7 @@ private:
 
 	GraphicPassHandles m_meshPassHandles;
 	GraphicPassHandles m_skyPassHandles;
+	GraphicPassHandles m_prePassHandles;
 
 	ComputePassHandles m_gammaCorrectionPass;
 
diff --git a/projects/indirect_dispatch/src/AppConfig.hpp b/projects/indirect_dispatch/src/AppConfig.hpp
index 2ff3d28e..e727efa6 100644
--- a/projects/indirect_dispatch/src/AppConfig.hpp
+++ b/projects/indirect_dispatch/src/AppConfig.hpp
@@ -6,4 +6,5 @@ namespace AppConfig{
 	const int           defaultWindowHeight = 720;
 	const vk::Format    depthBufferFormat   = vk::Format::eD32Sfloat;
     const vk::Format    colorBufferFormat   = vk::Format::eB10G11R11UfloatPack32;
+	const vk::Format    motionBufferFormat	= vk::Format::eR16G16Sfloat;
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/AppSetup.cpp b/projects/indirect_dispatch/src/AppSetup.cpp
index 687d8863..f827ae15 100644
--- a/projects/indirect_dispatch/src/AppSetup.cpp
+++ b/projects/indirect_dispatch/src/AppSetup.cpp
@@ -60,6 +60,7 @@ bool loadGraphicPass(
 	const std::filesystem::path vertexPath,
 	const std::filesystem::path fragmentPath,
 	const vkcv::PassConfig&     passConfig,
+	const vkcv::DepthTest       depthTest,
 	GraphicPassHandles*         outPassHandles) {
 
 	assert(outPassHandles);
@@ -92,7 +93,7 @@ bool loadGraphicPass(
 
 	const vkcv::VertexLayout vertexLayout(bindings);
 
-	const vkcv::PipelineConfig pipelineConfig{
+	vkcv::PipelineConfig pipelineConfig{
 		shaderProgram,
 		UINT32_MAX,
 		UINT32_MAX,
@@ -100,7 +101,8 @@ bool loadGraphicPass(
 		{ vertexLayout },
 		{},
 		true };
-	outPassHandles->pipeline = core.createGraphicsPipeline(pipelineConfig);
+	pipelineConfig.m_depthTest  = depthTest;
+	outPassHandles->pipeline    = core.createGraphicsPipeline(pipelineConfig);
 
 	if (!outPassHandles->pipeline) {
 		vkcv_log(vkcv::LogLevel::ERROR, "Error: Could not create graphics pipeline");
@@ -116,19 +118,20 @@ bool loadMeshPass(vkcv::Core& core, GraphicPassHandles* outHandles) {
 
 	vkcv::AttachmentDescription colorAttachment(
 		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
+		vkcv::AttachmentOperation::DONT_CARE,
 		AppConfig::colorBufferFormat);
 
 	vkcv::AttachmentDescription depthAttachment(
 		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
+		vkcv::AttachmentOperation::LOAD,
 		AppConfig::depthBufferFormat);
 
 	return loadGraphicPass(
-		core, 
-		"resources/shaders/mesh.vert", 
-		"resources/shaders/mesh.frag", 
-		vkcv::PassConfig({ colorAttachment, depthAttachment }), 
+		core,
+		"resources/shaders/mesh.vert",
+		"resources/shaders/mesh.frag",
+		vkcv::PassConfig({ colorAttachment, depthAttachment }),
+		vkcv::DepthTest::Equal,
 		outHandles);
 }
 
@@ -142,7 +145,7 @@ bool loadSkyPass(vkcv::Core& core, GraphicPassHandles* outHandles) {
 		AppConfig::colorBufferFormat);
 
 	vkcv::AttachmentDescription depthAttachment(
-		vkcv::AttachmentOperation::DONT_CARE,
+		vkcv::AttachmentOperation::STORE,
 		vkcv::AttachmentOperation::LOAD,
 		AppConfig::depthBufferFormat);
 
@@ -151,6 +154,29 @@ bool loadSkyPass(vkcv::Core& core, GraphicPassHandles* outHandles) {
 		"resources/shaders/sky.vert",
 		"resources/shaders/sky.frag",
 		vkcv::PassConfig({ colorAttachment, depthAttachment }),
+		vkcv::DepthTest::Equal,
+		outHandles);
+}
+
+bool loadPrePass(vkcv::Core& core, GraphicPassHandles* outHandles) {
+	assert(outHandles);
+
+	vkcv::AttachmentDescription motionAttachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		AppConfig::motionBufferFormat);
+
+	vkcv::AttachmentDescription depthAttachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		AppConfig::depthBufferFormat);
+
+	return loadGraphicPass(
+		core,
+		"resources/shaders/prepass.vert",
+		"resources/shaders/prepass.frag",
+		vkcv::PassConfig({ motionAttachment, depthAttachment }),
+		vkcv::DepthTest::LessEqual,
 		outHandles);
 }
 
@@ -204,5 +230,14 @@ RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const
 		false,
 		true).getHandle();
 
+	targets.motionBuffer = core.createImage(
+		AppConfig::motionBufferFormat,
+		width,
+		height,
+		1,
+		false,
+		false,
+		true).getHandle();
+
 	return targets;
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/AppSetup.hpp b/projects/indirect_dispatch/src/AppSetup.hpp
index 892701aa..aa7be2fd 100644
--- a/projects/indirect_dispatch/src/AppSetup.hpp
+++ b/projects/indirect_dispatch/src/AppSetup.hpp
@@ -4,6 +4,7 @@
 struct RenderTargets {
 	vkcv::ImageHandle depthBuffer;
 	vkcv::ImageHandle colorBuffer;
+	vkcv::ImageHandle motionBuffer;
 };
 
 struct GraphicPassHandles {
@@ -30,10 +31,12 @@ bool loadGraphicPass(
 	const std::filesystem::path vertexPath,
 	const std::filesystem::path fragmentPath,
 	const vkcv::PassConfig&     passConfig,
+	const vkcv::DepthTest       depthTest,
 	GraphicPassHandles*         outPassHandles);
 
 bool loadMeshPass(vkcv::Core& core, GraphicPassHandles* outHandles);
 bool loadSkyPass (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadPrePass (vkcv::Core& core, GraphicPassHandles* outHandles);
 
 bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass);
 
-- 
GitLab