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."