diff --git a/Flax.flaxproj b/Flax.flaxproj
index 4f24f4c52..d3c343532 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -3,7 +3,7 @@
"Version": {
"Major": 1,
"Minor": 4,
- "Build": 6331
+ "Build": 6332
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.",
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index 9a855eba2..a471b41c4 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -847,6 +847,8 @@ void Foliage::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Foliage rendering to Global SDF
+ if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
+ return; // Not supported
if (Instances.IsEmpty())
return;
auto& view = renderContext.View;
diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp
index bdb57b922..1aa14bacb 100644
--- a/Source/Engine/Foliage/FoliageType.cpp
+++ b/Source/Engine/Foliage/FoliageType.cpp
@@ -212,4 +212,7 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
+ // [Deprecated on 27.04.2022, expires on 27.04.2024]
+ if (modifier->EngineBuild <= 6331)
+ DrawModes |= DrawPass::GlobalSurfaceAtlas;
}
diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h
index 44e080035..0125fb94c 100644
--- a/Source/Engine/Graphics/Enums.h
+++ b/Source/Engine/Graphics/Enums.h
@@ -703,10 +703,15 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
MotionVectors = 1 << 4,
///
- /// The Global Sign Distance Field (SDF) rendering pass.
+ /// The Global Sign Distance Field (SDF) rendering pass. Used for software raytracing though the scene on a GPU.
///
GlobalSDF = 1 << 5,
+ ///
+ /// The Global Surface Atlas rendering pass. Used for software raytracing though the scene on a GPU to evaluate the object surface material properties.
+ ///
+ GlobalSurfaceAtlas = 1 << 6,
+
///
/// The debug quad overdraw rendering (editor-only).
///
@@ -717,13 +722,13 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
/// The default set of draw passes for the scene objects.
///
API_ENUM(Attributes="HideInEditor")
- Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
+ Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas,
///
/// The all draw passes combined into a single mask.
///
API_ENUM(Attributes="HideInEditor")
- All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
+ All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas,
};
DECLARE_ENUM_OPERATORS(DrawPass);
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 0303d3e04..eb385ce8b 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -694,6 +694,8 @@ void AnimatedModel::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Animated Model rendering to Global SDF
+ if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
+ return; // No supported
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world);
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass & (int32)renderContext.View.GetShadowsDrawPassMask(ShadowsMode));
@@ -815,6 +817,9 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
+ // [Deprecated on 27.04.2022, expires on 27.04.2024]
+ if (modifier->EngineBuild <= 6331)
+ DrawModes |= DrawPass::GlobalSurfaceAtlas;
}
bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal)
diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp
index 8ca4d675a..288a060c3 100644
--- a/Source/Engine/Level/Actors/Camera.cpp
+++ b/Source/Engine/Level/Actors/Camera.cpp
@@ -249,7 +249,9 @@ bool Camera::HasContentLoaded() const
void Camera::Draw(RenderContext& renderContext)
{
- if (renderContext.View.Flags & ViewFlags::EditorSprites && _previewModel && _previewModel->IsLoaded())
+ if (renderContext.View.Flags & ViewFlags::EditorSprites
+ && _previewModel
+ && _previewModel->IsLoaded())
{
GeometryDrawStateData drawState;
Mesh::DrawInfo draw;
@@ -259,14 +261,16 @@ void Camera::Draw(RenderContext& renderContext)
draw.Lightmap = nullptr;
draw.LightmapUVs = nullptr;
draw.Flags = StaticFlags::Transform;
- draw.DrawModes = (DrawPass)(DrawPass::Default & renderContext.View.Pass);
+ draw.DrawModes = (DrawPass)((DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward) & renderContext.View.Pass);
BoundingSphere::FromBox(_previewModelBox, draw.Bounds);
draw.PerInstanceRandom = GetPerInstanceRandom();
draw.LODBias = 0;
draw.ForcedLOD = -1;
draw.VertexColors = nullptr;
-
- _previewModel->Draw(renderContext, draw);
+ if (draw.DrawModes != DrawPass::None)
+ {
+ _previewModel->Draw(renderContext, draw);
+ }
}
}
diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp
index 7391691b3..38ac0407d 100644
--- a/Source/Engine/Level/Actors/DirectionalLight.cpp
+++ b/Source/Engine/Level/Actors/DirectionalLight.cpp
@@ -20,6 +20,7 @@ void DirectionalLight::Draw(RenderContext& renderContext)
AdjustBrightness(renderContext.View, brightness);
if (Brightness > ZeroTolerance
&& (renderContext.View.Flags & ViewFlags::DirectionalLights) != 0
+ && renderContext.View.Pass & DrawPass::GBuffer
&& (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance))
{
RendererDirectionalLightData data;
diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp
index 712051250..82d5dd7bb 100644
--- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp
+++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp
@@ -33,7 +33,11 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext)
{
// Render only when shader is valid and fog can be rendered
// Do not render exponential fog in orthographic views
- if ((renderContext.View.Flags & ViewFlags::Fog) != 0 && _shader && _shader->IsLoaded() && renderContext.View.IsPerspectiveProjection())
+ if ((renderContext.View.Flags & ViewFlags::Fog) != 0
+ && renderContext.View.Pass & DrawPass::GBuffer
+ && _shader
+ && _shader->IsLoaded()
+ && renderContext.View.IsPerspectiveProjection())
{
// Prepare
if (_psFog.States[0] == nullptr)
diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp
index d7bfa4c7b..6bab5440b 100644
--- a/Source/Engine/Level/Actors/SkyLight.cpp
+++ b/Source/Engine/Level/Actors/SkyLight.cpp
@@ -101,6 +101,7 @@ void SkyLight::Draw(RenderContext& renderContext)
float brightness = Brightness;
AdjustBrightness(renderContext.View, brightness);
if ((renderContext.View.Flags & ViewFlags::SkyLights) != 0
+ && renderContext.View.Pass & DrawPass::GBuffer
&& brightness > ZeroTolerance
&& (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance))
{
diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp
index c84226928..e7d47b97d 100644
--- a/Source/Engine/Level/Actors/SplineModel.cpp
+++ b/Source/Engine/Level/Actors/SplineModel.cpp
@@ -352,6 +352,8 @@ void SplineModel::Draw(RenderContext& renderContext)
auto model = Model.Get();
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Spline Model rendering to Global SDF
+ if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
+ return; // TODO: Spline Model rendering to Global Surface Atlas
if (!Entries.IsValidFor(model))
Entries.Setup(model);
@@ -475,6 +477,9 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
+ // [Deprecated on 27.04.2022, expires on 27.04.2024]
+ if (modifier->EngineBuild <= 6331)
+ DrawModes |= DrawPass::GlobalSurfaceAtlas;
}
void SplineModel::OnTransformChanged()
diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp
index 58328d223..2019ce3af 100644
--- a/Source/Engine/Level/Actors/StaticModel.cpp
+++ b/Source/Engine/Level/Actors/StaticModel.cpp
@@ -10,6 +10,7 @@
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
+#include "Engine/Renderer/GlobalSurfaceAtlasPass.h"
#include "Engine/Utilities/Encryption.h"
#if USE_EDITOR
#include "Editor/Editor.h"
@@ -244,6 +245,11 @@ void StaticModel::Draw(RenderContext& renderContext)
GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _world, _box);
return;
}
+ if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
+ {
+ GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, _world, Model->LODs.Last().GetBox());
+ return;
+ }
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world);
// Flush vertex colors if need to
@@ -443,6 +449,9 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
+ // [Deprecated on 27.04.2022, expires on 27.04.2024]
+ if (modifier->EngineBuild <= 6331)
+ DrawModes |= DrawPass::GlobalSurfaceAtlas;
{
const auto member = stream.FindMember("RenderPasses");
diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp
index 078690337..5d32aceff 100644
--- a/Source/Engine/Particles/ParticleEffect.cpp
+++ b/Source/Engine/Particles/ParticleEffect.cpp
@@ -494,7 +494,7 @@ bool ParticleEffect::HasContentLoaded() const
void ParticleEffect::Draw(RenderContext& renderContext)
{
- if (renderContext.View.Pass == DrawPass::GlobalSDF)
+ if (renderContext.View.Pass == DrawPass::GlobalSDF || renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
return;
_lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position));
Particles::DrawParticles(renderContext, this);
@@ -679,10 +679,6 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier*
{
ApplyModifiedParameters();
}
-
- // [Deprecated on 07.02.2022, expires on 07.02.2024]
- if (modifier->EngineBuild <= 6330)
- DrawModes |= DrawPass::GlobalSDF;
}
void ParticleEffect::EndPlay()
diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp
index dde8878b7..4473e9101 100644
--- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp
+++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp
@@ -129,6 +129,15 @@ public:
GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles
Dictionary Objects;
+ // Cached data to be reused during RasterizeActor
+ uint64 CurrentFrame;
+ float ResolutionInv;
+ Vector3 ViewPosition;
+ float TileTexelsPerWorldUnit;
+ float DistanceScalingStart;
+ float DistanceScalingEnd;
+ float DistanceScaling;
+
FORCE_INLINE void ClearObjects()
{
CulledObjectsCounterIndex = -1;
@@ -374,176 +383,30 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1);
// Add objects into the atlas
- _objectsBuffer->Clear();
- _dirtyObjectsBuffer.Clear();
{
PROFILE_CPU_NAMED("Draw");
+ _objectsBuffer->Clear();
+ _dirtyObjectsBuffer.Clear();
+ _surfaceAtlasData = &surfaceAtlasData;
+ renderContext.View.Pass = DrawPass::GlobalSurfaceAtlas;
+ surfaceAtlasData.CurrentFrame = currentFrame;
+ surfaceAtlasData.ResolutionInv = resolutionInv;
+ surfaceAtlasData.ViewPosition = renderContext.View.Position;
+ surfaceAtlasData.TileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution
+ surfaceAtlasData.DistanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down
+ surfaceAtlasData.DistanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down
+ surfaceAtlasData.DistanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away
+ // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas
const uint32 viewMask = renderContext.View.RenderLayersMask;
const Vector3 viewPosition = renderContext.View.Position;
- const uint16 minTileResolution = 8; // Minimum size (in texels) of the tile in atlas
- const uint16 maxTileResolution = 128; // Maximum size (in texels) of the tile in atlas
- const uint16 tileResolutionAlignment = 8; // Alignment to snap (down) tiles resolution which allows to reuse atlas slots once object gets resizes/replaced by other object
const float minObjectRadius = 20.0f; // Skip too small objects
- const float tileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution
- const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down
- const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down
- const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away
- // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas
- static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < minTileResolution, "Invalid tile size configuration.");
for (auto* scene : renderContext.List->Scenes)
{
- // TODO: optimize for static objects (SceneRendering could have separate and optimized caching for static actors)
for (auto& e : scene->Actors)
{
if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition) < distance)
{
- // TODO: move into actor-specific Draw() impl (eg. via GlobalSurfaceAtlas pass)
- auto* staticModel = ScriptingObject::Cast(e.Actor);
- if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered())
- {
- Matrix localToWorld;
- staticModel->GetWorld(&localToWorld);
- bool anyTile = false, dirty = false;
- GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(e.Actor);
- auto& lod = staticModel->Model->LODs.Last();
- BoundingBox localBounds = lod.GetBox();
- Vector3 boundsSize = localBounds.GetSize() * staticModel->GetScale();
- const float distanceScale = Math::Lerp(1.0f, distanceScaling, Math::InverseLerp(distanceScalingStart, distanceScalingEnd, CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition)));
- const float tilesScale = tileTexelsPerWorldUnit * distanceScale;
- for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
- {
- // Calculate optimal tile resolution for the object side
- Vector3 boundsSizeTile = boundsSize;
- boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size
- boundsSizeTile.Absolute();
- uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale);
- if (tileResolution < 4)
- {
- // Skip too small surfaces
- if (object && object->Tiles[tileIndex])
- {
- object->Tiles[tileIndex]->Free();
- object->Tiles[tileIndex] = nullptr;
- }
- continue;
- }
-
- // Clamp and snap to reduce atlas fragmentation
- tileResolution = Math::Clamp(tileResolution, minTileResolution, maxTileResolution);
- tileResolution = Math::AlignDown(tileResolution, tileResolutionAlignment);
-
- // Reuse current tile (refit only on a significant resolution change)
- if (object && object->Tiles[tileIndex])
- {
- const uint16 tileRefitResolutionStep = 32;
- const uint16 currentSize = object->Tiles[tileIndex]->Width;
- if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep)
- {
- anyTile = true;
- continue;
- }
- object->Tiles[tileIndex]->Free();
- }
-
- // Insert tile into atlas
- auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, e.Actor, tileIndex);
- if (tile)
- {
- if (!object)
- object = &surfaceAtlasData.Objects[e.Actor];
- object->Tiles[tileIndex] = tile;
- anyTile = true;
- dirty = true;
- }
- else
- {
- if (object)
- object->Tiles[tileIndex] = nullptr;
- surfaceAtlasData.LastFrameAtlasInsertFail = currentFrame;
- }
- }
- if (anyTile)
- {
- // Redraw objects from time-to-time (dynamic objects can be animated, static objects can have textures streamed)
- uint32 redrawFramesCount = staticModel->HasStaticFlag(StaticFlags::Lightmap) ? 120 : 4;
- if (currentFrame - object->LastFrameDirty >= (redrawFramesCount + (e.Actor->GetID().D & redrawFramesCount)))
- dirty = true;
-
- // Mark object as used
- object->LastFrameUsed = currentFrame;
- object->Bounds = OrientedBoundingBox(localBounds);
- object->Bounds.Transform(localToWorld);
- object->Radius = e.Bounds.Radius;
- if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES)
- {
- object->LastFrameDirty = currentFrame;
- _dirtyObjectsBuffer.Add(e.Actor);
- }
-
- // Write to objects buffer (this must match unpacking logic in HLSL)
- Matrix worldToLocalBounds;
- 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*)&e.Bounds;
- 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);
- objectData[4] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43);
- objectData[5] = Vector4(object->Bounds.Extents, 0.0f); // w unused
- auto tileOffsets = reinterpret_cast(&objectData[1]); // xyz used for tile offsets packed into uint16
- auto objectDataSize = reinterpret_cast(&objectData[1].W); // w used for object size (count of Vector4s for object+tiles)
- *objectDataSize = GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE;
- for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
- {
- auto* tile = object->Tiles[tileIndex];
- if (!tile)
- continue;
- tile->ObjectAddressOffset = *objectDataSize;
- tile->Address = objectAddress + tile->ObjectAddressOffset;
- tileOffsets[tileIndex] = tile->ObjectAddressOffset;
- *objectDataSize += GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE;
-
- // Setup view to render object from the side
- Vector3 xAxis, yAxis, zAxis = Vector3::Zero;
- zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f;
- yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up;
- Vector3::Cross(yAxis, zAxis, xAxis);
- Vector3 localSpaceOffset = -zAxis * object->Bounds.Extents;
- Vector3::TransformNormal(xAxis, object->Bounds.Transformation, xAxis);
- Vector3::TransformNormal(yAxis, object->Bounds.Transformation, yAxis);
- Vector3::TransformNormal(zAxis, object->Bounds.Transformation, zAxis);
- xAxis.NormalizeFast();
- yAxis.NormalizeFast();
- zAxis.NormalizeFast();
- Vector3::Transform(localSpaceOffset, object->Bounds.Transformation, tile->ViewPosition);
- tile->ViewDirection = zAxis;
-
- // Create view matrix
- tile->ViewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, tile->ViewPosition)));
- tile->ViewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, tile->ViewPosition)));
- tile->ViewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, tile->ViewPosition)));
- tile->ViewMatrix.SetColumn4(Vector4(0, 0, 0, 1));
-
- // Calculate object bounds size in the view
- OrientedBoundingBox viewBounds(object->Bounds);
- viewBounds.Transform(tile->ViewMatrix);
- Vector3 viewExtent;
- Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent);
- tile->ViewBoundsSize = viewExtent.GetAbsolute() * 2.0f;
-
- // Per-tile data
- const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING;
- const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING;
- auto* tileData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE);
- tileData[0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * resolutionInv;
- tileData[1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41);
- tileData[2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42);
- tileData[3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43);
- tileData[4] = Vector4(tile->ViewBoundsSize, 0.0f); // w unused
- }
- }
- }
+ e.Actor->Draw(renderContext);
}
}
}
@@ -981,3 +844,150 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
context->SetViewportAndScissors(outputSize.X, outputSize.Y);
context->DrawFullscreenTriangle();
}
+
+void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToWorld, const BoundingBox& localBounds)
+{
+ 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 tilesScale = surfaceAtlasData.TileTexelsPerWorldUnit * distanceScale;
+ GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(actor);
+ bool anyTile = false, dirty = false;
+ for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
+ {
+ // Calculate optimal tile resolution for the object side
+ Vector3 boundsSizeTile = boundsSize;
+ boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size
+ boundsSizeTile.Absolute();
+ uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale);
+ if (tileResolution < 4)
+ {
+ // Skip too small surfaces
+ if (object && object->Tiles[tileIndex])
+ {
+ object->Tiles[tileIndex]->Free();
+ object->Tiles[tileIndex] = nullptr;
+ }
+ continue;
+ }
+
+ // Clamp tile resolution (in pixels)
+ static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < 8, "Invalid tile size configuration. Minimum tile size must be larger than padding.");
+ tileResolution = Math::Clamp(tileResolution, 8, 128);
+
+ // Snap tiles resolution (down) which allows to reuse atlas slots once object gets resizes/replaced by other object
+ tileResolution = Math::AlignDown(tileResolution, 8);
+
+ // Reuse current tile (refit only on a significant resolution change)
+ if (object && object->Tiles[tileIndex])
+ {
+ const uint16 tileRefitResolutionStep = 32;
+ const uint16 currentSize = object->Tiles[tileIndex]->Width;
+ if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep)
+ {
+ anyTile = true;
+ continue;
+ }
+ object->Tiles[tileIndex]->Free();
+ }
+
+ // Insert tile into atlas
+ auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actor, tileIndex);
+ if (tile)
+ {
+ if (!object)
+ object = &surfaceAtlasData.Objects[actor];
+ object->Tiles[tileIndex] = tile;
+ anyTile = true;
+ dirty = true;
+ }
+ else
+ {
+ if (object)
+ object->Tiles[tileIndex] = nullptr;
+ surfaceAtlasData.LastFrameAtlasInsertFail = surfaceAtlasData.CurrentFrame;
+ }
+ }
+ if (!anyTile)
+ return;
+
+ // Redraw objects from time-to-time (dynamic objects can be animated, static objects can have textures streamed)
+ uint32 redrawFramesCount = actor->HasStaticFlag(StaticFlags::Lightmap) ? 120 : 4;
+ if (surfaceAtlasData.CurrentFrame - object->LastFrameDirty >= (redrawFramesCount + (actor->GetID().D & redrawFramesCount)))
+ dirty = true;
+
+ // Mark object as used
+ object->LastFrameUsed = surfaceAtlasData.CurrentFrame;
+ object->Bounds = OrientedBoundingBox(localBounds);
+ object->Bounds.Transform(localToWorld);
+ object->Radius = actorBounds.Radius;
+ if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES)
+ {
+ object->LastFrameDirty = surfaceAtlasData.CurrentFrame;
+ _dirtyObjectsBuffer.Add(actor);
+ }
+
+ // Write to objects buffer (this must match unpacking logic in HLSL)
+ Matrix worldToLocalBounds;
+ 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[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);
+ objectData[4] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43);
+ objectData[5] = Vector4(object->Bounds.Extents, 0.0f); // w unused
+ auto tileOffsets = reinterpret_cast(&objectData[1]); // xyz used for tile offsets packed into uint16
+ auto objectDataSize = reinterpret_cast(&objectData[1].W); // w used for object size (count of Vector4s for object+tiles)
+ *objectDataSize = GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE;
+ for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
+ {
+ auto* tile = object->Tiles[tileIndex];
+ if (!tile)
+ continue;
+ tile->ObjectAddressOffset = *objectDataSize;
+ tile->Address = objectAddress + tile->ObjectAddressOffset;
+ tileOffsets[tileIndex] = tile->ObjectAddressOffset;
+ *objectDataSize += GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE;
+
+ // Setup view to render object from the side
+ Vector3 xAxis, yAxis, zAxis = Vector3::Zero;
+ zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f;
+ yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up;
+ Vector3::Cross(yAxis, zAxis, xAxis);
+ Vector3 localSpaceOffset = -zAxis * object->Bounds.Extents;
+ Vector3::TransformNormal(xAxis, object->Bounds.Transformation, xAxis);
+ Vector3::TransformNormal(yAxis, object->Bounds.Transformation, yAxis);
+ Vector3::TransformNormal(zAxis, object->Bounds.Transformation, zAxis);
+ xAxis.NormalizeFast();
+ yAxis.NormalizeFast();
+ zAxis.NormalizeFast();
+ Vector3::Transform(localSpaceOffset, object->Bounds.Transformation, tile->ViewPosition);
+ tile->ViewDirection = zAxis;
+
+ // Create view matrix
+ tile->ViewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, tile->ViewPosition)));
+ tile->ViewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, tile->ViewPosition)));
+ tile->ViewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, tile->ViewPosition)));
+ tile->ViewMatrix.SetColumn4(Vector4(0, 0, 0, 1));
+
+ // Calculate object bounds size in the view
+ OrientedBoundingBox viewBounds(object->Bounds);
+ viewBounds.Transform(tile->ViewMatrix);
+ Vector3 viewExtent;
+ Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent);
+ tile->ViewBoundsSize = viewExtent.GetAbsolute() * 2.0f;
+
+ // Per-tile data
+ const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING;
+ const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING;
+ auto* tileData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE);
+ tileData[0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * surfaceAtlasData.ResolutionInv;
+ tileData[1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41);
+ tileData[2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42);
+ tileData[3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43);
+ tileData[4] = Vector4(tile->ViewBoundsSize, 0.0f); // w unused
+ }
+}
diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h
index a59631c74..70363caad 100644
--- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h
+++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h
@@ -44,6 +44,7 @@ private:
class GPUBuffer* _culledObjectsSizeBuffer = nullptr;
class DynamicTypedBuffer* _objectsBuffer = nullptr;
class DynamicVertexBuffer* _vertexBuffer = nullptr;
+ class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData;
Array _dirtyObjectsBuffer;
uint64 _culledObjectsSizeFrames[8];
@@ -65,6 +66,9 @@ public:
/// The output buffer.
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);
+
private:
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj);
diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp
index baba38123..d029410c3 100644
--- a/Source/Engine/Terrain/Terrain.cpp
+++ b/Source/Engine/Terrain/Terrain.cpp
@@ -507,6 +507,8 @@ void Terrain::Draw(RenderContext& renderContext)
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Terrain rendering to Global SDF
+ if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
+ return; // TODO: Terrain rendering to Global Surface Atlas
PROFILE_CPU();
@@ -733,6 +735,9 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
+ // [Deprecated on 27.04.2022, expires on 27.04.2024]
+ if (modifier->EngineBuild <= 6331)
+ DrawModes |= DrawPass::GlobalSurfaceAtlas;
}
RigidBody* Terrain::GetAttachedRigidBody() const
diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp
index 4b4402bbc..0ced24966 100644
--- a/Source/Engine/UI/SpriteRender.cpp
+++ b/Source/Engine/UI/SpriteRender.cpp
@@ -106,7 +106,7 @@ bool SpriteRender::HasContentLoaded() const
void SpriteRender::Draw(RenderContext& renderContext)
{
- if (renderContext.View.Pass == DrawPass::GlobalSDF)
+ if (renderContext.View.Pass == DrawPass::GlobalSDF || renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
return;
if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded())
return;
diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp
index ccce0530a..cf206f4bc 100644
--- a/Source/Engine/UI/TextRender.cpp
+++ b/Source/Engine/UI/TextRender.cpp
@@ -342,6 +342,8 @@ void TextRender::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Text rendering to Global SDF
+ if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
+ return; // TODO: Text rendering to Global Surface Atlas
if (_isDirty)
{
UpdateLayout();
@@ -483,6 +485,9 @@ void TextRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modi
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
+ // [Deprecated on 27.04.2022, expires on 27.04.2024]
+ if (modifier->EngineBuild <= 6331)
+ DrawModes |= DrawPass::GlobalSurfaceAtlas;
_isDirty = true;
}
diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs
index dbde71661..403e7e965 100644
--- a/Source/FlaxEngine.Gen.cs
+++ b/Source/FlaxEngine.Gen.cs
@@ -13,5 +13,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("b8442186-4a70-7c85-704a-857cc7990797")]
-[assembly: AssemblyVersion("1.4.6331")]
-[assembly: AssemblyFileVersion("1.4.6331")]
+[assembly: AssemblyVersion("1.4.6332")]
+[assembly: AssemblyFileVersion("1.4.6332")]
diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h
index fc0450288..fae7510a9 100644
--- a/Source/FlaxEngine.Gen.h
+++ b/Source/FlaxEngine.Gen.h
@@ -3,11 +3,11 @@
#pragma once
#define FLAXENGINE_NAME "FlaxEngine"
-#define FLAXENGINE_VERSION Version(1, 4, 6331)
-#define FLAXENGINE_VERSION_TEXT "1.4.6331"
+#define FLAXENGINE_VERSION Version(1, 4, 6332)
+#define FLAXENGINE_VERSION_TEXT "1.4.6332"
#define FLAXENGINE_VERSION_MAJOR 1
#define FLAXENGINE_VERSION_MINOR 4
-#define FLAXENGINE_VERSION_BUILD 6331
+#define FLAXENGINE_VERSION_BUILD 6332
#define FLAXENGINE_COMPANY "Flax"
#define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved."