r.ForceLOD

r.ForceLOD

#Overview

name: r.ForceLOD

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

It is referenced in 15 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of r.ForceLOD is to force a specific Level of Detail (LOD) for rendering in Unreal Engine 5. This setting variable is primarily used for the rendering system, specifically for controlling the LOD levels of various assets in the game.

The Unreal Engine subsystems that rely on this setting variable include:

  1. The rendering system
  2. The landscape system
  3. The foliage system
  4. The hierarchical instanced static mesh system

The value of this variable is typically set through the console or through code. It can be set using the console command “r.ForceLOD” followed by an integer value, where -1 means off (default LOD behavior), and 0 or higher forces a specific LOD level.

This variable interacts with several other variables, including:

  1. r.SkeletalMeshLODBias
  2. r.ParticleLODBias
  3. foliage.DitheredLOD
  4. foliage.ForceLOD

Developers must be aware that:

  1. This variable can significantly impact performance and visual quality.
  2. It overrides the normal LOD selection process, which can affect the intended visual appearance of the game.
  3. It’s mainly used for debugging, testing, or specific rendering scenarios.

Best practices when using this variable include:

  1. Use it primarily for debugging or testing purposes.
  2. Be cautious when using it in shipping builds, as it can negatively impact performance or visual quality.
  3. Remember to reset it to -1 (off) when not needed.

Regarding the associated variable CVarForceLOD:

The purpose of CVarForceLOD is to provide a console variable interface for r.ForceLOD. It’s used internally by the engine to manage the r.ForceLOD setting.

CVarForceLOD is used in the hierarchical instanced static mesh system and the rendering core. It’s typically not directly manipulated by developers but is used by the engine to access and modify the r.ForceLOD value.

The value of CVarForceLOD is set through the console variable system, usually by setting r.ForceLOD.

Developers should be aware that CVarForceLOD is not available in shipping or test builds unless WITH_EDITOR is defined. This is a safety measure to prevent unintended use in final game builds.

Best practices for CVarForceLOD are similar to those for r.ForceLOD, as they essentially control the same functionality.

#References in C++ code

#Callsites

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

#Loc: <Workspace>/Engine/Source/Runtime/RenderCore/Private/RenderCore.cpp:209

Scope: file

Source code excerpt:


static TAutoConsoleVariable<int32> CVarForceLOD(
	TEXT("r.ForceLOD"),
	-1,
	TEXT("LOD level to force, -1 is off."),
	ECVF_Scalability | ECVF_Default | ECVF_RenderThreadSafe
	);

static TAutoConsoleVariable<int32> CVarForceLODShadow(

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/Graph/Nodes/MovieGraphGlobalGameOverrides.cpp:50

Scope (from outer to inner):

file
function     void UMovieGraphGlobalGameOverridesNode::BuildNewProcessCommandLineArgsImpl

Source code excerpt:

	if (bDisableLODs)
	{
		InOutDeviceProfileCvars.Add(TEXT("r.ForceLOD=0"));
		InOutDeviceProfileCvars.Add(TEXT("r.SkeletalMeshLODBias=-10"));
		InOutDeviceProfileCvars.Add(TEXT("r.ParticleLODBias=-10"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.DitheredLOD=0"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.ForceLOD=0"));
	}

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/Graph/Nodes/MovieGraphGlobalGameOverrides.cpp:116

Scope (from outer to inner):

file
function     void UMovieGraphGlobalGameOverridesNode::ApplySettings

Source code excerpt:

	if (bDisableLODs)
	{
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousForceLOD, TEXT("r.ForceLOD"), 0, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousSkeletalMeshBias, TEXT("r.SkeletalMeshLODBias"), -10, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousParticleLODBias, TEXT("r.ParticleLODBias"), -10, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageDitheredLOD, TEXT("foliage.DitheredLOD"), 0, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageForceLOD, TEXT("foliage.ForceLOD"), 0, bOverrideValues);
	}

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineGameOverrideSetting.cpp:64

Scope (from outer to inner):

file
function     void UMoviePipelineGameOverrideSetting::ApplyCVarSettings

Source code excerpt:

	if (bUseLODZero)
	{
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousForceLOD, TEXT("r.ForceLOD"), 0, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousSkeletalMeshBias, TEXT("r.SkeletalMeshLODBias"), -10, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousParticleLODBias, TEXT("r.ParticleLODBias"), -10, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageDitheredLOD, TEXT("foliage.DitheredLOD"), 0, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageForceLOD, TEXT("foliage.ForceLOD"), 0, bOverrideValues);
	}

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineGameOverrideSetting.cpp:190

Scope (from outer to inner):

file
function     void UMoviePipelineGameOverrideSetting::BuildNewProcessCommandLineArgsImpl

