UE5 Mass LOD

LOD重要数据结构

两套LOD:FMassSimulationLODFragment、FMassRepresentationLODFragment

USTRUCT()
struct MASSLOD_API FMassSimulationLODFragment : public FMassFragment
{
    GENERATED_BODY()
    /** 距离Viewer最近的距离平方*/
    float ClosestViewerDistanceSq = FLT_MAX;
    /**当前LOD */
    TEnumAsByte<EMassLOD::Type> LOD = EMassLOD::Max;
    /** 上一次的LOD*/
    TEnumAsByte<EMassLOD::Type> PrevLOD = EMassLOD::Max;
};

USTRUCT()
struct MASSREPRESENTATION_API FMassRepresentationLODFragment : public FMassFragment
{
    GENERATED_BODY()
    /** 当前LOD以及上一次的LOD */
    TEnumAsByte<EMassLOD::Type> LOD = EMassLOD::Max;
    TEnumAsByte<EMassLOD::Type> PrevLOD = EMassLOD::Max;
   
    /** 可见性 */
    EMassVisibility Visibility = EMassVisibility::Max;
    EMassVisibility PrevVisibility = EMassVisibility::Max;
    /** LOD重要度,值在0~3之间,值越小越重要 */
    float LODSignificance = 0.0f;
};

RepresentationLOD控制参数

USTRUCT()
struct FMassVisualizationLODParameters : public FMassSharedFragment
{
    GENERATED_BODY()
    
    UPROPERTY(EditAnywhere, Category = "Mass|LOD", config)
    float BaseLODDistance[EMassLOD::Max] = { 0.f, 1000.f, 2500.f, 10000.f };/** 视野外各级LOD的距离划分 */
    UPROPERTY(EditAnywhere, Category = "Mass|LOD", config)
    float VisibleLODDistance[EMassLOD::Max] = { 0.f, 2000.f, 4000.f, 15000.f };/** 视野内各级LOD的距离划分 */
    UPROPERTY(EditAnywhere, Category = "Mass|LOD", meta = (ClampMin = "0.0", UIMin = "0.0"), config)
    float BufferHysteresisOnDistancePercentage = 10.0f; //LOD切换边界距离迟滞百分比   
    UPROPERTY(EditAnywhere, Category = "Mass|LOD", config)
    int32 LODMaxCount[EMassLOD::Max] = {50, 100, 500, MAX_int32};/** 各级LOD最大数量 */    
    UPROPERTY(EditAnywhere, Category = "Mass|LOD", meta = (ClampMin = "0.0", UIMin = "0.0"), config)
    float DistanceToFrustum = 0.0f;/** 远离视锥多远可以认为可见 */
    /** Once visible how much further than DistanceToFrustum does the entities need to be before being cull again */
    UPROPERTY(EditAnywhere, Category = "Mass|LOD", meta = (ClampMin = "0.0", UIMin = "0.0"), config)
    float DistanceToFrustumHysteresis = 0.0f;
    UPROPERTY(EditAnywhere, Category = "Mass|LOD", meta = (BaseStruct = "/Script/MassEntity.MassTag"))
    TObjectPtr<UScriptStruct> FilterTag = nullptr;/** 筛选tag */
};

SimulationLOD控制参数

USTRUCT()
struct MASSLOD_API FMassSimulationLODParameters : public FMassSharedFragment
{
    GENERATED_BODY()
    FMassSimulationLODParameters();
    
    UPROPERTY(EditAnywhere, Category = "LOD", config)
    float LODDistance[EMassLOD::Max];    /** 各级LOD距离边界 */
    UPROPERTY(EditAnywhere, Category = "LOD", meta = (ClampMin = "0.0", UIMin = "0.0"), config)
    float BufferHysteresisOnDistancePercentage = 10.0f; /** LOD切换边界距离迟滞百分比   */   
    UPROPERTY(EditAnywhere, Category = "LOD", config)
    int32 LODMaxCount[EMassLOD::Max]; /** 各级LOD最大数量 */       
    UPROPERTY(EditAnywhere, Category = "LOD", config)
    bool bSetLODTags = false; /** 是否设置Tag */
};

各级LOD所对应的LOD Tag,根据FMassSimulationLODFragment的LOD更新对应Tag。

USTRUCT()
struct MASSLOD_API FMassHighLODTag : public FMassTag
{
    GENERATED_BODY()
};
USTRUCT()
struct MASSLOD_API FMassMediumLODTag : public FMassTag
{
    GENERATED_BODY()
};
USTRUCT()
struct MASSLOD_API FMassLowLODTag : public FMassTag
{
    GENERATED_BODY()
};
USTRUCT()
struct MASSLOD_API FMassOffLODTag : public FMassTag
{
    GENERATED_BODY()
};

具体更新LOD Tag代码如下:

void UMassSimulationLODProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
    //....省略部分代码
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("SetLODTags"))
        check(World);
        EntityQuerySetLODTag.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
        {
            TConstArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
            const int32 NumEntities = Context.GetNumEntities();
            for (int32 Index = 0; Index < NumEntities; ++Index)
            {
                const FMassSimulationLODFragment& EntityLOD = SimulationLODFragments[Index];
                if (EntityLOD.PrevLOD != EntityLOD.LOD)
                {
                    const FMassEntityHandle Entity = Context.GetEntity(Index);
                    UE::MassLOD::PushSwapTagsCommand(Context.Defer(), Entity, EntityLOD.PrevLOD, EntityLOD.LOD);
                }
            }
        });
    }
    //....省略部分代码
}

LODCollector阶段

UMassLODCollectorProcessor

UMassLODCollectorProcessor借助TMassLODCollector计算Entity距离viewer的最近距离以及距离viewer视锥的最近距离

USTRUCT()
struct MASSLOD_API FMassViewerInfoFragment : public FMassFragment
{
    GENERATED_BODY()
    
    float ClosestViewerDistanceSq = 0.0f;// 距离viewer的最近距离平方
    float ClosestDistanceToFrustum = 0.0f;// 距离viewer视锥的最近距离
};

template <bool bLocalViewersOnly>
void UMassLODCollectorProcessor::CollectLODForChunk(FMassExecutionContext& Context)
{
    TConstArrayView<FTransformFragment> LocationList = Context.GetFragmentView<FTransformFragment>();
    TArrayView<FMassViewerInfoFragment> ViewerInfoList = Context.GetMutableFragmentView<FMassViewerInfoFragment>();
    Collector.CollectLODInfo<FTransformFragment, FMassViewerInfoFragment, bLocalViewersOnly, true/*bCollectDistanceToViewer*/>(Context, LocationList, ViewerInfoList);
}

void UMassLODCollectorProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
    const UMassLODSubsystem& LODSubsystem = Context.GetSubsystemChecked<UMassLODSubsystem>();
    const TArray<FViewerInfo>& Viewers = LODSubsystem.GetViewers();
    Collector.PrepareExecution(Viewers);
    UWorld* World = EntityManager.GetWorld();
    check(World);
    if (World->IsNetMode(NM_DedicatedServer))
    {
        ExecuteInternal<false/*bLocalViewersOnly*/>(EntityManager, Context);
    }
    else
    {
        ExecuteInternal<true/*bLocalViewersOnly*/>(EntityManager, Context);
    }
}

TMassLODCollector

PrepareExecution缓存所有Viewer最新的信息(坐标、朝向、视锥)。视锥不包括最近最远平面。

template <typename FLODLogic>
void TMassLODCollector<FLODLogic>::PrepareExecution(TConstArrayView<FViewerInfo> ViewersInfo)
{
    CacheViewerInformation(ViewersInfo);
}

void FMassLODBaseLogic::CacheViewerInformation(TConstArrayView<FViewerInfo> ViewerInfos)
{
    if(Viewers.Num() < ViewerInfos.Num())
    {
        Viewers.AddDefaulted(ViewerInfos.Num() - Viewers.Num());
    }
    // Cache viewer info
    for (int ViewerIdx = 0; ViewerIdx < Viewers.Num(); ++ViewerIdx)
    {
        const FViewerInfo& Viewer =  ViewerInfos.IsValidIndex(ViewerIdx) ? ViewerInfos[ViewerIdx] : FViewerInfo();
        const FMassViewerHandle ViewerHandle =  Viewer.bEnabled ? Viewer.Handle : FMassViewerHandle();
        // Check if it is the same client as before
        FViewerLODInfo& ViewerLOD = Viewers[ViewerIdx];
        ViewerLOD.bClearData = Viewers[ViewerIdx].Handle != ViewerHandle;
        ViewerLOD.Handle = ViewerHandle;
        ViewerLOD.bLocal = Viewer.IsLocal();
        if (ViewerHandle.IsValid())
        {
            ViewerLOD.Location = Viewer.Location;
            ViewerLOD.Direction = Viewer.Rotation.Vector();
            const float HalfHorizontalFOVAngle = Viewer.FOV * 0.5f;
            const float HalfVerticalFOVAngle = FMath::RadiansToDegrees(FMath::Atan(FMath::Tan(FMath::DegreesToRadians(HalfHorizontalFOVAngle)) * Viewer.AspectRatio));
            const FVector RightPlaneNormal = Viewer.Rotation.RotateVector(FRotator(0.0f, HalfHorizontalFOVAngle, 0.0f).RotateVector(FVector::RightVector));
            const FVector LeftPlaneNormal = Viewer.Rotation.RotateVector(FRotator(0.0f, -HalfHorizontalFOVAngle, 0.0f).RotateVector(FVector::LeftVector));
            const FVector TopPlaneNormal = Viewer.Rotation.RotateVector(FRotator(HalfVerticalFOVAngle, 0.0f, 0.0f).RotateVector(FVector::UpVector));
            const FVector BottomPlaneNormal = Viewer.Rotation.RotateVector(FRotator(-HalfVerticalFOVAngle, 0.0f, 0.0f).RotateVector(FVector::DownVector));
           
            TArray<FPlane, TInlineAllocator<6>> Planes;
            Planes.Emplace(ViewerLOD.Location, RightPlaneNormal);
            Planes.Emplace(ViewerLOD.Location, LeftPlaneNormal);
            Planes.Emplace(ViewerLOD.Location, TopPlaneNormal);
            Planes.Emplace(ViewerLOD.Location, BottomPlaneNormal);
            ViewerLOD.Frustum = FConvexVolume(Planes);
        }
    }
}

