diff --git a/data/config/defaultLayout.ini b/data/config/defaultLayout.ini index c249c57..717474c 100644 --- a/data/config/defaultLayout.ini +++ b/data/config/defaultLayout.ini @@ -74,6 +74,12 @@ Size=1233,1056 Collapsed=0 DockId=0x00000001,0 +[Window][ Shadow] +Pos=1572,580 +Size=348,185 +Collapsed=0 +DockId=0x0000000A,1 + [Docking][Data] DockSpace ID=0x4BBE4C7A Window=0x4647B76E Pos=0,24 Size=1920,1056 Split=X Selected=0xE87781F4 DockNode ID=0x00000005 Parent=0x4BBE4C7A SizeRef=335,1056 Split=Y Selected=0x6A2E32C2 @@ -86,6 +92,6 @@ DockSpace ID=0x4BBE4C7A Window=0x4647B76E Pos=0,24 Size=1920,1056 Spli DockNode ID=0x00000007 Parent=0x00000003 SizeRef=348,369 Selected=0xBE92E3B2 DockNode ID=0x00000008 Parent=0x00000003 SizeRef=348,370 Split=Y Selected=0xFCCC74B6 DockNode ID=0x00000009 Parent=0x00000008 SizeRef=348,183 Selected=0x4F8C3FAB - DockNode ID=0x0000000A Parent=0x00000008 SizeRef=348,185 Selected=0xC6672BD8 + DockNode ID=0x0000000A Parent=0x00000008 SizeRef=348,185 Selected=0x21E9B10B DockNode ID=0x00000004 Parent=0x00000002 SizeRef=348,313 Selected=0xE83C69A2 diff --git a/data/shaders/ShadeDeferredPbr.frag.glsl b/data/shaders/ShadeDeferredPbr.frag.glsl index 640d18d..6b507dd 100644 --- a/data/shaders/ShadeDeferredPbr.frag.glsl +++ b/data/shaders/ShadeDeferredPbr.frag.glsl @@ -55,6 +55,16 @@ layout(binding = 6, std430) readonly buffer LightBuffer GpuLight lights[]; }lightBuffer; +// Returns a shadow bias in the space of whatever texelWidth is in. +// For example, if texelWidth is 0.125 units in world space, the bias will be in world space too. +float GetShadowBias(vec3 N, vec3 L, float texelWidth) +{ + const float quantize = 2.0 / (1 << 23); + const float b = texelWidth / 2.0; + const float NoL = clamp(dot(N, L), 0.0, 1.0); + return quantize + b * length(cross(L, N)) / NoL; +} + struct ShadowVsmOut { float shadow; @@ -62,9 +72,10 @@ struct ShadowVsmOut vec2 vsmUv; float shadowDepth; float projectedDepth; + uint clipmapLevel; }; -ShadowVsmOut ShadowVsm(vec3 fragWorldPos) +ShadowVsmOut ShadowVsm(vec3 fragWorldPos, vec3 normal) { ShadowVsmOut ret; ret.pageData = 0; @@ -82,6 +93,7 @@ ShadowVsmOut ShadowVsm(vec3 fragWorldPos) ret.vsmUv = addr.pageUv; ret.projectedDepth = addr.projectedDepth; + ret.clipmapLevel = addr.clipmapLevel; ret.pageData = imageLoad(i_pageTables, addr.pageAddress).x; if (!GetIsPageBacked(ret.pageData)) @@ -94,7 +106,12 @@ ShadowVsmOut ShadowVsm(vec3 fragWorldPos) const uint physicalAddress = GetPagePhysicalAddress(ret.pageData); ret.shadowDepth = uintBitsToFloat(LoadPageTexel(pageTexel, physicalAddress)); - if (ret.shadowDepth + 1e-2 < ret.projectedDepth) + const float magicForFarClipmaps = 0.2; + const float magicConstantBias = 2.0 / (1 << 23); + const float halfOrthoFrustumLength = 200.0 / 2; + const float shadowTexelSize = exp2(addr.clipmapLevel + magicForFarClipmaps) * clipmapUniforms.firstClipmapTexelLength; + const float bias = magicConstantBias + GetShadowBias(normal, -shadingUniforms.sunDir.xyz, shadowTexelSize) / halfOrthoFrustumLength; + if (ret.shadowDepth + bias < ret.projectedDepth) { ret.shadow = 0.0; return ret; @@ -191,12 +208,9 @@ float Shadow(vec3 fragWorldPos, vec3 normal, vec3 lightDir) // Analytically compute slope-scaled bias const float maxBias = 0.0008; - const float quantize = 2.0 / (1 << 23); - ivec2 res = textureSize(s_rsmDepth, 0); - float b = 1.0 / max(res.x, res.y) / 2.0; - float NoD = clamp(-dot(shadingUniforms.sunDir.xyz, normal), 0.0, 1.0); - float bias = quantize + b * length(cross(-shadingUniforms.sunDir.xyz, normal)) / NoD; - bias = min(bias, maxBias); + float bias = maxBias; + //float bias = GetShadowBias(normal, -shadingUniforms.sunDir.xyz, textureSize(s_rsmDepthShadow, 0)); + //bias = min(bias, maxBias); switch (shadowUniforms.shadowMode) { @@ -240,7 +254,7 @@ void main() vec3 diffuse = albedo * cosTheta * shadingUniforms.sunStrength.rgb; //float shadow = Shadow(fragWorldPos, normal, -shadingUniforms.sunDir.xyz); - ShadowVsmOut shadowVsm = ShadowVsm(fragWorldPos); + ShadowVsmOut shadowVsm = ShadowVsm(fragWorldPos, normal); float shadowSun = shadowVsm.shadow; //shadowSun = 0; @@ -264,18 +278,21 @@ void main() o_color = finalColor; - // Disco view (hashed physical address + outline) + // Disco view (hashed physical address or clipmap level) // if (GetIsPageVisible(shadowVsm.pageData)) // { // const float GOLDEN_CONJ = 0.6180339887498948482045868343656; - // vec4 color = vec4(2.0 * hsv_to_rgb(vec3(float(GetPagePhysicalAddress(shadowVsm.pageData)) * GOLDEN_CONJ, 0.875, 0.85)), 1.0); - // o_color.rgb += color.rgb; - - // const vec2 pageUv = fract(shadowVsm.vsmUv * 128); - // if (pageUv.x < .05 || pageUv.y < .05 || pageUv.x > .95 || pageUv.y > .95) - // { - // o_color.rgb = vec3(1, 0, 0); - // } + // //vec3 color = 2.0 * hsv_to_rgb(vec3(float(GetPagePhysicalAddress(shadowVsm.pageData)) * GOLDEN_CONJ, 0.875, 0.85)); + // vec3 color = 2.0 * hsv_to_rgb(vec3(shadowVsm.clipmapLevel*2 * GOLDEN_CONJ, 0.875, 0.85)); + // //vec4 color = + // o_color.rgb += color; + // } + + // Page outlines + // const vec2 pageUv = fract(shadowVsm.vsmUv); + // if (pageUv.x < .02 || pageUv.y < .02 || pageUv.x > .98 || pageUv.y > .98) + // { + // o_color.rgb = vec3(1, 0, 0); // } // UV + shadow map depth view @@ -293,7 +310,7 @@ void main() // if (GetIsPageBacked(shadowVsm.pageData)) // o_color += vec3(0, .1, 0); // if (GetIsPageDirty(shadowVsm.pageData)) - // o_color += vec3(0, 0, 1); + // o_color += vec3(0, 0, .1); // if (shadowSun == 0.0) // o_color = vec3(1, 1, 0); } \ No newline at end of file diff --git a/data/shaders/shadows/vsm/VsmCommon.h.glsl b/data/shaders/shadows/vsm/VsmCommon.h.glsl index 4acf76b..b4e4c22 100644 --- a/data/shaders/shadows/vsm/VsmCommon.h.glsl +++ b/data/shaders/shadows/vsm/VsmCommon.h.glsl @@ -18,10 +18,15 @@ layout(binding = 5, std430) restrict readonly buffer VsmMarkPagesDirectionalUnif uint clipmapTableIndices[MAX_CLIPMAPS]; uint numClipmaps; - // The area, in world space, of a single texel in the first clipmap - float firstClipmapTexelArea; + // The length, in world space, of a side of single (square) texel in the first clipmap + float firstClipmapTexelLength; }clipmapUniforms; +layout(binding = 6, std140) uniform VsmGlobalUniforms +{ + float lodBias; +}vsmUniforms; + struct VsmPageAllocRequest { // Address of the requester @@ -167,19 +172,17 @@ bool GetClipmapPageFromDepth(sampler2D depthBuffer, ivec2 gid, out PageAddress a const vec2 uvCenter = (vec2(gid) + 0.5) * texel; const vec2 uvTopLeft = uvCenter + vec2(-texel.x, texel.y) * 0.5; const vec2 uvTopRight = uvCenter + vec2(texel.x, texel.y) * 0.5; - const vec2 uvBottomLeft = uvCenter + vec2(texel.x, -texel.y) * 0.5; const float depth = texelFetch(depthBuffer, gid, 0).x; const mat4 invProj = inverse(perFrameUniforms.proj); const vec3 topLeftV = UnprojectUV_ZO(depth, uvTopLeft, invProj); const vec3 topRightV = UnprojectUV_ZO(depth, uvTopRight, invProj); - const vec3 bottomLeftV = UnprojectUV_ZO(depth, uvBottomLeft, invProj); - const float projArea = distance(topLeftV, topRightV) * distance(topLeftV, bottomLeftV); + const float projLength = distance(topLeftV, topRightV); - // Assume each clipmap is 2x the size of the previous - const uint clipmapLevel = clamp(uint(ceil(log2(projArea / clipmapUniforms.firstClipmapTexelArea))), 0, clipmapUniforms.numClipmaps - 1); + // Assume each clipmap is 2x the side length of the previous + const uint clipmapLevel = clamp(uint(ceil(vsmUniforms.lodBias + log2(projLength / clipmapUniforms.firstClipmapTexelLength))), 0, clipmapUniforms.numClipmaps - 1); const uint clipmapIndex = clipmapUniforms.clipmapTableIndices[clipmapLevel]; const vec3 posW = UnprojectUV_ZO(depth, uvCenter, perFrameUniforms.invViewProj); diff --git a/data/shaders/shadows/vsm/VsmShadow.frag.glsl b/data/shaders/shadows/vsm/VsmShadow.frag.glsl index cbe1e6f..2a043fa 100644 --- a/data/shaders/shadows/vsm/VsmShadow.frag.glsl +++ b/data/shaders/shadows/vsm/VsmShadow.frag.glsl @@ -14,9 +14,7 @@ layout(binding = 0, std140) uniform VsmShadowUniforms void main() { const ivec2 pageAddressXy = ivec2(gl_FragCoord.xy) / PAGE_SIZE; - // TODO: implement for multiple clipmaps - //const uint pageData = imageLoad(i_pageTables, ivec3(pageAddressXy, vsmIndex)).x; - const uint pageData = imageLoad(i_pageTables, ivec3(pageAddressXy, 0)).x; + const uint pageData = imageLoad(i_pageTables, ivec3(pageAddressXy, vsmIndex)).x; const ivec2 pageTexel = ivec2(gl_FragCoord.xy) % PAGE_SIZE; if (GetIsPageBacked(pageData) && GetIsPageDirty(pageData)) { diff --git a/src/FrogRenderer.cpp b/src/FrogRenderer.cpp index 6192210..d971c8e 100644 --- a/src/FrogRenderer.cpp +++ b/src/FrogRenderer.cpp @@ -114,9 +114,10 @@ FrogRenderer::FrogRenderer(const Application::CreateInfo& createInfo, std::optio vsmSun({ .context = vsmContext, .virtualExtent = Techniques::VirtualShadowMaps::maxExtent, - .numClipmaps = 1, + .numClipmaps = 10, }), - vsmShadowPipeline(Pipelines::ShadowVsm()) + vsmShadowPipeline(Pipelines::ShadowVsm()), + vsmShadowUniformBuffer(Fwog::BufferStorageFlag::DYNAMIC_STORAGE) { ZoneScoped; int x = 0; @@ -459,7 +460,8 @@ void FrogRenderer::OnRender([[maybe_unused]] double dt) const auto viewProj = projJittered * mainCamera.GetViewMatrix(); const auto viewProjUnjittered = projUnjittered * mainCamera.GetViewMatrix(); - static constexpr auto viewCount = 3; // TODO: don't hardcode + const auto viewCount = 2 + vsmSun.NumClipmaps(); + std::array views = {}; views[0] = { // Main View .proj = projUnjittered, @@ -512,17 +514,20 @@ void FrogRenderer::OnRender([[maybe_unused]] double dt) }; MakeFrustumPlanes(shadingUniforms.sunViewProj, views[1].frustumPlanes); - vsmSun.Update(shadingUniforms.sunView, 10); + vsmSun.UpdateExpensive(shadingUniforms.sunView, 10); - views[2] = { - // VSM clipmap 0 - .proj = vsmSun.GetProjections()[0], - .view = shadingUniforms.sunView, - .viewProj = vsmSun.GetProjections()[0] * shadingUniforms.sunView, - .cameraPos = {}, // unused - .viewport = {0.f, 0.f, vsmSun.GetExtent().width, vsmSun.GetExtent().height}, - }; - MakeFrustumPlanes(shadingUniforms.sunViewProj, views[2].frustumPlanes); + for (uint32_t i = 0; i < vsmSun.NumClipmaps(); i++) + { + views[2 + i] = { + // sun vsm 0 + .proj = vsmSun.GetProjections()[i], + .view = shadingUniforms.sunView, + .viewProj = vsmSun.GetProjections()[i] * shadingUniforms.sunView, + .cameraPos = {}, // unused + .viewport = {0.f, 0.f, vsmSun.GetExtent().width, vsmSun.GetExtent().height}, + }; + MakeFrustumPlanes(views[2 + i].viewProj, views[2 + i].frustumPlanes); + } viewBuffer->UpdateData(std::span(views)); @@ -587,8 +592,14 @@ void FrogRenderer::OnRender([[maybe_unused]] double dt) Fwog::Cmd::BindUniformBuffer("PerFrameUniformsBuffer", globalUniformsBuffer); Fwog::Cmd::BindStorageBuffer("ViewBuffer", viewBuffer.value()); vsmSun.BindResourcesForDrawing(); + Fwog::Cmd::BindUniformBuffer("VsmShadowUniforms", vsmShadowUniformBuffer); Fwog::Cmd::BindIndexBuffer(*instancedMeshletBuffer, Fwog::IndexType::UNSIGNED_INT); - Fwog::Cmd::DrawIndexedIndirect(*meshletIndirectCommand, 2 * sizeof(Fwog::DrawIndexedIndirectCommand), 1, 0); + + for (uint32_t i = 0; i < vsmSun.NumClipmaps(); i++) + { + vsmShadowUniformBuffer.UpdateData(vsmSun.GetClipmapTableIndices()[i]); + Fwog::Cmd::DrawIndexedIndirect(*meshletIndirectCommand, (2 + i) * sizeof(Fwog::DrawIndexedIndirectCommand), 1, 0); + } }); auto shadowAttachment = Fwog::RenderDepthStencilAttachment{ diff --git a/src/FrogRenderer.h b/src/FrogRenderer.h index 1220cb8..24804e4 100644 --- a/src/FrogRenderer.h +++ b/src/FrogRenderer.h @@ -128,6 +128,7 @@ class FrogRenderer final : public Application void GuiDrawBloomWindow(); void GuiDrawAutoExposureWindow(); void GuiDrawCameraWindow(); + void GuiDrawShadowWindow(); // constants static constexpr int gMaxViews = 16; @@ -326,4 +327,6 @@ class FrogRenderer final : public Application Techniques::VirtualShadowMaps::Context vsmContext; Techniques::VirtualShadowMaps::DirectionalVirtualShadowMap vsmSun; Fwog::GraphicsPipeline vsmShadowPipeline; + Fwog::TypedBuffer vsmShadowUniformBuffer; + Techniques::VirtualShadowMaps::Context::VsmGlobalUniforms vsmUniforms{}; }; \ No newline at end of file diff --git a/src/Gui.cpp b/src/Gui.cpp index 54bfdbc..62eba3d 100644 --- a/src/Gui.cpp +++ b/src/Gui.cpp @@ -492,6 +492,19 @@ void FrogRenderer::GuiDrawCameraWindow() ImGui::End(); } +void FrogRenderer::GuiDrawShadowWindow() +{ + if (ImGui::Begin(" Shadow")) + { + ImGui::TextUnformatted("VSM"); + if (ImGui::SliderFloat("LoD Bias", &vsmUniforms.lodBias, -3, 3, "%.2f")) + { + vsmContext.UpdateUniforms(vsmUniforms); + } + } + ImGui::End(); +} + void FrogRenderer::OnGui(double dt) { GuiDrawDockspace(); @@ -625,14 +638,10 @@ void FrogRenderer::OnGui(double dt) ImGui::PopStyleVar(); GuiDrawMagnifier(viewportContentOffset, {viewportContentSize.x, viewportContentSize.y}, viewportIsHovered); - GuiDrawDebugWindow(); - GuiDrawLightsArray(); - GuiDrawBloomWindow(); - GuiDrawAutoExposureWindow(); - GuiDrawCameraWindow(); -} \ No newline at end of file + GuiDrawShadowWindow(); +} diff --git a/src/techniques/VirtualShadowMaps.cpp b/src/techniques/VirtualShadowMaps.cpp index 86f65b5..20dbb83 100644 --- a/src/techniques/VirtualShadowMaps.cpp +++ b/src/techniques/VirtualShadowMaps.cpp @@ -76,7 +76,7 @@ namespace Techniques::VirtualShadowMaps pages_(Fwog::CreateTexture2D({(uint32_t)std::ceil(std::sqrt(createInfo.numPages)) * pageSize, (uint32_t)std::ceil(std::sqrt(createInfo.numPages)) * pageSize}, Fwog::Format::R32_UINT, "VSM Physical Pages")), visiblePagesBitmask_(sizeof(uint32_t) * createInfo.numPages / 32), pageVisibleTimeTree_(sizeof(uint32_t) * createInfo.numPages * 2), - uniformBuffer_(Fwog::BufferStorageFlag::DYNAMIC_STORAGE), + uniformBuffer_(VsmGlobalUniforms{}, Fwog::BufferStorageFlag::DYNAMIC_STORAGE), pageAllocRequests_(sizeof(PageAllocRequest) * (createInfo.numPages + 1)), pagesToClear_(sizeof(uint32_t) + sizeof(uint32_t) * createInfo.numPages), pageClearDispatchParams_(Fwog::DispatchIndirectCommand{pageSize / 8, pageSize / 8, 0}), @@ -99,6 +99,11 @@ namespace Techniques::VirtualShadowMaps pageVisibleTimeTree_.FillData(); } + void Context::UpdateUniforms(const VsmGlobalUniforms& uniforms) + { + uniformBuffer_.UpdateData(uniforms); + } + std::optional Context::AllocateLayer() { for (size_t i = 0; i < freeLayersBitmask_.size(); i++) @@ -189,6 +194,7 @@ namespace Techniques::VirtualShadowMaps DirectionalVirtualShadowMap::DirectionalVirtualShadowMap(const CreateInfo& createInfo) : context_(createInfo.context), + numClipmaps_(createInfo.numClipmaps), virtualExtent_(createInfo.virtualExtent), uniformBuffer_(Fwog::BufferStorageFlag::DYNAMIC_STORAGE) { @@ -222,23 +228,21 @@ namespace Techniques::VirtualShadowMaps Fwog::Cmd::BindStorageBuffer("VsmVisiblePagesBitmask", context_.visiblePagesBitmask_); Fwog::Cmd::BindStorageBuffer("VsmPageAllocRequests", context_.pageAllocRequests_); Fwog::Cmd::BindStorageBuffer("VsmMarkPagesDirectionalUniforms", uniformBuffer_); + Fwog::Cmd::BindUniformBuffer("VsmGlobalUniforms", context_.uniformBuffer_); Fwog::Cmd::BindUniformBuffer(0, globalUniforms); Fwog::Cmd::DispatchInvocations(gDepth.Extent()); }); } - void DirectionalVirtualShadowMap::Update(const glm::mat4& viewMat, float firstClipmapWidth) + void DirectionalVirtualShadowMap::UpdateExpensive(const glm::mat4& viewMat, float firstClipmapWidth) { const auto sideLength = firstClipmapWidth / virtualExtent_; - uniforms_.firstClipmapTexelArea = sideLength * sideLength; - - //const auto view = glm::inverse(glm::mat4_cast(glm::angleAxis(0.0f, glm::normalize(-direction)))); - //const auto view = glm::lookAt(direction * -5.f, glm::vec3(0), glm::vec3(0, 1, 0)); + uniforms_.firstClipmapTexelLength = sideLength; for (uint32_t i = 0; i < uniforms_.numClipmaps; i++) { const auto width = firstClipmapWidth * (1 << i) / 2.0f; - clipmapProjections[i] = glm::orthoZO(-width, width, -width, width, -1.f, 10.f); + clipmapProjections[i] = glm::orthoZO(-width, width, -width, width, -100.f, 100.f); uniforms_.clipmapViewProjections[i] = clipmapProjections[i] * viewMat; } @@ -257,6 +261,7 @@ namespace Techniques::VirtualShadowMaps void DirectionalVirtualShadowMap::BindResourcesForDrawing() { Fwog::Cmd::BindStorageBuffer(5, uniformBuffer_); + Fwog::Cmd::BindUniformBuffer(6, context_.uniformBuffer_); Fwog::Cmd::BindImage(0, context_.pageTables_, 0); Fwog::Cmd::BindImage(1, context_.pages_, 0); } diff --git a/src/techniques/VirtualShadowMaps.h b/src/techniques/VirtualShadowMaps.h index 6f78f0b..7e03806 100644 --- a/src/techniques/VirtualShadowMaps.h +++ b/src/techniques/VirtualShadowMaps.h @@ -31,6 +31,14 @@ namespace Techniques::VirtualShadowMaps explicit Context(const CreateInfo& createInfo); + struct VsmGlobalUniforms + { + float lodBias{}; + char _padding[12]{}; + }; + + void UpdateUniforms(const VsmGlobalUniforms& uniforms); + /// TABLE MAPPINGS // If there is a free layer, returns its index, otherwise returns nothing [[nodiscard]] std::optional AllocateLayer(); @@ -72,12 +80,7 @@ namespace Techniques::VirtualShadowMaps Fwog::Buffer pageVisibleTimeTree_; /// BUFFERS - struct VsmUniforms - { - glm::mat4 invViewProj; - glm::mat4 invProj; - }; - Fwog::TypedBuffer uniformBuffer_; + Fwog::TypedBuffer uniformBuffer_; struct PageAllocRequest { @@ -124,14 +127,19 @@ namespace Techniques::VirtualShadowMaps void MarkVisiblePages(const Fwog::Texture& gDepth, const Fwog::Buffer& globalUniforms); // Invalidates ALL pages in the referenced VSMs. - // Call only when the light itself changes. - void Update(const glm::mat4& viewMat, float firstClipmapWidth); + // Call only when the light itself changes, since this invalidates ALL pages + void UpdateExpensive(const glm::mat4& viewMat, float firstClipmapWidth); void BindResourcesForDrawing(); [[nodiscard]] std::span GetProjections() const noexcept { - return clipmapProjections; + return {clipmapProjections.data(), numClipmaps_}; + } + + [[nodiscard]] std::span GetClipmapTableIndices() const noexcept + { + return {uniforms_.clipmapTableIndices.data(), numClipmaps_}; } [[nodiscard]] Fwog::Extent2D GetExtent() const noexcept @@ -139,16 +147,23 @@ namespace Techniques::VirtualShadowMaps return {virtualExtent_, virtualExtent_}; } + [[nodiscard]] uint32_t NumClipmaps() const noexcept + { + return numClipmaps_; + } + private: struct MarkVisiblePagesDirectionalUniforms { std::array clipmapViewProjections; std::array clipmapTableIndices; uint32_t numClipmaps; - float firstClipmapTexelArea; + float firstClipmapTexelLength; + float lodBias; }; Context& context_; + uint32_t numClipmaps_; uint32_t virtualExtent_; MarkVisiblePagesDirectionalUniforms uniforms_{}; std::array clipmapProjections{};