r.ForceLOD
r.ForceLOD
#Overview
name: r.ForceLOD
This variable is created as a Console Variable (cvar).
- type:
Var
- help:
LOD level to force, -1 is off.
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:
- The rendering system
- The landscape system
- The foliage system
- 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:
- r.SkeletalMeshLODBias
- r.ParticleLODBias
- foliage.DitheredLOD
- foliage.ForceLOD
Developers must be aware that:
- This variable can significantly impact performance and visual quality.
- It overrides the normal LOD selection process, which can affect the intended visual appearance of the game.
- It’s mainly used for debugging, testing, or specific rendering scenarios.
Best practices when using this variable include:
- Use it primarily for debugging or testing purposes.
- Be cautious when using it in shipping builds, as it can negatively impact performance or visual quality.
- 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