CollectLODInfo计算Entity距离Viewer的最近距离以及距离Viewer视锥的最近距离

template <typename FLODLogic>
template <typename TTransformFragment,
          typename TViewerInfoFragment,
          typename TPerViewerInfoFragment,
          bool bCollectLocalViewers,
          bool bCollectDistanceToFrustum,
          bool bCollectDistancePerViewer,
          bool bCollectDistanceToFrustumPerViewer>
void TMassLODCollector<FLODLogic>::CollectLODInfo(FMassExecutionContext& Context,
                                                  TConstArrayView<TTransformFragment> TransformList,
                                                  TArrayView<TViewerInfoFragment> ViewersInfoList,
                                                  TArrayView<TPerViewerInfoFragment> PerViewerInfoList)
{
    static TPerViewerInfoFragment DummyFragment;
    const int32 NumEntities = Context.GetNumEntities();
    for (int EntityIdx = 0; EntityIdx < NumEntities; EntityIdx++)
    {
        float ClosestViewerDistanceSq = FLT_MAX;
        float ClosestDistanceToFrustum = FLT_MAX;
        const TTransformFragment& EntityTransform = TransformList[EntityIdx];
        TViewerInfoFragment& EntityViewerInfo = ViewersInfoList[EntityIdx];
        TPerViewerInfoFragment& EntityInfoPerViewer = FLODLogic::bStoreInfoPerViewer ? PerViewerInfoList[EntityIdx] : DummyFragment;
        SetDistanceToViewerSqNum<bCollectDistancePerViewer>(EntityInfoPerViewer, Viewers.Num());
        SetDistanceToFrustumNum<bCollectDistanceToFrustumPerViewer>(EntityInfoPerViewer, Viewers.Num());
        for (int ViewerIdx = 0; ViewerIdx < Viewers.Num(); ++ViewerIdx)
        {
            const FViewerLODInfo& Viewer = Viewers[ViewerIdx];
            if (Viewer.bClearData)
            {
                SetDistanceToViewerSq<bCollectDistancePerViewer>(EntityInfoPerViewer, ViewerIdx, FLT_MAX);
                SetDistanceToFrustum<bCollectDistanceToFrustumPerViewer>(EntityInfoPerViewer, ViewerIdx, FLT_MAX);
            }
            // Check to see if we want only local viewer only
            if (bCollectLocalViewers && !Viewer.bLocal)
            {
                continue;
            }
            if (Viewer.Handle.IsValid())
            {
                const FVector& EntityLocation = EntityTransform.GetTransform().GetLocation();
                const FVector ViewerToEntity = EntityLocation - Viewer.Location;
                const float DistanceToViewerSq = static_cast<float>(ViewerToEntity.SizeSquared()); // float precision is acceptable for LOD
                if (ClosestViewerDistanceSq > DistanceToViewerSq)
                {
                    ClosestViewerDistanceSq = DistanceToViewerSq;
                }
                SetDistanceToViewerSq<bCollectDistancePerViewer>(EntityInfoPerViewer, ViewerIdx, DistanceToViewerSq);
                if (bCollectDistanceToFrustum)
                {
                    const float DistanceToFrustum = Viewer.Frustum.DistanceTo(EntityLocation);
                    SetDistanceToFrustum<bCollectDistanceToFrustumPerViewer>(EntityInfoPerViewer, ViewerIdx, DistanceToFrustum);
                    if (ClosestDistanceToFrustum > DistanceToFrustum)
                    {
                        ClosestDistanceToFrustum = DistanceToFrustum;
                    }
                }
            }
        }
        EntityViewerInfo.ClosestViewerDistanceSq = ClosestViewerDistanceSq;
        SetClosestDistanceToFrustum<bCollectDistanceToFrustum>(EntityViewerInfo, ClosestDistanceToFrustum);
    }
}

LOD计算阶段

SimulationLOD计算过程

void UMassSimulationLODProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("SimulationLOD"))
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("PrepareExecution"));
        const UMassLODSubsystem& LODSubsystem = Context.GetSubsystemChecked<UMassLODSubsystem>();
        const TArray<FViewerInfo>& Viewers = LODSubsystem.GetViewers();
        EntityManager.ForEachSharedFragment<FMassSimulationLODSharedFragment>([&Viewers](FMassSimulationLODSharedFragment& LODSharedFragment)
        {
            LODSharedFragment.LODCalculator.PrepareExecution(Viewers); // 缓存Viewer信息,数据重置(各级Bucket数量统计、调整后的各级LOD距离)
        });
    }
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("CalculateLOD"));
        EntityQueryCalculateLOD.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
        {
            FMassSimulationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassSimulationLODSharedFragment>();
            TConstArrayView<FMassViewerInfoFragment> ViewersInfoList = Context.GetFragmentView<FMassViewerInfoFragment>();
            TArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
            // 根据原始配置计算新的LOD并统计各级LOD的各个Bucket中Entity的数量,更新可见性
            LODSharedFragment.LODCalculator.CalculateLOD(Context, ViewersInfoList, SimulationLODFragments);
        });
    }
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("AdjustDistancesAndLODFromCount"));
        EntityManager.ForEachSharedFragment<FMassSimulationLODSharedFragment>([](FMassSimulationLODSharedFragment& LODSharedFragment)
        {
            // 根据各级LOD最大数量调整对应LOD边界距离
            LODSharedFragment.bHasAdjustedDistancesFromCount = LODSharedFragment.LODCalculator.AdjustDistancesFromCount();
        });
        EntityQueryAdjustDistances.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
        {
            FMassSimulationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassSimulationLODSharedFragment>();
            TConstArrayView<FMassViewerInfoFragment> ViewersInfoList = Context.GetFragmentView<FMassViewerInfoFragment>();
            TArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
            // 根据调整后的LOD边界距离重新计算LOD
            LODSharedFragment.LODCalculator.AdjustLODFromCount(Context, ViewersInfoList, SimulationLODFragments);
        });
    }
    UWorld* World = EntityManager.GetWorld();
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("VariableTickRates"))
        check(World);
        const double Time = World->GetTimeSeconds();
        EntityQueryVariableTick.ForEachEntityChunk(EntityManager, Context, [Time](FMassExecutionContext& Context)
        {
            FMassSimulationVariableTickSharedFragment& TickRateSharedFragment = Context.GetMutableSharedFragment<FMassSimulationVariableTickSharedFragment>();
            TConstArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
            TArrayView<FMassSimulationVariableTickFragment> SimulationVariableTickFragments = Context.GetMutableFragmentView<FMassSimulationVariableTickFragment>();
            TickRateSharedFragment.LODTickRateController.UpdateTickRateFromLOD(Context, SimulationLODFragments, SimulationVariableTickFragments, Time);
        });
    }
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("SetLODTags"))
        check(World);
        EntityQuerySetLODTag.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
        {
            TConstArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
            const int32 NumEntities = Context.GetNumEntities();
            for (int32 Index = 0; Index < NumEntities; ++Index)
            {
                const FMassSimulationLODFragment& EntityLOD = SimulationLODFragments[Index];
                if (EntityLOD.PrevLOD != EntityLOD.LOD)
                {
                    const FMassEntityHandle Entity = Context.GetEntity(Index);
                    // 根据最新的LOD更新对应LODTag
                    UE::MassLOD::PushSwapTagsCommand(Context.Defer(), Entity, EntityLOD.PrevLOD, EntityLOD.LOD);
                }
            }
        });
    }
    // 显示LOD调试信息
    if (UE::MassLOD::bDebugSimulationLOD)
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("DebugDisplayLOD"));
        EntityQuery.ForEachEntityChunk(EntityManager, Context, [World](FMassExecutionContext& Context)
        {
            FMassSimulationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassSimulationLODSharedFragment>();
            TConstArrayView<FTransformFragment> LocationList = Context.GetFragmentView<FTransformFragment>();
            TConstArrayView<FMassSimulationLODFragment> SimulationLODList = Context.GetFragmentView<FMassSimulationLODFragment>();
            LODSharedFragment.LODCalculator.DebugDisplayLOD(Context, SimulationLODList, LocationList, World);
        });
    }
}

RepresentationLOD计算过程

void UMassVisualizationLODProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
    if (bForceOFFLOD)
    {
        CloseEntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context)
        {
            FMassVisualizationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassVisualizationLODSharedFragment>();
            TArrayView<FMassRepresentationLODFragment> RepresentationLODList = Context.GetMutableFragmentView<FMassRepresentationLODFragment>();
            LODSharedFragment.LODCalculator.ForceOffLOD(Context, RepresentationLODList);
        });
        return;
    }
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(PrepareExecution)
        const UMassLODSubsystem& LODSubsystem = Context.GetSubsystemChecked<UMassLODSubsystem>();
        const TArray<FViewerInfo>& Viewers = LODSubsystem.GetViewers();
        EntityManager.ForEachSharedFragment<FMassVisualizationLODSharedFragment>([this, &Viewers](FMassVisualizationLODSharedFragment& LODSharedFragment)
        {
            if (FilterTag == LODSharedFragment.FilterTag)
            {
                LODSharedFragment.LODCalculator.PrepareExecution(Viewers);// 缓存Viewer信息,数据重置(各级Bucket数量统计、调整后的各级LOD距离)
            }
        });
    }
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(CalculateLOD)
        auto CalculateLOD = [this](FMassExecutionContext& Context)
        {
            FMassVisualizationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassVisualizationLODSharedFragment>();
            TArrayView<FMassRepresentationLODFragment> RepresentationLODList = Context.GetMutableFragmentView<FMassRepresentationLODFragment>();
            TConstArrayView<FMassViewerInfoFragment> ViewerInfoList = Context.GetFragmentView<FMassViewerInfoFragment>();
            // 根据原始配置计算新的LOD并统计各级LOD的各个Bucket中Entity的数量,更新可见性
            LODSharedFragment.LODCalculator.CalculateLOD(Context, ViewerInfoList, RepresentationLODList);
        };
        CloseEntityQuery.ForEachEntityChunk(EntityManager, Context, CalculateLOD);
        FarEntityQuery.ForEachEntityChunk(EntityManager, Context, CalculateLOD);
    }
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(AdjustDistanceAndLODFromCount)
        EntityManager.ForEachSharedFragment<FMassVisualizationLODSharedFragment>([this](FMassVisualizationLODSharedFragment& LODSharedFragment)
        {
            if (FilterTag == LODSharedFragment.FilterTag)
            {
                // 根据各级LOD最大数量调整对应LOD边界距离
                LODSharedFragment.bHasAdjustedDistancesFromCount = LODSharedFragment.LODCalculator.AdjustDistancesFromCount();
            }
        });
        CloseEntityAdjustDistanceQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context)
        {
            FMassVisualizationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassVisualizationLODSharedFragment>();
            TConstArrayView<FMassViewerInfoFragment> ViewerInfoList = Context.GetFragmentView<FMassViewerInfoFragment>();
            TArrayView<FMassRepresentationLODFragment> RepresentationLODList = Context.GetMutableFragmentView<FMassRepresentationLODFragment>();
            // 根据调整后的LOD边界距离重新计算LOD
            LODSharedFragment.LODCalculator.AdjustLODFromCount(Context, ViewerInfoList, RepresentationLODList);
        });
        // Far entities do not need to maximize count
    }
    // 显示LOD调试信息
    if (UE::MassRepresentation::bDebugRepresentationLOD)
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(DebugDisplayLOD)
        UWorld* World = EntityManager.GetWorld();
        DebugEntityQuery.ForEachEntityChunk(EntityManager, Context, [World](FMassExecutionContext& Context)
        {
            FMassVisualizationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassVisualizationLODSharedFragment>();
            TConstArrayView<FMassRepresentationLODFragment> RepresentationLODList = Context.GetFragmentView<FMassRepresentationLODFragment>();
            TConstArrayView<FTransformFragment> TransformList = Context.GetFragmentView<FTransformFragment>();
            LODSharedFragment.LODCalculator.DebugDisplayLOD(Context, RepresentationLODList, TransformList, World);
        });
    }
}

TMassLODCalculator

示意图

在这里插入图片描述

TMassLODCalculator计算LOD过程中的行为控制开关

struct FLODDefaultLogic
{
    enum
    {
        bStoreInfoPerViewer = false, // 是否每个Viewer信息单独存储
        bCalculateLODPerViewer = false, // Enable to calculate and store the result LOD per viewer in the FMassLODResultInfo::LODPerViewer and FMassLODResultInfo::PrevLODPerViewer, requires bStoreInfoPerViewer to be true as well.
        bMaximizeCountPerViewer = false, // Enable to maximize count per viewer, requires a valid InLODMaxCountPerViewer parameter during initialization of TMassLODCalculator.
        bDoVisibilityLogic = false, // 是否进行可见性计算
        bCalculateLODSignificance = false, // 是否进行重要度计算
        bLocalViewersOnly = false, // 只计算本地Viewer
    };
};
struct FMassSimulationLODLogic : public FLODDefaultLogic
{
};
struct FMassRepresentationLODLogic : public FLODDefaultLogic
{
    enum
    {
        bDoVisibilityLogic = true,
        bCalculateLODSignificance = true,
        bLocalViewersOnly = true,
    };
};

Bucket

桶,LOD内的细分区间,每一级LOD内桶是距离等分的,用于统计数量以及调整LOD边界。UE::MassLOD::MaxBucketsPerLOD定义了每一级LOD桶的数量。

CalculateLOD

根据原始配置计算新的LOD,并统计各级LOD的各个Bucket中Entity的数量,更新可见性。

template <typename FLODLogic>
template <typename TViewerInfoFragment, typename TLODFragment, typename TPerViewerInfoFragment,
          bool bCalculateLocalViewers, bool bCalculateVisibility, bool bCalculateLODPerViewer, bool bCalculateVisibilityPerViewer,bool bMaximizeCountPerViewer>
