// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "LightingSystem.h" #include "MonteCarlo.h" #include "Raster.h" #include "HAL/PlatformTime.h" namespace Lightmass { struct FVisibilitySamplePos { /** * Min and max world space heights of a single sample on a triangle. * This is necessary because supersampling is used during rasterization. */ FVector2f HeightRange; }; struct FCellHeights { /** Last triangle index that rasterized to this cell. */ uint64 TriangleIndex; /** World space X and Y position of this cell. */ FVector2f Position; /** Array of triangle hits on this cell. */ TArray HitTriangles; }; class FCellToHeightsMap { public: /** Initialization constructor. */ FCellToHeightsMap(int32 InSizeX,int32 InSizeY): Data(InSizeX * InSizeY), SizeX(InSizeX), SizeY(InSizeY) { // Clear the map to zero. for(int32 Y = 0;Y < SizeY;Y++) { for(int32 X = 0;X < SizeX;X++) { FCellHeights& CurrentCell = (*this)(X,Y); FMemory::Memzero(&CurrentCell,sizeof(FCellHeights)); CurrentCell.HitTriangles.Empty(50); } } } // Accessors. FCellHeights& operator()(int32 X,int32 Y) { const uint32 TexelIndex = Y * SizeX + X; return Data(TexelIndex); } const FCellHeights& operator()(int32 X,int32 Y) const { const int32 TexelIndex = Y * SizeX + X; return Data(TexelIndex); } int32 GetSizeX() const { return SizeX; } int32 GetSizeY() const { return SizeY; } SIZE_T GetAllocatedSize() const { return Data.GetAllocatedSize(); } private: /** The mapping data. */ TChunkedArray Data; /** The width of the mapping data. */ int32 SizeX; /** The height of the mapping data. */ int32 SizeY; }; class FCellPlacementRasterPolicy { public: typedef FVector4f InterpolantType; /** Initialization constructor. */ FCellPlacementRasterPolicy( FCellToHeightsMap& InHeightsMap, const FScene& InScene, FStaticLightingSystem& InSystem, const FBoxSphereBounds3f& InPrecomputedVisibilityBounds, float InCellSize) : HeightsMap(InHeightsMap), Scene(InScene), System(InSystem), PrecomputedVisibilityBounds(InPrecomputedVisibilityBounds), CellSize(InCellSize) { } void SetTriangleIndex(uint64 InTriangleIndex) { TriangleIndex = InTriangleIndex; } protected: // FTriangleRasterizer policy interface. int32 GetMinX() const { return 0; } int32 GetMaxX() const { return HeightsMap.GetSizeX(); } int32 GetMinY() const { return 0; } int32 GetMaxY() const { return HeightsMap.GetSizeY(); } void ProcessPixel(int32 X,int32 Y,const InterpolantType& Interpolant,bool BackFacing); private: uint64 TriangleIndex; FCellToHeightsMap& HeightsMap; const FScene& Scene; FStaticLightingSystem& System; FBoxSphereBounds3f PrecomputedVisibilityBounds; float CellSize; }; void FCellPlacementRasterPolicy::ProcessPixel(int32 X,int32 Y,const InterpolantType& WorldPosition,bool BackFacing) { if (Scene.IsPointInVisibilityVolume(WorldPosition)) { FCellHeights& Cell = HeightsMap(X, Y); if (Cell.TriangleIndex != TriangleIndex) { // If this is the first hit on this cell from the current triangle, add a new sample FVisibilitySamplePos Sample; const FVector3f GridPosition = PrecomputedVisibilityBounds.Origin - PrecomputedVisibilityBounds.BoxExtent + FVector3f(X, Y, 0) * CellSize; Sample.HeightRange = FVector2f(WorldPosition.Z, WorldPosition.Z); Cell.HitTriangles.Add(Sample); Cell.Position = FVector2f(GridPosition.X, GridPosition.Y); Cell.TriangleIndex = TriangleIndex; } else { // If this is not the first hit on this cell from the current triangle, expand the sample min and max FVisibilitySamplePos& TriangleEntry = Cell.HitTriangles.Last(); TriangleEntry.HeightRange.X = FMath::Min(TriangleEntry.HeightRange.X, WorldPosition.Z); TriangleEntry.HeightRange.Y = FMath::Max(TriangleEntry.HeightRange.Y, WorldPosition.Z); } } } int32 FStaticLightingSystem::GetGroupCellIndex(FVector3f BoxCenter) const { const FVector3f GridPosition = FVector3f(GroupVisibilityGridSizeXY, GroupVisibilityGridSizeXY, GroupVisibilityGridSizeZ) * (BoxCenter - VisibilityGridBounds.Min) / (VisibilityGridBounds.Max - VisibilityGridBounds.Min); const int32 GridX = FMath::TruncToInt(GridPosition.X); const int32 GridY = FMath::TruncToInt(GridPosition.Y); const int32 GridZ = FMath::TruncToInt(GridPosition.Z); const int32 CellIndex = GridZ * GroupVisibilityGridSizeXY * GroupVisibilityGridSizeZ + GridY * GroupVisibilityGridSizeXY + GridX; if (GridX > 0 && GridX < GroupVisibilityGridSizeXY && GridY > 0 && GridY < GroupVisibilityGridSizeXY && GridZ > 0 && GridZ < GroupVisibilityGridSizeZ) { return CellIndex; } else { return -1; } } class FVisibilityMeshSortInfo { public: float Distance; int32 Index; FBox3f Bounds; FVisibilityMeshSortInfo() : Bounds(FBox3f(ForceInit)) {} }; /** Determines visibility cell placement, called once at startup. */ void FStaticLightingSystem::SetupPrecomputedVisibility() { const double StartTime = FPlatformTime::Seconds(); FLMRandomStream RandomStream(0); const FBoxSphereBounds3f PrecomputedVisibilityBounds = Scene.GetVisibilityVolumeBounds(); const FVector4f VolumeSizes = PrecomputedVisibilityBounds.BoxExtent * 2.0f / PrecomputedVisibilitySettings.CellSize; const int32 SizeX = FMath::TruncToInt(VolumeSizes.X + DELTA) + 1; const int32 SizeY = FMath::TruncToInt(VolumeSizes.Y + DELTA) + 1; if (!PrecomputedVisibilitySettings.bPlaceCellsOnlyAlongCameraTracks) { FCellToHeightsMap HeightsMap(SizeX, SizeY); FTriangleRasterizer Rasterizer( FCellPlacementRasterPolicy( HeightsMap, Scene, *this, PrecomputedVisibilityBounds, PrecomputedVisibilitySettings.CellSize)); check(Meshes.Num() == AllMappings.Num()); uint64 NextTriangleIndex = 1; // Rasterize the scene to determine potential cell heights for (int32 MappingIndex = 0; MappingIndex < AllMappings.Num(); MappingIndex++) { const FStaticLightingMapping* CurrentMapping = AllMappings[MappingIndex]; const FStaticLightingMesh* CurrentMesh = CurrentMapping->Mesh; const uint32 GeoMeshLODIndex = CurrentMesh->GetLODIndices() & 0xFFFF; const uint32 GeoHLODTreeIndex = (CurrentMesh->GetLODIndices() & 0xFFFF0000) >> 16; const uint32 GeoHLODRange = CurrentMesh->GetHLODRange(); const uint32 GeoHLODRangeStart = GeoHLODRange & 0xFFFF; const uint32 GeoHLODRangeEnd = (GeoHLODRange & 0xFFFF0000) >> 16; bool bMeshBelongsToLOD0 = GeoMeshLODIndex == 0; if (GeoHLODTreeIndex > 0) { bMeshBelongsToLOD0 = GeoHLODRangeStart == GeoHLODRangeEnd; } // Only process meshes whose bounding box intersects a PVS volume if (Scene.DoesBoxIntersectVisibilityVolume(CurrentMesh->BoundingBox) && bMeshBelongsToLOD0) { // Whether mesh wants to be fully opaque for visibility step const bool bOpaqueMesh = CurrentMesh->IsAlwaysOpaqueForVisibility(); // Rasterize all triangles in the mesh for (int32 TriangleIndex = 0; TriangleIndex < CurrentMesh->NumTriangles; TriangleIndex++) { FStaticLightingVertex Vertices[3]; int32 ElementIndex; CurrentMesh->GetTriangle(TriangleIndex, Vertices[0], Vertices[1], Vertices[2], ElementIndex); // Only place cells on opaque surfaces if requested, which can save some memory for foliage maps if (!PrecomputedVisibilitySettings.bPlaceCellsOnOpaqueOnly || bOpaqueMesh || (!CurrentMesh->IsMasked(ElementIndex) && !CurrentMesh->IsTranslucent(ElementIndex))) { FVector2f XYPositions[3]; for (int32 VertIndex = 0; VertIndex < 3; VertIndex++) { // Transform world space positions from [PrecomputedVisibilityBounds.Origin - PrecomputedVisibilityBounds.BoxExtent, PrecomputedVisibilityBounds.Origin + PrecomputedVisibilityBounds.BoxExtent] into [0,1] const FVector4f TransformedPosition = (Vertices[VertIndex].WorldPosition - PrecomputedVisibilityBounds.Origin + PrecomputedVisibilityBounds.BoxExtent) / (2.0f * PrecomputedVisibilityBounds.BoxExtent); // Project positions onto the XY plane XYPositions[VertIndex] = FVector2f(TransformedPosition.X * SizeX, TransformedPosition.Y * SizeY); } const FVector4f TriangleNormal = (Vertices[2].WorldPosition - Vertices[0].WorldPosition) ^ (Vertices[1].WorldPosition - Vertices[0].WorldPosition); // Only rasterize upward facing triangles if (TriangleNormal.Z > 0.0f) { Rasterizer.SetTriangleIndex(NextTriangleIndex); FVector2f SubsamplePositions[9]; SubsamplePositions[0] = FVector2f(.5f, .5f); SubsamplePositions[1] = FVector2f(0, .5f); SubsamplePositions[2] = FVector2f(.5f, 0); SubsamplePositions[3] = FVector2f(1, .5f); SubsamplePositions[4] = FVector2f(.5f, 1); SubsamplePositions[5] = FVector2f(1, 1); SubsamplePositions[6] = FVector2f(0, 1); SubsamplePositions[7] = FVector2f(1, 0); SubsamplePositions[8] = FVector2f(0, 0); const float EdgePullback = .1f; for (int32 SampleIndex = 0; SampleIndex < UE_ARRAY_COUNT(SubsamplePositions); SampleIndex++) { const FVector2f SamplePosition = SubsamplePositions[SampleIndex] * (1 - 2 * EdgePullback) + FVector2f(EdgePullback, EdgePullback); Rasterizer.DrawTriangle( Vertices[0].WorldPosition, Vertices[1].WorldPosition, Vertices[2].WorldPosition, XYPositions[0] - SamplePosition, XYPositions[1] - SamplePosition, XYPositions[2] - SamplePosition, false ); } NextTriangleIndex++; } } } } } AllPrecomputedVisibilityCells.Empty(SizeX * SizeY * 2); TArray PlacedHeightRanges; for (int32 Y = 0; Y < SizeY; Y++) { for (int32 X = 0; X < SizeX; X++) { FCellHeights& Cell = HeightsMap(X, Y); const FVector2f CurrentPosition = Cell.Position; // Sort the heights from smallest to largest struct FCompareSampleZ { FORCEINLINE bool operator()(const FVisibilitySamplePos& A, const FVisibilitySamplePos& B) const { return A.HeightRange.Y < B.HeightRange.Y; } }; Cell.HitTriangles.Sort(FCompareSampleZ()); float LastSampleHeight = -FLT_MAX; PlacedHeightRanges.Reset(); // Pass 1 - only place cells in the largest holes which are most likely to be where the play area is // Place the bottom slightly above the surface, since cells that clip through the floor often have poor occlusion culling for (int32 HeightIndex = 0; HeightIndex < Cell.HitTriangles.Num(); HeightIndex++) { const float CurrentMaxHeight = Cell.HitTriangles[HeightIndex].HeightRange.Y; // Place a new cell if this is the highest height if (HeightIndex + 1 == Cell.HitTriangles.Num() // Or if there's a gap above this height of size PlayAreaHeight || ((Cell.HitTriangles[HeightIndex + 1].HeightRange.Y - CurrentMaxHeight) > PrecomputedVisibilitySettings.PlayAreaHeight // And this height is not within a cell that was just placed && CurrentMaxHeight - LastSampleHeight > PrecomputedVisibilitySettings.PlayAreaHeight)) { FPrecomputedVisibilityCell NewCell; NewCell.Bounds = FBox3f( FVector4f( CurrentPosition.X - PrecomputedVisibilitySettings.CellSize / 2, CurrentPosition.Y - PrecomputedVisibilitySettings.CellSize / 2, CurrentMaxHeight), FVector4f( CurrentPosition.X + PrecomputedVisibilitySettings.CellSize / 2, CurrentPosition.Y + PrecomputedVisibilitySettings.CellSize / 2, CurrentMaxHeight + PrecomputedVisibilitySettings.PlayAreaHeight)); AllPrecomputedVisibilityCells.Add(NewCell); LastSampleHeight = CurrentMaxHeight; PlacedHeightRanges.Add(FVector2f(NewCell.Bounds.Min.Z, NewCell.Bounds.Max.Z)); } } // Fractions of PrecomputedVisibilitySettings.PlayAreaHeight to guarantee have cell coverage float TestHeights[3] = {.4f, .6f, .8f}; // Pass 2 - make sure the space above every triangle is covered by precomputed visibility cells, even if the cells are placed poorly (intersecting the floor) for (int32 HeightIndex = 0; HeightIndex < Cell.HitTriangles.Num() - 1; HeightIndex++) { for (int32 ExtremaIndex = 0; ExtremaIndex < 2; ExtremaIndex++) { const float CurrentMaxHeight = ExtremaIndex == 0 ? Cell.HitTriangles[HeightIndex].HeightRange.X : Cell.HitTriangles[HeightIndex].HeightRange.Y; const float CompareHeight = CurrentMaxHeight + .5f * PrecomputedVisibilitySettings.PlayAreaHeight; for (int32 TestIndex = 0; TestIndex < UE_ARRAY_COUNT(TestHeights); TestIndex++) { const float TestHeight = CurrentMaxHeight + TestHeights[TestIndex] * PrecomputedVisibilitySettings.PlayAreaHeight; int32 ClosestCellInZIndex = -1; float ClosestCellInZDistance = FLT_MAX; bool bInsideCell = false; for (int32 PlacedHeightIndex = 0; PlacedHeightIndex < PlacedHeightRanges.Num(); PlacedHeightIndex++) { FVector2f CellHeightRange = PlacedHeightRanges[PlacedHeightIndex]; if (TestHeight > CellHeightRange.X && TestHeight < CellHeightRange.Y) { bInsideCell = true; break; } float AbsDistance = FMath::Min(FMath::Abs(CompareHeight - CellHeightRange.X), FMath::Abs(CompareHeight - CellHeightRange.Y)); if (AbsDistance < ClosestCellInZDistance) { ClosestCellInZDistance = AbsDistance; ClosestCellInZIndex = PlacedHeightIndex; } } // Place a cell if TestHeight was not inside any existing cells if (!bInsideCell) { FPrecomputedVisibilityCell NewCell; float DesiredCellBottom = CurrentMaxHeight; if (ClosestCellInZIndex >= 0) { const FVector2f NearestCellHeightRange = PlacedHeightRanges[ClosestCellInZIndex]; const float NearestCellCompareHeight = (NearestCellHeightRange.X + NearestCellHeightRange.Y) / 2; // Move the bottom of the cell to be placed such that it doesn't overlap the nearest cell // This makes use of the cell's full height to cover space if (CompareHeight < NearestCellCompareHeight) { DesiredCellBottom = FMath::Min(DesiredCellBottom, NearestCellHeightRange.X - PrecomputedVisibilitySettings.PlayAreaHeight); } else if (CompareHeight > NearestCellCompareHeight) { DesiredCellBottom = FMath::Max(DesiredCellBottom, NearestCellHeightRange.Y); } } NewCell.Bounds = FBox3f( FVector4f( CurrentPosition.X - PrecomputedVisibilitySettings.CellSize / 2, CurrentPosition.Y - PrecomputedVisibilitySettings.CellSize / 2, DesiredCellBottom), FVector4f( CurrentPosition.X + PrecomputedVisibilitySettings.CellSize / 2, CurrentPosition.Y + PrecomputedVisibilitySettings.CellSize / 2, DesiredCellBottom + PrecomputedVisibilitySettings.PlayAreaHeight)); AllPrecomputedVisibilityCells.Add(NewCell); PlacedHeightRanges.Add(FVector2f(NewCell.Bounds.Min.Z, NewCell.Bounds.Max.Z)); } } } } } } } // Place cells along camera tracks const int32 NumCellsPlacedOnSurfaces = AllPrecomputedVisibilityCells.Num(); for (int32 CameraPositionIndex = 0; CameraPositionIndex < Scene.CameraTrackPositions.Num(); CameraPositionIndex++) { const FVector4f& CurrentPosition = Scene.CameraTrackPositions[CameraPositionIndex]; bool bInsideCell = false; for (int32 CellIndex = 0; CellIndex < AllPrecomputedVisibilityCells.Num(); CellIndex++) { if (AllPrecomputedVisibilityCells[CellIndex].Bounds.IsInside(CurrentPosition)) { bInsideCell = true; break; } } if (!bInsideCell) { FPrecomputedVisibilityCell NewCell; // Snap the cell min to the nearest factor of CellSize from the visibility bounds min + CellSize / 2 // The CellSize / 2 offset is necessary to match up with cells produced by the rasterizer, since pixels are rasterized at cell centers const FVector4f PreSnapTranslation = FVector3f(PrecomputedVisibilitySettings.CellSize / 2) + PrecomputedVisibilityBounds.Origin - PrecomputedVisibilityBounds.BoxExtent; const FVector4f TranslatedPosition = CurrentPosition - PreSnapTranslation; // FMath::Fmod gives the offset to round up for negative numbers, when we always want the offset to round down const float XOffset = TranslatedPosition.X > 0.0f ? FMath::Fmod(TranslatedPosition.X, PrecomputedVisibilitySettings.CellSize) : PrecomputedVisibilitySettings.CellSize - FMath::Fmod(-TranslatedPosition.X, PrecomputedVisibilitySettings.CellSize); const float YOffset = TranslatedPosition.Y > 0.0f ? FMath::Fmod(TranslatedPosition.Y, PrecomputedVisibilitySettings.CellSize) : PrecomputedVisibilitySettings.CellSize - FMath::Fmod(-TranslatedPosition.Y, PrecomputedVisibilitySettings.CellSize); const FVector4f SnappedPosition(CurrentPosition.X - XOffset, CurrentPosition.Y - YOffset, CurrentPosition.Z); NewCell.Bounds = FBox3f( FVector4f( SnappedPosition.X, SnappedPosition.Y, SnappedPosition.Z - .5f * PrecomputedVisibilitySettings.PlayAreaHeight), FVector4f( SnappedPosition.X + PrecomputedVisibilitySettings.CellSize, SnappedPosition.Y + PrecomputedVisibilitySettings.CellSize, SnappedPosition.Z + .5f * PrecomputedVisibilitySettings.PlayAreaHeight)); AllPrecomputedVisibilityCells.Add(NewCell); // Verify that the camera track position is inside the placed cell checkSlow(NewCell.Bounds.IsInside(CurrentPosition)); } } { TArray SortMeshes; SortMeshes.Empty(VisibilityMeshes.Num()); FVector3f CenterPosition(0, 0, 0); if (VisibilityMeshes.Num() > 0) { // Initialize to first mesh position so we can handle lighting stuff away from the origin CenterPosition = VisibilityMeshes[0].Meshes[0]->BoundingBox.GetCenter() / VisibilityMeshes.Num(); } for (int32 VisibilityMeshIndex = 0; VisibilityMeshIndex < VisibilityMeshes.Num(); VisibilityMeshIndex++) { FVisibilityMeshSortInfo NewInfo; for (int32 OriginalMeshIndex = 0; OriginalMeshIndex < VisibilityMeshes[VisibilityMeshIndex].Meshes.Num(); OriginalMeshIndex++) { NewInfo.Bounds += VisibilityMeshes[VisibilityMeshIndex].Meshes[OriginalMeshIndex]->BoundingBox; } // First mesh already contributed if (VisibilityMeshIndex > 0) { CenterPosition += NewInfo.Bounds.GetCenter() / VisibilityMeshes.Num(); } NewInfo.Index = VisibilityMeshIndex; SortMeshes.Add(NewInfo); } FVector3f CubeCorners[8]; CubeCorners[0] = FVector3f(1, 1, 1); CubeCorners[1] = FVector3f(-1, 1, 1); CubeCorners[2] = FVector3f(1, -1, 1); CubeCorners[3] = FVector3f(-1, -1, 1); CubeCorners[4] = FVector3f(1, 1, -1); CubeCorners[5] = FVector3f(-1, 1, -1); CubeCorners[6] = FVector3f(1, -1, -1); CubeCorners[7] = FVector3f(-1, -1, -1); for (int32 MeshIndex = 0; MeshIndex < SortMeshes.Num(); MeshIndex++) { const FVector3f BoxCenter = SortMeshes[MeshIndex].Bounds.GetCenter(); const FVector3f BoxExtent = SortMeshes[MeshIndex].Bounds.GetExtent(); float LocalDistance = 0; for (int32 CornerIndex = 0; CornerIndex < UE_ARRAY_COUNT(CubeCorners); CornerIndex++) { // Calculate max distance to a corner of the bounds as a measure of how much this mesh will expand the grid bounds LocalDistance = FMath::Max(LocalDistance, (BoxCenter + BoxExtent * CubeCorners[CornerIndex]).SizeSquared()); } SortMeshes[MeshIndex].Distance = LocalDistance; } struct FCompareMeshesByDistance { FORCEINLINE bool operator()( const FVisibilityMeshSortInfo& A, const FVisibilityMeshSortInfo& B ) const { return A.Distance < B.Distance; } }; SortMeshes.Sort(FCompareMeshesByDistance()); VisibilityGridBounds = FBox3f(ForceInit); // Drop last 10% of meshes which will expand the grid bounds // This is to handle distant skybox type meshes const int32 MaxMeshIndex = FMath::Min(FMath::Max(FMath::TruncToInt(.9f * SortMeshes.Num()), 1), SortMeshes.Num() - 1); for (int32 MeshIndex = 0; MeshIndex < MaxMeshIndex; MeshIndex++) { VisibilityGridBounds += SortMeshes[MeshIndex].Bounds; } //@todo - expose const float TargetNumGroupsAsFractionOfMeshes = .3f; const int32 ZDimensionDivisor = 4; // Determine grid X and Y size using SizeX * SizeY * SizeZ = NumMeshes * TargetNumGroupsAsFractionOfMeshes GroupVisibilityGridSizeXY = FMath::Max(FMath::TruncToInt(FMath::Pow((ZDimensionDivisor * TargetNumGroupsAsFractionOfMeshes * VisibilityMeshes.Num()), 1.0f / 3.0f)), 1); GroupVisibilityGridSizeZ = FMath::Max(GroupVisibilityGridSizeXY / ZDimensionDivisor, 1); const int32 GridSizeXY = GroupVisibilityGridSizeXY; const int32 GridSizeZ = GroupVisibilityGridSizeZ; const FVector3f CellSize = VisibilityGridBounds.GetExtent() / FVector3f(GroupVisibilityGridSizeXY, GroupVisibilityGridSizeXY, GroupVisibilityGridSizeZ); const float GridCellBoundingRadius = CellSize.Size(); const float MeshGroupingCellRadiusThreshold = .5f; GroupGrid.Empty(GridSizeXY * GridSizeXY * GridSizeZ); // Initialize grid group indices to invalid for (int32 GridEntry = 0; GridEntry < GridSizeXY * GridSizeXY * GridSizeZ; GridEntry++) { GroupGrid.Add(-1); } for (int32 VisibilityMeshIndex = 0; VisibilityMeshIndex < VisibilityMeshes.Num(); VisibilityMeshIndex++) { FBox3f MeshBounds(ForceInit); for (int32 OriginalMeshIndex = 0; OriginalMeshIndex < VisibilityMeshes[VisibilityMeshIndex].Meshes.Num(); OriginalMeshIndex++) { MeshBounds += VisibilityMeshes[VisibilityMeshIndex].Meshes[OriginalMeshIndex]->BoundingBox; } const float MeshBoundingRadiusSqr = MeshBounds.GetExtent().SizeSquared(); // Only put the mesh in a group if its radius is small enough to keep the group effective bool bPutInGroup = MeshBoundingRadiusSqr < FMath::Square(GridCellBoundingRadius * MeshGroupingCellRadiusThreshold); if (bPutInGroup) { const int32 CellIndex = GetGroupCellIndex(MeshBounds.GetCenter()); if (CellIndex >= 0) { int32 GroupIndex = GroupGrid[CellIndex]; if (GroupIndex == -1) { // Add a new group if needed VisibilityGroups.Add(FVisibilityMeshGroup()); GroupIndex = VisibilityGroups.Num() - 1; GroupGrid[CellIndex] = GroupIndex; } // Add to list of meshes in the group VisibilityGroups[GroupIndex].VisibilityIds.Add(VisibilityMeshIndex); } else { // Mesh was not inside grid bPutInGroup = false; } } // Mark whether the mesh was put into a group so we can look it up during visibility tracing VisibilityMeshes[VisibilityMeshIndex].bInGroup = bPutInGroup; if (!bPutInGroup) { Stats.NumPrecomputedVisibilityMeshesExcludedFromGroups++; } } // Build group bounds for (int32 GroupIndex = 0; GroupIndex < VisibilityGroups.Num(); GroupIndex++) { FVisibilityMeshGroup& Group = VisibilityGroups[GroupIndex]; Group.GroupBounds = FBox3f(ForceInit); for (int32 EntryIndex = 0; EntryIndex < Group.VisibilityIds.Num(); EntryIndex++) { const int32 VisibilityId = Group.VisibilityIds[EntryIndex]; for (int32 MeshIndex = 0; MeshIndex < VisibilityMeshes[VisibilityId].Meshes.Num(); MeshIndex++) { Group.GroupBounds += VisibilityMeshes[VisibilityId].Meshes[MeshIndex]->BoundingBox; } } } } const SIZE_T NumVisDataBytes = static_cast(AllPrecomputedVisibilityCells.Num()) * VisibilityMeshes.Num() / 8; Stats.NumPrecomputedVisibilityCellsTotal = AllPrecomputedVisibilityCells.Num(); Stats.NumPrecomputedVisibilityCellsCamaraTracks = AllPrecomputedVisibilityCells.Num() - NumCellsPlacedOnSurfaces; Stats.NumPrecomputedVisibilityMeshes = VisibilityMeshes.Num(); Stats.PrecomputedVisibilityDataBytes = NumVisDataBytes; Stats.PrecomputedVisibilitySetupTime = FPlatformTime::Seconds() - StartTime; if (AllPrecomputedVisibilityCells.Num() > 0) { LogSolverMessage(FString::Printf(TEXT("Setup precomputed visibility %.1fs, %u meshes, %u Cells"), FPlatformTime::Seconds() - StartTime, Stats.NumPrecomputedVisibilityMeshes, Stats.NumPrecomputedVisibilityCellsTotal)); } } static bool IsMeshVisible(const TArray& VisibilityData, int32 MeshId) { return (VisibilityData[MeshId / 8] & 1 << (MeshId % 8)) != 0; } static void SetMeshVisible(TArray& VisibilityData, int32 MeshId) { VisibilityData[MeshId / 8] |= 1 << (MeshId % 8); } class FAxisAlignedCellFace { public: FAxisAlignedCellFace() {} FAxisAlignedCellFace(const FVector4f& InFaceDirection, const FVector4f& InFaceMin, const FVector4f& InFaceExtent) : FaceDirection(InFaceDirection), FaceMin(InFaceMin), FaceExtent(InFaceExtent) {} FVector4f FaceDirection; FVector4f FaceMin; FVector4f FaceExtent; }; /** Stores information about a single query sample between a visibility cell and a mesh. */ struct FVisibilityQuerySample { /** Sample position generated from the mesh. */ FVector4f MeshPosition; /** Sample position generated from the cell. */ FVector4f CellPosition; /** Position of the intersection with the scene between the sample positions. */ FVector4f IntersectionPosition; /** Distance along the vector perpendicular to the mesh->cell vector. */ float PerpendicularDistance; }; /** * Computes visibility from a cell to a box, returns true if the box is visible from the cell. * The TArrays are passed in to avoid allocations between cells, the same arrays are reused. */ bool ComputeBoxVisibility( FStaticLightingAggregateMeshType& AggregateMesh, FPrecomputedVisibilitySettings& PrecomputedVisibilitySettings, FPrecomputedVisibilityCell& CurrentCell, FAxisAlignedCellFace* CellFaces, const FBox3f& MeshBox, FStaticLightingMappingContext& MappingContext, FLMRandomStream& RandomStream, TArray& VisibleCellFaces, TArray& VisibleCellFacePDFs, TArray& VisibleCellFaceCDFs, TArray& VisibleMeshFaces, TArray& SamplePositions, TArray& FurthestSamples, TArray& DebugVisibilityRays, bool bDebugThisCell, bool bDebugThisMesh, bool bGroupQuery) { const double SampleGenerationStartTime = FPlatformTime::Seconds(); const FVector4f CenterCellPosition = .5f * (CurrentCell.Bounds.Min + CurrentCell.Bounds.Max); const FVector4f MeshToCellCenter = CenterCellPosition - MeshBox.GetCenter(); const float Distance = MeshToCellCenter.Size3(); const FVector4f MeshBoxExtent = MeshBox.GetExtent() * 2; FAxisAlignedCellFace MeshBoxFaces[6]; MeshBoxFaces[0] = FAxisAlignedCellFace(FVector4f(-1, 0, 0), FVector4f(MeshBox.Min.X, MeshBox.Min.Y, MeshBox.Min.Z), FVector4f(0, MeshBoxExtent.Y, MeshBoxExtent.Z)); MeshBoxFaces[1] = FAxisAlignedCellFace(FVector4f(1, 0, 0), FVector4f(MeshBox.Min.X + MeshBoxExtent.X, MeshBox.Min.Y, MeshBox.Min.Z), FVector4f(0, MeshBoxExtent.Y, MeshBoxExtent.Z)); MeshBoxFaces[2] = FAxisAlignedCellFace(FVector4f(0, -1, 0), FVector4f(MeshBox.Min.X, MeshBox.Min.Y, MeshBox.Min.Z), FVector4f(MeshBoxExtent.X, 0, MeshBoxExtent.Z)); MeshBoxFaces[3] = FAxisAlignedCellFace(FVector4f(0, 1, 0), FVector4f(MeshBox.Min.X, MeshBox.Min.Y + MeshBoxExtent.Y, MeshBox.Min.Z), FVector4f(MeshBoxExtent.X, 0, MeshBoxExtent.Z)); MeshBoxFaces[4] = FAxisAlignedCellFace(FVector4f(0, 0, -1), FVector4f(MeshBox.Min.X, MeshBox.Min.Y, MeshBox.Min.Z), FVector4f(MeshBoxExtent.X, MeshBoxExtent.Y, 0)); MeshBoxFaces[5] = FAxisAlignedCellFace(FVector4f(0, 0, 1), FVector4f(MeshBox.Min.X, MeshBox.Min.Y, MeshBox.Min.Z + MeshBoxExtent.Z), FVector4f(MeshBoxExtent.X, MeshBoxExtent.Y, 0)); VisibleCellFaces.Reset(); VisibleCellFacePDFs.Reset(); for (int32 i = 0; i < 6; i++) { const float DotProduct = -Dot3((MeshToCellCenter / Distance), CellFaces[i].FaceDirection); if (DotProduct > 0.0f) { VisibleCellFaces.Add(i); VisibleCellFacePDFs.Add(DotProduct); } } // Ensure that some of the faces will be sampled if (VisibleCellFacePDFs.Num() == 0) { for (int32 i = 0; i < 6; i++) { VisibleCellFaces.Add(i); VisibleCellFacePDFs.Add(i); } } float UnnormalizedIntegral; CalculateStep1dCDF(VisibleCellFacePDFs, VisibleCellFaceCDFs, UnnormalizedIntegral); VisibleMeshFaces.Reset(); for (int32 i = 0; i < 6; i++) { if (Dot3(MeshToCellCenter, MeshBoxFaces[i].FaceDirection) > 0.0f) { VisibleMeshFaces.Add(i); } } if (bGroupQuery) { MappingContext.Stats.NumPrecomputedVisibilityGroupQueries++; } else { MappingContext.Stats.NumPrecomputedVisibilityQueries++; } const float MeshSize = MeshBox.GetExtent().Size(); const float SizeRatio = MeshSize / Distance; // Use MaxMeshSamples for meshes with a large projected angle, and MinMeshSamples for meshes with a small projected angle // Meshes with a large projected angle require more samples to determine visibility accurately const int32 NumMeshSamples = FMath::Clamp(FMath::TruncToInt(SizeRatio * PrecomputedVisibilitySettings.MaxMeshSamples), PrecomputedVisibilitySettings.MinMeshSamples, PrecomputedVisibilitySettings.MaxMeshSamples); // Treat meshes whose projected angle is greater than 90 degrees as visible, since it becomes overly costly to determine if these are visible // (consider a large close mesh that only has a tiny part visible) bool bVisible = SizeRatio > 1.0f; if (bVisible) { MappingContext.Stats.NumQueriesVisibleByDistanceRatio++; } if (!bVisible) { const FVector4f PerpendicularVector = MeshToCellCenter ^ FVector4f(0, 0, 1); SamplePositions.Reset(); // Generate samples for explicit visibility sampling of the mesh for (int32 CellSampleIndex = 0; CellSampleIndex < PrecomputedVisibilitySettings.NumCellSamples; CellSampleIndex++) { for (int32 MeshSampleIndex = 0; MeshSampleIndex < NumMeshSamples; MeshSampleIndex++) { FVisibilityQuerySample NewSample; { float PDF; float Sample; // Generate a sample on the visible faces of the cell, picking a face with probability proportional to the projected angle of the cell face onto the mesh's origin //@todo - weight by face area, since cells have a different height from their x and y sizes Sample1dCDF(VisibleCellFacePDFs, VisibleCellFaceCDFs, UnnormalizedIntegral, RandomStream, PDF, Sample); const int32 ChosenCellFaceIndex = FMath::TruncToInt(Sample * VisibleCellFaces.Num()); const FAxisAlignedCellFace& ChosenFace = CellFaces[VisibleCellFaces[ChosenCellFaceIndex]]; NewSample.CellPosition = ChosenFace.FaceMin + ChosenFace.FaceExtent * FVector4f(RandomStream.GetFraction(), RandomStream.GetFraction(), RandomStream.GetFraction()); } { // Generate a sample on the visible faces of the mesh const int32 ChosenFaceIndex = FMath::TruncToInt(RandomStream.GetFraction() * VisibleMeshFaces.Num()); const FAxisAlignedCellFace& ChosenFace = MeshBoxFaces[VisibleMeshFaces[ChosenFaceIndex]]; NewSample.MeshPosition = ChosenFace.FaceMin + ChosenFace.FaceExtent * FVector4f(RandomStream.GetFraction(), RandomStream.GetFraction(), RandomStream.GetFraction()); } const FVector4f HalfPosition = .5f * (NewSample.CellPosition + NewSample.MeshPosition); NewSample.PerpendicularDistance = Dot3(HalfPosition, PerpendicularVector); SamplePositions.Add(NewSample); } } // Sorts two samples based on perpendicular distance. struct FCompareSampleDistance { FORCEINLINE bool operator()(const FVisibilityQuerySample& A, const FVisibilityQuerySample& B) const { return A.PerpendicularDistance < B.PerpendicularDistance; } }; // Sort the samples to make them more coherent in kDOP tree traversals, which provides a small speedup SamplePositions.Sort(FCompareSampleDistance()); const double SampleGenerationEndTime = FPlatformTime::Seconds(); MappingContext.Stats.PrecomputedVisibilitySampleSetupThreadTime += SampleGenerationEndTime - SampleGenerationStartTime; float FurthestDistanceSquared = 0; // Early out if any sample finds the mesh visible, unless we are debugging this cell and want to see all the samples for (int32 CellSampleIndex = 0; (CellSampleIndex < PrecomputedVisibilitySettings.NumCellSamples) && (!bVisible || bDebugThisCell); CellSampleIndex++) { for (int32 MeshSampleIndex = 0; MeshSampleIndex < NumMeshSamples; MeshSampleIndex++) { FVisibilityQuerySample& CurrentSample = SamplePositions[CellSampleIndex * NumMeshSamples + MeshSampleIndex]; const FVector4f CellSamplePosition = CurrentSample.CellPosition; const FVector4f MeshSamplePosition = CurrentSample.MeshPosition; FLightRay Ray( CellSamplePosition, MeshSamplePosition, NULL, NULL, // Masked materials often have small holes which increase visibility errors // This also allows us to use boolean visibility traces which are much faster than first hit traces // Only intersect with static objects since they will not move in game LIGHTRAY_STATIC_AND_OPAQUEONLY ); FLightRayIntersection Intersection; // Use boolean visibility traces AggregateMesh.IntersectLightRay(Ray, false, false, false, MappingContext.RayCache, Intersection); MappingContext.Stats.NumPrecomputedVisibilityRayTraces++; //Note: using intersection position even though we used a boolean ray trace, so the position may not be the closest CurrentSample.IntersectionPosition = Intersection.IntersectionVertex.WorldPosition; const float DistanceSquared = (CellSamplePosition - Intersection.IntersectionVertex.WorldPosition).SizeSquared3(); FurthestDistanceSquared = FMath::Max(FurthestDistanceSquared, DistanceSquared); if (bDebugThisMesh) { // Draw all the rays from the debug cell to the debug mesh FDebugStaticLightingRay DebugRay(CellSamplePosition, MeshSamplePosition, Intersection.bIntersects, false); if (Intersection.bIntersects) { DebugRay.End = Intersection.IntersectionVertex.WorldPosition; } DebugVisibilityRays.Add(DebugRay); } if (!Intersection.bIntersects) { MappingContext.Stats.NumQueriesVisibleExplicitSampling++; bVisible = true; if (!bDebugThisCell) { // The mesh is visible from the current cell, move on to the next mesh break; } } } } const double RayTraceEndTime = FPlatformTime::Seconds(); MappingContext.Stats.PrecomputedVisibilityRayTraceThreadTime += RayTraceEndTime - SampleGenerationEndTime; // If the meshes has not been determined to be visible by explicit sampling, // Do importance sampling to try and find visible meshes through cracks that have a low probability of being detected by explicit sampling. if (!bVisible) { FurthestSamples.Reset(); // Create an array of all the longest rays toward the mesh const float DistanceThreshold = FMath::Sqrt(FurthestDistanceSquared) * 7.0f / 8.0f; const float DistanceThresholdSq = DistanceThreshold * DistanceThreshold; for (int32 SampleIndex = 0; SampleIndex < SamplePositions.Num(); SampleIndex++) { const FVisibilityQuerySample& CurrentSample = SamplePositions[SampleIndex]; const float DistanceSquared = (CurrentSample.CellPosition - CurrentSample.IntersectionPosition).SizeSquared3(); if (DistanceSquared > DistanceThresholdSq) { FurthestSamples.Add(&CurrentSample); } } // Trace importance sampled rays to try and find visible meshes through small cracks // This is only slightly effective, but doesn't cost much compared to explicit sampling due to the small number of rays for (int32 ImportanceSampleIndex = 0; ImportanceSampleIndex < PrecomputedVisibilitySettings.NumImportanceSamples && !bVisible && FurthestSamples.Num() > 0; ImportanceSampleIndex++) { // Pick one of the furthest samples with uniform probability const int32 SampleIndex = FMath::TruncToInt(RandomStream.GetFraction() * FurthestSamples.Num()); const FVisibilityQuerySample& CurrentSample = *FurthestSamples[SampleIndex]; const float VectorLength = (CurrentSample.CellPosition - CurrentSample.MeshPosition).Size3(); const FVector4f CurrentDirection = (CurrentSample.MeshPosition - CurrentSample.CellPosition).GetSafeNormal(); FVector4f XAxis; FVector4f YAxis; GenerateCoordinateSystem(CurrentDirection, XAxis, YAxis); // Generate a new direction in a cone 2 degrees from the original direction, to find cracks nearby const FVector4f SampleDirection = UniformSampleCone( RandomStream, FMath::Cos(2.0f * PI / 180.0f), XAxis, YAxis, CurrentDirection); const FVector4f EndPoint = CurrentSample.CellPosition + SampleDirection * VectorLength; FLightRay Ray( CurrentSample.CellPosition, EndPoint, NULL, NULL, LIGHTRAY_STATIC_AND_OPAQUEONLY ); FLightRayIntersection Intersection; AggregateMesh.IntersectLightRay(Ray, false, false, false, MappingContext.RayCache, Intersection); MappingContext.Stats.NumPrecomputedVisibilityRayTraces++; if (bDebugThisMesh) { // Draw all the rays from the debug cell to the debug mesh FDebugStaticLightingRay DebugRay(CurrentSample.CellPosition, EndPoint, Intersection.bIntersects, true); if (Intersection.bIntersects) { DebugRay.End = Intersection.IntersectionVertex.WorldPosition; } DebugVisibilityRays.Add(DebugRay); } if (!Intersection.bIntersects) { MappingContext.Stats.NumQueriesVisibleImportanceSampling++; bVisible = true; if (!bDebugThisCell) { // The mesh is visible from the current cell, move on to the next mesh break; } } } MappingContext.Stats.PrecomputedVisibilityImportanceSampleThreadTime += FPlatformTime::Seconds() - RayTraceEndTime; } } return bVisible; } void AddMeshDebugLines(TArray& DebugVisibilityRays, const TArray& Meshes, const FBox3f& MeshBox) { // Draw the bounding boxes of each mesh and the combined bounds if (Meshes.Num() > 1) { for (int32 OriginalMeshIndex = 0; OriginalMeshIndex < Meshes.Num(); OriginalMeshIndex++) { const FVector4f Min = Meshes[OriginalMeshIndex]->BoundingBox.Min; const FVector4f Max = Meshes[OriginalMeshIndex]->BoundingBox.Max; DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Min.X, Max.Y, Min.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Min.Z), FVector4f(Min.X, Max.Y, Max.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Min.X, Min.Y, Max.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Max.Z), FVector4f(Min.X, Max.Y, Max.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Min.Z), FVector4f(Max.X, Max.Y, Min.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Max.Y, Min.Z), FVector4f(Max.X, Max.Y, Max.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Min.Z), FVector4f(Max.X, Min.Y, Max.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Max.Z), FVector4f(Max.X, Max.Y, Max.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Max.X, Min.Y, Min.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Max.Z), FVector4f(Max.X, Min.Y, Max.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Min.Z), FVector4f(Max.X, Max.Y, Min.Z), false)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Max.Z), FVector4f(Max.X, Max.Y, Max.Z), false)); } } const FVector4f Min = MeshBox.Min; const FVector4f Max = MeshBox.Max; DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Min.X, Max.Y, Min.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Min.Z), FVector4f(Min.X, Max.Y, Max.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Min.X, Min.Y, Max.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Max.Z), FVector4f(Min.X, Max.Y, Max.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Min.Z), FVector4f(Max.X, Max.Y, Min.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Max.Y, Min.Z), FVector4f(Max.X, Max.Y, Max.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Min.Z), FVector4f(Max.X, Min.Y, Max.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Max.Z), FVector4f(Max.X, Max.Y, Max.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Max.X, Min.Y, Min.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Max.Z), FVector4f(Max.X, Min.Y, Max.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Min.Z), FVector4f(Max.X, Max.Y, Min.Z), true)); DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Max.Z), FVector4f(Max.X, Max.Y, Max.Z), true)); } /** Calculates visibility for a given group of cells, called from all threads. */ void FStaticLightingSystem::CalculatePrecomputedVisibility(int32 BucketIndex) { const double StartTime = FPlatformTime::Seconds(); check(BucketIndex >= 0 && BucketIndex < PrecomputedVisibilitySettings.NumCellDistributionBuckets); // Create a new link for the output of this task TList* DataLink = new TList(FPrecomputedVisibilityData(),NULL); DataLink->Element.Guid = Scene.VisibilityBucketGuids[BucketIndex]; // Determine the range of cells to process from the bucket index const int32 StartCellIndex = BucketIndex * AllPrecomputedVisibilityCells.Num() / PrecomputedVisibilitySettings.NumCellDistributionBuckets; const int32 MaxCellIndex = BucketIndex + 1 == PrecomputedVisibilitySettings.NumCellDistributionBuckets ? // Last bucket processes up to the end of the array AllPrecomputedVisibilityCells.Num() : (BucketIndex + 1) * AllPrecomputedVisibilityCells.Num() / PrecomputedVisibilitySettings.NumCellDistributionBuckets; DataLink->Element.PrecomputedVisibilityCells.Empty(MaxCellIndex - StartCellIndex); FStaticLightingMappingContext MappingContext(NULL, *this); // These are re-used across operations on the same thread to reduce reallocations TArray VisibleCellFaces; TArray VisibleCellFacePDFs; TArray VisibleCellFaceCDFs; TArray VisibleMeshFaces; TArray SamplePositions; TArray FurthestSamples; TArray GroupVisibility; TArray AffectingOverrideVolumes; for (int32 CellIndex = StartCellIndex; CellIndex < MaxCellIndex; CellIndex++) { const double StartSampleTime = FPlatformTime::Seconds(); // Seed by absolute cell index for deterministic results regardless of how cell tasks are distributed FLMRandomStream RandomStream(CellIndex); // Reset cached information for this cell so that traces won't be affected by what previous cells did MappingContext.RayCache.Clear(); DataLink->Element.PrecomputedVisibilityCells.Add(AllPrecomputedVisibilityCells[CellIndex]); FPrecomputedVisibilityCell& CurrentCell = DataLink->Element.PrecomputedVisibilityCells.Last(); const bool bDebugThisCell = CurrentCell.Bounds.IsInside(Scene.DebugInput.CameraPosition) && PrecomputedVisibilitySettings.bVisualizePrecomputedVisibility; AffectingOverrideVolumes.Reset(); for (int32 VolumeIndex = 0; VolumeIndex < Scene.PrecomputedVisibilityOverrideVolumes.Num(); VolumeIndex++) { if (Scene.PrecomputedVisibilityOverrideVolumes[VolumeIndex].Bounds.Intersect(CurrentCell.Bounds)) { AffectingOverrideVolumes.Add(&Scene.PrecomputedVisibilityOverrideVolumes[VolumeIndex]); } } CurrentCell.VisibilityData.Empty(VisibilityMeshes.Num() / 8 + 1); CurrentCell.VisibilityData.AddZeroed(VisibilityMeshes.Num() / 8 + 1); FAxisAlignedCellFace CellFaces[6]; const FVector3f CellBoundsSize = CurrentCell.Bounds.GetSize(); CellFaces[0] = FAxisAlignedCellFace(FVector4f(-1, 0, 0), FVector4f(CurrentCell.Bounds.Min.X, CurrentCell.Bounds.Min.Y, CurrentCell.Bounds.Min.Z), FVector4f(0, CellBoundsSize.Y, CellBoundsSize.Z)); CellFaces[1] = FAxisAlignedCellFace(FVector4f(1, 0, 0), FVector4f(CurrentCell.Bounds.Max.X, CurrentCell.Bounds.Min.Y, CurrentCell.Bounds.Min.Z), FVector4f(0, CellBoundsSize.Y, CellBoundsSize.Z)); CellFaces[2] = FAxisAlignedCellFace(FVector4f(0, -1, 0), FVector4f(CurrentCell.Bounds.Min.X, CurrentCell.Bounds.Min.Y, CurrentCell.Bounds.Min.Z), FVector4f(CellBoundsSize.X, 0, CellBoundsSize.Z)); CellFaces[3] = FAxisAlignedCellFace(FVector4f(0, 1, 0), FVector4f(CurrentCell.Bounds.Min.X, CurrentCell.Bounds.Max.Y, CurrentCell.Bounds.Min.Z), FVector4f(CellBoundsSize.X, 0, CellBoundsSize.Z)); CellFaces[4] = FAxisAlignedCellFace(FVector4f(0, 0, -1), FVector4f(CurrentCell.Bounds.Min.X, CurrentCell.Bounds.Min.Y, CurrentCell.Bounds.Min.Z), FVector4f(CellBoundsSize.X, CellBoundsSize.Y, 0)); CellFaces[5] = FAxisAlignedCellFace(FVector4f(0, 0, 1), FVector4f(CurrentCell.Bounds.Min.X, CurrentCell.Bounds.Min.Y, CurrentCell.Bounds.Max.Z), FVector4f(CellBoundsSize.X, CellBoundsSize.Y, 0)); GroupVisibility.Reset(); // First determine group visibility using the combined bounds, so we can skip lots of mesh queries later (if they are all invisible) for (int32 GroupIndex = 0; GroupIndex < VisibilityGroups.Num(); GroupIndex++) { const FVisibilityMeshGroup& Group = VisibilityGroups[GroupIndex]; bool bDebugThisMesh = false; bool bVisible = ComputeBoxVisibility( *AggregateMesh, PrecomputedVisibilitySettings, CurrentCell, CellFaces, Group.GroupBounds, MappingContext, RandomStream, VisibleCellFaces, VisibleCellFacePDFs, VisibleCellFaceCDFs, VisibleMeshFaces, SamplePositions, FurthestSamples, DataLink->Element.DebugVisibilityRays, bDebugThisCell, bDebugThisMesh, true); GroupVisibility.Add(bVisible); } for (int32 VisibilityMeshIndex = 0; VisibilityMeshIndex < VisibilityMeshes.Num(); VisibilityMeshIndex++) { const FVisibilityMesh& VisibilityMesh = VisibilityMeshes[VisibilityMeshIndex]; FBox3f OriginalMeshBounds(ForceInit); // Combine mesh bounds, usually only BSP has multiple meshes per VisibilityId //@todo - could explicitly sample each bounds separately, but they tend to be pretty close together in world space for (int32 OriginalMeshIndex = 0; OriginalMeshIndex < VisibilityMesh.Meshes.Num(); OriginalMeshIndex++) { OriginalMeshBounds += VisibilityMesh.Meshes[OriginalMeshIndex]->BoundingBox; } const FBox3f MeshBox( OriginalMeshBounds.GetCenter() - OriginalMeshBounds.GetExtent() * PrecomputedVisibilitySettings.MeshBoundsScale, OriginalMeshBounds.GetCenter() + OriginalMeshBounds.GetExtent() * PrecomputedVisibilitySettings.MeshBoundsScale); const bool bDebugThisMesh = VisibilityMeshIndex == Scene.DebugInput.DebugVisibilityId && bDebugThisCell; if (bDebugThisMesh) { AddMeshDebugLines(DataLink->Element.DebugVisibilityRays, VisibilityMesh.Meshes, MeshBox); } bool bVisible = false; bool bForceInvisible = false; // Apply override volumes first in case they can save us some work for (int32 VolumeIndex = 0; VolumeIndex < AffectingOverrideVolumes.Num(); VolumeIndex++) { if (AffectingOverrideVolumes[VolumeIndex]->OverrideVisibilityIds.Contains(VisibilityMeshIndex)) { bVisible = true; break; } // This means forced visibility will override forced invisibility // Something to keep in mind when an LD complains that an actor // they put into the OverrideInvisibility list is still showing up! if (AffectingOverrideVolumes[VolumeIndex]->OverrideInvisibilityIds.Contains(VisibilityMeshIndex)) { bVisible = false; bForceInvisible = true; break; } } if (!bVisible && !bForceInvisible) { const int32 GroupCellIndex = GetGroupCellIndex(OriginalMeshBounds.GetCenter()); bool bGroupVisible = true; // Lookup group visibility, if this mesh was put into a group if (GroupCellIndex >= 0 && VisibilityMesh.bInGroup) { const int32 GroupId = GroupGrid[GroupCellIndex]; bGroupVisible = GroupVisibility[GroupGrid[GroupCellIndex]]; } // Only determine mesh visibility if the group containing the mesh was also visible if (bGroupVisible) { bVisible = ComputeBoxVisibility( *AggregateMesh, PrecomputedVisibilitySettings, CurrentCell, CellFaces, MeshBox, MappingContext, RandomStream, VisibleCellFaces, VisibleCellFacePDFs, VisibleCellFaceCDFs, VisibleMeshFaces, SamplePositions, FurthestSamples, DataLink->Element.DebugVisibilityRays, bDebugThisCell, bDebugThisMesh, false); } else { MappingContext.Stats.NumPrecomputedVisibilityMeshQueriesSkipped++; } } if (bVisible) { SetMeshVisible(CurrentCell.VisibilityData, VisibilityMeshIndex); } } if (bDebugThisCell) { // Draw the bounds of each cell processed const FVector4f Min = CurrentCell.Bounds.Min; const FVector4f Max = CurrentCell.Bounds.Max; DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Min.X, Max.Y, Min.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Min.Z), FVector4f(Min.X, Max.Y, Max.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Min.X, Min.Y, Max.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Max.Z), FVector4f(Min.X, Max.Y, Max.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Min.Z), FVector4f(Max.X, Max.Y, Min.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Max.Y, Min.Z), FVector4f(Max.X, Max.Y, Max.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Min.Z), FVector4f(Max.X, Min.Y, Max.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Max.X, Min.Y, Max.Z), FVector4f(Max.X, Max.Y, Max.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Min.Z), FVector4f(Max.X, Min.Y, Min.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Min.Y, Max.Z), FVector4f(Max.X, Min.Y, Max.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Min.Z), FVector4f(Max.X, Max.Y, Min.Z), false)); DataLink->Element.DebugVisibilityRays.Add(FDebugStaticLightingRay(FVector4f(Min.X, Max.Y, Max.Z), FVector4f(Max.X, Max.Y, Max.Z), false)); } } MappingContext.Stats.PrecomputedVisibilityThreadTime = FPlatformTime::Seconds() - StartTime; MappingContext.Stats.NumPrecomputedVisibilityCellsProcessed = MaxCellIndex - StartCellIndex; CompleteVisibilityTaskList.AddElement(DataLink); } }