diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 8b8cbd705..f08b3e829 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13070c079b7a2e5530be1b9d9392b9cc36f41bc566bf86595fe0460a7be4f536 -size 3046 +oid sha256:4f6d1ff40e046e496fe4a550cabf61e180cc46d5045798463d11aa1863578539 +size 5969 diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 007b8e5a2..ec2e8d359 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -254,6 +254,7 @@ True True True + True True True True diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 9e863d75d..5f6d72036 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -3,6 +3,7 @@ #include "GlobalSurfaceAtlasPass.h" #include "GlobalSignDistanceFieldPass.h" #include "RenderList.h" +#include "ShadowsPass.h" #include "Engine/Core/Math/Matrix3x3.h" #include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Engine/Engine.h" @@ -19,10 +20,11 @@ // This must match HLSL #define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles -#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 0 // Forces to redraw all object tiles every frame -#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) +#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) +#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES 0 // Forces to redraw all object tiles every frame +#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) -#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS #include "Engine/Debug/DebugDraw.h" #endif @@ -35,11 +37,15 @@ PACK_STRUCT(struct Data0 Vector4 ViewFrustumWorldRays[4]; GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; GlobalSurfaceAtlasPass::GlobalSurfaceAtlasData GlobalSurfaceAtlas; + LightData Light; }); PACK_STRUCT(struct AtlasTileVertex { Half2 Position; + Half2 TileUV; + uint16 ObjectIndex; + uint16 TileIndex; }); struct GlobalSurfaceAtlasTile : RectPack @@ -66,6 +72,8 @@ struct GlobalSurfaceAtlasObject uint64 LastFrameUsed; uint64 LastFrameDirty; GlobalSurfaceAtlasTile* Tiles[6]; + uint32 Index; + float Radius; OrientedBoundingBox Bounds; GlobalSurfaceAtlasObject() @@ -103,11 +111,13 @@ public: uint64 LastFrameAtlasInsertFail = 0; uint64 LastFrameAtlasDefragmentation = 0; GPUTexture* AtlasDepth = nullptr; + GPUTexture* AtlasEmissive = nullptr; GPUTexture* AtlasGBuffer0 = nullptr; GPUTexture* AtlasGBuffer1 = nullptr; GPUTexture* AtlasGBuffer2 = nullptr; GPUTexture* AtlasDirectLight = nullptr; DynamicTypedBuffer ObjectsBuffer; + uint32 ObjectIndexCounter; GlobalSurfaceAtlasPass::BindingData Result; GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles Dictionary Objects; @@ -128,6 +138,7 @@ public: FORCE_INLINE void Clear() { RenderTargetPool::Release(AtlasDepth); + RenderTargetPool::Release(AtlasEmissive); RenderTargetPool::Release(AtlasGBuffer0); RenderTargetPool::Release(AtlasGBuffer1); RenderTargetPool::Release(AtlasGBuffer2); @@ -198,11 +209,27 @@ bool GlobalSurfaceAtlasPass::setupResources() psDesc.DepthTestEnable = true; psDesc.DepthWriteEnable = true; psDesc.DepthFunc = ComparisonFunc::Always; - psDesc.VS = shader->GetVS("VS_Clear"); + psDesc.VS = shader->GetVS("VS_Atlas"); psDesc.PS = shader->GetPS("PS_Clear"); if (_psClear->Init(psDesc)) return true; } + if (!_psDirectLighting0) + { + _psDirectLighting0 = device->CreatePipelineState(); + psDesc.DepthTestEnable = false; + psDesc.DepthWriteEnable = false; + psDesc.DepthFunc = ComparisonFunc::Never; + psDesc.BlendMode = BlendingMode::Add; + psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; + psDesc.PS = shader->GetPS("PS_DirectLighting", 0); + if (_psDirectLighting0->Init(psDesc)) + return true; + _psDirectLighting1 = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_DirectLighting", 1); + if (_psDirectLighting1->Init(psDesc)) + return true; + } return false; } @@ -212,6 +239,8 @@ bool GlobalSurfaceAtlasPass::setupResources() void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj) { SAFE_DELETE_GPU_RESOURCE(_psClear); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); SAFE_DELETE_GPU_RESOURCE(_psDebug); invalidateResources(); } @@ -225,6 +254,8 @@ void GlobalSurfaceAtlasPass::Dispose() // Cleanup SAFE_DELETE(_vertexBuffer); SAFE_DELETE_GPU_RESOURCE(_psClear); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; _shader = nullptr; @@ -239,6 +270,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co return true; auto& surfaceAtlasData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSurfaceAtlas")); + // Render Global SDF (used for direct shadowing) + GlobalSignDistanceFieldPass::BindingData bindingDataSDF; + if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF)) + return true; + // Skip if already done in the current frame const auto currentFrame = Engine::FrameCount; if (surfaceAtlasData.LastFrameUsed == currentFrame) @@ -265,6 +301,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co uint64 memUsage = 0; // TODO: try using BC4/BC5/BC7 block compression for Surface Atlas (eg. for Tiles material properties) #define INIT_ATLAS_TEXTURE(texture, format) desc.Format = format; surfaceAtlasData.texture = RenderTargetPool::Get(desc); if (!surfaceAtlasData.texture) return true; memUsage += surfaceAtlasData.texture->GetMemoryUsage() + INIT_ATLAS_TEXTURE(AtlasEmissive, LIGHT_BUFFER_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer0, GBUFFER0_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer1, GBUFFER1_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer2, GBUFFER2_FORMAT); @@ -289,9 +326,37 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); if (!_vertexBuffer) _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); - + const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); + const Vector2 posToClipAdd(-1.0f, 1.0f); +#define VB_WRITE_TILE_POS_ONLY(tile) \ + Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); \ + Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ + auto* quad = _vertexBuffer->WriteReserve(6); \ + quad[0].Position = max; \ + quad[1].Position = { min.X, max.Y }; \ + quad[2].Position = min; \ + quad[3].Position = quad[2].Position; \ + quad[4].Position = { max.X, min.Y }; \ + quad[5].Position = quad[0].Position +#define VB_WRITE_TILE(tile) \ + Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); \ + Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ + Vector2 minUV(0, 0), maxUV(1, 1); \ + auto* quad = _vertexBuffer->WriteReserve(6); \ + quad[0] = { { max }, { maxUV }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[1] = { { min.X, max.Y }, { minUV.X, maxUV.Y }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[2] = { { min }, { minUV }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[3] = quad[2]; \ + quad[4] = { { max.X, min.Y }, { maxUV.X, minUV.Y }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[5] = quad[0] +#define VB_DRAW() \ + _vertexBuffer->Flush(context); \ + auto vb = _vertexBuffer->GetBuffer(); \ + context->BindVB(ToSpan(&vb, 1)); \ + context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); // Add objects into the atlas surfaceAtlasData.ObjectsBuffer.Clear(); + surfaceAtlasData.ObjectIndexCounter = 0; _dirtyObjectsBuffer.Clear(); { PROFILE_CPU_NAMED("Draw"); @@ -390,7 +455,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co object->LastFrameUsed = currentFrame; object->Bounds = OrientedBoundingBox(localBounds); object->Bounds.Transform(localToWorld); - if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + object->Radius = e.Bounds.Radius; + if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) { object->LastFrameDirty = currentFrame; _dirtyObjectsBuffer.Add(e.Actor); @@ -400,6 +466,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co Matrix worldToLocalBounds; Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) + object->Index = surfaceAtlasData.ObjectIndexCounter++; auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_SIZE); objectData[0] = *(Vector4*)&e.Bounds; objectData[1] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); @@ -500,7 +567,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co GPUTextureView* depthBuffer = surfaceAtlasData.AtlasDepth->View(); GPUTextureView* targetBuffers[4] = { - surfaceAtlasData.AtlasDirectLight->View(), + surfaceAtlasData.AtlasEmissive->View(), surfaceAtlasData.AtlasGBuffer0->View(), surfaceAtlasData.AtlasGBuffer1->View(), surfaceAtlasData.AtlasGBuffer2->View(), @@ -508,7 +575,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); { PROFILE_GPU_CPU("Clear"); - if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) { // Full-atlas hardware clear context->ClearDepth(depthBuffer); @@ -522,8 +589,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Per-tile clear (with a single draw call) _vertexBuffer->Clear(); _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); - const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); - const Vector2 posToClipAdd(-1.0f, 1.0f); for (Actor* actor : _dirtyObjectsBuffer) { const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; @@ -532,23 +597,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co auto* tile = object.Tiles[tileIndex]; if (!tile) continue; - Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); - Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); - auto* quad = _vertexBuffer->WriteReserve(6); - quad[0] = { { max } }; - quad[1] = { { min.X, max.Y } }; - quad[2] = { { min } }; - quad[3] = quad[2]; - quad[4] = { { max.X, min.Y } }; - quad[5] = quad[0]; + VB_WRITE_TILE_POS_ONLY(tile); } } - _vertexBuffer->Flush(context); - auto vb = _vertexBuffer->GetBuffer(); context->SetState(_psClear); context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); - context->BindVB(ToSpan(&vb, 1)); - context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); + VB_DRAW(); } } // TODO: limit dirty objects count on a first frame (eg. collect overflown objects to be redirty next frame) @@ -574,7 +628,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Render all tiles into the atlas const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; -#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f)); #endif for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) @@ -588,12 +642,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Setup projection to capture object from the side renderContextTiles.View.Position = tile->ViewPosition; renderContextTiles.View.Direction = tile->ViewDirection; - renderContextTiles.View.Near = -0.1f; // Small offset to prevent clipping with the closest triangles - renderContextTiles.View.Far = tile->ViewBoundsSize.Z + 0.2f; + renderContextTiles.View.Near = -GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + renderContextTiles.View.Far = tile->ViewBoundsSize.Z + 2 * GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; Matrix projectionMatrix; Matrix::Ortho(tile->ViewBoundsSize.X, tile->ViewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); renderContextTiles.View.SetUp(tile->ViewMatrix, projectionMatrix); -#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS DebugDraw::DrawLine(renderContextTiles.View.Position, renderContextTiles.View.Position + renderContextTiles.View.Direction * 20.0f, Color::Orange); DebugDraw::DrawWireSphere(BoundingSphere(renderContextTiles.View.Position, 10.0f), Color::Green); #endif @@ -608,12 +662,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co RenderList::ReturnToPool(renderContextTiles.List); } - // TODO: update direct lighting atlas (for modified tiles and lights) - // TODO: update static lights only for dirty tiles (dynamic lights every X frames) - // TODO: use custom dynamic vertex buffer to decide which atlas tiles to shade with a light - - // TODO: indirect lighting apply to get infinite bounces for GI - // Copy results result.Atlas[0] = surfaceAtlasData.AtlasDepth; result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; @@ -624,6 +672,114 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.GlobalSurfaceAtlas.Resolution = (float)resolution; result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); surfaceAtlasData.Result = result; + + // Render direct lighting into atlas + if (surfaceAtlasData.Objects.Count() != 0) + { + PROFILE_GPU_CPU("Direct Lighting"); + + // Copy emissive light into the final direct lighting atlas + // TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles + context->CopyTexture(surfaceAtlasData.AtlasDirectLight, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); + + context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); + context->SetRenderTarget(surfaceAtlasData.AtlasDirectLight->View()); + context->BindSR(0, surfaceAtlasData.AtlasGBuffer0->View()); + context->BindSR(1, surfaceAtlasData.AtlasGBuffer1->View()); + context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); + context->BindSR(3, surfaceAtlasData.AtlasDepth->View()); + context->BindSR(4, surfaceAtlasData.ObjectsBuffer.GetBuffer()->View()); + context->BindCB(0, _cb0); + Data0 data; + data.ViewWorldPos = renderContext.View.Position; + data.GlobalSDF = bindingDataSDF.GlobalSDF; + data.GlobalSurfaceAtlas = result.GlobalSurfaceAtlas; + + // Shade object tiles influenced by lights to calculate direct lighting + // TODO: reduce redraw frequency for static lights (StaticFlags::Lightmap) + for (auto& light : renderContext.List->DirectionalLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile || Vector3::Dot(tile->ViewDirection, light.Direction) < ZeroTolerance) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + // TODO: test perf/quality when using Shadow Map for directional light (ShadowsPass::Instance()->LastDirLightShadowMap) instead of Global SDF trace + light.SetupLightData(&data.Light, useShadow); + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting0); + VB_DRAW(); + } + for (auto& light : renderContext.List->PointLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + Vector3 lightToObject = object.Bounds.GetCenter() - light.Position; + if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius)) + continue; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + light.SetupLightData(&data.Light, useShadow); + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting1); + VB_DRAW(); + } + for (auto& light : renderContext.List->SpotLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + Vector3 lightToObject = object.Bounds.GetCenter() - light.Position; + if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius)) + continue; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile || Vector3::Dot(tile->ViewDirection, light.Direction) < ZeroTolerance) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + light.SetupLightData(&data.Light, useShadow); + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting1); + VB_DRAW(); + } + + context->ResetRenderTarget(); + } + + // TODO: indirect lighting apply to get infinite bounces for GI + +#undef WRITE_TILE return false; } @@ -639,7 +795,6 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex PROFILE_GPU_CPU("Global Surface Atlas Debug"); const Vector2 outputSize(output->Size()); - if (_cb0) { Data0 data; data.ViewWorldPos = renderContext.View.Position; @@ -660,10 +815,10 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(8, bindingData.Objects ? bindingData.Objects->View() : nullptr); context->BindSR(9, bindingData.Atlas[0]->View()); { - GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse + //GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse //GPUTexture* tex = bindingData.Atlas[2]; // Preview normals //GPUTexture* tex = bindingData.Atlas[3]; // Preview roughness/metalness/ao - //GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light + GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light context->BindSR(10, tex->View()); } context->SetState(_psDebug); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index c577732c7..2b225bc46 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -30,6 +30,8 @@ private: bool _supported = false; AssetReference _shader; GPUPipelineState* _psClear = nullptr; + GPUPipelineState* _psDirectLighting0 = nullptr; + GPUPipelineState* _psDirectLighting1 = nullptr; GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 2beeac5d8..c6ef07f51 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -141,27 +141,6 @@ bool LightPass::setupResources() return false; } -template -bool CanRenderShadow(RenderView& view, const T& light) -{ - bool result = false; - switch ((ShadowsCastingMode)light.ShadowsMode) - { - case ShadowsCastingMode::StaticOnly: - result = view.IsOfflinePass; - break; - case ShadowsCastingMode::DynamicOnly: - result = !view.IsOfflinePass; - break; - case ShadowsCastingMode::All: - result = true; - break; - default: - break; - } - return result && light.ShadowsStrength > ZeroTolerance; -} - void LightPass::Dispose() { // Base diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index 1f9a0db5f..c17757fb4 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -14,6 +14,27 @@ /// #define SHADOWS_PASS_SS_RR_FORMAT PixelFormat::R11G11B10_Float +template +bool CanRenderShadow(RenderView& view, const T& light) +{ + bool result = false; + switch ((ShadowsCastingMode)light.ShadowsMode) + { + case ShadowsCastingMode::StaticOnly: + result = view.IsOfflinePass; + break; + case ShadowsCastingMode::DynamicOnly: + result = !view.IsOfflinePass; + break; + case ShadowsCastingMode::All: + result = true; + break; + default: + break; + } + return result && light.ShadowsStrength > ZeroTolerance; +} + /// /// Shadows rendering service. /// diff --git a/Source/Shaders/GBuffer.hlsl b/Source/Shaders/GBuffer.hlsl index 0edcbef27..9ec390135 100644 --- a/Source/Shaders/GBuffer.hlsl +++ b/Source/Shaders/GBuffer.hlsl @@ -144,24 +144,6 @@ GBufferSample SampleGBufferFast(GBufferData gBuffer, float2 uv) return result; } -// Sample GBuffer normal vector, shading model and view space position -GBufferSample SampleGBufferNormalVPos(GBufferData gBuffer, float2 uv) -{ - GBufferSample result; - - // Sample GBuffer - float4 gBuffer1 = SAMPLE_RT(GBuffer1, uv); - - // Decode normal and shading model - result.Normal = DecodeNormal(gBuffer1.rgb); - result.ShadingModel = (int)(gBuffer1.a * 3.999); - - // Calculate view space position - result.ViewPos = GetViewPos(gBuffer, uv); - - return result; -} - #if defined(USE_GBUFFER_CUSTOM_DATA) // Sample GBuffer custom data only diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index dab389ab9..540425a8b 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -6,6 +6,7 @@ // This must match C++ #define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling +#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) struct GlobalSurfaceTile { diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 91f7bb0f0..8d1286b18 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -1,9 +1,13 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +// Diffuse-only lighting +#define NO_SPECULAR + #include "./Flax/Common.hlsl" #include "./Flax/Math.hlsl" #include "./Flax/GlobalSurfaceAtlas.hlsl" #include "./Flax/GlobalSignDistanceField.hlsl" +#include "./Flax/LightingCommon.hlsl" META_CB_BEGIN(0, Data) float3 ViewWorldPos; @@ -13,14 +17,35 @@ float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; +LightData Light; META_CB_END -// Vertex shader for Global Surface Atlas software clearing +struct AtlasVertexIput +{ + float2 Position : POSITION0; + float2 TileUV : TEXCOORD0; + uint2 Index : TEXCOORD1; +}; + +struct AtlasVertexOutput +{ + float4 Position : SV_Position; + float2 TileUV : TEXCOORD0; + nointerpolation uint2 Index : TEXCOORD1; +}; + +// Vertex shader for Global Surface Atlas rendering (custom vertex buffer to render per-tile) META_VS(true, FEATURE_LEVEL_SM5) META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -float4 VS_Clear(float2 Position : POSITION0) : SV_Position +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_UINT, 0, ALIGN, PER_VERTEX, 0, true) +AtlasVertexOutput VS_Atlas(AtlasVertexIput input) { - return float4(Position, 1, 1); + AtlasVertexOutput output; + output.Position = float4(input.Position, 1, 1); + output.TileUV = input.TileUV; + output.Index = input.Index; + return output; } // Pixel shader for Global Surface Atlas software clearing @@ -33,6 +58,70 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl RT2 = float4(1, 0, 0, 0); } +#ifdef _PS_DirectLighting + +#include "./Flax/GBuffer.hlsl" +#include "./Flax/Matrix.hlsl" +#include "./Flax/Lighting.hlsl" + +// GBuffer+Depth at 0-3 slots +Buffer GlobalSurfaceAtlasObjects : register(t4); + +// Pixel shader for Global Surface Atlas shading with direct light contribution +META_PS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(RADIAL_LIGHT=0) +META_PERMUTATION_1(RADIAL_LIGHT=1) +float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target +{ + // Load current tile info + //GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(GlobalSurfaceAtlasObjects, input.Index.x); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.Index.x, input.Index.y); + float2 atlasUV = input.TileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; + + // Load GBuffer sample from atlas + GBufferData gBufferData = (GBufferData)0; + GBufferSample gBuffer = SampleGBuffer(gBufferData, atlasUV); + + // Skip unlit pixels + BRANCH + if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) + { + discard; + return 0; + } + + // Reconstruct world-space position manually (from uv+depth within a tile) + float tileDepth = SampleZ(atlasUV); + //float tileNear = -GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + //float tileFar = tile.ViewBoundsSize.z + 2 * GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + //gBufferData.ViewInfo.zw = float2(tileFar / (tileFar - tileNear), (-tileFar * tileNear) / (tileFar - tileNear) / tileFar); + //gBufferData.ViewInfo.zw = float2(1, 0); + //float tileLinearDepth = LinearizeZ(gBufferData, tileDepth); + float3 tileSpacePos = float3(input.TileUV.x - 0.5f, 0.5f - input.TileUV.y, tileDepth); + float3 gBufferTilePos = tileSpacePos * tile.ViewBoundsSize; + float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal); + gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz; + + float4 shadowMask = 1; + BRANCH + if (Light.CastShadows > 0) + { + // TODO: calculate shadow for the light (use Global SDF) + } + + // Calculate lighting +#if RADIAL_LIGHT + bool isSpotLight = Light.SpotAngles.x > -2.0f; +#else + bool isSpotLight = false; +#endif + float4 light = GetLighting(ViewWorldPos, Light, gBuffer, shadowMask, RADIAL_LIGHT, isSpotLight); + + return light; +} + +#endif + #ifdef _PS_Debug Texture3D GlobalSDFTex[4] : register(t0); diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 9b81504a6..4a1cabac6 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -3,10 +3,6 @@ #ifndef __LIGHTING__ #define __LIGHTING__ -#if !defined(USE_GBUFFER_CUSTOM_DATA) -#error "Cannot calculate lighting without custom data in GBuffer. Define USE_GBUFFER_CUSTOM_DATA." -#endif - #include "./Flax/LightingCommon.hlsl" ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMask) @@ -20,21 +16,23 @@ ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMa LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { float3 diffuseColor = GetDiffuseColor(gBuffer); - float3 specularColor = GetSpecularColor(gBuffer); - float3 H = normalize(V + L); float NoL = saturate(dot(N, L)); float NoV = max(dot(N, V), 1e-5); float NoH = saturate(dot(N, H)); float VoH = saturate(dot(V, H)); - float D = D_GGX(gBuffer.Roughness, NoH) * energy; - float Vis = Vis_SmithJointApprox(gBuffer.Roughness, NoV, NoL); - float3 F = F_Schlick(specularColor, VoH); - LightingData lighting; lighting.Diffuse = Diffuse_Lambert(diffuseColor); +#if defined(NO_SPECULAR) + lighting.Specular = 0; +#else + float3 specularColor = GetSpecularColor(gBuffer); + float3 F = F_Schlick(specularColor, VoH); + float D = D_GGX(gBuffer.Roughness, NoH) * energy; + float Vis = Vis_SmithJointApprox(gBuffer.Roughness, NoV, NoL); lighting.Specular = (D * Vis) * F; +#endif lighting.Transmission = 0; return lighting; } @@ -42,7 +40,7 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { LightingData lighting = StandardShading(gBuffer, energy, L, V, N); - +#if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the material float3 subsurfaceColor = gBuffer.CustomData.rgb; float opacity = gBuffer.CustomData.a; @@ -51,21 +49,21 @@ LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, fl float normalContribution = saturate(dot(N, H) * opacity + 1.0f - opacity); float backScatter = gBuffer.AO * normalContribution / (PI * 2.0f); lighting.Transmission = lerp(backScatter, 1, inscatter) * subsurfaceColor; - +#endif return lighting; } LightingData FoliageShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { LightingData lighting = StandardShading(gBuffer, energy, L, V, N); - +#if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the thin foliage float3 subsurfaceColor = gBuffer.CustomData.rgb; float wrapNoL = saturate((-dot(N, L) + 0.5f) / 2.25); float VoL = dot(V, L); float scatter = D_GGX(0.36, saturate(-VoL)); lighting.Transmission = subsurfaceColor * (wrapNoL * scatter); - +#endif return lighting; } @@ -144,9 +142,6 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f // Calculate direct lighting LightingData lighting = SurfaceShading(gBuffer, energy, L, V, N); -#if NO_SPECULAR - lighting.Specular = float3(0, 0, 0); -#endif // Calculate final light color float3 surfaceLight = (lighting.Diffuse + lighting.Specular) * (NoL * attenuation * shadow.SurfaceShadow);