void TMassLODCalculator<FLODLogic>::CalculateLOD(FMassExecutionContext& Context,
                                                 TConstArrayView<TViewerInfoFragment> ViewersInfoList,
                                                 TArrayView<TLODFragment> LODList,
                                                 TConstArrayView<TPerViewerInfoFragment> PerViewerInfoList)
{
    static_assert(!bCalculateVisibility || FLODLogic::bDoVisibilityLogic, "FLODLogic must have bDoVisibilityLogic enabled to calculate visibility.");
    static_assert(!bCalculateLODPerViewer || FLODLogic::bCalculateLODPerViewer, "FLODLogic must have bCalculateLODPerViewer enabled to calculate LOD Per viewer.");
    static_assert(!bCalculateVisibilityPerViewer || (FLODLogic::bDoVisibilityLogic && FLODLogic::bStoreInfoPerViewer), "FLODLogic must have bDoVisibilityLogic and bStoreInfoPerViewer enabled to calculate visibility per viewer.");
    static_assert(!bMaximizeCountPerViewer || FLODLogic::bMaximizeCountPerViewer, "FLODLogic must have bMaximizeCountPerViewer enabled to maximize count per viewer.");
#if WITH_MASSGAMEPLAY_DEBUG
    if (UE::MassLOD::Debug::bLODCalculationsPaused)
    {
        return;
    }
#endif // WITH_MASSGAMEPLAY_DEBUG
    const int32 NumEntities = Context.GetNumEntities();
    for (int EntityIdx = 0; EntityIdx < NumEntities; EntityIdx++)
    {
        // Calculate the LOD purely upon distances
        const TViewerInfoFragment& EntityViewersInfo = ViewersInfoList[EntityIdx];
        TLODFragment& EntityLOD = LODList[EntityIdx];
        const float ClosestDistanceToFrustum = GetClosestDistanceToFrustum<bCalculateVisibility>(EntityViewersInfo, FLT_MAX);
        const EMassVisibility PrevVisibility = GetVisibility<bCalculateVisibility>(EntityLOD, EMassVisibility::Max);
        const bool bIsVisibleByAViewer = CalculateVisibility(PrevVisibility == EMassVisibility::CanBeSeen, ClosestDistanceToFrustum);// 计算可见性
        bool bIsInAVisibleRange = false;        
        EntityLOD.PrevLOD = EntityLOD.LOD;
        // 根据原始配置计算新的LOD
        EntityLOD.LOD = ComputeLODFromSettings<bCalculateVisibility>(EntityLOD.PrevLOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, &bIsInAVisibleRange, RuntimeData);
        // 设置最新的可见性
        SetPrevVisibility<bCalculateVisibility>(EntityLOD, PrevVisibility);
        SetVisibility<bCalculateVisibility>(EntityLOD, bIsInAVisibleRange ? (bIsVisibleByAViewer ? EMassVisibility::CanBeSeen : EMassVisibility::CulledByFrustum) : EMassVisibility::CulledByDistance);
        // 将当前entity对应LOD中对应Bucket的数量加1,并返回LOD重要度
        const float LODSignificance = AccumulateCountInRuntimeData<bCalculateVisibility, FLODLogic::bCalculateLODSignificance>(EntityLOD.LOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, RuntimeData);
        SetLODSignificance<FLODLogic::bCalculateLODSignificance>(EntityLOD, LODSignificance);
        // Do per viewer logic if asked for
        if (bCalculateLODPerViewer || bCalculateVisibilityPerViewer)
        {
            // 省略部分代码...
        }
    }
}

AccumulateCountInRuntimeData

数量累加

template <typename FLODLogic>
template <bool bCalculateVisibility, bool bCalculateLODSignificance>
float TMassLODCalculator<FLODLogic>::AccumulateCountInRuntimeData(const EMassLOD::Type LOD, const float ViewerDistanceSq, const bool bIsVisible, FMassLODRuntimeData& Data) const
{
    TStaticArray< TStaticArray<int32, UE::MassLOD::MaxBucketsPerLOD>, EMassLOD::Max>& BucketCounts = bCalculateVisibility && bIsVisible ? Data.VisibleBucketCounts : Data.BaseBucketCounts;
    // Cumulate LOD in buckets for Max LOD count calculation
    if (LOD == EMassLOD::Off)
    {
        // Off LOD共用同一个bucket
        BucketCounts[EMassLOD::Off][0]++;
        if (bCalculateLODSignificance)
        {
            return float(EMassLOD::Off);
        }
    }
    else
    {
        const TStaticArray<float, EMassLOD::Max>& BucketSize = bCalculateVisibility && bIsVisible ? VisibleBucketSize : BaseBucketSize;
        const TStaticArray<float, EMassLOD::Max>& AdjustedLODDistance = bCalculateVisibility && bIsVisible ? Data.AdjustedVisibleLODDistance : Data.AdjustedBaseLODDistance;
        const int32 LODDistIdx = (int32)LOD;
        // Need to clamp as the Sqrt is not precise enough and always end up with floating calculation errors
        const int32 BucketIdx = FMath::Clamp((int32)((FMath::Sqrt(ViewerDistanceSq) - AdjustedLODDistance[LODDistIdx]) / BucketSize[LODDistIdx]), 0, UE::MassLOD::MaxBucketsPerLOD - 1);
        BucketCounts[LODDistIdx][BucketIdx]++;
        if (bCalculateLODSignificance)
        {
            // 重要度是由LOD结合BucketIdx计算而来
            const float PartialLODSignificance = float(BucketIdx) / float(UE::MassLOD::MaxBucketsPerLOD);
            return float(LODDistIdx) + PartialLODSignificance;
        }
    }
    return 0.0f;
}

AdjustDistancesFromCountForRuntimeData

根据各级LOD最大数量调整对应LOD边界距离

template <typename FLODLogic>
template <bool bCalculateVisibility>
bool TMassLODCalculator<FLODLogic>::AdjustDistancesFromCountForRuntimeData(const TStaticArray<int32, EMassLOD::Max>& MaxCount, FMassLODRuntimeData& Data) const
{
    int32 Count = 0;
    int32 ProcessingLODIdx = EMassLOD::High;
    bool bNeedAdjustments = false;
    // 从high LOD开始到Low LOD结束
    for (int32 BucketLODIdx = 0; BucketLODIdx < EMassLOD::Max - 1; ++BucketLODIdx)
    {
        // 如果前一个LOD数量没有达到数量上限就将ProcessingLODIdx更新成BucketLODIdx
        if (ProcessingLODIdx < BucketLODIdx)
        {
#if WITH_MASSGAMEPLAY_DEBUG
            // Save the count of this LOD for this frame
            Data.LastCalculatedLODCount[ProcessingLODIdx] = Count;
#endif // WITH_MASSGAMEPLAY_DEBUG
            // Switch to next LOD
            ProcessingLODIdx = BucketLODIdx;
            // Restart the count
            Count = 0;
        }
        // 遍历当前LOD的所有桶进行计数
        for (int32 BucketIdx = 0; BucketIdx < UE::MassLOD::MaxBucketsPerLOD; ++BucketIdx)
        {
            if (bCalculateVisibility)
            {
                // 优先累加可见区域桶的数量
                Count += Data.VisibleBucketCounts[BucketLODIdx][BucketIdx];
                while (Count > MaxCount[ProcessingLODIdx]) //累计数量超过当前LOD数量上限
                {
#if WITH_MASSGAMEPLAY_DEBUG
                    // Save the count of this LOD for this frame
                    Data.LastCalculatedLODCount[ProcessingLODIdx] = Count - Data.VisibleBucketCounts[BucketLODIdx][BucketIdx];
#endif // WITH_MASSGAMEPLAY_DEBUG
                    // 切换到下一级LOD
                    ProcessingLODIdx++;
                    // 调整LOD距离边界(包括可见区域和不可见区域的LOD边界)
                    Data.AdjustedBaseLODDistance[ProcessingLODIdx] = BaseLODDistance[BucketLODIdx] + (BucketIdx * BaseBucketSize[BucketLODIdx]);
                    Data.AdjustedBaseLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedBaseLODDistance[ProcessingLODIdx]);
                    Data.AdjustedVisibleLODDistance[ProcessingLODIdx] = VisibleLODDistance[BucketLODIdx] + (BucketIdx * VisibleBucketSize[BucketLODIdx]);
                    Data.AdjustedVisibleLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedVisibleLODDistance[ProcessingLODIdx]);
                    // Off LOD不需要额外统计数量
                    if (ProcessingLODIdx == EMassLOD::Off)
                    {
                        return true;
                    }
                    // Start the next LOD count with the bucket count that made it go over
                    Count = Data.VisibleBucketCounts[BucketLODIdx][BucketIdx];
                    bNeedAdjustments = true;
                }
            }
            // 对不可见区域进行处理
            Count += Data.BaseBucketCounts[BucketLODIdx][BucketIdx];           
            while (Count > MaxCount[ProcessingLODIdx])//累计数量超过当前LOD数量上限
            {
#if WITH_MASSGAMEPLAY_DEBUG
                // Save the count of this LOD for this frame
                Data.LastCalculatedLODCount[ProcessingLODIdx] = Count - Data.BaseBucketCounts[BucketLODIdx][BucketIdx];
#endif // WITH_MASSGAMEPLAY_DEBUG
                // 切换到下一级LOD
                ProcessingLODIdx++;
                // 调整LOD距离边界
                Data.AdjustedBaseLODDistance[ProcessingLODIdx] = BaseLODDistance[BucketLODIdx] + (BucketIdx * BaseBucketSize[BucketLODIdx]);
                Data.AdjustedBaseLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedBaseLODDistance[ProcessingLODIdx]);
                if (bCalculateVisibility)
                {
                    Data.AdjustedVisibleLODDistance[ProcessingLODIdx] = VisibleLODDistance[BucketLODIdx] + ((BucketIdx + 1) * VisibleBucketSize[BucketLODIdx]);
                    Data.AdjustedVisibleLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedVisibleLODDistance[ProcessingLODIdx]);
                }
                // Off LOD不需要额外统计数量
                if (ProcessingLODIdx == EMassLOD::Off)
                {
                    return true;
                }
                // Start the next LOD count with the bucket count that made it go over
                Count = Data.BaseBucketCounts[BucketLODIdx][BucketIdx];
                bNeedAdjustments = true;
            }
        }
    }
