Add terrain rendering support for Global Surface Atlas
This commit is contained in:
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
Binary file not shown.
@@ -102,6 +102,11 @@ public:
|
||||
/// </summary>
|
||||
API_FIELD() bool IsSingleFrame = false;
|
||||
|
||||
/// <summary>
|
||||
/// Flag used by custom passes to skip any object culling when drawing.
|
||||
/// </summary>
|
||||
API_FIELD() bool IsCullingDisabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -67,7 +67,7 @@ struct GlobalSurfaceAtlasTile : RectPack<GlobalSurfaceAtlasTile, uint16>
|
||||
{
|
||||
}
|
||||
|
||||
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<Actor*, GlobalSurfaceAtlasObject> Objects;
|
||||
Dictionary<void*, GlobalSurfaceAtlasObject> 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<Actor*, GlobalSurfaceAtlasObject>&)surfaceAtlasData.Objects)[actor];
|
||||
const auto& object = ((const Dictionary<Actor*, GlobalSurfaceAtlasObject>&)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<Actor*, GlobalSurfaceAtlasObject>&)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<Actor*, GlobalSurfaceAtlasObject>&)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<Vector4>(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);
|
||||
|
||||
@@ -45,7 +45,7 @@ private:
|
||||
class DynamicTypedBuffer* _objectsBuffer = nullptr;
|
||||
class DynamicVertexBuffer* _vertexBuffer = nullptr;
|
||||
class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData;
|
||||
Array<Actor*> _dirtyObjectsBuffer;
|
||||
Array<void*> _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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -55,7 +55,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the x coordinate.
|
||||
/// </summary>
|
||||
/// <returns>The x position.</returns>
|
||||
FORCE_INLINE int32 GetX() const
|
||||
{
|
||||
return _x;
|
||||
@@ -64,7 +63,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the z coordinate.
|
||||
/// </summary>
|
||||
/// <returns>The z position.</returns>
|
||||
FORCE_INLINE int32 GetZ() const
|
||||
{
|
||||
return _z;
|
||||
@@ -73,7 +71,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the patch.
|
||||
/// </summary>
|
||||
/// <returns>The terrain patch,</returns>
|
||||
FORCE_INLINE TerrainPatch* GetPatch() const
|
||||
{
|
||||
return _patch;
|
||||
@@ -82,19 +79,17 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the chunk world bounds.
|
||||
/// </summary>
|
||||
/// <returns>The bounding box.</returns>
|
||||
FORCE_INLINE const BoundingBox& GetBounds() const
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model world matrix transform.
|
||||
/// Gets the chunk world matrix transform.
|
||||
/// </summary>
|
||||
/// <param name="world">The result world matrix.</param>
|
||||
FORCE_INLINE void GetWorld(Matrix* world) const
|
||||
FORCE_INLINE const Matrix& GetWorld() const
|
||||
{
|
||||
*world = _world;
|
||||
return _world;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,7 +104,6 @@ public:
|
||||
/// <summary>
|
||||
/// Determines whether this chunk has valid lightmap data.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if this chunk has valid lightmap data; otherwise, <c>false</c>.</returns>
|
||||
FORCE_INLINE bool HasLightmap() const
|
||||
{
|
||||
return Lightmap.TextureIndex != INVALID_INDEX;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user