LODGroups

LODGroups

#Overview

name: LODGroups

The value of this variable can be defined or overridden in .ini config files. 4 .ini config files referencing this setting variable.

This variable is created as a Console Variable (cvar).

It is referenced in 17 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of LODGroups is to manage Level of Detail (LOD) settings for various assets in Unreal Engine 5, particularly for skeletal meshes and static meshes. LODGroups allow developers to define and organize different levels of detail for 3D models, which can be used to optimize rendering performance by displaying simpler versions of objects at greater distances or under specific conditions.

This setting variable is primarily used in the rendering system and asset management subsystems of Unreal Engine. Based on the callsites, the following modules and subsystems rely on LODGroups:

  1. Skeletal Mesh System
  2. Static Mesh System
  3. FBX Import System
  4. Device Profile Management
  5. Landscape Rendering System

The value of this variable is typically set in various places:

  1. In the USkeletalMeshLODSettings class, where it’s defined as a TArray of FSkeletalMeshLODGroupSettings.
  2. During asset import, particularly when importing FBX files.
  3. In device profiles, where LOD settings can be customized for different hardware configurations.
  4. Through editor tools and customizations, such as the Skeletal Mesh Editor and Static Mesh Editor.

LODGroups interact with several other variables and settings, including:

  1. ReductionSettings for mesh simplification
  2. ScreenSize for determining when to switch LOD levels
  3. LODHysteresis for smoothing LOD transitions
  4. Various import and export settings in the FBX import system

Developers should be aware of the following when using LODGroups:

  1. LODGroups settings can significantly impact performance and visual quality, so they should be carefully tuned.
  2. Different asset types (skeletal meshes, static meshes, landscapes) may have slightly different LODGroups implementations.
  3. LODGroups settings may need to be adjusted when targeting different platforms or device profiles.
  4. Older assets or HLOD setups may need to be rebuilt to fully utilize LODGroups functionality.

Best practices for using LODGroups include:

  1. Regularly review and update LODGroups settings as project requirements evolve.
  2. Use appropriate tools and visualizations to verify LOD transitions and performance impacts.
  3. Consider platform-specific optimizations when setting up LODGroups.
  4. Ensure that LODGroups settings are consistent across related assets for visual coherence.
  5. Collaborate with both artists and performance optimization specialists when defining LODGroups settings.

#Setting Variables

#References In INI files

Location: <Workspace>/Engine/Config/BaseEngine.ini:3319, section: [/Script/Engine.SkeletalMeshLODSettings]

Location: <Workspace>/Engine/Config/BaseEngine.ini:3320, section: [/Script/Engine.SkeletalMeshLODSettings]

Location: <Workspace>/Engine/Config/BaseEngine.ini:3321, section: [/Script/Engine.SkeletalMeshLODSettings]

Location: <Workspace>/Engine/Config/BaseEngine.ini:3322, section: [/Script/Engine.SkeletalMeshLODSettings]

#References in C++ code

#Callsites

This variable is referenced in the following C++ source code:

#Loc: <Workspace>/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp:94

Scope (from outer to inner):

file
function     void FSkeletalMeshReductionSettingsDetails::CustomizeChildren
lambda-function

Source code excerpt:

		{
			if (StructPropertyHandle->GetParentHandle()->GetProperty()->GetFName() == GET_MEMBER_NAME_CHECKED(FSkeletalMeshObject, LODInfo) ||
				StructPropertyHandle->GetParentHandle()->GetProperty()->GetFName() == GET_MEMBER_NAME_CHECKED(USkeletalMeshLODSettings, LODGroups))
			{
				return StructPropertyHandle->GetParentHandle()->GetIndexInArray();
			}
		}

		return INDEX_NONE;

#Loc: <Workspace>/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorSubsystem.cpp:599

Scope (from outer to inner):

file
function     bool UStaticMeshEditorSubsystem::SetLODGroup

Source code excerpt:

