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);