diff --git a/samples/04_scenes/CMakeLists.txt b/samples/04_scenes/CMakeLists.txt index ef40a12..ba57faf 100644 --- a/samples/04_scenes/CMakeLists.txt +++ b/samples/04_scenes/CMakeLists.txt @@ -13,11 +13,18 @@ add_executable(scene_render main.cpp pipeline_utils.cpp pipeline_utils.h core_components.h ecs_adapter.h - camera.h) + camera.h + ibl_helpers.cpp ibl_helpers.h + light_manager.cpp light_manager.h) add_shader(scene_render shader/model.frag.glsl) add_shader(scene_render shader/model.vert.glsl) -# add_shader(scene_render shader/model.vs.hlsl) +add_shader(scene_render shader/eqrect_to_cube.cs.hlsl) +add_shader(scene_render shader/diffuse_irradiance.cs.hlsl) +add_shader(scene_render shader/prefilter.cs.hlsl) +add_shader(scene_render shader/brdf_lut.cs.hlsl) +add_shader(scene_render shader/background.vert.glsl) +add_shader(scene_render shader/background.frag.glsl) target_link_libraries(scene_render PRIVATE aster_core) target_link_libraries(scene_render PRIVATE util_helper) diff --git a/samples/04_scenes/asset_loader.cpp b/samples/04_scenes/asset_loader.cpp index cf7d5b9..4290083 100644 --- a/samples/04_scenes/asset_loader.cpp +++ b/samples/04_scenes/asset_loader.cpp @@ -99,9 +99,8 @@ AssetLoader::LoadHdrImage(Texture *texture, cstr path, cstr name) const i32 x, y, nChannels; f32 *data = stbi_loadf(path, &x, &y, &nChannels, 4); - assert(nChannels == 3); - ERROR_IF(!data, "Could not load {}", path) THEN_ABORT(-1); + assert(nChannels == 3); u32 width = Cast(x); u32 height = Cast(y); diff --git a/samples/04_scenes/asset_loader.h b/samples/04_scenes/asset_loader.h index 8ea83a5..dc5ce9d 100644 --- a/samples/04_scenes/asset_loader.h +++ b/samples/04_scenes/asset_loader.h @@ -35,8 +35,6 @@ struct Material TextureHandle m_MetalRoughTex; // 04 48 TextureHandle m_OcclusionTex; // 04 52 TextureHandle m_EmissionTex; // 04 56 - - static constexpr usize ALIGNMENT = 4; }; static_assert(sizeof(Material) == 56); @@ -47,8 +45,6 @@ struct VertexData vec2 m_TexCoord0 = vec2{0.0f, 0.0f}; vec2 m_TexCoord1 = vec2{0.0f, 0.0f}; vec4 m_Color0 = vec4{1.0f, 1.0f, 1.0f, 1.0f}; - - static constexpr usize ALIGNMENT = 16; }; struct Model diff --git a/samples/04_scenes/ibl_helpers.cpp b/samples/04_scenes/ibl_helpers.cpp new file mode 100644 index 0000000..b52e48e --- /dev/null +++ b/samples/04_scenes/ibl_helpers.cpp @@ -0,0 +1,395 @@ +// ============================================= +// Aster: ibl_helpers.cpp +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "ibl_helpers.h" + +#include "EASTL/fixed_vector.h" +#include "EASTL/tuple.h" +#include "asset_loader.h" +#include "device.h" +#include "render_resource_manager.h" +#include "helpers.h" +#include "image.h" +#include "pipeline_utils.h" + +constexpr cstr EQUIRECT_TO_CUBE_SHADER_FILE = "shader/eqrect_to_cube.cs.hlsl.spv"; +constexpr cstr DIFFUSE_IRRADIANCE_SHADER_FILE = "shader/diffuse_irradiance.cs.hlsl.spv"; +constexpr cstr PREFILTER_SHADER_FILE = "shader/prefilter.cs.hlsl.spv"; +constexpr cstr BRDF_LUT_SHADER_FILE = "shader/brdf_lut.cs.hlsl.spv"; + +void +Environment::Destroy(RenderResourceManager *resourceManager) +{ + resourceManager->Release(Take(m_Skybox)); + resourceManager->Release(Take(m_Diffuse)); + resourceManager->Release(Take(m_Prefilter)); + resourceManager->Release(Take(m_BrdfLut)); +} + +Environment +CreateEnvironment(AssetLoader *assetLoader, vk::Queue computeQueue, const u32 cubeSide, TextureHandle hdrEnv, + const cstr name) +{ + RenderResourceManager *resMan = assetLoader->m_ResourceManager; + const Device *pDevice = resMan->m_Device; + + vk::SamplerCreateInfo brdfLutSamplerCreateInfo = resMan->m_DefaultSamplerCreateInfo; + brdfLutSamplerCreateInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; + brdfLutSamplerCreateInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; + brdfLutSamplerCreateInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; + + StorageTextureCube skybox; + StorageTextureCube diffuseIrradiance; + StorageTextureCube prefilterCube; + StorageTexture brdfLut; + SamplerHandle brdfLutSampler; + + skybox.Init(pDevice, cubeSide, vk::Format::eR16G16B16A16Sfloat, true, true, "Skybox"); + TextureHandle skyboxHandle = resMan->CommitTexture(&skybox); + StorageTextureHandle skyboxStorageHandle = resMan->CommitStorageTexture(&skybox); + + diffuseIrradiance.Init(pDevice, 64, vk::Format::eR16G16B16A16Sfloat, true, false, "Diffuse Irradiance"); + TextureHandle diffuseIrradianceHandle = resMan->CommitTexture(&diffuseIrradiance); + StorageTextureHandle diffuseIrradianceStorageHandle = resMan->CommitStorageTexture(&diffuseIrradiance); + + prefilterCube.Init(pDevice, cubeSide, vk::Format::eR16G16B16A16Sfloat, true, true, "Prefilter"); + TextureHandle prefilterHandle = resMan->CommitTexture(&prefilterCube); // This stores the original view for us. + constexpr u32 prefilterMipCountMax = 6; + eastl::array prefilterStorageHandles; + // All non-owning copies. + for (u32 mipLevel = 0; auto &tex : prefilterStorageHandles) + { + vk::ImageViewCreateInfo imageViewCreateInfo = { + .image = prefilterCube.m_Image, + .viewType = vk::ImageViewType::eCube, + .format = vk::Format::eR16G16B16A16Sfloat, + .components = vk::ComponentMapping{}, + .subresourceRange = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = mipLevel++, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 6, + }, + }; + AbortIfFailed(pDevice->m_Device.createImageView(&imageViewCreateInfo, nullptr, &prefilterCube.m_View)); + tex = resMan->CommitStorageTexture(&prefilterCube); + } + + brdfLut.Init(pDevice, {512, 512}, vk::Format::eR16G16Sfloat, true, "BRDF LUT"); + brdfLutSampler = resMan->CreateSampler(&brdfLutSamplerCreateInfo); + TextureHandle brdfLutHandle = resMan->CommitTexture(&brdfLut, brdfLutSampler); + StorageTextureHandle brdfLutStorageHandle = resMan->CommitStorageTexture(&brdfLut); + +#pragma region Dependencies and Copies + vk::ImageSubresourceRange cubeSubresRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 6, + }; + vk::ImageSubresourceRange lutSubresRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + vk::ImageMemoryBarrier2 readyToWriteBarrierTemplate = { + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .dstAccessMask = vk::AccessFlagBits2::eShaderStorageWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eGeneral, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .subresourceRange = cubeSubresRange, + }; + eastl::fixed_vector readyToWriteBarriers(4, readyToWriteBarrierTemplate); + readyToWriteBarriers[0].image = skybox.m_Image; + readyToWriteBarriers[1].image = diffuseIrradiance.m_Image; + readyToWriteBarriers[2].image = prefilterCube.m_Image; + readyToWriteBarriers[3].image = brdfLut.m_Image; + readyToWriteBarriers[3].subresourceRange = lutSubresRange; + + vk::DependencyInfo readyToWriteDependency = { + .imageMemoryBarrierCount = Cast(readyToWriteBarriers.size()), + .pImageMemoryBarriers = readyToWriteBarriers.data(), + }; + + vk::ImageMemoryBarrier2 readyToSampleBarrierTemplate = { + .srcStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .srcAccessMask = vk::AccessFlagBits2::eShaderStorageWrite | vk::AccessFlagBits2::eShaderStorageRead, + .dstStageMask = vk::PipelineStageFlagBits2::eBottomOfPipe, + .oldLayout = vk::ImageLayout::eGeneral, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .subresourceRange = cubeSubresRange, + }; + auto skyboxToSampleBarrier = readyToSampleBarrierTemplate; + skyboxToSampleBarrier.image = skybox.m_Image; + + auto diffIrrToSampleBarrier = readyToSampleBarrierTemplate; + diffIrrToSampleBarrier.image = diffuseIrradiance.m_Image; + + auto prefilterToSampleBarrier = readyToSampleBarrierTemplate; + prefilterToSampleBarrier.image = prefilterCube.m_Image; + + auto brdfToSampleBarrier = readyToSampleBarrierTemplate; + prefilterToSampleBarrier.image = brdfLut.m_Image; + prefilterToSampleBarrier.subresourceRange = lutSubresRange; + + vk::DependencyInfo skyboxToSampleDependency = { + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &skyboxToSampleBarrier, + }; + vk::DependencyInfo diffIrrToSampleDependency = { + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &diffIrrToSampleBarrier, + }; + vk::DependencyInfo prefilterToSampleDependency = { + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &prefilterToSampleBarrier, + }; + vk::DependencyInfo brdfToSampleDependency = { + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &brdfToSampleBarrier, + }; + +#pragma endregion + + struct SkyboxPushConstants + { + TextureHandle m_HdrEnvHandle; + StorageTextureHandle m_OutputTexture; + u32 m_CubeSide; + }; + struct DiffuseIrradiancePushConstants + { + TextureHandle m_SkyboxHandle; + StorageTextureHandle m_OutputTexture; + u32 m_CubeSide; + }; + struct PrefilterPushConstants + { + TextureHandle m_SkyboxHandle; + StorageTextureHandle m_OutputTexture; + u32 m_CubeSide; + f32 m_Roughness; + u32 m_EnvSide; + }; + struct BrdfLutPushConstants + { + StorageTextureHandle m_OutputTexture; + }; + +#pragma region Pipeline Creation etc + + vk::PushConstantRange pcr = { + .stageFlags = vk::ShaderStageFlagBits::eCompute, + .offset = 0, + .size = eastl::max(eastl::max(sizeof(SkyboxPushConstants), sizeof(BrdfLutPushConstants)), + eastl::max(sizeof(DiffuseIrradiancePushConstants), sizeof(PrefilterPushConstants))), + }; + + vk::PipelineLayout pipelineLayout; + const vk::PipelineLayoutCreateInfo layoutCreateInfo = { + .setLayoutCount = 1, + .pSetLayouts = &resMan->m_SetLayout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &pcr, + }; + AbortIfFailed(pDevice->m_Device.createPipelineLayout(&layoutCreateInfo, nullptr, &pipelineLayout)); + + const auto eqRectToCubeShader = CreateShader(pDevice, EQUIRECT_TO_CUBE_SHADER_FILE); + const auto diffuseRadianceShader = CreateShader(pDevice, DIFFUSE_IRRADIANCE_SHADER_FILE); + const auto prefilterShader = CreateShader(pDevice, PREFILTER_SHADER_FILE); + const auto brdfLutShader = CreateShader(pDevice, BRDF_LUT_SHADER_FILE); + eastl::array computePipelineCreateInfo = { + vk::ComputePipelineCreateInfo{ + .stage = + { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = eqRectToCubeShader, + .pName = "main", + }, + .layout = pipelineLayout, + }, + vk::ComputePipelineCreateInfo{ + .stage = + { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = diffuseRadianceShader, + .pName = "main", + }, + .layout = pipelineLayout, + }, + vk::ComputePipelineCreateInfo{ + .stage = + { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = prefilterShader, + .pName = "main", + }, + .layout = pipelineLayout, + }, + vk::ComputePipelineCreateInfo{ + .stage = + { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = brdfLutShader, + .pName = "main", + }, + .layout = pipelineLayout, + }, + }; + + eastl::array pipelines; + AbortIfFailed(pDevice->m_Device.createComputePipelines(pDevice->m_PipelineCache, computePipelineCreateInfo.size(), + computePipelineCreateInfo.data(), nullptr, + pipelines.data())); + + vk::Pipeline eqRectToCubePipeline = pipelines[0]; + vk::Pipeline diffuseIrradiancePipeline = pipelines[1]; + vk::Pipeline prefilterPipeline = pipelines[2]; + vk::Pipeline brdfLutPipeline = pipelines[3]; + + for (auto &createInfos : computePipelineCreateInfo) + { + pDevice->m_Device.destroy(createInfos.stage.module, nullptr); + } + +#pragma endregion + + SkyboxPushConstants skyboxPushConstant = { + .m_HdrEnvHandle = hdrEnv, + .m_OutputTexture = skyboxStorageHandle, + .m_CubeSide = cubeSide, + }; + DiffuseIrradiancePushConstants diffuseIrradiancePushConstants = { + .m_SkyboxHandle = skyboxHandle, + .m_OutputTexture = diffuseIrradianceStorageHandle, + .m_CubeSide = diffuseIrradiance.m_Extent.width, + }; + PrefilterPushConstants prefilterPushConstants = { + .m_SkyboxHandle = skyboxHandle, + .m_EnvSide = cubeSide, + }; + BrdfLutPushConstants brdfLutPushConstants = { + .m_OutputTexture = brdfLutStorageHandle, + }; + + resMan->Update(); + + auto cmd = assetLoader->m_CommandBuffer; + constexpr vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + AbortIfFailed(cmd.begin(&beginInfo)); + +#if !defined(ASTER_NDEBUG) + StackString<128> labelName = "Eqrect -> Cubemap: "; + labelName += name ? name : ""; + vk::DebugUtilsLabelEXT label = { + .pLabelName = labelName.c_str(), + .color = std::array{1.0f, 1.0f, 1.0f, 1.0f}, + }; + cmd.beginDebugUtilsLabelEXT(&label); +#endif + + cmd.pipelineBarrier2(&readyToWriteDependency); + + cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipelineLayout, 0, 1, &resMan->m_DescriptorSet, 0, nullptr); + cmd.bindPipeline(vk::PipelineBindPoint::eCompute, eqRectToCubePipeline); + cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof skyboxPushConstant, + &skyboxPushConstant); + assert(skybox.m_Extent.width % 16 == 0 && skybox.m_Extent.height % 16 == 0); + cmd.dispatch(skybox.m_Extent.width / 16, skybox.m_Extent.height / 16, 6); + + GenerateMipMaps(cmd, &skybox, vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral, + vk::PipelineStageFlagBits2::eComputeShader, vk::PipelineStageFlagBits2::eComputeShader); + + cmd.bindPipeline(vk::PipelineBindPoint::eCompute, diffuseIrradiancePipeline); + cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof skyboxPushConstant, + &diffuseIrradiancePushConstants); + assert(diffuseIrradiance.m_Extent.width % 16 == 0 && diffuseIrradiance.m_Extent.height % 16 == 0); + cmd.dispatch(diffuseIrradiance.m_Extent.width / 16, diffuseIrradiance.m_Extent.width / 16, 6); + + cmd.pipelineBarrier2(&diffIrrToSampleDependency); + + cmd.bindPipeline(vk::PipelineBindPoint::eCompute, prefilterPipeline); + u32 mipSize = prefilterCube.m_Extent.width; + assert(mipSize % 16 == 0); + for (u32 mipCount = 0; auto &tex : prefilterStorageHandles) + { + prefilterPushConstants.m_OutputTexture = tex; + prefilterPushConstants.m_CubeSide = mipSize; + prefilterPushConstants.m_Roughness = Cast(mipCount) / Cast(prefilterMipCountMax); + cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof prefilterPushConstants, + &prefilterPushConstants); + u32 groupCount = eastl::max(mipSize / 16u, 1u); + cmd.dispatch(groupCount, groupCount, 6); + + ++mipCount; + mipSize = mipSize >> 1; + } + + cmd.pipelineBarrier2(&skyboxToSampleDependency); + cmd.pipelineBarrier2(&prefilterToSampleDependency); + + cmd.bindPipeline(vk::PipelineBindPoint::eCompute, brdfLutPipeline); + cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof brdfLutPushConstants, + &brdfLutPushConstants); + assert(brdfLut.m_Extent.width % 16 == 0 && brdfLut.m_Extent.height % 16 == 0); + cmd.dispatch(brdfLut.m_Extent.width / 16, brdfLut.m_Extent.height / 16, 1); + +#if !defined(ASTER_NDEBUG) + cmd.endDebugUtilsLabelEXT(); +#endif + + AbortIfFailed(cmd.end()); + + vk::SubmitInfo submitInfo = { + .waitSemaphoreCount = 0, + .pWaitDstStageMask = nullptr, + .commandBufferCount = 1, + .pCommandBuffers = &cmd, + }; + + vk::Fence fence; + vk::FenceCreateInfo fenceCreateInfo = {}; + AbortIfFailed(pDevice->m_Device.createFence(&fenceCreateInfo, nullptr, &fence)); + AbortIfFailed(computeQueue.submit(1, &submitInfo, fence)); + AbortIfFailed(pDevice->m_Device.waitForFences(1, &fence, true, MaxValue)); + pDevice->m_Device.destroy(fence, nullptr); + + AbortIfFailed(pDevice->m_Device.resetCommandPool(assetLoader->m_CommandPool, {})); + + skybox = {}; + resMan->Release(skyboxStorageHandle); + resMan->Release(diffuseIrradianceStorageHandle); + resMan->Release(brdfLutStorageHandle); + for (auto &texHandles : prefilterStorageHandles) + { + StorageTextureCube st; + resMan->Release(&st, texHandles); + pDevice->m_Device.destroy(st.m_View, nullptr); + } + for (auto &pipeline : pipelines) + { + pDevice->m_Device.destroy(pipeline, nullptr); + } + pDevice->m_Device.destroy(pipelineLayout, nullptr); + + return { + .m_Skybox = skyboxHandle, + .m_Diffuse = diffuseIrradianceHandle, + .m_Prefilter = prefilterHandle, + .m_BrdfLut = brdfLutHandle, + }; +} \ No newline at end of file diff --git a/samples/04_scenes/ibl_helpers.h b/samples/04_scenes/ibl_helpers.h new file mode 100644 index 0000000..624b5df --- /dev/null +++ b/samples/04_scenes/ibl_helpers.h @@ -0,0 +1,28 @@ +// ============================================= +// Aster: ibl_helpers.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "global.h" +#include "render_resource_manager.h" + +struct Pipeline; +struct Texture; +struct TextureCube; +struct AssetLoader; + +struct Environment +{ + TextureHandle m_Skybox; + TextureHandle m_Diffuse; + TextureHandle m_Prefilter; + TextureHandle m_BrdfLut; + + void Destroy(RenderResourceManager *resourceManager); +}; + +Environment +CreateEnvironment(AssetLoader *assetLoader, vk::Queue computeQueue, u32 cubeSide, TextureHandle hdrEnv, + cstr name = nullptr); \ No newline at end of file diff --git a/samples/04_scenes/image/photo_studio_loft_hall_4k.hdr b/samples/04_scenes/image/photo_studio_loft_hall_4k.hdr new file mode 100644 index 0000000..beea1dc --- /dev/null +++ b/samples/04_scenes/image/photo_studio_loft_hall_4k.hdr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b1e43955efa60a24e8021994d3ab6e630ba142fbd02d1530a0cc0ab53ec41a2 +size 25245847 diff --git a/samples/04_scenes/light_manager.cpp b/samples/04_scenes/light_manager.cpp new file mode 100644 index 0000000..bbf9b0b --- /dev/null +++ b/samples/04_scenes/light_manager.cpp @@ -0,0 +1,303 @@ +// ============================================= +// Aster: light_manager.cpp +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "light_manager.h" + +#include "buffer.h" +#include "ibl_helpers.h" +#include "glm/ext/matrix_transform.hpp" + +struct Light +{ + union { + vec3 um_Position; + vec3 um_Direction; + }; + f32 m_Range; // < 0.0 for invalid + u32 m_Color_; // LSB is used for flags. (R G B Flags) + f32 m_Intensity; + + constexpr static u32 MAX_GEN = 0x40; + constexpr static u32 GEN_MASK = MAX_GEN - 1; + + constexpr static u32 TYPE_MASK = 0xC0; + constexpr static u32 TYPE_INVALID = 0x0; + constexpr static u32 TYPE_DIRECTIONAL = 1 << 6; + constexpr static u32 TYPE_POINT = 2 << 6; + constexpr static u32 TYPE_SPOT = 3 << 6; // Currently Unused + + constexpr static u32 COLOR_MASK = ~(GEN_MASK | TYPE_MASK); +}; + +// Static Checks + +// Ensure layouts are exact. +static_assert(offsetof(DirectionalLight, m_Direction) == offsetof(Light, um_Direction)); +static_assert(offsetof(DirectionalLight, m_Color_) == offsetof(Light, m_Color_)); +static_assert(offsetof(DirectionalLight, m_Intensity) == offsetof(Light, m_Intensity)); +static_assert(sizeof(DirectionalLight) <= sizeof(Light)); + +// Ensure layouts are exact. +static_assert(offsetof(PointLight, m_Position) == offsetof(Light, um_Position)); +static_assert(offsetof(PointLight, m_Range) == offsetof(Light, m_Range)); +static_assert(offsetof(PointLight, m_Color_) == offsetof(Light, m_Color_)); +static_assert(offsetof(PointLight, m_Intensity) == offsetof(Light, m_Intensity)); +static_assert(sizeof(PointLight) <= sizeof(Light)); + +// Ensure bitmask are in the right place. +static_assert((Light::TYPE_MASK & Light::TYPE_INVALID) == Light::TYPE_INVALID); +static_assert((Light::TYPE_MASK & Light::TYPE_DIRECTIONAL) == Light::TYPE_DIRECTIONAL); +static_assert((Light::TYPE_MASK & Light::TYPE_POINT) == Light::TYPE_POINT); +static_assert((Light::TYPE_MASK & Light::TYPE_SPOT) == Light::TYPE_SPOT); +static_assert(Light::COLOR_MASK == 0xFFFFFF00); + +inline u32 +ToColor32(const vec4 &col) +{ + const u32 r = Cast(eastl::min(col.r, 1.0f) * 255.99f); + const u32 g = Cast(eastl::min(col.g, 1.0f) * 255.99f); + const u32 b = Cast(eastl::min(col.b, 1.0f) * 255.99f); + const u32 a = Cast(eastl::min(col.a, 1.0f) * 255.99f); + + return r << 24 | g << 16 | b << 8 | a; +} + +inline u32 +ToColor32(const vec3 &col) +{ + const u32 r = Cast(eastl::min(col.r, 1.0f) * 255.99f); + const u32 g = Cast(eastl::min(col.g, 1.0f) * 255.99f); + const u32 b = Cast(eastl::min(col.b, 1.0f) * 255.99f); + constexpr u32 a = 255; + + return r << 24 | g << 16 | b << 8 | a; +} + +LightManager::LightManager(RenderResourceManager *resourceManager) + : m_ResourceManager{resourceManager} + , m_DirectionalLightCount{} + , m_PointLightCount{} + , m_MetaInfo{} + , m_GpuBufferCapacity_{0} +{ +} + +LightManager::~LightManager() +{ + m_ResourceManager->Release(m_MetaInfo.m_LightBuffer); + m_ResourceManager->Release(m_MetaInfo.m_Skybox); + m_ResourceManager->Release(m_MetaInfo.m_Diffuse); + m_ResourceManager->Release(m_MetaInfo.m_Prefilter); + m_ResourceManager->Release(m_MetaInfo.m_BrdfLut); +} + +LightManager::LightManager(LightManager &&other) noexcept + : m_ResourceManager(other.m_ResourceManager) + , m_Lights(std::move(other.m_Lights)) + , m_DirectionalLightCount(other.m_DirectionalLightCount) + , m_PointLightCount(other.m_PointLightCount) + , m_MetaInfo(other.m_MetaInfo) + , m_GpuBufferCapacity_(other.m_GpuBufferCapacity_) +{ + other.m_MetaInfo.m_LightBuffer = {}; +} + +LightManager & +LightManager::operator=(LightManager &&other) noexcept +{ + if (this == &other) + return *this; + m_ResourceManager = other.m_ResourceManager; + m_Lights = std::move(other.m_Lights); + m_DirectionalLightCount = other.m_DirectionalLightCount; + m_PointLightCount = other.m_PointLightCount; + m_MetaInfo = other.m_MetaInfo; + other.m_MetaInfo.m_LightBuffer = {}; + m_GpuBufferCapacity_ = other.m_GpuBufferCapacity_; + return *this; +} + +LightHandle +LightManager::AddDirectional(const vec3 &direction, const vec3 &color, f32 intensity) +{ + const vec3 normDirection = normalize(direction); + if (m_DirectionalLightCount < m_MetaInfo.m_DirectionalLightMaxCount) + { + u16 index = 0; + for (auto &light : m_Lights) + { + if (light.m_Range < 0) + { + const u8 gen = light.m_Color_ & Light::GEN_MASK; + + light.m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen; + light.m_Range = 1.0f; + light.um_Direction = normDirection; + light.m_Intensity = intensity; + + m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT; + + return {Light::TYPE_DIRECTIONAL, gen, index}; + } + ++index; + assert(index < m_MetaInfo.m_DirectionalLightMaxCount); // Gap not found. But must exist + } + } + + // In case we will end up intersecting, we move point lights away. (2 at a time) + if (m_DirectionalLightCount == m_MetaInfo.m_DirectionalLightMaxCount && + m_MetaInfo.m_DirectionalLightMaxCount == m_MetaInfo.m_PointLightOffset) + { + const u16 oldPointLightOffset = m_MetaInfo.m_PointLightOffset; + const u32 pointLightMaxCount = m_MetaInfo.m_PointLightMaxCount; + // Might cause a capacity increase, but I want to use that for my gpu buffer resize. + m_Lights.push_back(); + m_Lights.push_back(); + + if (m_MetaInfo.m_PointLightMaxCount > 0) // Edge Case: nullptr at size 0 + { + Light *oldPointStart = m_Lights.data() + oldPointLightOffset; + Light *oldPointEnd = oldPointStart + pointLightMaxCount; + Light *newPointEnd = oldPointEnd + 2; + + static_assert(std::is_trivially_copyable_v); + + // Overlaps since 0 -> 2, 1 -> 3, 2 -> 4 (old 2 is lost) + // Backward copy fixes this. + std::copy_backward(oldPointStart, oldPointEnd, newPointEnd); + } + + m_MetaInfo.m_PointLightOffset += 2; + } + + constexpr u8 gen = 0; // New light + m_Lights[m_DirectionalLightCount].m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen; + m_Lights[m_DirectionalLightCount].m_Range = 1.0f; + m_Lights[m_DirectionalLightCount].um_Direction = normDirection; + m_Lights[m_DirectionalLightCount].m_Intensity = intensity; + const u16 index = m_DirectionalLightCount; + + ++m_DirectionalLightCount; + ++m_MetaInfo.m_DirectionalLightMaxCount; + + return {Light::TYPE_DIRECTIONAL, gen, index}; +} + +LightHandle +LightManager::AddPoint(const vec3 &position, const vec3 &color, const f32 radius, f32 intensity) +{ + assert(m_PointLightCount <= m_MetaInfo.m_PointLightMaxCount); + assert(radius >= 0.0f); + if (m_PointLightCount < m_MetaInfo.m_PointLightMaxCount) + { + Light *light = m_Lights.data() + m_MetaInfo.m_PointLightOffset; + for (u32 index = 0; index < m_MetaInfo.m_PointLightMaxCount; ++index) + { + if (light->m_Range < 0) + { + const u8 gen = light->m_Color_ & Light::GEN_MASK; + + light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen; + light->m_Range = radius; + light->um_Position = position; + light->m_Intensity = intensity; + + m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT; + + return {Light::TYPE_POINT, gen, Cast(index)}; + } + ++light; + } + assert(false); // gap must exists. + return {}; + } + + m_Lights.push_back(); + const u16 index = m_PointLightCount; + + Light *light = &m_Lights[index + m_MetaInfo.m_PointLightOffset]; + constexpr u8 gen = 0; // New light + + light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen; + light->m_Range = radius; + light->um_Position = position; + light->m_Intensity = intensity; + + ++m_PointLightCount; + ++m_MetaInfo.m_PointLightMaxCount; + + m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT; + + return {Light::TYPE_POINT, gen, index}; +} + +void +LightManager::Update() +{ + const u16 requiredBufferCapacity = eastl::min(Cast(m_Lights.capacity()), MAX_LIGHTS); + if ((m_GpuBufferCapacity_ & CAPACITY_MASK) < requiredBufferCapacity) + { + StorageBuffer newBuffer; + newBuffer.Init(m_ResourceManager->m_Device, requiredBufferCapacity * sizeof m_Lights[0], true, "Light Buffer"); + m_GpuBufferCapacity_ = requiredBufferCapacity | UPDATE_REQUIRED_BIT; + + m_ResourceManager->Release(m_MetaInfo.m_LightBuffer); + m_MetaInfo.m_LightBuffer = m_ResourceManager->Commit(&newBuffer); + } + if (m_GpuBufferCapacity_ & UPDATE_REQUIRED_BIT) + { + m_ResourceManager->Write(m_MetaInfo.m_LightBuffer, 0, m_Lights.size() * sizeof m_Lights[0], m_Lights.data()); + } +} + +void +LightManager::RemoveLight(const LightHandle handle) +{ + const u8 handleGen = handle.m_Generation; + + if (handle.m_Type == Light::TYPE_DIRECTIONAL) + { + Light *lightSlot = &m_Lights[handle.m_Index]; + const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK; + if (slotGen > handleGen) + { + WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen); + return; + } + + lightSlot->m_Range = -1.0f; + lightSlot->m_Color_ = Light::TYPE_INVALID | (slotGen + 1) % Light::MAX_GEN; + --m_DirectionalLightCount; + } + + if (handle.m_Type == Light::TYPE_POINT) + { + Light *lightSlot = &m_Lights[handle.m_Index + m_MetaInfo.m_PointLightOffset]; + const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK; + if (slotGen > handleGen) + { + WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen); + return; + } + + lightSlot->m_Range = -1.0f; + lightSlot->m_Color_ = Light::TYPE_INVALID | (slotGen + 1) % Light::MAX_GEN; + --m_PointLightCount; + } +} + +void +LightManager::SetEnvironment(Environment &&environment) +{ + m_ResourceManager->Release(m_MetaInfo.m_Skybox); + m_ResourceManager->Release(m_MetaInfo.m_Diffuse); + m_ResourceManager->Release(m_MetaInfo.m_Prefilter); + m_ResourceManager->Release(m_MetaInfo.m_BrdfLut); + + m_MetaInfo.m_Skybox = environment.m_Skybox; + m_MetaInfo.m_Diffuse = environment.m_Diffuse; + m_MetaInfo.m_Prefilter = environment.m_Prefilter; + m_MetaInfo.m_BrdfLut = environment.m_BrdfLut; +} \ No newline at end of file diff --git a/samples/04_scenes/light_manager.h b/samples/04_scenes/light_manager.h new file mode 100644 index 0000000..8e83940 --- /dev/null +++ b/samples/04_scenes/light_manager.h @@ -0,0 +1,91 @@ +// ============================================= +// Aster: light_manager.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "global.h" + +// TODO: Separate files so you only import handles. +#include "render_resource_manager.h" + +struct Environment; + +struct DirectionalLight +{ + vec3 m_Direction; + u32 m_UnusedPadding0_; + u32 m_Color_; // LSB is used for flags. (R G B Flags) + f32 m_Intensity; +}; + +struct PointLight +{ + vec3 m_Position; + f32 m_Range; + u32 m_Color_; // LSB is used for flags. (R G B Flags) + f32 m_Intensity; +}; + +struct LightHandle +{ + u8 m_Type; + u8 m_Generation; + u16 m_Index; +}; + +struct Light; + +struct LightManager +{ + constexpr static u16 MAX_LIGHTS = MaxValue; + struct LightMetaInfo + { + TextureHandle m_Skybox; // 04 04 + TextureHandle m_Diffuse; // 04 08 + TextureHandle m_Prefilter; // 04 12 + TextureHandle m_BrdfLut; // 04 16 + + // The number of directional lights is relatively low (1 - 2) and will almost never change in a scene. + // We can use that with Offset = 0, and point light at further offsets. + // This way we don't need to move point lights often. + BufferHandle m_LightBuffer; // 04 20 + u16 m_PointLightMaxCount; // 02 22 + u16 m_PointLightOffset; // 02 24 + u16 m_DirectionalLightMaxCount; // 02 26 + u16 m_UnusedPadding0 = 0; // 02 28 + }; + + RenderResourceManager *m_ResourceManager; + eastl::vector m_Lights; + + // We don't need a Directional Light free list. We will just brute force iterate. + u16 m_DirectionalLightCount; + // TODO: A point light free list. We will brute force until we have a lot (100+) of point lights. + u16 m_PointLightCount; + + LightMetaInfo m_MetaInfo; + // Using lower bit for flags. Use CAPACITY_MASK for value. + u16 m_GpuBufferCapacity_; + + // Using lower bit. Capacity can be directly a multiple of 2 + // Thus, range is up to MaxValue + constexpr static u16 UPDATE_REQUIRED_BIT = 1; + constexpr static u16 CAPACITY_MASK = ~(UPDATE_REQUIRED_BIT); + + LightHandle AddDirectional(const vec3 &direction, const vec3 &color, f32 intensity); + LightHandle AddPoint(const vec3 &position, const vec3 &color, f32 radius, f32 intensity); + void Update(); + void RemoveLight(LightHandle handle); + + void SetEnvironment(Environment &&environment); + + explicit LightManager(RenderResourceManager *resourceManager); + ~LightManager(); + + LightManager(LightManager &&other) noexcept; + LightManager &operator=(LightManager &&other) noexcept; + + DISALLOW_COPY_AND_ASSIGN(LightManager); +}; \ No newline at end of file diff --git a/samples/04_scenes/main.cpp b/samples/04_scenes/main.cpp index db43aef..16bde5e 100644 --- a/samples/04_scenes/main.cpp +++ b/samples/04_scenes/main.cpp @@ -11,12 +11,14 @@ #include "swapchain.h" #include "window.h" +#include "light_manager.h" #include "asset_loader.h" #include "camera.h" #include "core_components.h" #include "ecs_adapter.h" #include "frame.h" +#include "ibl_helpers.h" #include "image.h" #include "pipeline.h" @@ -93,16 +95,30 @@ main(int, char *[]) AssetLoader assetLoader = {&resourceManager, ®istry, graphicsQueue, queueAllocation.m_Family, queueAllocation.m_Family}; + LightManager lightManager = LightManager{&resourceManager}; + + Texture environmentHdri; + assetLoader.LoadHdrImage(&environmentHdri, BACKDROP_FILE); + auto envHdriHandle = resourceManager.CommitTexture(&environmentHdri); + + auto environment = CreateEnvironment(&assetLoader, graphicsQueue, 512, envHdriHandle, "Cube Env"); + + resourceManager.Release(envHdriHandle); + + lightManager.SetEnvironment(std::move(environment)); Model model = assetLoader.LoadModelToGpu(MODEL_FILE, "Main Model"); Model model2 = assetLoader.LoadModelToGpu(MODEL_FILE2, "Main Model 2"); registry.get(model2.m_RootEntity).m_Position.x += 1.0f; UniformBuffer ubo; - ubo.Init(&device, sizeof cameraController.m_Camera, "Desc1 UBO"); + constexpr usize uboTotalSize = sizeof cameraController.m_Camera + sizeof lightManager.m_MetaInfo; + ubo.Init(&device, uboTotalSize, "Desc1 UBO"); ubo.Write(&device, 0, sizeof cameraController.m_Camera, &cameraController.m_Camera); + ubo.Write(&device, sizeof cameraController.m_Camera, sizeof lightManager.m_MetaInfo, &lightManager.m_MetaInfo); Pipeline pipeline = CreatePipeline(&device, attachmentFormat, &resourceManager); + Pipeline backgroundPipeline = CreateBackgroundPipeline(&device, attachmentFormat, &resourceManager); vk::DescriptorPool descriptorPool; vk::DescriptorSet perFrameDescriptor; @@ -127,10 +143,10 @@ main(int, char *[]) AbortIfFailed(device.m_Device.allocateDescriptorSets(&descriptorSetAllocateInfo, &perFrameDescriptor)); } - vk::DescriptorBufferInfo cameraBufferInfo = { + vk::DescriptorBufferInfo camLightBufferInfo = { .buffer = ubo.m_Buffer, .offset = 0, - .range = sizeof(Camera), + .range = uboTotalSize, }; eastl::array writeDescriptors = { vk::WriteDescriptorSet{ @@ -139,7 +155,7 @@ main(int, char *[]) .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &cameraBufferInfo, + .pBufferInfo = &camLightBufferInfo, }, }; device.m_Device.updateDescriptorSets(Cast(writeDescriptors.size()), writeDescriptors.data(), 0, nullptr); @@ -438,7 +454,6 @@ main(int, char *[]) cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 1, 1, &perFrameDescriptor, 0, nullptr); - //TODO("Unify index buffers"); cmd.bindIndexBuffer(resourceManager.GetIndexBuffer(), 0, vk::IndexType::eUint32); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.m_Pipeline); @@ -452,6 +467,10 @@ main(int, char *[]) cmd.drawIndexed(node.m_IndexCount, 1, node.m_FirstIndex, 0, 0); } + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, backgroundPipeline.m_Pipeline); + + cmd.draw(3, 1, 0, 0); + cmd.endRendering(); cmd.pipelineBarrier2(&postRenderDependencies); diff --git a/samples/04_scenes/pipeline_utils.cpp b/samples/04_scenes/pipeline_utils.cpp index 50812b3..5604abd 100644 --- a/samples/04_scenes/pipeline_utils.cpp +++ b/samples/04_scenes/pipeline_utils.cpp @@ -43,18 +43,6 @@ CreatePipeline(const Device *device, vk::Format attachmentFormat, const RenderRe .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eAll, }, - vk::DescriptorSetLayoutBinding{ - .binding = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eAll, - }, - vk::DescriptorSetLayoutBinding{ - .binding = 2, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eAll, - }, }; vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { .bindingCount = Cast(descriptorSetLayoutBindings.size()), @@ -67,7 +55,7 @@ CreatePipeline(const Device *device, vk::Format attachmentFormat, const RenderRe } vk::PushConstantRange pushConstantRange = { - .stageFlags = vk::ShaderStageFlagBits::eAll, + .stageFlags = vk::ShaderStageFlagBits::eAll, .offset = 0, .size = 96, }; @@ -80,7 +68,7 @@ CreatePipeline(const Device *device, vk::Format attachmentFormat, const RenderRe }; vk::PipelineLayout pipelineLayout; AbortIfFailed(device->m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - device->SetName(pipelineLayout, "Box Layout"); + device->SetName(pipelineLayout, "Scene Layout"); descriptorSetLayouts[0] = nullptr; // Not owned. @@ -172,6 +160,157 @@ CreatePipeline(const Device *device, vk::Format attachmentFormat, const RenderRe return {device, pipelineLayout, pipeline, std::move(descriptorSetLayouts)}; } + + +Pipeline +CreateBackgroundPipeline(const Device *device, vk::Format attachmentFormat, const RenderResourceManager *resourceManager) +{ + // Pipeline Setup + auto vertexShaderModule = CreateShader(device, BACKGROUND_VERTEX_SHADER_FILE); + auto fragmentShaderModule = CreateShader(device, BACKGROUND_FRAGMENT_SHADER_FILE); + + eastl::array shaderStages = {{ + { + .stage = vk::ShaderStageFlagBits::eVertex, + .module = vertexShaderModule, + .pName = "main", + }, + { + .stage = vk::ShaderStageFlagBits::eFragment, + .module = fragmentShaderModule, + .pName = "main", + }, + }}; + + eastl::vector descriptorSetLayouts; + + descriptorSetLayouts.push_back(resourceManager->m_SetLayout); + + { + eastl::array descriptorSetLayoutBindings = { + vk::DescriptorSetLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eAll, + }, + }; + vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { + .bindingCount = Cast(descriptorSetLayoutBindings.size()), + .pBindings = descriptorSetLayoutBindings.data(), + }; + vk::DescriptorSetLayout descriptorSetLayout; + AbortIfFailed( + device->m_Device.createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayout)); + descriptorSetLayouts.push_back(descriptorSetLayout); + } + + vk::PushConstantRange pushConstantRange = { + .stageFlags = vk::ShaderStageFlagBits::eAll, + .offset = 0, + .size = 96, + }; + + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = { + .setLayoutCount = Cast(descriptorSetLayouts.size()), + .pSetLayouts = descriptorSetLayouts.data(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &pushConstantRange, + }; + vk::PipelineLayout pipelineLayout; + AbortIfFailed(device->m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + device->SetName(pipelineLayout, "Scene BG Layout"); + + descriptorSetLayouts[0] = nullptr; // Not owned. + + vk::PipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = {}; + vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo = { + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = false, + }; + + vk::PipelineViewportStateCreateInfo viewportStateCreateInfo = { + .viewportCount = 1, + .scissorCount = 1, + }; + + vk::PipelineRasterizationStateCreateInfo rasterizationStateCreateInfo = { + .depthClampEnable = false, + .rasterizerDiscardEnable = false, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = false, + .lineWidth = 1.0, + }; + vk::PipelineMultisampleStateCreateInfo multisampleStateCreateInfo = { + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = false, + }; + vk::PipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo = { + .depthTestEnable = true, + .depthWriteEnable = true, + .depthCompareOp = vk::CompareOp::eLessOrEqual, + }; + vk::PipelineColorBlendAttachmentState colorBlendAttachmentState = { + .blendEnable = false, + .srcColorBlendFactor = vk::BlendFactor::eSrcColor, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcColor, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }; + vk::PipelineColorBlendStateCreateInfo colorBlendStateCreateInfo = { + .logicOpEnable = false, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachmentState, + }; + + eastl::array dynamicStates = { + vk::DynamicState::eScissor, + vk::DynamicState::eViewport, + }; + + vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo = { + .dynamicStateCount = Cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data(), + }; + + vk::PipelineRenderingCreateInfo renderingCreateInfo = { + .viewMask = 0, + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &attachmentFormat, + .depthAttachmentFormat = vk::Format::eD24UnormS8Uint, + }; + + vk::GraphicsPipelineCreateInfo pipelineCreateInfo = { + .pNext = &renderingCreateInfo, + .stageCount = Cast(shaderStages.size()), + .pStages = shaderStages.data(), + .pVertexInputState = &vertexInputStateCreateInfo, + .pInputAssemblyState = &inputAssemblyStateCreateInfo, + .pViewportState = &viewportStateCreateInfo, + .pRasterizationState = &rasterizationStateCreateInfo, + .pMultisampleState = &multisampleStateCreateInfo, + .pDepthStencilState = &depthStencilStateCreateInfo, + .pColorBlendState = &colorBlendStateCreateInfo, + .pDynamicState = &dynamicStateCreateInfo, + .layout = pipelineLayout, + }; + vk::Pipeline pipeline; + AbortIfFailed( + device->m_Device.createGraphicsPipelines(device->m_PipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); + device->SetName(pipeline, "BG Pipeline"); + + device->m_Device.destroy(vertexShaderModule, nullptr); + device->m_Device.destroy(fragmentShaderModule, nullptr); + + return {device, pipelineLayout, pipeline, std::move(descriptorSetLayouts)}; +} + vk::ShaderModule CreateShader(const Device *device, cstr shaderFile) { diff --git a/samples/04_scenes/pipeline_utils.h b/samples/04_scenes/pipeline_utils.h index 01ac264..9459aaa 100644 --- a/samples/04_scenes/pipeline_utils.h +++ b/samples/04_scenes/pipeline_utils.h @@ -14,7 +14,12 @@ struct Device; constexpr auto VERTEX_SHADER_FILE = "shader/model.vert.glsl.spv"; constexpr auto FRAGMENT_SHADER_FILE = "shader/model.frag.glsl.spv"; +constexpr auto BACKGROUND_VERTEX_SHADER_FILE = "shader/background.vert.glsl.spv"; +constexpr auto BACKGROUND_FRAGMENT_SHADER_FILE = "shader/background.frag.glsl.spv"; vk::ShaderModule CreateShader(const Device *device, cstr shaderFile); Pipeline CreatePipeline(const Device *device, vk::Format attachmentFormat, const RenderResourceManager *resourceManager); + +Pipeline +CreateBackgroundPipeline(const Device *device, vk::Format attachmentFormat, const RenderResourceManager *resourceManager); \ No newline at end of file diff --git a/samples/04_scenes/shader/background.frag.glsl b/samples/04_scenes/shader/background.frag.glsl new file mode 100644 index 0000000..ab4471b --- /dev/null +++ b/samples/04_scenes/shader/background.frag.glsl @@ -0,0 +1,35 @@ +#version 450 +#pragma shader_stage(fragment) +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : enable +#extension GL_EXT_buffer_reference : require +#extension GL_EXT_nonuniform_qualifier : enable + +#include "bindless_structs.glsl" +#include "graphics_bindings.glsl" + +layout (location=0) in vec3 in_WorldPosition; + +layout (location=0) out vec4 out_Color; + +void main() +{ + vec3 direction = normalize(in_WorldPosition - camera.m_Position.xyz); +#ifndef _DEBUG + vec4 color = texture(textureCubes[lights.m_EnvCubeHandle], direction); +#else + vec4 color; +// if ((PushConstant.DebugFlags & SHOW_DIFFUSE_BIT) > 0) +// { +// Color = TextureCubes[Lights.DiffuseIrradianceHandle].Sample(ImmutableSamplers[Lights.DiffuseIrradianceHandle], Direction); +// } +// else if ((PushConstant.DebugFlags & SHOW_PREFILTER_BIT) > 0) +// { +// Color = TextureCubes[Lights.PrefilterHandle].Sample(ImmutableSamplers[Lights.PrefilterHandle], Direction); +// } +// else + { + color = texture(textureCubes[lights.m_EnvCubeHandle], direction); + } +#endif + out_Color = vec4(Uncharted2Tonemap(color.rgb), color.a); +} \ No newline at end of file diff --git a/samples/04_scenes/shader/background.vert.glsl b/samples/04_scenes/shader/background.vert.glsl new file mode 100644 index 0000000..fe98078 --- /dev/null +++ b/samples/04_scenes/shader/background.vert.glsl @@ -0,0 +1,24 @@ +#version 450 +#pragma shader_stage(vertex) +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : enable +#extension GL_EXT_buffer_reference : require + +#include "graphics_bindings.glsl" + +layout (location = 0) out vec3 out_WorldPosition; + +void main() +{ + vec3 points[] = + { + vec3(-1.0f, -1.0f, 1.0f), + vec3(3.0f, -1.0f, 1.0f), + vec3(-1.0f, 3.0f, 1.0f), + }; + + gl_Position = vec4(points[gl_VertexIndex], 1.0f); + + vec4 clipSpace = camera.m_InvProjection * vec4(points[gl_VertexIndex], 1.0f); + vec4 worldSpace = camera.m_InvView * (clipSpace / clipSpace.wwww); + out_WorldPosition = worldSpace.xyz; +} \ No newline at end of file diff --git a/samples/04_scenes/shader/bindless_structs.glsl b/samples/04_scenes/shader/bindless_structs.glsl new file mode 100644 index 0000000..ce0ee91 --- /dev/null +++ b/samples/04_scenes/shader/bindless_structs.glsl @@ -0,0 +1,53 @@ + +struct VertexData { + vec4 Normal; + vec2 TexCoord0; + vec2 TexCoord1; + vec4 Color; +}; + +struct Material +{ + vec4 m_AlbedoFactor; // 16 16 + vec3 m_EmissionFactor; // 12 28 + float m_MetalFactor; // 04 32 + float m_RoughFactor; // 04 36 + uint m_AlbedoTex; // 04 40 + uint m_NormalTex; // 04 44 + uint m_MetalRoughTex; // 04 48 + uint m_OcclusionTex; // 04 52 + uint m_EmissionTex; // 04 56 + // We might be able to go upto 64 without pains. +}; + +// ---------- Bindless Bindings ---------- +#define INVALID_HANDLE 0xFFFFFFFF + +layout (set = 0, binding = 1) uniform sampler2D textures[]; +layout (set = 0, binding = 1) uniform samplerCube textureCubes[]; + + +// ---------- Buffer References ---------- +layout(std430, buffer_reference, buffer_reference_align=16) readonly buffer VPositionRef { + vec4 Positions[]; +}; + +layout(std430, buffer_reference, buffer_reference_align=16) readonly buffer VDataRef { + VertexData Data[]; +}; + +layout(std430, buffer_reference, buffer_reference_align=8) readonly buffer MaterialsRef { + Material materials[]; +}; + +layout(std430, buffer_reference, buffer_reference_align=8) readonly buffer MaterialRef { + vec4 m_AlbedoFactor; // 16 16 + vec3 m_EmissionFactor; // 12 28 + float m_MetalFactor; // 04 32 + float m_RoughFactor; // 04 36 + uint m_AlbedoTex; // 04 40 + uint m_NormalTex; // 04 44 + uint m_MetalRoughTex; // 04 48 + uint m_OcclusionTex; // 04 52 + uint m_EmissionTex; // 04 56 +}; \ No newline at end of file diff --git a/samples/04_scenes/shader/bindless_structs.hlsli b/samples/04_scenes/shader/bindless_structs.hlsli new file mode 100644 index 0000000..3e9bf13 --- /dev/null +++ b/samples/04_scenes/shader/bindless_structs.hlsli @@ -0,0 +1,69 @@ + +struct VertexData +{ + float4 Normal; + float2 TexCoord0; + float2 TexCoord1; + float4 Color0; +}; + +struct TransformData +{ + float4x4 Transform; + float4x4 NormalTransform; +}; + +struct MaterialData +{ + float AlbedoFactor[4]; + float EmissionFactor[3]; + float MetalFactor; + float RoughFactor; + uint AlbedoTex; + uint NormalTex; + uint MetalRoughTex; + uint OcclusionTex; + uint EmissionTex; +}; + +struct PointLight +{ + float Position[3]; + float Range; + uint Color; + float Intensity; +}; + +struct DirectionalLight +{ + float Direction[3]; + float Validity_; + uint Color; + float Intensity; +}; + +// Little Endian storage. First short is least significant. +#define IndexerCount(Indexer) (Indexer & 0xFFFF) +#define IndexerOffset(Indexer) ((Indexer & 0xFFFF0000) >> 16); + +#define INVALID_HANDLE 0xFFFFFFFF + +static const float HALF_PI = 1.57079633f; +static const float PI = 3.14159265f; +static const float TAU = 6.28318530f; + +[[vk::binding(0, 0)]] StructuredBuffer VertexBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer VertexDataBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer MaterialsBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer NodeBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer PointLightBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer DirectionalLightBuffer[]; + +[[vk::binding(1, 0)]] Texture2D Textures[]; +[[vk::binding(1, 0)]] Texture2D TexturesRG[]; +[[vk::binding(1, 0)]] TextureCube TextureCubes[]; +[[vk::binding(1, 0)]] SamplerState ImmutableSamplers[]; + +[[vk::binding(2, 0)]] RWTexture2D StorageTextures[]; +[[vk::binding(2, 0)]] RWTexture2D StorageTexturesRG[]; +[[vk::binding(2, 0)]] RWTexture2DArray StorageTextureArrays[]; diff --git a/samples/04_scenes/shader/brdf_lut.cs.hlsl b/samples/04_scenes/shader/brdf_lut.cs.hlsl new file mode 100644 index 0000000..b515ed7 --- /dev/null +++ b/samples/04_scenes/shader/brdf_lut.cs.hlsl @@ -0,0 +1,82 @@ +#include "ibl_common.hlsli" + +struct Block +{ + uint OutputTextureHandle; +}; + +[[vk::push_constant]] +Block Pcb; + +float GeometrySchlickGGX(float NdotV, float Roughness) +{ + float R = Roughness; + // (Rough + 1)^2 / 8 for Punctual Lights + // Rough^2 / 2 for IBL + float K = (R * R) / 2.0; + + float Numerator = NdotV; + float Denominator = NdotV * (1.0f - K) + K; + + return Numerator / Denominator; +} + +float GeometrySmith(float NdotV, float NdotL, float Roughness) +{ + float GGX1 = GeometrySchlickGGX(NdotV, Roughness); + float GGX2 = GeometrySchlickGGX(NdotL, Roughness); + + return GGX1 * GGX2; +} + +float2 IntegrateBRDF(float NdotV, float Roughness) +{ + float3 ViewDir; + ViewDir.x = sqrt(1.0f - NdotV * NdotV); + ViewDir.y = 0.0f; + ViewDir.z = NdotV; + + float A = 0.0f; + float B = 0.0f; + + float3 Normal = float3(0.0f, 0.0f, 1.0f); + + const uint SAMPLE_COUNT = 1024u; + + for (uint i = 0u; i < SAMPLE_COUNT; ++i) + { + float2 Xi = Hammersley(i, SAMPLE_COUNT); + float3 Halfway = ImportanceSampleGGX(Xi, Normal, Roughness); + float3 LightDir = normalize(2.0f * dot(ViewDir, Halfway) * Halfway - ViewDir); + + float NdotL = max(LightDir.z, 0.0f); + float NdotH = max(Halfway.z, 0.0f); + float VdotH = max(dot(ViewDir, Halfway), 0.0f); + + if (NdotL > 0.0f) + { + float G = GeometrySmith(NdotV, NdotL, Roughness); + float G_Vis = (G * VdotH) / max((NdotH * NdotV), 0.0001f); + float Fc = pow(1.0f - VdotH, 5.0f); + + A += (1.0f - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + + return float2(A, B); +} + +[numthreads(16, 16, 1)] +void main(uint3 GlobalInvocationID : SV_DispatchThreadID) +{ + float Width, Height; + StorageTexturesRG[Pcb.OutputTextureHandle].GetDimensions(Width, Height); + + float2 UV = GlobalInvocationID.xy / float2(Width, Height); + + float2 IntegratedBRDF = IntegrateBRDF(UV.x, UV.y); + StorageTexturesRG[Pcb.OutputTextureHandle][GlobalInvocationID.xy] = IntegratedBRDF; +} \ No newline at end of file diff --git a/samples/04_scenes/shader/diffuse_irradiance.cs.hlsl b/samples/04_scenes/shader/diffuse_irradiance.cs.hlsl new file mode 100644 index 0000000..21f3313 --- /dev/null +++ b/samples/04_scenes/shader/diffuse_irradiance.cs.hlsl @@ -0,0 +1,52 @@ +#include "ibl_common.hlsli" + +struct Block +{ + uint SkyboxHandle; + uint OutputTextureHandle; + int CubeSide; +}; + +[[vk::push_constant]] +Block pcb; + +/* +| Axis | Layer | Up | +|:----:|:-----:|:--:| +| +x | 0 | -y | +| -x | 1 | -y | +| +y | 2 | +z | +| -y | 3 | -z | +| -z | 4 | -y | +| +z | 5 | -y | +*/ + +[numthreads(16, 16, 1)] +void main(uint3 GlobalInvocationID : SV_DispatchThreadID) +{ + float3 Forward, Up, Right; + + Forward = GetCubeDir(GlobalInvocationID, pcb.CubeSide); + Up = abs(Forward.y) < 1.0f ? float3(0.0f, 1.0f, 0.0f) : float3(1.0f, 0.0f, 0.0f); // 0.01f offset to + Right = normalize(cross(Up, Forward)); + Up = normalize(cross(Forward, Right)); + + float3 Irradiance = 0.0f.xxx; + float3 IrrDirr = 0.0f.xxx; + float SampleStep = 0.005f; + float SampleCount = 0.0f; + + for (float Azimuth = 0.0f; Azimuth < TAU; Azimuth += SampleStep) + { + for (float Zenith = 0.0f; Zenith < HALF_PI; Zenith += SampleStep) + { + float3 DirectionTanSpace = float3(sin(Zenith) * cos(Azimuth), sin(Zenith) * sin(Azimuth), cos(Zenith)); + float3 DirectionWorld = DirectionTanSpace.x * Right + DirectionTanSpace.y * Up + DirectionTanSpace.z * Forward; + + Irradiance += TextureCubes[pcb.SkyboxHandle].SampleLevel(ImmutableSamplers[pcb.SkyboxHandle], DirectionWorld, 0).xyz * (cos(Zenith) * sin(Zenith)); + SampleCount++; + } + } + + StorageTextureArrays[pcb.OutputTextureHandle][GlobalInvocationID.xyz] = PI * float4(Irradiance * (1.0f / SampleCount), 1.0f); +} \ No newline at end of file diff --git a/samples/04_scenes/shader/eqrect_to_cube.cs.hlsl b/samples/04_scenes/shader/eqrect_to_cube.cs.hlsl new file mode 100644 index 0000000..d80a0e3 --- /dev/null +++ b/samples/04_scenes/shader/eqrect_to_cube.cs.hlsl @@ -0,0 +1,29 @@ +#include "ibl_common.hlsli" + +struct Block +{ + uint HdrEnvHandle; + uint OutputTextureHandle; + int CubeSide; +}; + +[[vk::push_constant]] +Block pcb; + +float2 SampleSphericalMap(float3 v) +{ + const float2 InvTan = float2(0.1591f, 0.3183f); // (1/2PI, 1/PI) + float2 UV = float2(atan2(-v.x, v.z), asin(-v.y)); // (-PI, -PI/2) to (PI, PI/2) + UV *= InvTan; // (-1/2, -1/2) to (1/2, 1/2) + UV += 0.5f.xx; // (0, 0) to (1, 1) + return UV; +} + +[numthreads(16, 16, 1)] +void main(uint3 GlobalInvocationID : SV_DispatchThreadID) +{ + float3 LocalDir = GetCubeDir(GlobalInvocationID, pcb.CubeSide); + + float2 UV = SampleSphericalMap(LocalDir); + StorageTextureArrays[pcb.OutputTextureHandle][GlobalInvocationID.xyz] = Textures[pcb.HdrEnvHandle].SampleLevel(ImmutableSamplers[pcb.HdrEnvHandle], UV, 0); +} \ No newline at end of file diff --git a/samples/04_scenes/shader/graphics_bindings.glsl b/samples/04_scenes/shader/graphics_bindings.glsl new file mode 100644 index 0000000..47f7fde --- /dev/null +++ b/samples/04_scenes/shader/graphics_bindings.glsl @@ -0,0 +1,42 @@ + +struct Camera { + mat4 m_View; // 64 + mat4 m_Projection; // 128 + mat4 m_InvView; // 192 + mat4 m_InvProjection; // 256 + vec4 m_Position; // 272 +}; + +struct Lighting +{ + uint m_EnvCubeHandle; // 4 + uint m_DiffuseIrradianceHandle; // 8 + uint m_PrefilterHandle; // 12 + uint m_BrdfLutHandle; // 16 + uint m_LightHandle; // 20 + uint m_PointLightIndexer; // 24 + uint m_DirectionalLightIndexer; // 28 +}; + +layout(set=1, binding=0) uniform Ubo { + Camera camera; + Lighting lights; +}; + +// Little Endian storage. First short is least significant. +#define IndexerCount(Indexer) (Indexer & 0xFFFF) +#define IndexerOffset(Indexer) ((Indexer & 0xFFFF0000) >> 16) + +#define PI 3.1415926535 + +vec3 Uncharted2Tonemap(vec3 color) +{ + float A = 0.15f; + float B = 0.50f; + float C = 0.10f; + float D = 0.20f; + float E = 0.02f; + float F = 0.30f; + float W = 11.2f; + return ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F; +} \ No newline at end of file diff --git a/samples/04_scenes/shader/ibl_common.hlsli b/samples/04_scenes/shader/ibl_common.hlsli new file mode 100644 index 0000000..bec0adc --- /dev/null +++ b/samples/04_scenes/shader/ibl_common.hlsli @@ -0,0 +1,77 @@ +#include "bindless_structs.hlsli" + +/* + The Goal is simply to convert from a (0,0,0) to (CubeSide, CubeSide, FaceCount) Invocation ID space to a + (-1,-1,-1) to (1, 1, 1) space. + +| Axis | Layer | Up | +|:----:|:-----:|:--:| +| +x | 0 | -y | +| -x | 1 | -y | +| +y | 2 | +z | +| -y | 3 | -z | +| -z | 4 | -y | +| +z | 5 | -y | +*/ +float3 GetCubeDir(uint3 GlobalInvocationID, float SideLength) +{ + float2 FaceUV = float2(GlobalInvocationID.xy) / SideLength; // (0, SideLength) -> (0, 1) + FaceUV = 2.0f * FaceUV - 1.0f; // (0, 1) -> (-1, 1) + + switch (GlobalInvocationID.z) + { + case 0: + return normalize(float3(1.0f, -FaceUV.y, -FaceUV.x)); // Face +X; x = 1, y = -v, z = -u + case 1: + return normalize(float3(-1.0f, -FaceUV.y, FaceUV.x)); // Face -X; x = -1, y = -v, z = u + case 2: + return normalize(float3(FaceUV.x, 1.0f, FaceUV.y)); // Face +Y; x = u, y = 1, z = v + case 3: + return normalize(float3(FaceUV.x, -1.0f, -FaceUV.y)); // Face -Y; x=u, y=-1, z=-v + case 4: + return normalize(float3(FaceUV.x, -FaceUV.y, 1.0f)); // Face +Z; x=u,y=-v, z=1 + case 5: + return normalize(float3(-FaceUV.x, -FaceUV.y, -1.0f)); // Face -Z; x=u,y=-v, z=-1 + default: + // Never reach here. + return 0.0f.xxx; + } +} + +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 +} + +float2 Hammersley(uint SampleIndex, uint SampleCount) +{ + return float2(float(SampleIndex) / float(SampleCount), RadicalInverse_VdC(SampleIndex)); +} + +float3 ImportanceSampleGGX(float2 Xi, float3 Normal, float Roughness) +{ + float A = Roughness * Roughness; + + float Phi = 2.0f * PI * Xi.x; + float CosTheta = sqrt((1.0f - Xi.y) / (1.0f + (A * A - 1.0f) * Xi.y)); + float SinTheta = sqrt(1.0f - CosTheta * CosTheta); + + // from spherical coordinates to cartesian coordinates + float3 H; + H.x = cos(Phi) * SinTheta; + H.y = sin(Phi) * SinTheta; + H.z = CosTheta; + + // from tangent-space vector to world-space sample vector + float3 Up = abs(Normal.z) < 0.999f ? float3(0.0f, 0.0f, 1.0f) : float3(1.0f, 0.0f, 0.0f); + float3 Tangent = normalize(cross(Up, Normal)); + float3 Bitangent = cross(Normal, Tangent); + + float3 SampleVec = Tangent * H.x + Bitangent * H.y + Normal * H.z; + return normalize(SampleVec); +} \ No newline at end of file diff --git a/samples/04_scenes/shader/model.frag.glsl b/samples/04_scenes/shader/model.frag.glsl index 3bec0da..5df242e 100644 --- a/samples/04_scenes/shader/model.frag.glsl +++ b/samples/04_scenes/shader/model.frag.glsl @@ -2,17 +2,299 @@ #pragma shader_stage(fragment) #extension GL_EXT_shader_explicit_arithmetic_types_int64 : enable #extension GL_EXT_buffer_reference : require +#extension GL_EXT_nonuniform_qualifier : enable + +#include "bindless_structs.glsl" +#include "graphics_bindings.glsl" + +layout (location = 0) in vec4 in_Position; +layout (location = 1) in vec4 in_Normal; +layout (location = 2) in vec4 in_Color0; +layout (location = 3) in vec2 in_TexCoord0; -layout (location = 2) in vec4 inColor; layout (location = 0) out vec4 outColor; -layout(push_constant) uniform Constants { +layout(push_constant) uniform Constants +{ mat4 globalTransform; uint64_t vertexPos; uint64_t vertexDat; - uint64_t materialIdx; -} pcb; + MaterialRef g_Material; +}; + +vec4 GetAlbedo(vec4 albedoFactor, uint texHandle, vec4 in_Color0, vec2 uv) +{ + return albedoFactor * in_Color0 * ((texHandle != INVALID_HANDLE) ? texture(textures[texHandle], uv) : vec4(1.0f)); +} + +float GetOcclusion(uint texHandle, vec2 uv) +{ + return texHandle != INVALID_HANDLE ? texture(textures[texHandle], uv).r : 1.0f; +} + +vec3 GetEmissive(vec3 emissionFactor, uint texHandle, vec2 uv) +{ + return emissionFactor * (texHandle != INVALID_HANDLE ? texture(textures[texHandle], uv).rgb : vec3(1.0f)); +} + +vec3 GetNormal(uint texHandle, vec3 position, vec3 normal, vec2 uv) +{ + vec3 N = normalize(normal); + + if (texHandle == INVALID_HANDLE) + { + return N; + } + + vec3 tanSpaceNormal = texture(textures[texHandle], uv).xyz * 2.0f - 1.0f; + + vec3 q1 = dFdx(position); + vec3 q2 = dFdy(position); + vec2 st1 = dFdx(uv); + vec2 st2 = dFdy(uv); + + vec3 T = normalize(q1 * st2.y - q2 * st1.y).xyz; + vec3 B = -normalize(cross(N, T)); + mat3 TBN = mat3(T, B, N); // Construction is Col by Col + + return normalize(TBN * tanSpaceNormal); +} + +vec2 GetMetalRough(float metalFactor, float roughFactor, uint texHandle, vec2 uv) +{ + return vec2(metalFactor, roughFactor) * (texHandle != INVALID_HANDLE ? texture(textures[texHandle], uv).bg : vec2(1.0f)); // Metal is B, Rough is G. +} + +vec3 SampleIrradiance(vec3 direction) +{ + uint texHandle = lights.m_DiffuseIrradianceHandle; + return ((texHandle != INVALID_HANDLE) ? texture(textureCubes[texHandle], direction).rgb : vec3(0.04f)); +} + +vec3 SamplePrefiltered(vec3 direction, float roughness) +{ + const float MAX_MIP_LEVEL = 5.0f; + float mipLevel = MAX_MIP_LEVEL * roughness; + + uint texHandle = lights.m_PrefilterHandle; + + return (texHandle != INVALID_HANDLE) ? textureLod(textureCubes[texHandle], direction, mipLevel).rgb : vec3(0.0f); +} + +vec2 SampleBrdfLut(float ndotv, float roughness) +{ + return lights.m_BrdfLutHandle != INVALID_HANDLE ? texture(textures[lights.m_BrdfLutHandle], vec2(ndotv, roughness)).rg : 0.0f.xx; +} + + +float TrowbridgeReitzGGX(vec3 normal, vec3 halfway, float roughness) +{ + float coeff = roughness * roughness; + float coeff2 = coeff * coeff; + float ndoth = max(dot(normal, halfway), 0.0f); + float ndoth2 = ndoth * ndoth; + + float numerator = coeff2; + float denominator = ndoth2 * (coeff2 - 1.0f) + 1.0f; + denominator = PI * denominator * denominator; + + return numerator / denominator; +} + +float GeometrySchlickGGX(float ndotv, float roughness) +{ + float r = roughness + 1.0f; + float k = (r * r) / 8.0f; + + float numerator = ndotv; + float denominator = ndotv * (1.0f - k) + k; + + return numerator / denominator; +} + +float GeometrySmith(float ndotv, float ndotl, float roughness) +{ + float ggx1 = GeometrySchlickGGX(ndotv, roughness); + float ggx2 = GeometrySchlickGGX(ndotl, roughness); + + return ggx1 * ggx2; +} + +// https://en.wikipedia.org/wiki/Schlick%27s_approximation +// TODO: Possibly needs fixing. unreal vs LearnOpenGL impl. +vec3 FresnelSchlick(float cosine, vec3 f0) +{ + return f0 + (1.0f - f0) * pow(clamp(1.0f - cosine, 0.0f, 1.0f), 5.0f); // Clamp to avoid artifacts. +} + +// Sebastian Lagarde +vec3 FresnelSchlickRoughness(float cosine, vec3 f0, float roughness) +{ + return f0 + (max((1.0f - roughness).xxx, f0) - f0) * pow(clamp(1.0f - cosine, 0.0f, 1.0f), 5.0f); // Clamp to avoid artifacts. +} + +vec3 GetPBRContrib(vec3 albedo, vec3 lightColor, vec3 viewDir, vec3 normal, float metallic, float roughness, vec3 f0, vec3 lightDir, float lightDistance) +{ + float attenuation = 1.0f / (lightDistance * lightDistance); // TODO: Controlled Attenuation + vec3 halfway = normalize(viewDir + lightDir); + + float cosineFactor = max(dot(halfway, viewDir), 0.0f); + float ndotv = max(dot(normal, viewDir), 0.0f); + float ndotl = max(dot(normal, lightDir), 0.0f); + + vec3 radiance = lightColor * attenuation; + + float normalDistribution = TrowbridgeReitzGGX(normal, halfway, roughness); + float geometry = GeometrySmith(ndotv, ndotl, roughness); + vec3 fresnel = FresnelSchlickRoughness(cosineFactor, f0, roughness); + + vec3 numerator = (normalDistribution * geometry) * fresnel; + float denominator = 4.0f * ndotv * ndotl; + vec3 specular = numerator / (denominator + 0.00001f); + + vec3 kSpecular = fresnel; + vec3 kDiffuse = 1.0f.xxx - kSpecular; + + kDiffuse *= 1.0f - metallic; + + return ndotl * radiance * (kDiffuse * albedo / PI + specular); +} +// +//vec3 GetPointLightInfluence(vec3 albedo, vec2 MetalRough, vec3 Position, vec3 Normal) +//{ +// if (lights.LightHandle == INVALID_HANDLE) +// return 0.0f.xxx; +// +// uint Offset = IndexerOffset(lights.PointLightIndexer); +// uint Count = IndexerCount(lights.PointLightIndexer); +// +// vec3 ViewDir = normalize(Camera.Position.xyz - Position); +// +// float Metallic = MetalRough.r; +// float Roughness = MetalRough.g; +// +// // Dielectric F_0 based on LearnOpenGL. +// // TODO: Cite +// vec3 F_0 = 0.04f.xxx; +// F_0 = lerp(F_0, albedo, Metallic); +// +// vec3 Contrib = 0.0f; +// for (uint i = 0; i < Count; ++i) +// { +// PointLight Light = PointLightBuffer[lights.LightHandle][i + Offset]; +// +// if (Light.Range < 0.0f) +// continue; +// +// vec3 LightDir = vec3(Light.Position) - Position; +// float LightDistance = length(LightDir); +// +// if (LightDistance > Light.Range) +// continue; +// +// LightDir /= LightDistance; // Normalization +// +// // Color Unpack +// float R = (Light.Color & 0xFF000000) >> 24; +// float G = (Light.Color & 0x00FF0000) >> 16; +// float B = (Light.Color & 0x0000FF00) >> 8; +// +// vec3 LightColor = Light.Intensity * vec3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255 +// +// Contrib += GetPBRContrib(albedo, LightColor, ViewDir, Normal, Metallic, Roughness, F_0, LightDir, LightDistance); +// } +// +// return Contrib; +//} +// +//vec3 GetDirectionalLightInfluence(vec3 albedo, float2 MetalRough, vec3 Position, vec3 Normal) +//{ +// if (lights.LightHandle == INVALID_HANDLE) +// return 0.0f.xxx; +// +// uint Count = IndexerCount(lights.DirectionalLightIndexer); +// +// vec3 ViewDir = normalize(Camera.Position.xyz - Position); +// +// float Metallic = MetalRough.r; +// float Roughness = MetalRough.g; +// +// // Dielectric F_0 based on LearnOpenGL. +// // TODO: Cite +// vec3 F_0 = 0.04f.xxx; +// F_0 = lerp(F_0, albedo, Metallic); +// +// vec3 Contrib = 0.0f; +// for (uint i = 0; i < Count; ++i) +// { +// DirectionalLight Light = DirectionalLightBuffer[lights.LightHandle][i]; +// +// if (Light.Validity_ < 0.0f) +// continue; +// +// vec3 LightDir = -normalize(vec3(Light.Direction)); +// +// // Color Unpack +// float R = (Light.Color & 0xFF000000) >> 24; +// float G = (Light.Color & 0x00FF0000) >> 16; +// float B = (Light.Color & 0x0000FF00) >> 8; +// +// vec3 LightColor = Light.Intensity * vec3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255 +// +// Contrib += GetPBRContrib(albedo, LightColor, ViewDir, Normal, Metallic, Roughness, F_0, LightDir, 1.0f); +// } +// +// return Contrib; +//} +// +vec3 GetAmbientInfluence(vec3 albedo, float metal, float roughness, vec3 Position, vec3 Normal, float Occlusion) +{ + vec3 ViewDir = normalize(camera.m_Position.xyz - Position); + float CosineFactor = max(dot(Normal, ViewDir), 0.0f); // Normal instead of Halfway since there's no halfway in ambient. + + vec3 F_0 = 0.04f.xxx; + F_0 = mix(F_0, albedo, metal); + vec3 K_Specular = FresnelSchlickRoughness(CosineFactor, F_0, roughness); + vec3 K_Diffuse = 1.0f.xxx - K_Specular; + + K_Diffuse *= 1.0f - metal; // Metals don't have diffuse/refractions. + + vec3 ReflectionDir = reflect(-ViewDir, Normal); + + float NdotV = max(dot(Normal, ViewDir), 0.0f); + vec3 PrefilteredColor = SamplePrefiltered(ReflectionDir, roughness).rgb; + vec2 EnvBRDF = SampleBrdfLut(NdotV, roughness); + vec3 Specular = PrefilteredColor * (K_Specular * EnvBRDF.x + EnvBRDF.y); + + vec3 DiffuseIrradiance = albedo * SampleIrradiance(Normal); +//#ifdef _DEBUG +// if ((PushConstant.DebugFlags & USE_DIFFUSE_BIT) == 0) { +// DiffuseIrradiance = 0.0f.xxx; +// } +// if ((PushConstant.DebugFlags & USE_SPECULAR_BIT) == 0) { +// Specular = 0.0f.xxx; +// } +//#endif + + return (K_Diffuse * DiffuseIrradiance + Specular) * Occlusion; +} void main() { - outColor = vec4(inColor.rgb, 1.0f); + + vec4 albedoA = GetAlbedo(g_Material.m_AlbedoFactor, g_Material.m_AlbedoTex, in_Color0, in_TexCoord0); + vec3 albedo = albedoA.rgb; + float alpha = albedoA.a; + + vec3 normal = GetNormal(g_Material.m_NormalTex, in_Position.xyz, normalize(in_Normal.xyz), in_TexCoord0); + + vec3 emission = GetEmissive(g_Material.m_EmissionFactor, g_Material.m_EmissionTex, in_TexCoord0); + + vec2 metalRough = GetMetalRough(g_Material.m_MetalFactor, g_Material.m_RoughFactor, g_Material.m_MetalRoughTex, in_TexCoord0); + float occlusion = GetOcclusion(g_Material.m_OcclusionTex, in_TexCoord0); + + vec3 ambientLumin = GetAmbientInfluence(albedo, metalRough.r, metalRough.g, in_Position.xyz, normal, occlusion); + + outColor = vec4(Uncharted2Tonemap(ambientLumin + emission), alpha); + +// outColor = vec4(0.5f + 0.5f * normal, 1.0f); } \ No newline at end of file diff --git a/samples/04_scenes/shader/model.vert.glsl b/samples/04_scenes/shader/model.vert.glsl index 50083d8..5283fe2 100644 --- a/samples/04_scenes/shader/model.vert.glsl +++ b/samples/04_scenes/shader/model.vert.glsl @@ -3,40 +3,20 @@ #extension GL_EXT_shader_explicit_arithmetic_types_int64 : enable #extension GL_EXT_buffer_reference : require +#include "bindless_structs.glsl" +#include "graphics_bindings.glsl" + layout(location=0) out vec4 outWorldNormal; layout(location=1) out vec4 outWorldPosition; layout(location=2) out vec4 outColor; layout(location=3) out vec2 outUV0; -struct VertexData { - vec4 Normal; - vec2 TexCoord0; - vec2 TexCoord1; - vec4 Color; -}; - -layout(std430, buffer_reference, buffer_reference_align=16) readonly buffer VPositionRef { - vec4 Positions[]; -}; - -layout(std430, buffer_reference, buffer_reference_align=16) readonly buffer VDataRef { - VertexData Data[]; -}; - -layout(set=1, binding=0) uniform Camera { - mat4 View; // 64 - mat4 Projection; // 128 - mat4 InvView; // 192 - mat4 InvProjection; // 256 - vec4 Position; // 272 -} camera; - layout(push_constant) uniform Constants { mat4 globalTransform; - VPositionRef vertexPos; - VDataRef vertexDat; + VPositionRef pVertexPosition; + VDataRef pVertexData; uint64_t materialIdx; -} pcb; +}; void main() { vec3 colors[] = { @@ -45,12 +25,10 @@ void main() { vec3( 0.0f, 0.0f, 1.0f ), }; - gl_Position = camera.Projection * camera.View * pcb.globalTransform * vec4(pcb.vertexPos.Positions[gl_VertexIndex].xyz, 1.0f); - outColor = vec4(pcb.vertexDat.Data[gl_VertexIndex].Color.rgb, 1.0f); //vec3(colors[gl_VertexIndex % 3]); - - // TODO - // layout(location=0) out vec4 outWorldNormal; - // layout(location=1) out vec4 outWorldPosition; - // layout(location=2) out vec4 outColor; - // layout(location=3) out vec2 outUV0; + outWorldNormal = vec4(normalize(transpose(inverse(mat3(globalTransform))) * pVertexData.Data[gl_VertexIndex].Normal.xyz), 0.0f); + + outWorldPosition = globalTransform * vec4(pVertexPosition.Positions[gl_VertexIndex].xyz, 1.0f); + gl_Position = camera.m_Projection * camera.m_View * outWorldPosition; + outColor = vec4(pVertexData.Data[gl_VertexIndex].Color.rgb, 1.0f); //vec3(colors[gl_VertexIndex % 3]); + outUV0 = pVertexData.Data[gl_VertexIndex].TexCoord0; } \ No newline at end of file diff --git a/samples/04_scenes/shader/model.vs.hlsl b/samples/04_scenes/shader/model.vs.hlsl deleted file mode 100644 index 0ac6b79..0000000 --- a/samples/04_scenes/shader/model.vs.hlsl +++ /dev/null @@ -1,62 +0,0 @@ - -struct VS_Input -{ - uint VertexIndex : SV_VertexID; -}; - -struct VS_Out { - float4 WorldNormal : NORMAL; - float4 WorldPosition : POSITION; - float4 Color : COLOR0; - float2 TexCoord0 : TEXCOORD0; -}; - -struct CameraData -{ - float4x4 View; // 64 - float4x4 Projection; // 128 - float4x4 InvView; // 192 - float4x4 InvProjection; // 256 - float4 Position; // 272 -}; - -[[vk::binding(0, 1)]] ConstantBuffer Camera; - -struct VertexData { - float4 Normal; - float2 TexCoord0; - float2 TexCoord1; - float4 Color; -}; - -[[vk::binding(0, 0)]] ByteAddressBuffer GeometryBuffer[]; - -layout(set=1, binding=0) uniform Camera { - float4x4 View; // 64 - float4x4 Projection; // 128 - float4x4 InvView; // 192 - float4x4 InvProjection; // 256 - float4 Position; // 272 -} Camera; - -layout(push_constant) uniform Constants { - mat4 globalTransform; - uint64 vertexPos; - VDataRef vertexDat; - uint64_t materialIdx; -} pcb; - -void main() { - VS_Output Output; - - float4 GlobalPosition = mul(globalTransform, GeometryBuffer[0].Load()); - float4 ClipSpace = mul(Camera.View, GlobalPosition); - - Output.VertexPosition = mul(Camera.Projection, ClipSpace); - Output.WorldPosition = GlobalPosition; - Output.UV0 = GetUV(StageInput.VertexIndex); - Output.VertexColor = GetColor(StageInput.VertexIndex); - - Output.WorldNormal = mul(GetNormalTransform(PushConstant.NodeIdx), GetNormal(StageInput.VertexIndex)); - return Output; -} \ No newline at end of file diff --git a/samples/04_scenes/shader/prefilter.cs.hlsl b/samples/04_scenes/shader/prefilter.cs.hlsl new file mode 100644 index 0000000..738d019 --- /dev/null +++ b/samples/04_scenes/shader/prefilter.cs.hlsl @@ -0,0 +1,68 @@ +#include "ibl_common.hlsli" + +struct Block +{ + uint SkyboxHandle; + uint OutputTextureHandle; + int CubeSide; + float Roughness; + uint EnvRes; +}; + +[[vk::push_constant]] +Block Pcb; + +float TrowbridgeReitzGGX(float NdotH, float Roughness) +{ + float Coeff = Roughness * Roughness; + float Alpha2 = Coeff * Coeff; + float NdotH2 = NdotH * NdotH; + + float Numerator = Alpha2; + float Denominator = NdotH2 * (Alpha2 - 1.0f) + 1.0f; + Denominator = PI * Denominator * Denominator; + + return Numerator / Denominator; +} + +float GetSampleMipLevel(float NdotH, float HdotV, float SampleCount) +{ + float D = TrowbridgeReitzGGX(NdotH, Pcb.Roughness); + float Pdf = (D * NdotH / (4.0f * HdotV)) + 0.0001f; + + float SurfAreaTexel = 4.0f * PI / (6.0f * Pcb.EnvRes * Pcb.EnvRes); + float SurfAreaSample = 1.0f / (SampleCount * Pdf + 0.0001f); + + return Pcb.Roughness == 0.0f ? 0.0f : 0.5f * log2(SurfAreaSample / SurfAreaTexel); +} + +[numthreads(16, 16, 1)] +void main(uint3 GlobalInvocationID : SV_DispatchThreadID) +{ + float3 Normal = GetCubeDir(GlobalInvocationID, Pcb.CubeSide); + float3 ViewDir = Normal; + + const uint SAMPLE_COUNT = 2048u; + float TotalWeight = 0.0f; + float3 PrefilterColor = 0.0f.xxx; + for (uint i = 0u; i < SAMPLE_COUNT; ++i) + { + float2 Xi = Hammersley(i, SAMPLE_COUNT); + float3 Halfway = ImportanceSampleGGX(Xi, Normal, Pcb.Roughness); + float3 LightDir = normalize(2.0 * dot(ViewDir, Halfway) * Halfway - ViewDir); + + float NdotH = max(dot(Normal, Halfway), 0.0f); + + float MipLevel = GetSampleMipLevel(NdotH, NdotH /* N = V :: NdotH = HdotV */, SAMPLE_COUNT); + + float NdotL = max(dot(Normal, LightDir), 0.0); + if (NdotL > 0.0) + { + PrefilterColor += TextureCubes[Pcb.SkyboxHandle].SampleLevel(ImmutableSamplers[Pcb.SkyboxHandle], LightDir, MipLevel).rgb * NdotL; + TotalWeight += NdotL; + } + } + PrefilterColor = PrefilterColor / TotalWeight; + + StorageTextureArrays[Pcb.OutputTextureHandle][GlobalInvocationID] = float4(PrefilterColor, 1.0f); +} \ No newline at end of file