diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax
index 2fc860f85..b07e84262 100644
--- a/Content/Shaders/GlobalSignDistanceField.flax
+++ b/Content/Shaders/GlobalSignDistanceField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:46a726df8a90ca98b038e6f7d278b0d77e2e18e89859d1bf490b880acd6333b3
+oid sha256:618c12f75584c82a5e64a0161ef94b02373af3d5497d9f292dcdbd90d716b62a
size 10943
diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h
index 597e436a3..8ba994767 100644
--- a/Source/Engine/Graphics/RenderView.h
+++ b/Source/Engine/Graphics/RenderView.h
@@ -102,6 +102,11 @@ public:
///
API_FIELD() bool IsSingleFrame = false;
+ ///
+ /// Flag used by custom passes to skip any object culling when drawing.
+ ///
+ API_FIELD() bool IsCullingDisabled = false;
+
///
/// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap.
///
diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp
index e7d47b97d..f2443570e 100644
--- a/Source/Engine/Level/Actors/SplineModel.cpp
+++ b/Source/Engine/Level/Actors/SplineModel.cpp
@@ -378,7 +378,7 @@ void SplineModel::Draw(RenderContext& renderContext)
for (int32 segment = 0; segment < _instances.Count(); segment++)
{
auto& instance = _instances[segment];
- if (!renderContext.View.CullingFrustum.Intersects(instance.Sphere))
+ if (!(renderContext.View.IsCullingDisabled || renderContext.View.CullingFrustum.Intersects(instance.Sphere)))
continue;
drawCall.Deformable.Segment = (float)segment;
diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp
index 2019ce3af..ccf355195 100644
--- a/Source/Engine/Level/Actors/StaticModel.cpp
+++ b/Source/Engine/Level/Actors/StaticModel.cpp
@@ -247,7 +247,7 @@ void StaticModel::Draw(RenderContext& renderContext)
}
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
{
- GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, _world, Model->LODs.Last().GetBox());
+ GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _world, Model->LODs.Last().GetBox());
return;
}
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world);
diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp
index fa18b6b6e..07aa96eaa 100644
--- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp
+++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp
@@ -67,7 +67,7 @@ struct GlobalSurfaceAtlasTile : RectPack
{
}
- void OnInsert(class GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex);
+ void OnInsert(class GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex);
void OnFree()
{
@@ -78,6 +78,7 @@ struct GlobalSurfaceAtlasObject
{
uint64 LastFrameUsed;
uint64 LastFrameDirty;
+ Actor* Actor;
GlobalSurfaceAtlasTile* Tiles[6];
float Radius;
OrientedBoundingBox Bounds;
@@ -127,7 +128,7 @@ public:
int32 CulledObjectsCounterIndex = -1;
GlobalSurfaceAtlasPass::BindingData Result;
GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles
- Dictionary Objects;
+ Dictionary Objects;
// Cached data to be reused during RasterizeActor
uint64 CurrentFrame;
@@ -165,9 +166,9 @@ public:
}
};
-void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex)
+void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex)
{
- buffer->Objects[actor].Tiles[tileIndex] = this;
+ buffer->Objects[actorObject].Tiles[tileIndex] = this;
}
String GlobalSurfaceAtlasPass::ToString() const
@@ -438,6 +439,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
renderContextTiles.View.ModelLODBias += 100000;
renderContextTiles.View.ShadowModelLODBias += 100000;
renderContextTiles.View.IsSingleFrame = true;
+ renderContextTiles.View.IsCullingDisabled = true;
renderContextTiles.View.Near = 0.0f;
renderContextTiles.View.Prepare(renderContextTiles);
@@ -466,9 +468,9 @@ 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));
- for (Actor* actor : _dirtyObjectsBuffer)
+ for (void* actorObject : _dirtyObjectsBuffer)
{
- const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor];
+ const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actorObject];
for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
{
auto* tile = object.Tiles[tileIndex];
@@ -487,8 +489,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
auto& drawCallsListGBufferNoDecals = renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals];
drawCallsListGBuffer.CanUseInstancing = false;
drawCallsListGBufferNoDecals.CanUseInstancing = false;
- for (Actor* actor : _dirtyObjectsBuffer)
+ for (void* actorObject : _dirtyObjectsBuffer)
{
+ const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actorObject];
+
// Clear draw calls list
renderContextTiles.List->DrawCalls.Clear();
renderContextTiles.List->BatchedDrawCalls.Clear();
@@ -501,10 +505,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
renderContextTiles.View.Projection.Values[0][0] = 10000.0f;
// Collect draw calls for the object
- actor->Draw(renderContextTiles);
+ object.Actor->Draw(renderContextTiles);
// Render all tiles into the atlas
- const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor];
#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS
DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f));
#endif
@@ -838,19 +841,19 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
Vector2 outputSizeThird = outputSize * 0.333f;
Vector2 outputSizeTwoThird = outputSize * 0.666f;
- // Full screen - diffuse
- context->BindSR(11, bindingData.Atlas[1]->View());
+ // Full screen - direct light
+ context->BindSR(11, bindingData.Atlas[4]->View());
context->SetViewport(outputSize.X, outputSize.Y);
context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y));
context->DrawFullscreenTriangle();
- // Bottom left - normals
- context->BindSR(11, bindingData.Atlas[2]->View());
+ // Bottom left - diffuse
+ context->BindSR(11, bindingData.Atlas[1]->View());
context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y));
context->DrawFullscreenTriangle();
- // Bottom middle - direct light
- context->BindSR(11, bindingData.Atlas[4]->View());
+ // Bottom middle - normals
+ context->BindSR(11, bindingData.Atlas[2]->View());
context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeThird.Y, outputSizeThird.X, outputSizeThird.Y));
context->DrawFullscreenTriangle();
@@ -861,17 +864,19 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
}
}
-void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToWorld, const BoundingBox& localBounds)
+void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask)
{
GlobalSurfaceAtlasCustomBuffer& surfaceAtlasData = *_surfaceAtlasData;
- BoundingSphere actorBounds = actor->GetSphere();
Vector3 boundsSize = localBounds.GetSize() * actor->GetScale();
- const float distanceScale = Math::Lerp(1.0f, surfaceAtlasData.DistanceScaling, Math::InverseLerp(surfaceAtlasData.DistanceScalingStart, surfaceAtlasData.DistanceScalingEnd, CollisionsHelper::DistanceSpherePoint(actorBounds, surfaceAtlasData.ViewPosition)));
+ const float distanceScale = Math::Lerp(1.0f, surfaceAtlasData.DistanceScaling, Math::InverseLerp(surfaceAtlasData.DistanceScalingStart, surfaceAtlasData.DistanceScalingEnd, CollisionsHelper::DistanceSpherePoint(actorObjectBounds, surfaceAtlasData.ViewPosition)));
const float tilesScale = surfaceAtlasData.TileTexelsPerWorldUnit * distanceScale;
- GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(actor);
+ GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(actorObject);
bool anyTile = false, dirty = false;
for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
{
+ if (((1 << tileIndex) & tilesMask) == 0)
+ continue;
+
// Calculate optimal tile resolution for the object side
Vector3 boundsSizeTile = boundsSize;
boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size
@@ -909,11 +914,11 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToW
}
// Insert tile into atlas
- auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actor, tileIndex);
+ auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actorObject, tileIndex);
if (tile)
{
if (!object)
- object = &surfaceAtlasData.Objects[actor];
+ object = &surfaceAtlasData.Objects[actorObject];
object->Tiles[tileIndex] = tile;
anyTile = true;
dirty = true;
@@ -934,14 +939,15 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToW
dirty = true;
// Mark object as used
+ object->Actor = actor;
object->LastFrameUsed = surfaceAtlasData.CurrentFrame;
object->Bounds = OrientedBoundingBox(localBounds);
object->Bounds.Transform(localToWorld);
- object->Radius = actorBounds.Radius;
+ object->Radius = actorObjectBounds.Radius;
if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES)
{
object->LastFrameDirty = surfaceAtlasData.CurrentFrame;
- _dirtyObjectsBuffer.Add(actor);
+ _dirtyObjectsBuffer.Add(actorObject);
}
// Write to objects buffer (this must match unpacking logic in HLSL)
@@ -949,7 +955,7 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToW
Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds);
uint32 objectAddress = _objectsBuffer->Data.Count() / sizeof(Vector4);
auto* objectData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE);
- objectData[0] = *(Vector4*)&actorBounds;
+ objectData[0] = *(Vector4*)&actorObjectBounds;
objectData[1] = Vector4::Zero; // w unused
objectData[2] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41);
objectData[3] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42);
diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h
index 70363caad..f245ac7f0 100644
--- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h
+++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h
@@ -45,7 +45,7 @@ private:
class DynamicTypedBuffer* _objectsBuffer = nullptr;
class DynamicVertexBuffer* _vertexBuffer = nullptr;
class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData;
- Array _dirtyObjectsBuffer;
+ Array _dirtyObjectsBuffer;
uint64 _culledObjectsSizeFrames[8];
public:
@@ -67,7 +67,7 @@ public:
void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output);
// Rasterize actor into the Global Surface Atlas. Call it from actor Draw() method during DrawPass::GlobalSurfaceAtlas.
- void RasterizeActor(Actor* actor, const Matrix& localToWorld, const BoundingBox& localBounds);
+ void RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask = MAX_uint32);
private:
#if COMPILE_WITH_DEV_ENV
diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
index 169036aa9..4293c267e 100644
--- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
+++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
@@ -152,8 +152,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context)
auto chunkSize = terrain->GetChunkSize();
const auto heightmap = patch->Heightmap.Get()->GetTexture();
- Matrix world;
- chunk->GetWorld(&world);
+ const Matrix& world = chunk->GetWorld();
Matrix::Transpose(world, shaderData.WorldMatrix);
shaderData.LightmapArea = chunk->Lightmap.UVsArea;
shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp
index 5515ce707..021384376 100644
--- a/Source/Engine/Terrain/Terrain.cpp
+++ b/Source/Engine/Terrain/Terrain.cpp
@@ -14,6 +14,7 @@
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
+#include "Engine/Renderer/GlobalSurfaceAtlasPass.h"
Terrain::Terrain(const SpawnParams& params)
: PhysicsColliderActor(params)
@@ -527,7 +528,25 @@ void Terrain::Draw(RenderContext& renderContext)
return;
}
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
- return; // TODO: Terrain rendering to Global Surface Atlas
+ {
+ for (TerrainPatch* patch : _patches)
+ {
+ if (!patch->Heightmap)
+ continue;
+ Matrix worldToLocal;
+ BoundingSphere chunkSphere;
+ BoundingBox localBounds;
+ for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ {
+ TerrainChunk* chunk = &patch->Chunks[chunkIndex];
+ Matrix::Invert(chunk->GetWorld(), worldToLocal);
+ BoundingBox::Transform(chunk->GetBounds(), worldToLocal, localBounds);
+ BoundingSphere::FromBox(chunk->GetBounds(), chunkSphere);
+ GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, chunk, chunkSphere, chunk->GetWorld(), localBounds, 1 << 2);
+ }
+ }
+ return;
+ }
PROFILE_CPU();
@@ -541,7 +560,7 @@ void Terrain::Draw(RenderContext& renderContext)
for (int32 patchIndex = 0; patchIndex < _patches.Count(); patchIndex++)
{
const auto patch = _patches[patchIndex];
- if (frustum.Intersects(patch->_bounds))
+ if (renderContext.View.IsCullingDisabled || frustum.Intersects(patch->_bounds))
{
// Skip if has no heightmap or it's not loaded
if (patch->Heightmap == nullptr || patch->Heightmap->GetTexture()->ResidentMipLevels() == 0)
@@ -552,7 +571,7 @@ void Terrain::Draw(RenderContext& renderContext)
{
auto chunk = &patch->Chunks[chunkIndex];
chunk->_cachedDrawLOD = 0;
- if (frustum.Intersects(chunk->_bounds))
+ if (renderContext.View.IsCullingDisabled || frustum.Intersects(chunk->_bounds))
{
if (chunk->PrepareDraw(renderContext))
{
diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h
index 611ff46a4..5764844fb 100644
--- a/Source/Engine/Terrain/TerrainChunk.h
+++ b/Source/Engine/Terrain/TerrainChunk.h
@@ -55,7 +55,6 @@ public:
///
/// Gets the x coordinate.
///
- /// The x position.
FORCE_INLINE int32 GetX() const
{
return _x;
@@ -64,7 +63,6 @@ public:
///
/// Gets the z coordinate.
///
- /// The z position.
FORCE_INLINE int32 GetZ() const
{
return _z;
@@ -73,7 +71,6 @@ public:
///
/// Gets the patch.
///
- /// The terrain patch,
FORCE_INLINE TerrainPatch* GetPatch() const
{
return _patch;
@@ -82,19 +79,17 @@ public:
///
/// Gets the chunk world bounds.
///
- /// The bounding box.
FORCE_INLINE const BoundingBox& GetBounds() const
{
return _bounds;
}
///
- /// Gets the model world matrix transform.
+ /// Gets the chunk world matrix transform.
///
- /// The result world matrix.
- FORCE_INLINE void GetWorld(Matrix* world) const
+ FORCE_INLINE const Matrix& GetWorld() const
{
- *world = _world;
+ return _world;
}
///
@@ -109,7 +104,6 @@ public:
///
/// Determines whether this chunk has valid lightmap data.
///
- /// true if this chunk has valid lightmap data; otherwise, false.
FORCE_INLINE bool HasLightmap() const
{
return Lightmap.TextureIndex != INVALID_INDEX;
diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl
index 16c6078bb..b90488cd8 100644
--- a/Source/Shaders/GlobalSurfaceAtlas.hlsl
+++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl
@@ -193,8 +193,21 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu
if (any(localPosition > localExtent) || any(localPosition < -localExtent))
continue;
+ // Remove the scale vector from the transformation matrix
+ float3x3 worldToLocal = object.WorldToLocal;
+ float scaleX = length(worldToLocal[0]);
+ float scaleY = length(worldToLocal[1]);
+ float scaleZ = length(worldToLocal[2]);
+ float3 invScale = float3(
+ scaleX > 0.00001f ? 1.0f / scaleX : 0.0f,
+ scaleY > 0.00001f ? 1.0f / scaleY : 0.0f,
+ scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f);
+ worldToLocal[0] *= invScale.x;
+ worldToLocal[1] *= invScale.y;
+ worldToLocal[2] *= invScale.z;
+
// Sample tiles based on the directionality
- float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal));
+ float3 localNormal = normalize(mul(worldNormal, worldToLocal));
float3 localNormalSq = localNormal * localNormal;
uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1];
if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0)