diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index a98b13d91..51e4a3436 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -111,6 +111,16 @@ GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context) return HalfResDepth; } +const RenderBuffers::CustomBuffer* RenderBuffers::FindCustomBuffer(const StringView& name) const +{ + for (const CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return e; + } + return nullptr; +} + uint64 RenderBuffers::GetMemoryUsage() const { uint64 result = 0; diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 149b4c12b..a6f231c0b 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -156,28 +156,24 @@ public: return _viewport; } + const CustomBuffer* FindCustomBuffer(const StringView& name) const; + template const T* FindCustomBuffer(const StringView& name) const { - for (CustomBuffer* e : CustomBuffers) - { - if (e->Name == name) - return (const T*)e; - } - return nullptr; + return (const T*)FindCustomBuffer(name); } template T* GetCustomBuffer(const StringView& name) { - for (CustomBuffer* e : CustomBuffers) + CustomBuffer* result = (CustomBuffer*)FindCustomBuffer(name); + if (!result) { - if (e->Name == name) - return (T*)e; + result = New(); + result->Name = name; + CustomBuffers.Add(result); } - CustomBuffer* result = New(); - result->Name = name; - CustomBuffers.Add(result); return (T*)result; } diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 59d95d3f8..6c6f73494 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -195,6 +195,11 @@ void Sky::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureV context->DrawFullscreenTriangle(); } +bool Sky::IsDynamicSky() const +{ + return !IsStatic() || (SunLight && !SunLight->IsStatic()); +} + void Sky::ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) { // Get precomputed cache and bind it to the pipeline diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 5f2f78a2f..389745e14 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -74,6 +74,7 @@ public: void DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) override; // [ISkyRenderer] + bool IsDynamicSky() const override; void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index f76beff7e..af14c6488 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -87,6 +87,11 @@ bool Skybox::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) return false; } +bool Skybox::IsDynamicSky() const +{ + return !IsStatic(); +} + void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) { // Prepare mock draw call data diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index 3e44dd015..99c28bdd8 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -68,6 +68,7 @@ public: bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; // [ISkyRenderer] + bool IsDynamicSky() const override; void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 16f2dd590..0c353dd39 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -35,6 +35,11 @@ class ISkyRenderer { public: + /// + /// Returns true if sky is realtime, otherwise it's static. + /// + virtual bool IsDynamicSky() const = 0; + /// /// Apply sky material/shader state to the GPU pipeline with custom parameters set (render to GBuffer). /// diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index e8b753b09..e410bce96 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -14,10 +14,12 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Level/Actors/Decal.h" +#include "Engine/Engine/Engine.h" PACK_STRUCT(struct GBufferPassData{ GBufferData GBuffer; @@ -144,7 +146,6 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer // Cache data auto device = GPUDevice::Instance; auto context = device->GetMainContext(); - auto& view = renderContext.View; GPUTextureView* targetBuffers[5] = { lightBuffer, @@ -153,7 +154,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer renderContext.Buffers->GBuffer2->View(), renderContext.Buffers->GBuffer3->View(), }; - view.Pass = DrawPass::GBuffer; + renderContext.View.Pass = DrawPass::GBuffer; // Clear GBuffer { @@ -222,20 +223,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered()) { PROFILE_GPU_CPU("Sky"); - - // Cache data - auto model = _skyModel.Get(); - auto box = model->GetBox(); - - // Calculate sphere model transform to cover far plane - Matrix m1, m2; - Matrix::Scaling(view.Far / (box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum - Matrix::CreateWorld(view.Position, Vector3::Up, Vector3::Backward, m2); // Rotate sphere model - m1 *= m2; - - // Draw sky - renderContext.List->Sky->ApplySky(context, renderContext, m1); - model->Render(context); + DrawSky(renderContext, context); } context->ResetRenderTarget(); @@ -280,6 +268,65 @@ void GBufferPass::RenderDebug(RenderContext& renderContext) context->ResetSR(); } +// Custom render buffer for realtime skybox capturing (eg. used by GI). +class SkyboxCustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + uint64 LastCaptureFrame = 0; + GPUTexture* Skybox = nullptr; + + ~SkyboxCustomBuffer() + { + RenderTargetPool::Release(Skybox); + } +}; + +GPUTextureView* GBufferPass::RenderSkybox(RenderContext& renderContext, GPUContext* context) +{ + GPUTextureView* result = nullptr; + if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered()) + { + // Initialize skybox texture + auto& skyboxData = *renderContext.Buffers->GetCustomBuffer(TEXT("Skybox")); + bool dirty = false; + const int32 resolution = 16; + if (!skyboxData.Skybox) + { + const auto desc = GPUTextureDescription::NewCube(resolution, PixelFormat::R11G11B10_Float); + skyboxData.Skybox = RenderTargetPool::Get(desc); + if (!skyboxData.Skybox) + return nullptr; + dirty = true; + } + + // Redraw sky from time-to-time (dynamic skies can be animated, static skies can have textures streamed) + const uint32 redrawFramesCount = renderContext.List->Sky->IsDynamicSky() ? 4 : 240; + if (Engine::FrameCount - skyboxData.LastCaptureFrame >= redrawFramesCount) + dirty = true; + + if (dirty) + { + PROFILE_GPU_CPU("Skybox"); + skyboxData.LastCaptureFrame = Engine::FrameCount; + const RenderView originalView = renderContext.View; + renderContext.View.Pass = DrawPass::GBuffer; + renderContext.View.SetUpCube(10.0f, 10000.0f, originalView.Position); + for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) + { + renderContext.View.SetFace(faceIndex); + context->SetRenderTarget(skyboxData.Skybox->View(faceIndex)); + context->SetViewportAndScissors(resolution, resolution); + DrawSky(renderContext, context); + } + renderContext.View = originalView; + context->ResetRenderTarget(); + } + + result = skyboxData.Skybox->ViewArray(); + } + return result; +} + #if USE_EDITOR void GBufferPass::DrawMaterialComplexity(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer) @@ -326,6 +373,23 @@ void GBufferPass::SetInputs(const RenderView& view, GBufferData& gBuffer) Matrix::Transpose(view.IP, gBuffer.InvProjectionMatrix); } +void GBufferPass::DrawSky(RenderContext& renderContext, GPUContext* context) +{ + // Cache data + auto model = _skyModel.Get(); + auto box = model->GetBox(); + + // Calculate sphere model transform to cover far plane + Matrix m1, m2; + Matrix::Scaling(renderContext.View.Far / (box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum + Matrix::CreateWorld(renderContext.View.Position, Vector3::Up, Vector3::Backward, m2); // Rotate sphere model + m1 *= m2; + + // Draw sky + renderContext.List->Sky->ApplySky(context, renderContext, m1); + model->Render(context); +} + void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer) { // Skip if no decals to render diff --git a/Source/Engine/Renderer/GBufferPass.h b/Source/Engine/Renderer/GBufferPass.h index e31b0d873..8ff3f6091 100644 --- a/Source/Engine/Renderer/GBufferPass.h +++ b/Source/Engine/Renderer/GBufferPass.h @@ -37,6 +37,14 @@ public: /// The rendering context. void RenderDebug(RenderContext& renderContext); + /// + /// Renders the sky or skybox into low-resolution cubemap. Can be used to sample realtime sky lighting in GI passes. + /// + /// The rendering context. + /// The GPU context. + /// Rendered cubemap or null if not ready or failed. + GPUTextureView* RenderSkybox(RenderContext& renderContext, GPUContext* context); + #if USE_EDITOR void DrawMaterialComplexity(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer); #endif @@ -54,6 +62,7 @@ public: private: + void DrawSky(RenderContext& renderContext, GPUContext* context); void DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer); #if COMPILE_WITH_DEV_ENV diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 45fb8ba58..952fbce3e 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -272,6 +272,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, GlobalSurfaceAtlasPass::BindingData bindingDataSurfaceAtlas; if (GlobalSurfaceAtlasPass::Instance()->Render(renderContext, context, bindingDataSurfaceAtlas)) return true; + GPUTextureView* skybox = GBufferPass::Instance()->RenderSkybox(renderContext, context); // Skip if already done in the current frame const auto currentFrame = Engine::FrameCount; @@ -411,6 +412,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); context->BindSR(12, ddgiData.Result.ProbesState); + context->BindSR(13, skybox); context->BindUA(0, ddgiData.ProbesTrace->View()); context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesCount, 1); context->ResetUA(); diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 8803c7e5b..4aa4382a1 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -2,6 +2,7 @@ #include "GlobalSurfaceAtlasPass.h" #include "../GlobalSignDistanceFieldPass.h" +#include "../GBufferPass.h" #include "../RenderList.h" #include "../ShadowsPass.h" #include "Engine/Core/Math/Matrix3x3.h" @@ -36,7 +37,7 @@ PACK_STRUCT(struct Data0 { Vector3 ViewWorldPos; float ViewNearPlane; - float Padding00; + float SkyboxIntensity; uint32 CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; @@ -815,11 +816,12 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->Draw(output, renderContext.Buffers->GBuffer0); return; } + GPUTextureView* skybox = GBufferPass::Instance()->RenderSkybox(renderContext, context); PROFILE_GPU_CPU("Global Surface Atlas Debug"); const Vector2 outputSize(output->Size()); + Data0 data; { - Data0 data; data.ViewWorldPos = renderContext.View.Position; data.ViewNearPlane = renderContext.View.Near; data.ViewFarPlane = renderContext.View.Far; @@ -827,6 +829,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSurfaceAtlas = bindingData.Constants; + data.SkyboxIntensity = 1.0f; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } @@ -838,6 +841,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); context->BindSR(10, bindingData.AtlasDepth->View()); + context->BindSR(12, skybox); context->SetState(_psDebug); context->SetRenderTarget(output->View()); { @@ -850,6 +854,10 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); context->DrawFullscreenTriangle(); + // Disable skybox + data.SkyboxIntensity = 0.0f; + context->UpdateCB(_cb0, &data); + // Bottom left - diffuse context->BindSR(11, bindingData.AtlasGBuffer0->View()); context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y)); diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 59e7b0468..178707c08 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -121,6 +121,7 @@ Buffer GlobalSurfaceAtlasCulledObjects : register(t9); Texture2D GlobalSurfaceAtlasDepth : register(t10); Texture2D GlobalSurfaceAtlasTex : register(t11); Texture2D ProbesState : register(t12); +TextureCube Skybox : register(t13); // Compute shader for tracing rays for probes using Global SDF and Global Surface Atlas. META_CS(true, FEATURE_LEVEL_SM5) @@ -167,7 +168,7 @@ void CS_TraceRays(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_Dispat else { // Ray hits sky - radiance.rgb = float3(0, 0, 0); // TODO: sample sky/skybox with a fallback radiance + radiance.rgb = Skybox.SampleLevel(SamplerLinearClamp, probeRayDirection, 0); radiance.a = 1e27f; // Sky is the limit } diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index c36f99b8f..9057419a5 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -12,7 +12,7 @@ META_CB_BEGIN(0, Data) float3 ViewWorldPos; float ViewNearPlane; -float Padding00; +float SkyboxIntensity; uint CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; @@ -245,6 +245,7 @@ ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8); Buffer GlobalSurfaceAtlasCulledObjects : register(t9); Texture2D GlobalSurfaceAtlasDepth : register(t10); Texture2D GlobalSurfaceAtlasTex : register(t11); +TextureCube Skybox : register(t12); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) @@ -262,14 +263,23 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); trace.NeedsHitNormal = true; GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); - if (!hit.IsHit()) - return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); - //return float4(hit.HitNormal * 0.5f + 0.5f, 1); - // Sample Global Surface Atlas at the hit location - float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold); - return float4(surfaceColor.rgb, 1); + float3 color; + if (hit.IsHit()) + { + // Sample Global Surface Atlas at the hit location + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); + color = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold).rgb; + //color = hit.HitNormal * 0.5f + 0.5f; + } + else + { + // Sample skybox + float3 skybox = Skybox.SampleLevel(SamplerLinearClamp, viewRay, 0); + float3 sky = float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f); + color = lerp(sky, skybox, SkyboxIntensity); + } + return float4(color, 1); } #endif