{
	
	TArray<FName> LODGroups;
	UStaticMesh::GetLODGroups(LODGroups);
	
	if(LODGroups.Contains(LODGroup))
	{
		GWarn->BeginSlowTask(FText::Format(FText::FromString("SetLODGroup: Applying changes to %s"), FText::FromString(StaticMesh->GetName())), true, false);
		// Close the mesh editor to prevent crashing. If changes are applied, reopen it after the mesh has been built.
		
		bool bStaticMeshIsEdited = false;
		UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();

#Loc: <Workspace>/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp:690

Scope: file

Source code excerpt:

										FbxImporter->FindAllLODGroupNode(NodeInLod, Node, LODIndex);
									}
									else // in less some LODGroups have less level, use the last level
									{
										FbxImporter->FindAllLODGroupNode(NodeInLod, Node, Node->GetChildCount() - 1);
									}

									for (FbxNode *MeshNode : NodeInLod)
									{

#Loc: <Workspace>/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportFactory.cpp:1911

Scope (from outer to inner):

file
function     UObject* UFbxSceneImportFactory::ImportOneSkeletalMesh

Source code excerpt:

					FbxImporter->FindAllLODGroupNode(NodeInLod, Node, LODIndex);
				}
				else // in less some LODGroups have less level, use the last level
				{
					FbxImporter->FindAllLODGroupNode(NodeInLod, Node, Node->GetChildCount() - 1);
				}
				for (FbxNode *MeshNode : NodeInLod)
				{
					SkelMeshNodeArray.Add(MeshNode);

#Loc: <Workspace>/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp:2620

Scope (from outer to inner):

file
function     USkeletalMesh* UnFbx::FFbxImporter::ReimportSkeletalMesh

Source code excerpt:

						FindAllLODGroupNode(NodeInLod, Node, LODIndex);
					}
					else // in less some LODGroups have less level, use the last level
					{
						FindAllLODGroupNode(NodeInLod, Node, Node->GetChildCount() - 1);
					}
					for (FbxNode *MeshNode : NodeInLod)
					{
						SkelMeshNodeArray.Add(MeshNode);

#Loc: <Workspace>/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp:720

Scope (from outer to inner):

file
namespace    FbxMeshUtils
function     bool ImportSkeletalMeshLOD

Source code excerpt:

								FFbxImporter->FindAllLODGroupNode(NodeInLod, Node, SelectedLOD);
							}
							else // in less some LODGroups have less level, use the last level
							{
								FFbxImporter->FindAllLODGroupNode(NodeInLod, Node, Node->GetChildCount() - 1);
							}

							for (FbxNode *MeshNode : NodeInLod)
							{

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Classes/Engine/SkeletalMeshLODSettings.h:153

Scope (from outer to inner):

file
class        class USkeletalMeshLODSettings : public UDataAsset

Source code excerpt:


	UPROPERTY(globalconfig, EditAnywhere, Category=LODGroups)
	TArray<FSkeletalMeshLODGroupSettings> LODGroups;

	friend class FSkeletalMeshReductionSettingsDetails;
public:
	/** Retrieves the Skeletal mesh LOD group settings for the given name */
	ENGINE_API const FSkeletalMeshLODGroupSettings& GetSettingsForLODLevel(const int32 LODIndex) const;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Classes/Engine/SkeletalMeshLODSettings.h:163

Scope (from outer to inner):

file
class        class USkeletalMeshLODSettings : public UDataAsset
function     const bool HasValidSettings

Source code excerpt:

	const bool HasValidSettings() const
	{
		return LODGroups.Num() > 0;
	}

	/** Returns the number of settings parsed from the ini file */
	ENGINE_API int32 GetNumberOfSettings() const;

	/*

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/DeviceProfiles/DeviceProfileManager.cpp:754

Scope (from outer to inner):

file
function     UDeviceProfile* UDeviceProfileManager::CreateProfile

Source code excerpt:

		}

		// make sure the DP has all the LODGroups it needs
		DeviceProfile->ValidateProfile();

		// if the config didn't specify a DeviceType, use the passed in one
		if (DeviceProfile->DeviceType.IsEmpty())
		{
			DeviceProfile->DeviceType = ProfileType;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/DeviceProfiles/DeviceProfileManager.cpp:1456

Scope (from outer to inner):

file
class        class FPlatformCVarExec : public FSelfRegisteringExec
function     virtual bool Exec_Runtime

Source code excerpt:

				}

				// log out the LODGroups fully
				FArrayProperty* LODGroupsProperty = FindFProperty<FArrayProperty>(UDeviceProfile::StaticClass(), GET_MEMBER_NAME_CHECKED(UTextureLODSettings, TextureLODGroups));
				FScriptArrayHelper_InContainer ArrayHelper(LODGroupsProperty, DeviceProfile);
				for (int32 Index = 0; Index < ArrayHelper.Num(); Index++)
				{
					FString	Buffer;
					LODGroupsProperty->Inner->ExportTextItem_Direct(Buffer, ArrayHelper.GetRawPtr(Index), ArrayHelper.GetRawPtr(Index), DeviceProfile, 0);

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODSettings.cpp:29

Scope (from outer to inner):

file
function     const FSkeletalMeshLODGroupSettings& USkeletalMeshLODSettings::GetSettingsForLODLevel

Source code excerpt:

const FSkeletalMeshLODGroupSettings& USkeletalMeshLODSettings::GetSettingsForLODLevel(const int32 LODIndex) const
{
	if (LODIndex < LODGroups.Num())
	{
		return LODGroups[LODIndex];
	}

	// This should not happen as of right now, since the function is only called with 'Default' as name
	ensureMsgf(false, TEXT("Invalid Skeletal mesh default settings LOD Level"));

	// Default return value to prevent compile issue

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODSettings.cpp:45

Scope (from outer to inner):

file
function     int32 USkeletalMeshLODSettings::GetNumberOfSettings

Source code excerpt:

int32 USkeletalMeshLODSettings::GetNumberOfSettings() const
{
	return LODGroups.Num();
}

bool USkeletalMeshLODSettings::SetLODSettingsToMesh(USkeletalMesh* InMesh, int32 LODIndex) const
{
	if (InMesh->IsValidLODIndex(LODIndex) && LODGroups.IsValidIndex(LODIndex))
	{
		FSkeletalMeshLODInfo* LODInfo = InMesh->GetLODInfo(LODIndex);
		const FSkeletalMeshLODGroupSettings& Setting = LODGroups[LODIndex];
		LODInfo->ReductionSettings = Setting.ReductionSettings;
		LODInfo->ScreenSize = Setting.ScreenSize;
		LODInfo->LODHysteresis = Setting.LODHysteresis;
		LODInfo->WeightOfPrioritization = Setting.WeightOfPrioritization;
		LODInfo->bAllowMeshDeformer = Setting.bAllowMeshDeformer;
		// if we have available bake pose

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODSettings.cpp:213

Scope (from outer to inner):

file
function     int32 USkeletalMeshLODSettings::SetLODSettingsToMesh

Source code excerpt:

#endif
		// we only fill up until we have enough LODs
		const int32 NumSettings = FMath::Min(LODGroups.Num(), InMesh->GetLODNum());
		for (int32 Index = 0; Index < NumSettings; ++Index)
		{
			SetLODSettingsToMesh(InMesh, Index);
		}

		return NumSettings;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODSettings.cpp:241

Scope (from outer to inner):

file
function     int32 USkeletalMeshLODSettings::SetLODSettingsFromMesh

Source code excerpt:

		// we only fill up until we have enough LODs
		const int32 NumSettings = InMesh->GetLODNum();
		LODGroups.Reset(NumSettings);
		LODGroups.AddDefaulted(NumSettings);

		for (int32 Index = 0; Index < NumSettings; ++Index)
		{
			FSkeletalMeshLODInfo* LODInfo = InMesh->GetLODInfo(Index);
			FSkeletalMeshLODGroupSettings& Setting = LODGroups[Index];
			Setting.ReductionSettings = LODInfo->ReductionSettings;
			Setting.ScreenSize = LODInfo->ScreenSize;
			Setting.LODHysteresis = LODInfo->LODHysteresis;
			// copy mesh setting to shared setting
			Setting.BonesToPrioritize.Reset();
			for (auto& Bone : LODInfo->BonesToPrioritize)

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODSettings.cpp:330

Scope (from outer to inner):

file
function     void USkeletalMeshLODSettings::Serialize

Source code excerpt:

	if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::ConvertReductionSettingOptions)
	{
		for (int32 Index = 0; Index < LODGroups.Num(); ++Index)
		{
			FSkeletalMeshOptimizationSettings& ReductionSettings = LODGroups[Index].ReductionSettings;
			// prior to this version, both of them were used
			ReductionSettings.ReductionMethod = SMOT_TriangleOrDeviation;
			if (ReductionSettings.MaxDeviationPercentage == 0.f)
			{
				// 0.f and 1.f should produce same result. However, it is bad to display 0.f in the slider
				// as 0.01 and 0.f causes extreme confusion. 

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp:2967

Scope: file

Source code excerpt:

	}
	else if (FParse::Command(&Cmd,TEXT("TextureGroups"))
		|| FParse::Command(&Cmd, TEXT("LODGroups")))
	{
		return HandleLODGroupsCommand( Cmd, Ar );
	}
	else if (FParse::Command(&Cmd,TEXT("InvestigateTexture"))
		|| FParse::Command(&Cmd, TEXT("InvestigateRenderAsset")))
	{

#Loc: <Workspace>/Engine/Source/Runtime/Landscape/Private/LandscapeRender.cpp:4773

Scope (from outer to inner):

file
function     FPrimitiveSceneProxy* ULandscapeMeshProxyComponent::CreateSceneProxy

Source code excerpt:

	if ((LODGroupKey != 0) && (ComponentResolution == 0))
	{
		// this is an OLD HLOD not built with the new resolution/x/y/center information, which we need if LODGroups are used.
		UE_LOG(LogLandscape, Warning, TEXT("HLOD for Landscape needs to be rebuilt!  Landscape Mesh Proxy Component '%s' is using LODGroups but does not have the position and orientation information necessary to register it with the Landscape renderer.  This HLOD will not render until it is fixed."),
			*GetName());
		return nullptr;
	}

	return new FLandscapeMeshProxySceneProxy(this, LandscapeGuid, ProxyComponentBases, ProxyComponentCentersObjectSpace, ComponentXVectorObjectSpace, ComponentYVectorObjectSpace, GetComponentTransform(), ComponentResolution, ProxyLOD, LODGroupKey, ALandscapeProxy::ComputeLandscapeKey(GetWorld(), LODGroupKey, LandscapeGuid));