#if WITH_MASSGAMEPLAY_DEBUG
    if (ProcessingLODIdx < EMassLOD::Max - 1)
    {
        // Save the count of this LOD for this frame
        Data.LastCalculatedLODCount[ProcessingLODIdx] = Count;
    }
#endif // WITH_MASSGAMEPLAY_DEBUG
    return bNeedAdjustments;
}

AdjustLODFromCount

根据调整后的LOD边界距离重新计算LOD

template <typename FLODLogic>
template <typename TViewerInfoFragment, typename TLODFragment, typename TPerViewerInfoFragment,
          bool bCalculateLocalViewers, bool bCalculateVisibility, bool bCalculateLODPerViewer, bool bCalculateVisibilityPerViewer, bool bMaximizeCountPerViewer>
void TMassLODCalculator<FLODLogic>::AdjustLODFromCount(FMassExecutionContext& Context,
                                                       TConstArrayView<TViewerInfoFragment> ViewersInfoList,
                                                       TArrayView<TLODFragment> LODList,
                                                       TConstArrayView<TPerViewerInfoFragment> PerViewerInfoList)
{
    static_assert(!bCalculateVisibility || FLODLogic::bDoVisibilityLogic, "FLODLogic must have bDoVisibilityLogic enabled to calculate visibility.");
    static_assert(!bCalculateLODPerViewer || FLODLogic::bCalculateLODPerViewer, "FLODLogic must have bCalculateLODPerViewer enabled to calculate LOD Per viewer.");
    static_assert(!bCalculateVisibilityPerViewer || (FLODLogic::bDoVisibilityLogic && FLODLogic::bStoreInfoPerViewer), "FLODLogic must have bDoVisibilityLogic and bStoreInfoPerViewer enabled to calculate visibility per viewer.");
    static_assert(!bMaximizeCountPerViewer || FLODLogic::bMaximizeCountPerViewer, "FLODLogic must have bMaximizeCountPerViewer enabled to maximize count per viewer.");
    const int32 NumEntities = Context.GetNumEntities();
    // Adjust LOD for each viewer and remember the new highest
    for (int EntityIdx = 0; EntityIdx < NumEntities; EntityIdx++)
    {
        const auto EntityHandle = Context.GetEntity(EntityIdx);
        EntityHandle.Index;
        const TViewerInfoFragment& EntityViewersInfo = ViewersInfoList[EntityIdx];
        TLODFragment& EntityLOD = LODList[EntityIdx];
        EMassLOD::Type HighestViewerLOD = EMassLOD::Off;
        if (bMaximizeCountPerViewer)
        {
            // 省略部分代码...
        }        
        const bool bIsVisibleByAViewer = GetPrevVisibility<bCalculateVisibility>(EntityLOD, EMassVisibility::Max) == EMassVisibility::CanBeSeen;
        // 根据调整后的LOD边界距离重新计算LOD
        EMassLOD::Type NewLOD = ComputeLODFromSettings<bCalculateVisibility>(EntityLOD.PrevLOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, nullptr, RuntimeData);
        // Maybe the highest of all the viewers is now lower than the global entity LOD, make sure to update the it accordingly
        if (bMaximizeCountPerViewer && NewLOD > HighestViewerLOD)
        {
            NewLOD = HighestViewerLOD;
        }
        if (EntityLOD.LOD != NewLOD)
        {
            EntityLOD.LOD = NewLOD;
            if (FLODLogic::bCalculateLODSignificance)// 是否计算LOD重要度
            {
                float LODSignificance = 0.f;
                if (NewLOD == EMassLOD::Off)
                {
                    LODSignificance = float(EMassLOD::Off);
                }
                else
                {
                    const TStaticArray<float, EMassLOD::Max>& AdjustedLODDistance = bCalculateVisibility && bIsVisibleByAViewer ? RuntimeData.AdjustedVisibleLODDistance : RuntimeData.AdjustedBaseLODDistance;
                    // Need to clamp as the Sqrt is not precise enough and always end up with floating calculation errors
                    const float DistanceBetweenLODThresholdAndEntity = FMath::Max(FMath::Sqrt(EntityViewersInfo.ClosestViewerDistanceSq) - AdjustedLODDistance[NewLOD], 0.f);
                    // Derive significance from distance between viewer and where the agent stands between both LOD threshold
                    const float AdjustedDistanceBetweenCurrentLODAndNext = AdjustedLODDistance[NewLOD + 1] - AdjustedLODDistance[NewLOD];//计算当前LOD区间宽度
                    const float PartialLODSignificance = DistanceBetweenLODThresholdAndEntity / AdjustedDistanceBetweenCurrentLODAndNext;// 计算距离在当前LOD区间的百分比处 [0, 1]
                    LODSignificance = float(NewLOD) + PartialLODSignificance;// 结合LOD和区间内重要度
                }
                SetLODSignificance<FLODLogic::bCalculateLODSignificance>(EntityLOD, LODSignificance);
            }
        }
    }
}
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值