diff --git a/data/shaders/hdr/bloom.frag b/data/shaders/hdr/bloom.frag new file mode 100644 index 00000000..b005e15e --- /dev/null +++ b/data/shaders/hdr/bloom.frag @@ -0,0 +1,66 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (binding = 0) uniform sampler2D samplerColor0; +layout (binding = 1) uniform sampler2D samplerColor1; + +layout (location = 0) in vec2 inUV; + +layout (location = 0) out vec4 outColor; + +layout (constant_id = 0) const int dir = 0; + +void main(void) +{ + // From the OpenGL Super bible + const float weights[] = float[](0.0024499299678342, + 0.0043538453346397, + 0.0073599963704157, + 0.0118349786570722, + 0.0181026699707781, + 0.0263392293891488, + 0.0364543006660986, + 0.0479932050577658, + 0.0601029809166942, + 0.0715974486241365, + 0.0811305381519717, + 0.0874493212267511, + 0.0896631113333857, + 0.0874493212267511, + 0.0811305381519717, + 0.0715974486241365, + 0.0601029809166942, + 0.0479932050577658, + 0.0364543006660986, + 0.0263392293891488, + 0.0181026699707781, + 0.0118349786570722, + 0.0073599963704157, + 0.0043538453346397, + 0.0024499299678342); + + + const float blurScale = 0.003; + const float blurStrength = 1.0; + + float ar = 1.0; + // Aspect ratio for vertical blur pass + if (dir == 1) + { + vec2 ts = textureSize(samplerColor1, 0); + ar = ts.y / ts.x; + } + + vec2 P = inUV.yx - vec2(0, (weights.length() >> 1) * ar * blurScale); + + vec4 color = vec4(0.0); + for (int i = 0; i < weights.length(); i++) + { + vec2 dv = vec2(0.0, i * blurScale) * ar; + color += texture(samplerColor1, P + dv) * weights[i] * blurStrength; + } + + outColor = color; +} \ No newline at end of file diff --git a/data/shaders/hdr/bloom.frag.spv b/data/shaders/hdr/bloom.frag.spv new file mode 100644 index 00000000..9ae7a1a3 Binary files /dev/null and b/data/shaders/hdr/bloom.frag.spv differ diff --git a/data/shaders/hdr/bloom.vert b/data/shaders/hdr/bloom.vert new file mode 100644 index 00000000..8a38fdeb --- /dev/null +++ b/data/shaders/hdr/bloom.vert @@ -0,0 +1,17 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) out vec2 outUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f); +} diff --git a/data/shaders/hdr/bloom.vert.spv b/data/shaders/hdr/bloom.vert.spv new file mode 100644 index 00000000..77f02b8b Binary files /dev/null and b/data/shaders/hdr/bloom.vert.spv differ diff --git a/data/shaders/hdr/composition.frag b/data/shaders/hdr/composition.frag new file mode 100644 index 00000000..230944db --- /dev/null +++ b/data/shaders/hdr/composition.frag @@ -0,0 +1,16 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (binding = 0) uniform sampler2D samplerColor0; +layout (binding = 1) uniform sampler2D samplerColor1; + +layout (location = 0) in vec2 inUV; + +layout (location = 0) out vec4 outColor; + +void main() +{ + outColor = texture(samplerColor0, inUV); +} \ No newline at end of file diff --git a/data/shaders/hdr/composition.frag.spv b/data/shaders/hdr/composition.frag.spv new file mode 100644 index 00000000..1acc5f18 Binary files /dev/null and b/data/shaders/hdr/composition.frag.spv differ diff --git a/data/shaders/hdr/composition.vert b/data/shaders/hdr/composition.vert new file mode 100644 index 00000000..8a38fdeb --- /dev/null +++ b/data/shaders/hdr/composition.vert @@ -0,0 +1,17 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) out vec2 outUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f); +} diff --git a/data/shaders/hdr/composition.vert.spv b/data/shaders/hdr/composition.vert.spv new file mode 100644 index 00000000..77f02b8b Binary files /dev/null and b/data/shaders/hdr/composition.vert.spv differ diff --git a/data/shaders/hdr/gbuffer.frag b/data/shaders/hdr/gbuffer.frag new file mode 100644 index 00000000..e303aff0 --- /dev/null +++ b/data/shaders/hdr/gbuffer.frag @@ -0,0 +1,101 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (binding = 1) uniform sampler2D samplerEnvMap; + +layout (location = 0) in vec3 inUVW; +layout (location = 1) in vec3 inPos; +layout (location = 2) in vec3 inNormal; +layout (location = 3) in vec3 inViewVec; +layout (location = 4) in vec3 inLightVec; +layout (location = 5) in mat4 inInvModelView; + +layout (location = 0) out vec4 outColor0; +layout (location = 1) out vec4 outColor1; + +layout (constant_id = 0) const int type = 0; + +#define PI 3.1415926 +#define TwoPI (2.0 * PI) + +layout (binding = 2) uniform UBO { + float exposure; +} ubo; + +vec2 envMapEquirect(vec3 normal) +{ + float phi = acos(-normal.y); + float theta = atan(-1.0 * normal.x, normal.z) + PI; + return vec2(theta / TwoPI, phi / PI); +} + +void main() +{ + vec4 color; + vec3 wcNormal; + + switch (type) { + case 0: // Skybox + { + vec3 normal = normalize(inUVW); + color = texture(samplerEnvMap, envMapEquirect(normal)); + } + break; + + case 1: // Reflect + { + vec3 wViewVec = mat3(inInvModelView) * normalize(inViewVec); + vec3 normal = normalize(inNormal); + vec3 wNormal = mat3(inInvModelView) * normal; + + float NdotL = max(dot(normal, inLightVec), 0.0); + + vec3 eyeDir = normalize(inViewVec); + vec3 halfVec = normalize(inLightVec + eyeDir); + float NdotH = max(dot(normal, halfVec), 0.0); + float NdotV = max(dot(normal, eyeDir), 0.0); + float VdotH = max(dot(eyeDir, halfVec), 0.0); + + // Geometric attenuation + float NH2 = 2.0 * NdotH; + float g1 = (NH2 * NdotV) / VdotH; + float g2 = (NH2 * NdotL) / VdotH; + float geoAtt = min(1.0, min(g1, g2)); + + const float F0 = 0.6; + const float k = 0.2; + + // Fresnel (schlick approximation) + float fresnel = pow(1.0 - VdotH, 5.0); + fresnel *= (1.0 - F0); + fresnel += F0; + + float spec = (fresnel * geoAtt) / (NdotV * NdotL * 3.14); + + color = texture(samplerEnvMap, envMapEquirect(reflect(-wViewVec, wNormal))); + + color = vec4(color.rgb * NdotL * (k + spec * (1.0 - k)), 1.0); + } + break; + + case 2: // Refract + { + vec3 wViewVec = mat3(inInvModelView) * normalize(inViewVec); + vec3 wNormal = mat3(inInvModelView) * inNormal; + color = texture(samplerEnvMap, envMapEquirect(refract(-wViewVec, wNormal, 1.0/1.6))); + } + break; + } + + + // Color with manual exposure into attachment 0 + outColor0.rgb = vec3(1.0) - exp(-color.rgb * ubo.exposure); + + // Bright parts for bloom into attachment 1 + float l = dot(outColor0.rgb, vec3(0.2126, 0.7152, 0.0722)); + float threshold = 0.75; + outColor1.rgb = (l > threshold) ? outColor0.rgb : vec3(0.0); + outColor1.a = 1.0; +} \ No newline at end of file diff --git a/data/shaders/hdr/gbuffer.frag.spv b/data/shaders/hdr/gbuffer.frag.spv new file mode 100644 index 00000000..9c63f2de Binary files /dev/null and b/data/shaders/hdr/gbuffer.frag.spv differ diff --git a/data/shaders/hdr/gbuffer.vert b/data/shaders/hdr/gbuffer.vert new file mode 100644 index 00000000..12a5760e --- /dev/null +++ b/data/shaders/hdr/gbuffer.vert @@ -0,0 +1,51 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec3 inNormal; + +layout (constant_id = 0) const int type = 0; + +layout (binding = 0) uniform UBO { + mat4 projection; + mat4 modelview; +} ubo; + +layout (location = 0) out vec3 outUVW; +layout (location = 1) out vec3 outPos; +layout (location = 2) out vec3 outNormal; +layout (location = 3) out vec3 outViewVec; +layout (location = 4) out vec3 outLightVec; +layout (location = 5) out mat4 outInvModelView; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outUVW = inPos; + outUVW.x *= -1.0; + + switch(type) { + case 0: // Skybox + outPos = vec3(mat3(ubo.modelview) * inPos); + gl_Position = vec4(ubo.projection * vec4(outPos, 1.0)); + break; + case 1: // Object + outPos = vec3(ubo.modelview * vec4(inPos, 1.0)); + gl_Position = ubo.projection * ubo.modelview * vec4(inPos.xyz, 1.0); + break; + } + outPos = vec3(ubo.modelview * vec4(inPos, 1.0)); + outNormal = mat3(ubo.modelview) * inNormal; + + outInvModelView = inverse(ubo.modelview); + + vec3 lightPos = vec3(0.0f, -5.0f, 5.0f); + outLightVec = lightPos.xyz - outPos.xyz; + outViewVec = -outPos.xyz; +} diff --git a/data/shaders/hdr/gbuffer.vert.spv b/data/shaders/hdr/gbuffer.vert.spv new file mode 100644 index 00000000..3490403f Binary files /dev/null and b/data/shaders/hdr/gbuffer.vert.spv differ diff --git a/data/textures/hdr_uffizi_bc6uf.DDS b/data/textures/hdr_uffizi_bc6uf.DDS new file mode 100644 index 00000000..8b5115a6 Binary files /dev/null and b/data/textures/hdr_uffizi_bc6uf.DDS differ diff --git a/hdr/hdr.cpp b/hdr/hdr.cpp new file mode 100644 index 00000000..05dc3891 --- /dev/null +++ b/hdr/hdr.cpp @@ -0,0 +1,1074 @@ +/* +* Vulkan Example - HDR +* +* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +#include +#include "vulkanexamplebase.h" +#include "vulkanbuffer.hpp" +#include "VulkanModel.hpp" +#include "VulkanTexture.hpp" + +#define ENABLE_VALIDATION false + +class VulkanExample : public VulkanExampleBase +{ +public: + bool bloom = true; + bool displaySkybox = true; + + // Vertex layout for the models + vks::VertexLayout vertexLayout = vks::VertexLayout({ + vks::VERTEX_COMPONENT_POSITION, + vks::VERTEX_COMPONENT_NORMAL, + vks::VERTEX_COMPONENT_UV, + }); + + struct { + vks::Texture2D envmap; + } textures; + + struct Models { + vks::Model skybox; + std::vector objects; + uint32_t objectIndex = 1; + } models; + + struct { + vk::Buffer matrices; + vk::Buffer params; + } uniformBuffers; + + struct UBOVS { + glm::mat4 projection; + glm::mat4 modelview; + } uboVS; + + struct UBOParams { + float exposure = 1.0f; + } uboParams; + + struct { + VkPipeline skybox; + VkPipeline reflect; + VkPipeline composition; + VkPipeline bloom[2]; + } pipelines; + + struct { + VkPipelineLayout models; + VkPipelineLayout composition; + VkPipelineLayout bloomFilter; + } pipelineLayouts; + + struct { + VkDescriptorSet object; + VkDescriptorSet skybox; + VkDescriptorSet composition; + VkDescriptorSet bloomFilter; + } descriptorSets; + + struct { + VkDescriptorSetLayout models; + VkDescriptorSetLayout composition; + VkDescriptorSetLayout bloomFilter; + } descriptorSetLayouts; + + // Framebuffer for offscreen rendering + struct FrameBufferAttachment { + VkImage image; + VkDeviceMemory mem; + VkImageView view; + VkFormat format; + void destroy(VkDevice device) + { + vkDestroyImageView(device, view, nullptr); + vkDestroyImage(device, image, nullptr); + vkFreeMemory(device, mem, nullptr); + } + }; + struct FrameBuffer { + int32_t width, height; + VkFramebuffer frameBuffer; + FrameBufferAttachment color[2]; + FrameBufferAttachment depth; + VkRenderPass renderPass; + VkSampler sampler; + VkCommandBuffer cmdBuffer = VK_NULL_HANDLE; + VkSemaphore semaphore = VK_NULL_HANDLE; + } offscreen; + + struct { + int32_t width, height; + VkFramebuffer frameBuffer; + FrameBufferAttachment color[1]; + VkRenderPass renderPass; + VkSampler sampler; + } filterPass; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + title = "Vulkan Example - HDR rendering"; + enableTextOverlay = true; + camera.type = Camera::CameraType::lookat; + camera.setPosition(glm::vec3(0.0f, 0.0f, -4.0f)); + camera.setRotation(glm::vec3(0.0f, 180.0f, 0.0f)); + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); + } + + ~VulkanExample() + { + vkDestroyPipeline(device, pipelines.skybox, nullptr); + vkDestroyPipeline(device, pipelines.reflect, nullptr); + vkDestroyPipeline(device, pipelines.composition, nullptr); + vkDestroyPipeline(device, pipelines.bloom[0], nullptr); + vkDestroyPipeline(device, pipelines.bloom[1], nullptr); + + vkDestroyPipelineLayout(device, pipelineLayouts.models, nullptr); + vkDestroyPipelineLayout(device, pipelineLayouts.composition, nullptr); + vkDestroyPipelineLayout(device, pipelineLayouts.bloomFilter, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.models, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.composition, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.bloomFilter, nullptr); + + vkDestroySemaphore(device, offscreen.semaphore, nullptr); + + vkDestroyRenderPass(device, offscreen.renderPass, nullptr); + vkDestroyRenderPass(device, filterPass.renderPass, nullptr); + + vkDestroyFramebuffer(device, offscreen.frameBuffer, nullptr); + vkDestroyFramebuffer(device, filterPass.frameBuffer, nullptr); + + vkDestroySampler(device, offscreen.sampler, nullptr); + vkDestroySampler(device, filterPass.sampler, nullptr); + + offscreen.depth.destroy(device); + offscreen.color[0].destroy(device); + offscreen.color[1].destroy(device); + + filterPass.color[0].destroy(device); + + for (auto& model : models.objects) { + model.destroy(); + } + models.skybox.destroy(); + uniformBuffers.matrices.destroy(); + uniformBuffers.params.destroy(); + textures.envmap.destroy(); + } + + void reBuildCommandBuffers() + { + if (!checkCommandBuffers()) + { + destroyCommandBuffers(); + createCommandBuffers(); + } + buildCommandBuffers(); + buildDeferredCommandBuffer(); + } + + void buildCommandBuffers() + { + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + + VkClearValue clearValues[2]; + clearValues[0].color = defaultClearColor; + clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };; + clearValues[1].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo = vkTools::initializers::renderPassBeginInfo(); + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.renderArea.offset.x = 0; + renderPassBeginInfo.renderArea.offset.y = 0; + renderPassBeginInfo.clearValueCount = 2; + renderPassBeginInfo.pClearValues = clearValues; + + VkViewport viewport; + VkRect2D scissor; + VkDeviceSize offsets[1] = { 0 }; + + for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) + { + + VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); + + // Bloom filter + renderPassBeginInfo.framebuffer = filterPass.frameBuffer; + renderPassBeginInfo.renderPass = filterPass.renderPass; + renderPassBeginInfo.clearValueCount = 1; + renderPassBeginInfo.renderArea.extent.width = filterPass.width; + renderPassBeginInfo.renderArea.extent.height = filterPass.height; + + viewport = vkTools::initializers::viewport((float)filterPass.width, (float)filterPass.height, 0.0f, 1.0f); + scissor = vkTools::initializers::rect2D(filterPass.width, filterPass.height, 0, 0); + + vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); + vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); + + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.bloomFilter, 0, 1, &descriptorSets.bloomFilter, 0, NULL); + + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[1]); + vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0); + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + viewport = vkTools::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + scissor = vkTools::initializers::rect2D(width, height, 0, 0); + + // Final composition + renderPassBeginInfo.framebuffer = frameBuffers[i]; + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.clearValueCount = 2; + renderPassBeginInfo.renderArea.extent.width = width; + renderPassBeginInfo.renderArea.extent.height = height; + + vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); + vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); + + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.composition, 0, 1, &descriptorSets.composition, 0, NULL); + + // Scene + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition); + vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0); + + // Bloom + if (bloom) + { + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[0]); + vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0); + } + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void createAttachment(VkFormat format, VkImageUsageFlagBits usage, FrameBufferAttachment *attachment) + { + VkImageAspectFlags aspectMask = 0; + VkImageLayout imageLayout; + + attachment->format = format; + + if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) + { + aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } + if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) + { + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + + assert(aspectMask > 0); + + VkImageCreateInfo image = vkTools::initializers::imageCreateInfo(); + image.imageType = VK_IMAGE_TYPE_2D; + image.format = format; + image.extent.width = offscreen.width; + image.extent.height = offscreen.height; + image.extent.depth = 1; + image.mipLevels = 1; + image.arrayLayers = 1; + image.samples = VK_SAMPLE_COUNT_1_BIT; + image.tiling = VK_IMAGE_TILING_OPTIMAL; + image.usage = usage | VK_IMAGE_USAGE_SAMPLED_BIT; + + VkMemoryAllocateInfo memAlloc = vkTools::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + + VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &attachment->image)); + vkGetImageMemoryRequirements(device, attachment->image, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &attachment->mem)); + VK_CHECK_RESULT(vkBindImageMemory(device, attachment->image, attachment->mem, 0)); + + VkImageViewCreateInfo imageView = vkTools::initializers::imageViewCreateInfo(); + imageView.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageView.format = format; + imageView.subresourceRange = {}; + imageView.subresourceRange.aspectMask = aspectMask; + imageView.subresourceRange.baseMipLevel = 0; + imageView.subresourceRange.levelCount = 1; + imageView.subresourceRange.baseArrayLayer = 0; + imageView.subresourceRange.layerCount = 1; + imageView.image = attachment->image; + VK_CHECK_RESULT(vkCreateImageView(device, &imageView, nullptr, &attachment->view)); + } + + // Prepare a new framebuffer and attachments for offscreen rendering (G-Buffer) + void prepareoffscreenfer() + { + { + offscreen.width = width; + offscreen.height = height; + + // Color attachments + + // Two floating point color buffers + createAttachment(VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &offscreen.color[0]); + createAttachment(VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &offscreen.color[1]); + // Depth attachment + createAttachment(depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &offscreen.depth); + + // Set up separate renderpass with references to the colorand depth attachments + std::array attachmentDescs = {}; + + // Init attachment properties + for (uint32_t i = 0; i < 3; ++i) + { + attachmentDescs[i].samples = VK_SAMPLE_COUNT_1_BIT; + attachmentDescs[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachmentDescs[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachmentDescs[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachmentDescs[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + if (i == 2) + { + attachmentDescs[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachmentDescs[i].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + else + { + attachmentDescs[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachmentDescs[i].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + } + + // Formats + attachmentDescs[0].format = offscreen.color[0].format; + attachmentDescs[1].format = offscreen.color[1].format; + attachmentDescs[2].format = offscreen.depth.format; + + std::vector colorReferences; + colorReferences.push_back({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); + colorReferences.push_back({ 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); + + VkAttachmentReference depthReference = {}; + depthReference.attachment = 2; + depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.pColorAttachments = colorReferences.data(); + subpass.colorAttachmentCount = 2; + subpass.pDepthStencilAttachment = &depthReference; + + // Use subpass dependencies for attachment layput transitions + std::array dependencies; + + 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_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_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_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_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.pAttachments = attachmentDescs.data(); + renderPassInfo.attachmentCount = static_cast(attachmentDescs.size()); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 2; + renderPassInfo.pDependencies = dependencies.data(); + + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offscreen.renderPass)); + + std::array attachments; + attachments[0] = offscreen.color[0].view; + attachments[1] = offscreen.color[1].view; + attachments[2] = offscreen.depth.view; + + VkFramebufferCreateInfo fbufCreateInfo = {}; + fbufCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + fbufCreateInfo.pNext = NULL; + fbufCreateInfo.renderPass = offscreen.renderPass; + fbufCreateInfo.pAttachments = attachments.data(); + fbufCreateInfo.attachmentCount = static_cast(attachments.size()); + fbufCreateInfo.width = offscreen.width; + fbufCreateInfo.height = offscreen.height; + fbufCreateInfo.layers = 1; + VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.frameBuffer)); + + // Create sampler to sample from the color attachments + VkSamplerCreateInfo sampler = vkTools::initializers::samplerCreateInfo(); + sampler.magFilter = VK_FILTER_NEAREST; + sampler.minFilter = VK_FILTER_NEAREST; + sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler.addressModeV = sampler.addressModeU; + sampler.addressModeW = sampler.addressModeU; + sampler.mipLodBias = 0.0f; + sampler.maxAnisotropy = 0; + sampler.minLod = 0.0f; + sampler.maxLod = 1.0f; + sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &offscreen.sampler)); + } + + // Bloom separable filter pass + { + filterPass.width = width; + filterPass.height = height; + + // Color attachments + + // Two floating point color buffers + createAttachment(VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &filterPass.color[0]); + + // Set up separate renderpass with references to the colorand depth attachments + std::array attachmentDescs = {}; + + // Init attachment properties + attachmentDescs[0].samples = VK_SAMPLE_COUNT_1_BIT; + attachmentDescs[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachmentDescs[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachmentDescs[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachmentDescs[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachmentDescs[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachmentDescs[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + attachmentDescs[0].format = filterPass.color[0].format; + + std::vector colorReferences; + colorReferences.push_back({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.pColorAttachments = colorReferences.data(); + subpass.colorAttachmentCount = 1; + + // Use subpass dependencies for attachment layput transitions + std::array dependencies; + + 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_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_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_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_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.pAttachments = attachmentDescs.data(); + renderPassInfo.attachmentCount = static_cast(attachmentDescs.size()); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 2; + renderPassInfo.pDependencies = dependencies.data(); + + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &filterPass.renderPass)); + + std::array attachments; + attachments[0] = filterPass.color[0].view; + + VkFramebufferCreateInfo fbufCreateInfo = {}; + fbufCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + fbufCreateInfo.pNext = NULL; + fbufCreateInfo.renderPass = filterPass.renderPass; + fbufCreateInfo.pAttachments = attachments.data(); + fbufCreateInfo.attachmentCount = static_cast(attachments.size()); + fbufCreateInfo.width = filterPass.width; + fbufCreateInfo.height = filterPass.height; + fbufCreateInfo.layers = 1; + VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &filterPass.frameBuffer)); + + // Create sampler to sample from the color attachments + VkSamplerCreateInfo sampler = vkTools::initializers::samplerCreateInfo(); + sampler.magFilter = VK_FILTER_NEAREST; + sampler.minFilter = VK_FILTER_NEAREST; + sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler.addressModeV = sampler.addressModeU; + sampler.addressModeW = sampler.addressModeU; + sampler.mipLodBias = 0.0f; + sampler.maxAnisotropy = 0; + sampler.minLod = 0.0f; + sampler.maxLod = 1.0f; + sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &filterPass.sampler)); + } + } + + // Build command buffer for rendering the scene to the offscreen frame buffer attachments + void buildDeferredCommandBuffer() + { + if (offscreen.cmdBuffer == VK_NULL_HANDLE) + { + offscreen.cmdBuffer = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false); + } + + // Create a semaphore used to synchronize offscreen rendering and usage + if (offscreen.semaphore == VK_NULL_HANDLE) + { + VkSemaphoreCreateInfo semaphoreCreateInfo = vkTools::initializers::semaphoreCreateInfo(); + VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &offscreen.semaphore)); + } + + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + + // Clear values for all attachments written in the fragment sahder + std::array clearValues; + clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } }; + clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 0.0f } }; + clearValues[2].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo = vkTools::initializers::renderPassBeginInfo(); + renderPassBeginInfo.renderPass = offscreen.renderPass; + renderPassBeginInfo.framebuffer = offscreen.frameBuffer; + renderPassBeginInfo.renderArea.extent.width = offscreen.width; + renderPassBeginInfo.renderArea.extent.height = offscreen.height; + renderPassBeginInfo.clearValueCount = 3; + renderPassBeginInfo.pClearValues = clearValues.data(); + + VK_CHECK_RESULT(vkBeginCommandBuffer(offscreen.cmdBuffer, &cmdBufInfo)); + + vkCmdBeginRenderPass(offscreen.cmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport = vkTools::initializers::viewport((float)offscreen.width, (float)offscreen.height, 0.0f, 1.0f); + vkCmdSetViewport(offscreen.cmdBuffer, 0, 1, &viewport); + + VkRect2D scissor = vkTools::initializers::rect2D(offscreen.width, offscreen.height, 0, 0); + vkCmdSetScissor(offscreen.cmdBuffer, 0, 1, &scissor); + + VkDeviceSize offsets[1] = { 0 }; + + // Skybox + if (displaySkybox) + { + vkCmdBindDescriptorSets(offscreen.cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.models, 0, 1, &descriptorSets.skybox, 0, NULL); + vkCmdBindVertexBuffers(offscreen.cmdBuffer, 0, 1, &models.skybox.vertices.buffer, offsets); + vkCmdBindIndexBuffer(offscreen.cmdBuffer, models.skybox.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindPipeline(offscreen.cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox); + vkCmdDrawIndexed(offscreen.cmdBuffer, models.skybox.indexCount, 1, 0, 0, 0); + } + + // 3D object + vkCmdBindDescriptorSets(offscreen.cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.models, 0, 1, &descriptorSets.object, 0, NULL); + vkCmdBindVertexBuffers(offscreen.cmdBuffer, 0, 1, &models.objects[models.objectIndex].vertices.buffer, offsets); + vkCmdBindIndexBuffer(offscreen.cmdBuffer, models.objects[models.objectIndex].indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindPipeline(offscreen.cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect); + vkCmdDrawIndexed(offscreen.cmdBuffer, models.objects[models.objectIndex].indexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(offscreen.cmdBuffer); + + VK_CHECK_RESULT(vkEndCommandBuffer(offscreen.cmdBuffer)); + } + + void loadAssets() + { + // Models + models.skybox.loadFromFile(vulkanDevice, getAssetPath() + "models/cube.obj", vertexLayout, 0.05f, queue); + std::vector filenames = {"sphere.obj", "teapot.dae", "torusknot.obj"}; + for (auto file : filenames) { + vks::Model model; + model.loadFromFile(vulkanDevice, getAssetPath() + "models/" + file, vertexLayout, 0.05f, queue); + models.objects.push_back(model); + } + + // Load HDR texture (equirectangular projected) + // VK_FORMAT_BC6H_UFLOAT_BLOCK is a compressed 16 bit unsigned floating point format for storing HDR content + textures.envmap.loadFromFile(getAssetPath() + "textures/hdr_uffizi_bc6uf.DDS", VK_FORMAT_BC6H_UFLOAT_BLOCK, vulkanDevice, queue); + + // Custom sampler with clamping adress mode + vkDestroySampler(device, textures.envmap.sampler, nullptr); + VkSamplerCreateInfo sampler{}; + sampler.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler.magFilter = VK_FILTER_LINEAR; + sampler.minFilter = VK_FILTER_LINEAR; + sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler.addressModeV = sampler.addressModeU; + sampler.addressModeW = sampler.addressModeU; + sampler.maxLod = (float)textures.envmap.mipLevels; + sampler.maxLod = 1.0f; + sampler.anisotropyEnable = VK_TRUE; + sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy; + sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + VK_CHECK_RESULT(vkCreateSampler(vulkanDevice->logicalDevice, &sampler, nullptr, &textures.envmap.sampler)); + textures.envmap.descriptor.sampler = textures.envmap.sampler; + } + + void setupDescriptorPool() + { + std::vector poolSizes = { + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4), + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6) + }; + uint32_t numDescriptorSets = 4; + VkDescriptorPoolCreateInfo descriptorPoolInfo = + vkTools::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), numDescriptorSets); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + } + + void setupDescriptorSetLayout() + { + std::vector setLayoutBindings = { + vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), + vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), + vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2), + }; + + VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo = + vkTools::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); + + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.models)); + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = + vkTools::initializers::pipelineLayoutCreateInfo( + &descriptorSetLayouts.models, + 1); + + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.models)); + + // Bloom filter + setLayoutBindings = { + vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), + vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), + }; + + descriptorLayoutInfo = vkTools::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.bloomFilter)); + + pipelineLayoutCreateInfo = vkTools::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.bloomFilter, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.bloomFilter)); + + // G-Buffer composition + setLayoutBindings = { + vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), + vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), + }; + + descriptorLayoutInfo = vkTools::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.composition)); + + pipelineLayoutCreateInfo = vkTools::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.composition, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.composition)); + } + + void setupDescriptorSets() + { + VkDescriptorSetAllocateInfo allocInfo = + vkTools::initializers::descriptorSetAllocateInfo( + descriptorPool, + &descriptorSetLayouts.models, + 1); + + // 3D object descriptor set + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object)); + + std::vector writeDescriptorSets = { + vkTools::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.matrices.descriptor), + vkTools::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.envmap.descriptor), + vkTools::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniformBuffers.params.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + + // Sky box descriptor set + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox)); + + writeDescriptorSets = { + vkTools::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0,&uniformBuffers.matrices.descriptor), + vkTools::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.envmap.descriptor), + vkTools::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniformBuffers.params.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + + // Bloom filter + allocInfo = vkTools::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.bloomFilter, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.bloomFilter)); + + std::vector colorDescriptors = { + vkTools::initializers::descriptorImageInfo(offscreen.sampler, offscreen.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), + vkTools::initializers::descriptorImageInfo(offscreen.sampler, offscreen.color[1].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), + }; + + writeDescriptorSets = { + vkTools::initializers::writeDescriptorSet(descriptorSets.bloomFilter, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorDescriptors[0]), + vkTools::initializers::writeDescriptorSet(descriptorSets.bloomFilter, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &colorDescriptors[1]), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + + // Composition descriptor set + allocInfo = vkTools::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.composition, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.composition)); + + colorDescriptors = { + vkTools::initializers::descriptorImageInfo(offscreen.sampler, offscreen.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), + vkTools::initializers::descriptorImageInfo(offscreen.sampler, filterPass.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), + }; + + writeDescriptorSets = { + vkTools::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorDescriptors[0]), + vkTools::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &colorDescriptors[1]), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + } + + void preparePipelines() + { + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = + vkTools::initializers::pipelineInputAssemblyStateCreateInfo( + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + 0, + VK_FALSE); + + VkPipelineRasterizationStateCreateInfo rasterizationState = + vkTools::initializers::pipelineRasterizationStateCreateInfo( + VK_POLYGON_MODE_FILL, + VK_CULL_MODE_BACK_BIT, + VK_FRONT_FACE_COUNTER_CLOCKWISE, + 0); + + VkPipelineColorBlendAttachmentState blendAttachmentState = + vkTools::initializers::pipelineColorBlendAttachmentState( + 0xf, + VK_FALSE); + + VkPipelineColorBlendStateCreateInfo colorBlendState = + vkTools::initializers::pipelineColorBlendStateCreateInfo( + 1, + &blendAttachmentState); + + VkPipelineDepthStencilStateCreateInfo depthStencilState = + vkTools::initializers::pipelineDepthStencilStateCreateInfo( + VK_FALSE, + VK_FALSE, + VK_COMPARE_OP_LESS_OR_EQUAL); + + VkPipelineViewportStateCreateInfo viewportState = + vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + + VkPipelineMultisampleStateCreateInfo multisampleState = + vkTools::initializers::pipelineMultisampleStateCreateInfo( + VK_SAMPLE_COUNT_1_BIT, + 0); + + std::vector dynamicStateEnables = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState = + vkTools::initializers::pipelineDynamicStateCreateInfo( + dynamicStateEnables.data(), + static_cast(dynamicStateEnables.size()), + 0); + + VkGraphicsPipelineCreateInfo pipelineCreateInfo = + vkTools::initializers::pipelineCreateInfo( + pipelineLayouts.models, + renderPass, + 0); + + std::vector blendAttachmentStates = { + vkTools::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), + vkTools::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), + }; + + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; + pipelineCreateInfo.pRasterizationState = &rasterizationState; + pipelineCreateInfo.pColorBlendState = &colorBlendState; + pipelineCreateInfo.pMultisampleState = &multisampleState; + pipelineCreateInfo.pViewportState = &viewportState; + pipelineCreateInfo.pDepthStencilState = &depthStencilState; + pipelineCreateInfo.pDynamicState = &dynamicState; + + std::array shaderStages; + pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); + pipelineCreateInfo.pStages = shaderStages.data(); + + VkSpecializationInfo specializationInfo; + std::array specializationMapEntries; + + // Full screen pipelines + + // Empty vertex input state, full screen triangles are generated by the vertex shader + VkPipelineVertexInputStateCreateInfo emptyInputState = vkTools::initializers::pipelineVertexInputStateCreateInfo(); + pipelineCreateInfo.pVertexInputState = &emptyInputState; + + // Final fullscreen composition pass pipeline + shaderStages[0] = loadShader(getAssetPath() + "shaders/hdr/composition.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getAssetPath() + "shaders/hdr/composition.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + pipelineCreateInfo.layout = pipelineLayouts.composition; + pipelineCreateInfo.renderPass = renderPass; + rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizationState.cullMode = VK_CULL_MODE_NONE; + colorBlendState.attachmentCount = 1; + colorBlendState.pAttachments = blendAttachmentStates.data(); + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.composition)); + + // Bloom pass + shaderStages[0] = loadShader(getAssetPath() + "shaders/hdr/bloom.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getAssetPath() + "shaders/hdr/bloom.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + colorBlendState.pAttachments = &blendAttachmentState; + blendAttachmentState.colorWriteMask = 0xF; + blendAttachmentState.blendEnable = VK_TRUE; + blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; + blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; + blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; + blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA; + + // Set constant parameters via specialization constants + specializationMapEntries[0] = vkTools::initializers::specializationMapEntry(0, 0, sizeof(uint32_t)); + uint32_t dir = 1; + specializationInfo = vkTools::initializers::specializationInfo(1, specializationMapEntries.data(), sizeof(dir), &dir); + shaderStages[1].pSpecializationInfo = &specializationInfo; + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.bloom[0])); + + // Second blur pass (into separate framebuffer) + pipelineCreateInfo.renderPass = filterPass.renderPass; + dir = 0; + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.bloom[1])); + + // Object rendering pipelines + + // Vertex bindings an attributes for model rendering + // Binding description + std::vector vertexInputBindings = { + vkTools::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX), + }; + + // Attribute descriptions + std::vector vertexInputAttributes = { + vkTools::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position + vkTools::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Normal + vkTools::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 5), // UV + }; + + VkPipelineVertexInputStateCreateInfo vertexInputState = vkTools::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); + vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); + vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); + vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); + + pipelineCreateInfo.renderPass = renderPass; + pipelineCreateInfo.pVertexInputState = &vertexInputState; + + // Skybox pipeline (background cube) + blendAttachmentState.blendEnable = VK_FALSE; + pipelineCreateInfo.layout = pipelineLayouts.models; + pipelineCreateInfo.renderPass = offscreen.renderPass; + colorBlendState.attachmentCount = 2; + colorBlendState.pAttachments = blendAttachmentStates.data(); + + shaderStages[0] = loadShader(getAssetPath() + "shaders/hdr/gbuffer.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getAssetPath() + "shaders/hdr/gbuffer.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + + // Set constant parameters via specialization constants + specializationMapEntries[0] = vkTools::initializers::specializationMapEntry(0, 0, sizeof(uint32_t)); + uint32_t shadertype = 0; + specializationInfo = vkTools::initializers::specializationInfo(1, specializationMapEntries.data(), sizeof(shadertype), &shadertype); + shaderStages[0].pSpecializationInfo = &specializationInfo; + shaderStages[1].pSpecializationInfo = &specializationInfo; + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.skybox)); + + // Object rendering pipeline + shadertype = 1; + + // Enable depth test and write + depthStencilState.depthWriteEnable = VK_TRUE; + depthStencilState.depthTestEnable = VK_TRUE; + // Flip cull mode + rasterizationState.cullMode = VK_CULL_MODE_NONE; + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.reflect)); + } + + // Prepare and initialize uniform buffer containing shader uniforms + void prepareUniformBuffers() + { + // Matrices vertex shader uniform buffer + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffers.matrices, + sizeof(uboVS))); + + // Params + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffers.params, + sizeof(uboParams))); + + // Map persistent + VK_CHECK_RESULT(uniformBuffers.matrices.map()); + VK_CHECK_RESULT(uniformBuffers.params.map()); + + updateUniformBuffers(); + updateParams(); + } + + void updateUniformBuffers() + { + uboVS.projection = camera.matrices.perspective; + uboVS.modelview = camera.matrices.view; + memcpy(uniformBuffers.matrices.mapped, &uboVS, sizeof(uboVS)); + } + + void updateParams() + { + memcpy(uniformBuffers.params.mapped, &uboParams, sizeof(uboParams)); + } + + void draw() + { + VulkanExampleBase::prepareFrame(); + + submitInfo.pWaitSemaphores = &semaphores.presentComplete; + submitInfo.pSignalSemaphores = &offscreen.semaphore; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &offscreen.cmdBuffer; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + + submitInfo.pWaitSemaphores = &offscreen.semaphore; + submitInfo.pSignalSemaphores = &semaphores.renderComplete; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + + VulkanExampleBase::submitFrame(); + } + + void prepare() + { + VulkanExampleBase::prepare(); + loadAssets(); + prepareUniformBuffers(); + prepareoffscreenfer(); + setupDescriptorSetLayout(); + preparePipelines(); + setupDescriptorPool(); + setupDescriptorSets(); + buildCommandBuffers(); + buildDeferredCommandBuffer(); + prepared = true; + } + + virtual void render() + { + if (!prepared) + return; + draw(); + } + + virtual void viewChanged() + { + updateUniformBuffers(); + } + + void toggleSkyBox() + { + displaySkybox = !displaySkybox; + reBuildCommandBuffers(); + } + + void toggleBloom() + { + bloom = !bloom; + reBuildCommandBuffers(); + } + + void toggleObject() + { + models.objectIndex++; + if (models.objectIndex >= static_cast(models.objects.size())) + { + models.objectIndex = 0; + } + reBuildCommandBuffers(); + } + + void changeExpoure(float delta) + { + uboParams.exposure += delta; + if (uboParams.exposure < 0.0f) { + uboParams.exposure = 0.0f; + } + updateParams(); + updateTextOverlay(); + } + + virtual void keyPressed(uint32_t keyCode) + { + switch (keyCode) + { + case KEY_B: + toggleBloom(); + break; + case KEY_S: + case GAMEPAD_BUTTON_A: + toggleSkyBox(); + break; + case KEY_SPACE: + case GAMEPAD_BUTTON_X: + toggleObject(); + break; + case KEY_KPADD: + case GAMEPAD_BUTTON_R1: + changeExpoure(0.05f); + break; + case KEY_KPSUB: + case GAMEPAD_BUTTON_L1: + changeExpoure(-0.05f); + break; + } + } + + virtual void getOverlayText(VulkanTextOverlay *textOverlay) + { + std::stringstream ss; + ss << std::setprecision(2) << std::fixed << uboParams.exposure; +#if defined(__ANDROID__) + textOverlay->addText("Expsoure: " + ss.str() + " (L1/R1)", 5.0f, 85.0f, VulkanTextOverlay::alignLeft); +#else + textOverlay->addText("Expsoure: " + ss.str() + " (+/-)", 5.0f, 85.0f, VulkanTextOverlay::alignLeft); +#endif + } +}; + +VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/hdr/hdr.vcxproj b/hdr/hdr.vcxproj new file mode 100644 index 00000000..064f960d --- /dev/null +++ b/hdr/hdr.vcxproj @@ -0,0 +1,103 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {582913B3-9B37-48CA-AEBD-69023DA9F7C8} + Win32Proj + 8.1 + + + + Application + true + v140 + + + Application + false + v140 + + + + + + + + + + + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + + WIN32;_DEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + true + Windows + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + WIN32;NDEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + + + true + Windows + true + true + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hdr/hdr.vcxproj.filters b/hdr/hdr.vcxproj.filters new file mode 100644 index 00000000..2c8eacce --- /dev/null +++ b/hdr/hdr.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {3adf072b-32a9-412a-8eb5-5329a601df1a} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + \ No newline at end of file diff --git a/vulkanExamples.sln b/vulkanExamples.sln index 1427c4ae..31695cc0 100644 --- a/vulkanExamples.sln +++ b/vulkanExamples.sln @@ -90,10 +90,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Base", "Base", "{09B9A54B-F base\vulkanheightmap.hpp = base\vulkanheightmap.hpp base\VulkanInitializers.hpp = base\VulkanInitializers.hpp base\vulkanMeshLoader.hpp = base\vulkanMeshLoader.hpp - base\VulkanModelLoader.hpp = base\VulkanModelLoader.hpp + base\VulkanModel.hpp = base\VulkanModel.hpp base\vulkanscene.hpp = base\vulkanscene.hpp base\vulkanswapchain.hpp = base\vulkanswapchain.hpp base\vulkantextoverlay.hpp = base\vulkantextoverlay.hpp + base\VulkanTexture.hpp = base\VulkanTexture.hpp base\vulkanTextureLoader.hpp = base\vulkanTextureLoader.hpp base\vulkantools.cpp = base\vulkantools.cpp base\vulkantools.h = base\vulkantools.h @@ -137,6 +138,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dynamicuniformbuffer", "dyn EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "specializationconstants", "specializationconstants\specializationconstants.vcxproj", "{00F600D2-DF0E-4F07-B722-633F7C4B2F65}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hdr", "hdr\hdr.vcxproj", "{582913B3-9B37-48CA-AEBD-69023DA9F7C8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -331,6 +334,10 @@ Global {00F600D2-DF0E-4F07-B722-633F7C4B2F65}.Debug|x64.Build.0 = Debug|x64 {00F600D2-DF0E-4F07-B722-633F7C4B2F65}.Release|x64.ActiveCfg = Release|x64 {00F600D2-DF0E-4F07-B722-633F7C4B2F65}.Release|x64.Build.0 = Release|x64 + {582913B3-9B37-48CA-AEBD-69023DA9F7C8}.Debug|x64.ActiveCfg = Debug|x64 + {582913B3-9B37-48CA-AEBD-69023DA9F7C8}.Debug|x64.Build.0 = Debug|x64 + {582913B3-9B37-48CA-AEBD-69023DA9F7C8}.Release|x64.ActiveCfg = Release|x64 + {582913B3-9B37-48CA-AEBD-69023DA9F7C8}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE