diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 54647c13c..1352bfa92 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -739,6 +739,7 @@ set (VULKAN_SOURCES common/rendering/vulkan/vk_pprenderstate.cpp common/rendering/vulkan/vk_levelmesh.cpp common/rendering/vulkan/vk_lightmapper.cpp + common/rendering/vulkan/vk_lightprober.cpp common/rendering/vulkan/commands/vk_commandbuffer.cpp common/rendering/vulkan/buffers/vk_rsbuffers.cpp common/rendering/vulkan/buffers/vk_hwbuffer.cpp diff --git a/src/common/rendering/vulkan/vk_lightprober.cpp b/src/common/rendering/vulkan/vk_lightprober.cpp new file mode 100644 index 000000000..a58d1fc39 --- /dev/null +++ b/src/common/rendering/vulkan/vk_lightprober.cpp @@ -0,0 +1,426 @@ + +#include "vk_lightprober.h" +#include "vulkan/vk_renderdevice.h" +#include "vulkan/textures/vk_texture.h" +#include "vulkan/commands/vk_commandbuffer.h" +#include "vulkan/descriptorsets/vk_descriptorset.h" +#include "zvulkan/vulkanbuilders.h" +#include "filesystem.h" +#include "cmdlib.h" + +VkLightprober::VkLightprober(VulkanRenderDevice* fb) : fb(fb) +{ + CreateIrradianceMap(); + CreatePrefilterMap(); +} + +VkLightprober::~VkLightprober() +{ +} + +void VkLightprober::CreateBrdfLutResources() +{ + brdfLut.shader = CompileShader("comp_brdf_convolute.glsl", "shaders/lightprobe/comp_brdf_convolute.glsl", "VkLightprober.BrdfLut"); + + brdfLut.descriptorSetLayout = DescriptorSetLayoutBuilder() + .AddBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT) + .Create(fb->GetDevice()); + + brdfLut.pipelineLayout = PipelineLayoutBuilder() + .AddSetLayout(brdfLut.descriptorSetLayout.get()) + .Create(fb->GetDevice()); + + brdfLut.pipeline = ComputePipelineBuilder() + .ComputeShader(brdfLut.shader.get()) + .Layout(brdfLut.pipelineLayout.get()) + .Create(fb->GetDevice()); + + brdfLut.descriptorPool = DescriptorPoolBuilder() + .AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1) + .MaxSets(1) + .Create(fb->GetDevice()); + + brdfLut.descriptorSet = brdfLut.descriptorPool->allocate(brdfLut.descriptorSetLayout.get()); + + brdfLut.image = ImageBuilder() + .Size(512, 512) + .Format(VK_FORMAT_R16G16_SFLOAT) + .Usage(VK_IMAGE_USAGE_STORAGE_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) + .Create(fb->GetDevice()); + + brdfLut.view = ImageViewBuilder() + .Image(brdfLut.image.get(), VK_FORMAT_R16G16_SFLOAT) + .Create(fb->GetDevice()); + + WriteDescriptors() + .AddStorageImage(brdfLut.descriptorSet.get(), 0, brdfLut.view.get(), VK_IMAGE_LAYOUT_GENERAL) + .Execute(fb->GetDevice()); +} + +void VkLightprober::GenerateBrdfLut() +{ + auto staging = BufferBuilder() + .Size(512 * 512 * 2 * sizeof(uint16_t)) + .Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU) + .Create(fb->GetDevice()); + + auto cmdbuffer = fb->GetCommands()->GetTransferCommands(); + + PipelineBarrier() + .AddImage(brdfLut.image.get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_SHADER_WRITE_BIT) + .Execute(cmdbuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + + cmdbuffer->bindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, brdfLut.pipeline.get()); + cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, brdfLut.pipelineLayout.get(), 0, brdfLut.descriptorSet.get()); + cmdbuffer->dispatch(512, 512, 1); + + PipelineBarrier() + .AddImage(brdfLut.image.get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT) + .Execute(cmdbuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy region = {}; + region.imageExtent.width = brdfLut.image->width; + region.imageExtent.height = brdfLut.image->height; + region.imageExtent.depth = 1; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + cmdbuffer->copyImageToBuffer(brdfLut.image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, ®ion); + + fb->GetCommands()->WaitForCommands(false); + + std::vector databuffer(512 * 512 * 2); + auto src = staging->Map(0, databuffer.size() * sizeof(uint16_t)); + memcpy(databuffer.data(), src, databuffer.size()); + staging->Unmap(); + + std::unique_ptr file(FileWriter::Open("bdrf.lut")); + file->Write(databuffer.data(), databuffer.size() * sizeof(uint16_t)); +} + +void VkLightprober::CreateIrradianceMap() +{ + irradianceMap.shader = CompileShader("comp_irradiance_convolute.glsl", "shaders/lightprobe/comp_irradiance_convolute.glsl", "VkLightprober.IrradianceMap"); + + irradianceMap.descriptorSetLayout = DescriptorSetLayoutBuilder() + .AddBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT) + .AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT) + .Create(fb->GetDevice()); + + irradianceMap.descriptorSetLayout = DescriptorSetLayoutBuilder() + .AddBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT) + .AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT) + .Create(fb->GetDevice()); + + irradianceMap.pipelineLayout = PipelineLayoutBuilder() + .AddSetLayout(irradianceMap.descriptorSetLayout.get()) + .AddPushConstantRange(VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(IrradianceMapPushConstants)) + .Create(fb->GetDevice()); + + irradianceMap.pipeline = ComputePipelineBuilder() + .ComputeShader(irradianceMap.shader.get()) + .Layout(irradianceMap.pipelineLayout.get()) + .Create(fb->GetDevice()); + + irradianceMap.sampler = SamplerBuilder() + .Create(fb->GetDevice()); + + irradianceMap.descriptorPool = DescriptorPoolBuilder() + .AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 6) + .AddPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6) + .MaxSets(6) + .Create(fb->GetDevice()); + + for (int i = 0; i < 6; i++) + { + irradianceMap.images[i] = ImageBuilder() + .Size(32, 32) + .Format(VK_FORMAT_R16G16B16A16_SFLOAT) + .Usage(VK_IMAGE_USAGE_STORAGE_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) + .Create(fb->GetDevice()); + + irradianceMap.views[i] = ImageViewBuilder() + .Image(irradianceMap.images[i].get(), VK_FORMAT_R16G16B16A16_SFLOAT) + .Create(fb->GetDevice()); + + irradianceMap.descriptorSets[i] = irradianceMap.descriptorPool->allocate(irradianceMap.descriptorSetLayout.get()); + } +} + +void VkLightprober::GenerateIrradianceMap(VulkanImageView* environmentcubemap) +{ + auto staging = BufferBuilder() + .Size(32 * 32 * 8 * 6) + .Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU) + .Create(fb->GetDevice()); + + WriteDescriptors write; + for (int i = 0; i < 6; i++) + { + write.AddStorageImage(irradianceMap.descriptorSets[i].get(), 0, irradianceMap.views[i].get(), VK_IMAGE_LAYOUT_GENERAL); + write.AddCombinedImageSampler(irradianceMap.descriptorSets[i].get(), 1, environmentcubemap, irradianceMap.sampler.get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + write.Execute(fb->GetDevice()); + + auto cmdbuffer = fb->GetCommands()->GetDrawCommands(); + + PipelineBarrier barrier0; + for (int i = 0; i < 6; i++) + barrier0.AddImage(irradianceMap.images[i].get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_SHADER_WRITE_BIT); + barrier0.Execute(cmdbuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + + cmdbuffer->bindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, irradianceMap.pipeline.get()); + + // +x, +y, +z, -x, -y, -z + + FVector3 dir[6] = { + FVector3( 1.0f, 0.0f, 0.0f), + FVector3( 0.0f, 1.0f, 0.0f), + FVector3( 0.0f, 0.0f, 1.0f), + FVector3(-1.0f, 0.0f, 0.0f), + FVector3( 0.0f, -1.0f, 0.0f), + FVector3( 0.0f, 0.0f, -1.0f) + }; + + FVector3 up[6] = { + FVector3(0.0f, -1.0f, 0.0f), + FVector3(0.0f, 0.0f, 1.0f), + FVector3(0.0f, -1.0f, 0.0f), + FVector3(0.0f, -1.0f, 0.0f), + FVector3(0.0f, 0.0f, -1.0f), + FVector3(0.0f, -1.0f, 0.0f) + }; + + for (int i = 0; i < 6; i++) + { + FVector3 side = dir[i] ^ up[i]; + + IrradianceMapPushConstants push; + push.topLeft = FVector4(dir[i] - up[i] - side, 0.0f); + push.bottomRight = FVector4(dir[i] + up[i] + side, 0.0f); + + cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, irradianceMap.pipelineLayout.get(), 0, irradianceMap.descriptorSets[i].get()); + cmdbuffer->pushConstants(irradianceMap.pipelineLayout.get(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, sizeof(PushConstants), &push); + cmdbuffer->dispatch(32, 32, 1); + } + + PipelineBarrier barrier1; + for (int i = 0; i < 6; i++) + barrier1.AddImage(irradianceMap.images[i].get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + barrier1.Execute(cmdbuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + for (int i = 0; i < 6; i++) + { + VkBufferImageCopy region = { }; + region.bufferOffset = 32 * 32 * 8 * i; + region.imageExtent.width = irradianceMap.images[i]->width; + region.imageExtent.height = irradianceMap.images[i]->height; + region.imageExtent.depth = 1; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + cmdbuffer->copyImageToBuffer(irradianceMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, ®ion); + } + + fb->GetCommands()->WaitForCommands(false); + + std::vector databuffer(32 * 32 * 8 * 6); + auto src = staging->Map(0, databuffer.size()); + memcpy(databuffer.data(), src, databuffer.size()); + staging->Unmap(); +} + +void VkLightprober::CreatePrefilterMap() +{ + prefilterMap.shader = CompileShader("comp_prefilter_convolute.glsl", "shaders/lightprobe/comp_prefilter_convolute.glsl", "VkLightprober.PrefilterMap"); + + prefilterMap.descriptorSetLayout = DescriptorSetLayoutBuilder() + .AddBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT) + .AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT) + .Create(fb->GetDevice()); + + prefilterMap.pipelineLayout = PipelineLayoutBuilder() + .AddSetLayout(prefilterMap.descriptorSetLayout.get()) + .AddPushConstantRange(VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(PrefilterMapPushConstants)) + .Create(fb->GetDevice()); + + prefilterMap.pipeline = ComputePipelineBuilder() + .ComputeShader(prefilterMap.shader.get()) + .Layout(prefilterMap.pipelineLayout.get()) + .Create(fb->GetDevice()); + + prefilterMap.sampler = SamplerBuilder() + .Create(fb->GetDevice()); + + prefilterMap.descriptorPool = DescriptorPoolBuilder() + .AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 6 * prefilterMap.maxlevels) + .AddPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6 * prefilterMap.maxlevels) + .MaxSets(6 * prefilterMap.maxlevels) + .Create(fb->GetDevice()); + + for (int i = 0; i < 6; i++) + { + for (int level = 0; level < prefilterMap.maxlevels; level++) + { + int idx = i * prefilterMap.maxlevels + level; + + prefilterMap.images[idx] = ImageBuilder() + .Size(128 >> level, 128 >> level) + .Format(VK_FORMAT_R16G16B16A16_SFLOAT) + .Usage(VK_IMAGE_USAGE_STORAGE_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) + .Create(fb->GetDevice()); + + prefilterMap.views[idx] = ImageViewBuilder() + .Image(prefilterMap.images[idx].get(), VK_FORMAT_R16G16B16A16_SFLOAT) + .Create(fb->GetDevice()); + + prefilterMap.descriptorSets[idx] = prefilterMap.descriptorPool->allocate(prefilterMap.descriptorSetLayout.get()); + } + } +} + +void VkLightprober::GeneratePrefilterMap(VulkanImageView* environmentcubemap) +{ + auto staging = BufferBuilder() + .Size(prefilterMap.levelsSize * 6) + .Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU) + .Create(fb->GetDevice()); + + WriteDescriptors write; + for (int i = 0; i < 6 * prefilterMap.maxlevels; i++) + { + write.AddStorageImage(prefilterMap.descriptorSets[i].get(), 0, prefilterMap.views[i].get(), VK_IMAGE_LAYOUT_GENERAL); + write.AddCombinedImageSampler(prefilterMap.descriptorSets[i].get(), 1, environmentcubemap, prefilterMap.sampler.get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + write.Execute(fb->GetDevice()); + + auto cmdbuffer = fb->GetCommands()->GetDrawCommands(); + + PipelineBarrier barrier0; + for (int i = 0; i < 6 * prefilterMap.maxlevels; i++) + barrier0.AddImage(prefilterMap.images[i].get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_SHADER_WRITE_BIT); + barrier0.Execute(cmdbuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + + cmdbuffer->bindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, prefilterMap.pipeline.get()); + + // +x, +y, +z, -x, -y, -z + + FVector3 dir[6] = { + FVector3( 1.0f, 0.0f, 0.0f), + FVector3( 0.0f, 1.0f, 0.0f), + FVector3( 0.0f, 0.0f, 1.0f), + FVector3(-1.0f, 0.0f, 0.0f), + FVector3( 0.0f, -1.0f, 0.0f), + FVector3( 0.0f, 0.0f, -1.0f) + }; + + FVector3 up[6] = { + FVector3(0.0f, -1.0f, 0.0f), + FVector3(0.0f, 0.0f, 1.0f), + FVector3(0.0f, -1.0f, 0.0f), + FVector3(0.0f, -1.0f, 0.0f), + FVector3(0.0f, 0.0f, -1.0f), + FVector3(0.0f, -1.0f, 0.0f) + }; + + for (int i = 0; i < 6; i++) + { + FVector3 side = dir[i] ^ up[i]; + + PrefilterMapPushConstants push; + push.topLeft = FVector4(dir[i] - up[i] - side, 0.0f); + push.bottomRight = FVector4(dir[i] + up[i] + side, 0.0f); + + for (int level = 0; level < prefilterMap.maxlevels; level++) + { + push.roughness = (float)level / (float)(prefilterMap.maxlevels - 1); + + cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, prefilterMap.pipelineLayout.get(), 0, prefilterMap.descriptorSets[i * prefilterMap.maxlevels + level].get()); + cmdbuffer->pushConstants(prefilterMap.pipelineLayout.get(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, sizeof(PrefilterMapPushConstants), &push); + cmdbuffer->dispatch(128 >> level, 128 >> level, 1); + } + } + + PipelineBarrier barrier1; + for (int i = 0; i < 6 * prefilterMap.maxlevels; i++) + barrier1.AddImage(prefilterMap.images[i].get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + barrier1.Execute(cmdbuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + int offset = 0; + for (int i = 0; i < 6 * prefilterMap.maxlevels; i++) + { + VkBufferImageCopy region = { }; + region.bufferOffset = offset; + region.imageExtent.width = prefilterMap.images[i]->width; + region.imageExtent.height = prefilterMap.images[i]->height; + region.imageExtent.depth = 1; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + cmdbuffer->copyImageToBuffer(prefilterMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, ®ion); + + offset += prefilterMap.images[i]->width * prefilterMap.images[i]->height * 8; + } + + fb->GetCommands()->WaitForCommands(false); + + std::vector databuffer(prefilterMap.levelsSize * 6); + auto src = staging->Map(0, databuffer.size()); + memcpy(databuffer.data(), src, databuffer.size()); + staging->Unmap(); +} + +std::unique_ptr VkLightprober::CompileShader(const std::string& name, const std::string& filename, const char* debugName) +{ + std::string prefix = "#version 460\r\n"; + prefix += "#extension GL_GOOGLE_include_directive : enable\n"; + prefix += "#extension GL_ARB_separate_shader_objects : enable\n"; + + auto onIncludeLocal = [](std::string headerName, std::string includerName, size_t depth) { return OnInclude(headerName.c_str(), includerName.c_str(), depth, false); }; + auto onIncludeSystem = [](std::string headerName, std::string includerName, size_t depth) { return OnInclude(headerName.c_str(), includerName.c_str(), depth, true); }; + + return ShaderBuilder() + .Type(ShaderType::Compute) + .AddSource("VersionBlock", prefix) + .AddSource(name, LoadPrivateShaderLump(filename.c_str()).GetChars()) + .OnIncludeLocal(onIncludeLocal) + .OnIncludeSystem(onIncludeSystem) + .DebugName(debugName) + .Create(debugName, fb->GetDevice()); +} + +FString VkLightprober::LoadPrivateShaderLump(const char* lumpname) +{ + int lump = fileSystem.CheckNumForFullName(lumpname, 0); + if (lump == -1) I_Error("Unable to load '%s'", lumpname); + return GetStringFromLump(lump); +} + +FString VkLightprober::LoadPublicShaderLump(const char* lumpname) +{ + int lump = fileSystem.CheckNumForFullName(lumpname, 0); + if (lump == -1) lump = fileSystem.CheckNumForFullName(lumpname); + if (lump == -1) I_Error("Unable to load '%s'", lumpname); + return GetStringFromLump(lump); +} + +ShaderIncludeResult VkLightprober::OnInclude(FString headerName, FString includerName, size_t depth, bool system) +{ + if (depth > 8) + I_Error("Too much include recursion!"); + + FString includeguardname; + includeguardname << "_HEADERGUARD_" << headerName.GetChars(); + includeguardname.ReplaceChars("/\\.", '_'); + + FString code; + code << "#ifndef " << includeguardname.GetChars() << "\n"; + code << "#define " << includeguardname.GetChars() << "\n"; + code << "#line 1\n"; + + if (system) + code << LoadPrivateShaderLump(headerName.GetChars()).GetChars() << "\n"; + else + code << LoadPublicShaderLump(headerName.GetChars()).GetChars() << "\n"; + + code << "#endif\n"; + + return ShaderIncludeResult(headerName.GetChars(), code.GetChars()); +} diff --git a/src/common/rendering/vulkan/vk_lightprober.h b/src/common/rendering/vulkan/vk_lightprober.h new file mode 100644 index 000000000..89bf86f76 --- /dev/null +++ b/src/common/rendering/vulkan/vk_lightprober.h @@ -0,0 +1,88 @@ +#pragma once + +#include "zvulkan/vulkanobjects.h" +#include "vectors.h" + +class VulkanRenderDevice; +class FString; +class ShaderIncludeResult; + +struct IrradianceMapPushConstants +{ + FVector4 topLeft; + FVector4 bottomRight; +}; + +struct PrefilterMapPushConstants +{ + FVector4 topLeft; + FVector4 bottomRight; + float roughness; + float padding1, padding2, padding3; +}; + +class VkLightprober +{ +public: + VkLightprober(VulkanRenderDevice* fb); + ~VkLightprober(); + +private: + void CreateBrdfLutResources(); + void CreateIrradianceMap(); + void CreatePrefilterMap(); + void GenerateBrdfLut(); + void GenerateIrradianceMap(VulkanImageView* environmentcubemap); + void GeneratePrefilterMap(VulkanImageView* environmentcubemap); + + std::unique_ptr CompileShader(const std::string& name, const std::string& filename, const char* debugName); + + static FString LoadPrivateShaderLump(const char* lumpname); + static FString LoadPublicShaderLump(const char* lumpname); + static ShaderIncludeResult OnInclude(FString headerName, FString includerName, size_t depth, bool system); + + struct + { + std::unique_ptr shader; + std::unique_ptr descriptorSetLayout; + std::unique_ptr descriptorPool; + std::unique_ptr descriptorSet; + std::unique_ptr pipelineLayout; + std::unique_ptr pipeline; + std::unique_ptr image; + std::unique_ptr view; + } brdfLut; + + struct + { + std::unique_ptr shader; + std::unique_ptr descriptorSetLayout; + std::unique_ptr descriptorPool; + std::unique_ptr descriptorSets[6]; + std::unique_ptr pipelineLayout; + std::unique_ptr pipeline; + std::unique_ptr sampler; + std::unique_ptr images[6]; + std::unique_ptr views[6]; + } irradianceMap; + + struct + { + enum + { + maxlevels = 5, + levelsSize = (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8) * 8 + }; + std::unique_ptr shader; + std::unique_ptr descriptorSetLayout; + std::unique_ptr descriptorPool; + std::unique_ptr descriptorSets[6 * maxlevels]; + std::unique_ptr pipelineLayout; + std::unique_ptr pipeline; + std::unique_ptr sampler; + std::unique_ptr images[6 * maxlevels]; + std::unique_ptr views[6 * maxlevels]; + } prefilterMap; + + VulkanRenderDevice* fb = nullptr; +}; diff --git a/wadsrc/static/bdrf.lut b/wadsrc/static/bdrf.lut new file mode 100644 index 000000000..22f4a3fb7 Binary files /dev/null and b/wadsrc/static/bdrf.lut differ diff --git a/wadsrc/static/shaders/lightprobe/comp_brdf_convolute.glsl b/wadsrc/static/shaders/lightprobe/comp_brdf_convolute.glsl new file mode 100644 index 000000000..ab648c415 --- /dev/null +++ b/wadsrc/static/shaders/lightprobe/comp_brdf_convolute.glsl @@ -0,0 +1,109 @@ + +layout(local_size_x = 16, local_size_y = 16) in; +layout(binding = 0, rg16f) writeonly uniform image2D colorBuffer; + +const float PI = 3.14159265359; + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness); +float GeometrySchlickGGX(float NdotV, float roughness); +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); +vec2 Hammersley(uint i, uint N); +float RadicalInverse_VdC(uint bits); + +void main() +{ + uvec2 colorBufferSize = imageSize(colorBuffer); + if (gl_GlobalInvocationID.x >= colorBufferSize.x || gl_GlobalInvocationID.y >= colorBufferSize.y) + return; + + vec2 uv = (vec2(gl_GlobalInvocationID.xy) + 0.5) / vec2(colorBufferSize); + + float NdotV = uv.x; + float roughness = uv.y; + + const uint SAMPLE_COUNT = 1024u; + + vec3 V = vec3(sqrt(1.0 - NdotV*NdotV), 0.0, NdotV); + vec3 N = vec3(0.0, 0.0, 1.0); + + float A = 0.0; + float B = 0.0; + + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + + if (NdotL > 0.0) + { + float G = GeometrySmith(N, V, L, roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + + imageStore(colorBuffer, ivec2(gl_GlobalInvocationID.xy), vec4(A, B, 0.0, 1.0)); +} + +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); +} + +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) +{ + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +} + +float GeometrySchlickGGX(float NdotV, float roughness) +{ + float a = roughness; + float k = (a * a) / 2.0; + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + return nom / denom; +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + return ggx1 * ggx2; +} diff --git a/wadsrc/static/shaders/lightprobe/comp_irradiance_convolute.glsl b/wadsrc/static/shaders/lightprobe/comp_irradiance_convolute.glsl new file mode 100644 index 000000000..e293a569d --- /dev/null +++ b/wadsrc/static/shaders/lightprobe/comp_irradiance_convolute.glsl @@ -0,0 +1,50 @@ + +layout(local_size_x = 16, local_size_y = 16) in; +layout(binding = 0, rgba16f) writeonly uniform image2D colorBuffer; +layout(binding = 1) uniform samplerCube EnvironmentMap; + +layout(push_constant) uniform PushConstants +{ + vec4 topLeft; + vec4 bottomRight; +}; + +const float PI = 3.14159265359; + +void main() +{ + uvec2 colorBufferSize = imageSize(colorBuffer); + if (gl_GlobalInvocationID.x >= colorBufferSize.x || gl_GlobalInvocationID.y >= colorBufferSize.y) + return; + + vec2 uv = (vec2(gl_GlobalInvocationID.xy) + 0.5) / vec2(colorBufferSize); + vec3 FragPos = mix(topLeft.xyz, bottomRight.xyz, uv); + + vec3 normal = normalize(FragPos); + vec3 right = normalize(cross(vec3(0.0, 1.0, 0.0), normal)); + vec3 up = normalize(cross(normal, right)); + + float sampleDelta = 0.025; + float sampleCount = 0.0; + + vec4 irradiance = vec4(0.0); + + for (float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) + { + for (float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) + { + // spherical to cartesian (in tangent space) + vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + + // tangent space to world + vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * normal; + + irradiance += texture(EnvironmentMap, sampleVec) * cos(theta) * sin(theta); + sampleCount++; + } + } + + irradiance = PI * irradiance * (1.0 / sampleCount); + + imageStore(colorBuffer, ivec2(gl_GlobalInvocationID.xy), irradiance); +} diff --git a/wadsrc/static/shaders/lightprobe/comp_prefilter_convolute.glsl b/wadsrc/static/shaders/lightprobe/comp_prefilter_convolute.glsl new file mode 100644 index 000000000..15f646c20 --- /dev/null +++ b/wadsrc/static/shaders/lightprobe/comp_prefilter_convolute.glsl @@ -0,0 +1,87 @@ + +layout(local_size_x = 16, local_size_y = 16) in; +layout(binding = 0, rgba16f) writeonly uniform image2D colorBuffer; +layout(binding = 1) uniform samplerCube EnvironmentMap; + +layout(push_constant) uniform PushConstants +{ + vec4 topLeft; + vec4 bottomRight; + float roughness; + float padding1, padding2, padding3; +}; + +const float PI = 3.14159265359; + +float RadicalInverse_VdC(uint bits); +vec2 Hammersley(uint i, uint N); +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); + +void main() +{ + uvec2 colorBufferSize = imageSize(colorBuffer); + if (gl_GlobalInvocationID.x >= colorBufferSize.x || gl_GlobalInvocationID.y >= colorBufferSize.y) + return; + + vec2 uv = (vec2(gl_GlobalInvocationID.xy) + 0.5) / vec2(colorBufferSize); + vec3 FragPos = mix(topLeft.xyz, bottomRight.xyz, uv); + + vec3 normal = normalize(FragPos); + + const int SAMPLE_COUNT = 1024; + + float totalWeight = 0.0; + vec4 prefilteredColor = vec4(0.0); + + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = Hammersley(uint(i), uint(SAMPLE_COUNT)); + vec3 H = ImportanceSampleGGX(Xi, normal, roughness); + vec3 L = normalize(2.0 * dot(normal, H) * H - normal); + + float NdotL = clamp(dot(normal, L), 0.0, 1.0); + if(NdotL > 0.0) + { + prefilteredColor += texture(EnvironmentMap, L) * NdotL; + totalWeight += NdotL; + } + } + + prefilteredColor = prefilteredColor / totalWeight; + + imageStore(colorBuffer, ivec2(gl_GlobalInvocationID.xy), prefilteredColor); +} + +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); +} + +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) +{ + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +}