Add direct lighting rendering into Global Surface Atlas
This commit is contained in:
Binary file not shown.
@@ -254,6 +254,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Delaunay/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Defocus/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deinitialize/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -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<GlobalSurfaceAtlasTile, uint16>
|
||||
@@ -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<Actor*, GlobalSurfaceAtlasObject> 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<GlobalSurfaceAtlasCustomBuffer>(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<GlobalSurfaceAtlasTile>(0, 0, resolution, resolution);
|
||||
if (!_vertexBuffer)
|
||||
_vertexBuffer = New<DynamicVertexBuffer>(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<AtlasTileVertex>(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<AtlasTileVertex>(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<Vector4>(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<Actor*, GlobalSurfaceAtlasObject>&)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<AtlasTileVertex>(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<Actor*, GlobalSurfaceAtlasObject>&)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);
|
||||
|
||||
@@ -30,6 +30,8 @@ private:
|
||||
bool _supported = false;
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psClear = nullptr;
|
||||
GPUPipelineState* _psDirectLighting0 = nullptr;
|
||||
GPUPipelineState* _psDirectLighting1 = nullptr;
|
||||
GPUPipelineState* _psDebug = nullptr;
|
||||
GPUConstantBuffer* _cb0 = nullptr;
|
||||
|
||||
|
||||
@@ -141,27 +141,6 @@ bool LightPass::setupResources()
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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
|
||||
|
||||
@@ -14,6 +14,27 @@
|
||||
/// </summary>
|
||||
#define SHADOWS_PASS_SS_RR_FORMAT PixelFormat::R11G11B10_Float
|
||||
|
||||
template<typename T>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shadows rendering service.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<float4> 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<float> GlobalSDFTex[4] : register(t0);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user