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);
}
}
}
}