Add GlobalSurfaceAtlas draw pass
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -703,10 +703,15 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
|
||||
MotionVectors = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
GlobalSDF = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// The Global Surface Atlas rendering pass. Used for software raytracing though the scene on a GPU to evaluate the object surface material properties.
|
||||
/// </summary>
|
||||
GlobalSurfaceAtlas = 1 << 6,
|
||||
|
||||
/// <summary>
|
||||
/// The debug quad overdraw rendering (editor-only).
|
||||
/// </summary>
|
||||
@@ -717,13 +722,13 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
|
||||
/// The default set of draw passes for the scene objects.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="HideInEditor")
|
||||
Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
|
||||
Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas,
|
||||
|
||||
/// <summary>
|
||||
/// The all draw passes combined into a single mask.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="HideInEditor")
|
||||
All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
|
||||
All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas,
|
||||
};
|
||||
|
||||
DECLARE_ENUM_OPERATORS(DrawPass);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -129,6 +129,15 @@ public:
|
||||
GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles
|
||||
Dictionary<Actor*, GlobalSurfaceAtlasObject> 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<StaticModel>(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<Vector4>(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<uint16*>(&objectData[1]); // xyz used for tile offsets packed into uint16
|
||||
auto objectDataSize = reinterpret_cast<uint32*>(&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<Vector4>(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<uint16>(tileResolution, 8, 128);
|
||||
|
||||
// Snap tiles resolution (down) which allows to reuse atlas slots once object gets resizes/replaced by other object
|
||||
tileResolution = Math::AlignDown<uint16>(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<Vector4>(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<uint16*>(&objectData[1]); // xyz used for tile offsets packed into uint16
|
||||
auto objectDataSize = reinterpret_cast<uint32*>(&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<Vector4>(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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ private:
|
||||
class GPUBuffer* _culledObjectsSizeBuffer = nullptr;
|
||||
class DynamicTypedBuffer* _objectsBuffer = nullptr;
|
||||
class DynamicVertexBuffer* _vertexBuffer = nullptr;
|
||||
class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData;
|
||||
Array<Actor*> _dirtyObjectsBuffer;
|
||||
uint64 _culledObjectsSizeFrames[8];
|
||||
|
||||
@@ -65,6 +66,9 @@ public:
|
||||
/// <param name="output">The output buffer.</param>
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user