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