Source code excerpt:

	if (bUseLODZero)
	{
		InOutDeviceProfileCvars.Add(TEXT("r.ForceLOD=0"));
		InOutDeviceProfileCvars.Add(TEXT("r.SkeletalMeshLODBias=-10"));
		InOutDeviceProfileCvars.Add(TEXT("r.ParticleLODBias=-10"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.DitheredLOD=0"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.ForceLOD=0"));
	}

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/Graph/Nodes/MovieGraphGlobalGameOverrides.h:117

Scope (from outer to inner):

file
function     class MOVIERENDERPIPELINECORE_API UMovieGraphGlobalGameOverridesNode : public UMovieGraphSettingNode { GENERATED_BODY

Source code excerpt:

	 *
	 * Configures the following cvars:
	 * - r.ForceLOD
	 * - r.SkeletalMeshLODBias
	 * - r.ParticleLODBias
	 * - foliage.DitheredLOD
	 * - foliage.ForceLOD
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering", meta = (EditCondition = "bOverride_bDisableLODs"))
	bool bDisableLODs;

	/**
	 * Determines if hierarchical LODs should be disabled and their real meshes used instead, regardless of distance.
	 * Note that this does not affect World Partition HLODs.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering", meta = (EditCondition = "bOverride_bDisableHLODs"))
	bool bDisableHLODs;

	/**

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Components/LODSyncComponent.cpp:191

Scope (from outer to inner):

file
function     void ULODSyncComponent::UpdateLOD

Source code excerpt:

		
		//Make sure we respect r.ForceLod before starting to apply lodsync logic
		static IConsoleVariable* ForceLODCvar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ForceLOD"));
		if (ForceLODCvar && ForceLODCvar->GetInt() >= 0)
		{
			HighestPriLOD = ForceLODCvar->GetInt();
			bHaveValidSetting = true;
			UE_LOG(LogLODSync, Verbose, TEXT("LOD Sync : Using Cvar r.ForceLod Value to set lodsync [%d]"), HighestPriLOD);
		}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/UnrealClient.cpp:1445

Scope (from outer to inner):

file
function     void FViewport::HighResScreenshot

Source code excerpt:

	static IConsoleVariable* SceneColorFormatVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.SceneColorFormat"));
	static IConsoleVariable* PostColorFormatVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.PostProcessingColorFormat"));
	static IConsoleVariable* ForceLODVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ForceLOD"));

	check(SceneColorFormatVar && PostColorFormatVar);
	const int32 OldSceneColorFormat = SceneColorFormatVar->GetInt();
	const int32 OldPostColorFormat = PostColorFormatVar->GetInt();
	const int32 OldForceLOD = ForceLODVar ? ForceLODVar->GetInt() : -1;

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

Scope (from outer to inner):

file
function     static int32 GetViewLodOverride

Source code excerpt:

static int32 GetViewLodOverride(FSceneView const& View, uint32 LandscapeKey)
{
	// Apply r.ForceLOD override
	int32 LodOverride = GetCVarForceLOD_AnyThread();
	// Apply editor landscape lod override
	LodOverride = View.Family->LandscapeLODOverride >= 0 ? View.Family->LandscapeLODOverride : LodOverride;
	// Use lod 0 if lodding is disabled
	LodOverride = View.Family->EngineShowFlags.LOD == 0 ? 0 : LodOverride;
	

#Associated Variable and Callsites

This variable is associated with another variable named CVarForceLOD. They share the same value. See the following C++ source code.

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:41

Scope: file

Source code excerpt:

static TAutoConsoleVariable<int32> CVarFoliageSplitFactor(
	TEXT("foliage.SplitFactor"),
	16,
	TEXT("This controls the branching factor of the foliage tree."));

static TAutoConsoleVariable<int32> CVarForceLOD(
	TEXT("foliage.ForceLOD"),
	-1,
	TEXT("If greater than or equal to zero, forces the foliage LOD to that level."));

static TAutoConsoleVariable<int32> CVarOnlyLOD(
	TEXT("foliage.OnlyLOD"),
	-1,
	TEXT("If greater than or equal to zero, only renders the foliage LOD at that level."));

static TAutoConsoleVariable<int32> CVarDisableCull(
	TEXT("foliage.DisableCull"),

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:1489

Scope (from outer to inner):

file
function     void FHierarchicalStaticMeshSceneProxy::GetDynamicMeshElements

Source code excerpt:

	QUICK_SCOPE_CYCLE_COUNTER(STAT_HierarchicalInstancedStaticMeshSceneProxy_GetMeshElements);
	SCOPE_CYCLE_COUNTER(STAT_HISMCGetDynamicMeshElement);

	bool bMultipleSections = bDitheredLODTransitions && CVarDitheredLOD.GetValueOnRenderThread() > 0;
	// Disable multiple selections when forced LOD is set
	bMultipleSections = bMultipleSections && ForcedLodModel <= 0 && CVarForceLOD.GetValueOnRenderThread() < 0;

	bool bSingleSections = !bMultipleSections;
	bool bOverestimate = CVarOverestimateLOD.GetValueOnRenderThread() > 0;

	int32 MinVertsToSplitNode = CVarMinVertsToSplitNode.GetValueOnRenderThread();

	const FMatrix WorldToLocal = GetLocalToWorld().Inverse();

	for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
	{
		if (VisibilityMap & (1 << ViewIndex))

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:1745

Scope (from outer to inner):

file
function     void FHierarchicalStaticMeshSceneProxy::GetDynamicMeshElements

Source code excerpt:

					{
						UseMinLOD = FMath::Max(UseMinLOD, DebugMin);
					}
					int32 UseMaxLOD = InstanceParams.LODs;

					int32 Force = CVarForceLOD.GetValueOnRenderThread() >= 0 ? CVarForceLOD.GetValueOnRenderThread() : (ForcedLodModel > 0 ? ForcedLodModel : -1); 
					if (Force >= 0)
					{
						UseMinLOD = FMath::Clamp(Force, 0, InstanceParams.LODs - 1);
						UseMaxLOD = FMath::Clamp(Force, 0, InstanceParams.LODs - 1);
					}

					// Clamp the min LOD to available LOD taking mesh streaming into account as well
					const int8 CurFirstLODIdx = GetCurrentFirstLODIdx_RenderThread();
					UseMinLOD = FMath::Max(UseMinLOD, CurFirstLODIdx);

					if (CVarCullAll.GetValueOnRenderThread() < 1)

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:1823

Scope (from outer to inner):

file
function     void FHierarchicalStaticMeshSceneProxy::GetDynamicMeshElements

Source code excerpt:


				if (UnbuiltInstanceCount < 1000 && UnbuiltBounds.Num() >= UnbuiltInstanceCount)
				{
					const int32 NumLODs = RenderData->LODResources.Num();

					int32 Force = CVarForceLOD.GetValueOnRenderThread() >= 0 ? CVarForceLOD.GetValueOnRenderThread() : (ForcedLodModel > 0 ? ForcedLodModel : -1);
					if (Force >= 0)
					{
						Force = FMath::Clamp(Force, 0, NumLODs - 1);
						int32 LastInstanceIndex = FirstUnbuiltIndex + UnbuiltInstanceCount - 1;
						InstanceParams.AddRun(Force, Force, FirstUnbuiltIndex, LastInstanceIndex);
					}
					else
					{
						FVector ViewOriginInLocalZero = WorldToLocal.TransformPosition(View->GetTemporalLODOrigin(0, bMultipleSections));
						FVector ViewOriginInLocalOne  = WorldToLocal.TransformPosition(View->GetTemporalLODOrigin(1, bMultipleSections));
						float LODPlanesMax[MAX_STATIC_MESH_LODS];

#Loc: <Workspace>/Engine/Source/Runtime/RenderCore/Private/RenderCore.cpp:205

Scope: file

Source code excerpt:


#define EXPOSE_FORCE_LOD !(UE_BUILD_SHIPPING || UE_BUILD_TEST) || WITH_EDITOR

#if EXPOSE_FORCE_LOD

static TAutoConsoleVariable<int32> CVarForceLOD(
	TEXT("r.ForceLOD"),
	-1,
	TEXT("LOD level to force, -1 is off."),
	ECVF_Scalability | ECVF_Default | ECVF_RenderThreadSafe
	);

static TAutoConsoleVariable<int32> CVarForceLODShadow(
	TEXT("r.ForceLODShadow"),
	-1,
	TEXT("LOD level to force for the shadow map generation only, -1 is off."),
	ECVF_Scalability | ECVF_Default | ECVF_RenderThreadSafe

#Loc: <Workspace>/Engine/Source/Runtime/RenderCore/Private/RenderCore.cpp:278

Scope (from outer to inner):

file
function     int32 GetCVarForceLOD

Source code excerpt:

{
	int32 Ret = -1;

#if EXPOSE_FORCE_LOD
	{
		Ret = CVarForceLOD.GetValueOnRenderThread();
	}
#endif // EXPOSE_FORCE_LOD

	return Ret;
}

RENDERCORE_API int32 GetCVarForceLOD_AnyThread()
{
	int32 Ret = -1;

#if EXPOSE_FORCE_LOD
	{
		Ret = CVarForceLOD.GetValueOnAnyThread();
	}
#endif // EXPOSE_FORCE_LOD

	return Ret;
}

RENDERCORE_API int32 GetCVarForceLODShadow()
{
	int32 Ret = -1;

#if EXPOSE_FORCE_LOD