If your VirtualHeightfieldMesh terrain started popping between detail levels after moving to 5.8,
it’s an engine bug, not your content. I couldn’t find this reported anywhere, so here’s the cause
and the fix.
Symptom
Whole terrain sections snap to a different detail level on very small camera movements — small
enough that it reads as instability rather than normal LOD transition. Identical content is stable
on 5.7.
It is not a streaming or residency problem, which is where I wasted my time: VT residency is
healthy, Num page throttled is zero, and raising r.VT.PoolSizeScale does nothing. The pages are
resident and correct — they’re just the wrong level. That’s why it presents as a content or
LOD-tuning issue.
Cause
5.8 changed the virtual-texture feedback level encoding engine-wide, from Level+1 to
Level + VirtualTextureFeedbackBias (= 3), defined in the new
Engine/Shaders/Shared/VirtualTextureDefinitions.h — a file that doesn’t exist in 5.7. The engine’s
own encode and decode were both updated:
- encode:
VirtualTextureCommon.ush:407,457→vLevel + VirtualTextureFeedbackBias - decode:
VirtualTextureSystem.cpp:1575→Max(vLevelPlusFixedOffset, 3u) - 3
Engine/Plugins/Experimental/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.usf was
not updated. It’s byte-identical between 5.7 and 5.8, still writes Level+1, and never includes
VirtualTextureDefinitions.h — so it can’t pick up the constant.
VHM requests level N, writes N+1, and the CPU decodes max(N+1,3)-3:
N=0 -> 0 N=3 -> 1
N=1 -> 0 N=4 -> 2
N=2 -> 0 N=5 -> 3
Every height page request resolves two mips finer than intended, and levels 0–2 collapse to 0, so
the force-loaded root pages are never requested at their real level.
VHM samples the height RVT in the vertex shader to displace geometry. So the mesh’s quadtree LOD
and its height mip end up decoupled by two levels and cross their thresholds at different distances,
changing independently. A small camera move flips a whole tile’s height source while its
tessellation stays put — so the rendered ground surface shifts vertically for that tile.
Fix
Three edits in VirtualHeightfieldMesh.usf:
- Add
#include "/Engine/Shared/VirtualTextureDefinitions.h" - Priming loop (~line 201):
1 + MaxLevel - Level→VirtualTextureFeedbackBias + MaxLevel - Level - Per-frame write (~line 330):
FeedbackLevel + 1→FeedbackLevel + VirtualTextureFeedbackBias
No clamp needed — VHM’s FeedbackLevel is already clamped to [Level, MaxLevel], both non-negative.
(The engine clamps because its own vLevel can go negative; that’s the reason the bias exists.)
There’s no cvar workaround — VirtualTextureFeedbackBias is a compile-time static const int.
Since a patched engine install is useless to a team, the practical fix is to copy the plugin into
your project’s Plugins/ folder and patch it there. A project plugin of the same name takes
precedence over the engine one (PluginManager.cpp, ReadAllPlugins: engine dirs are scanned first,
project dirs second, and the source comment says “assume that the game plugin version is preferred”).
Keep the name VirtualHeightfieldMesh — renaming changes UVirtualHeightfieldMeshComponent’s class
path and breaks every level that references it.
Upstream
Still unfixed on ue5-main and dev-5.8 as of 2026-07-20, so it ships again unless it gets picked
up. PR: https://github.com/EpicGames/UnrealEngine/pull/14992