Skip to content

RVT Landscape Blend

Summary: A reusable pattern for making world-placed meshes (rocks, cliffs, props) fade seamlessly into the landscape they sit on, using a Runtime Virtual Texture. The mesh samples the landscape's base color, normal and roughness near its base and blends toward its own material over a height-based fade window, dithered by procedural noise. First shipped for desert cliffs (2026-05-10) but the material stack is generic.

Table of Contents


Why RVT Blend

A hard color/normal cutoff where a mesh intersects the landscape reads badly, especially in a stylized/painterly art direction. A Runtime Virtual Texture lets the mesh read the landscape's shaded surface (base color, normal, roughness, world height) at its own world position and blend into it near the contact line, so the transition dissolves instead of cutting.


Pattern Overview

┌──────────────────────────┐     writes      ┌────────────────────────┐
│ Landscape material        │ ──────────────▶ │ RVT pages              │
│ (…_RVTBlend variant with  │                 │  • BC + Normal + Rough │
│  RuntimeVirtualTextureOut)│                 │  • WorldHeight         │
└──────────────────────────┘                 └───────────┬────────────┘
                                                          │ samples
                                              ┌───────────▼────────────┐
                                              │ Mesh master (…_RVTBlend)│
                                              │  height-fade + noise    │
                                              │  lerp( mesh, RVT )      │
                                              │  static switch on/off   │
                                              └─────────────────────────┘

Three moving parts: 1. A landscape material variant that adds a RuntimeVirtualTextureOutput node, writing BC/Normal/Roughness (and a separate WorldHeight RVT). 2. RVT volume actors covering the landscape so the pages get populated. 3. A mesh master material that samples the RVT and lerps between its own surface and the landscape's, gated by a height-based fade window and procedural-noise dither, behind a static switch so blend-off meshes pay no cost.


Asset Set

The desert instance (paths illustrative of the pattern, under MaterialLibrary):

Role Asset
RVT (BC + Normal + Roughness) /Game/MaterialLibrary/Landscape/RVT/RVT_Desert_BCNR
RVT (WorldHeight) /Game/MaterialLibrary/Landscape/RVT/RVT_Desert_Height
Landscape material variant /Game/MaterialLibrary/M_Ground_Layered_RVTBlend (clone of M_Ground_Layered + RuntimeVirtualTextureOutput)
Mesh master /Game/MaterialLibrary/M_ORM_RVTBlend (clone of M_ORM + RVT blend stack + static switch)
Per-mesh MIs MI_Cliff_{0B,0C,1C}_RVTBlend in …/Shared/Materials/Rocks/
RVT volumes RVTVolume_* actors covering the landscape

MI_Landscape_Desert is reparented to the landscape variant. To reuse the pattern in another biome, clone the two masters, author a matching pair of RVTs, and place RVT volumes over that landscape.


Parameters

Exposed on the mesh master (M_ORM_RVTBlend):

Parameter Default Purpose
bUseLandscapeBlend (static switch) Toggle blend on/off; off = pure mesh, zero added cost
RVT_FadeStart 5 Height above landscape where the blend begins
RVT_FadeRange 200 Fade thickness
RVT_Tint (vector) white Color bias on the blended result
RVT_BlendNoiseTile 0.005 Procedural noise scale (lower = bigger blobs)
RVT_BlendNoiseStrength 0.6 Dither chaos in the transition zone
Occlusion (static switch) false Toggle baked AO from M.R
Roughness Value 1 Multiplier on M.G

RVT_PDO_Amount existed but the PDO chain was removed (see wiring notes) — mild z-fighting at the intersection is accepted; fade range + dither carry the transition.


Critical Wiring Details

These are the parts that are easy to get wrong and produce dark/wrong-lit meshes:

  • Material output: tangent_space_normal = False on the mesh master, so the Normal pin takes a world-space vector directly.
  • Mesh normal → world space: put a Transform node (TANGENT → WORLD) on the mesh's tangent-space normal map before the lerp — the RVT normal is world-space, so both sides must be world-space before mixing.
  • Landscape RVT normal: the LandscapeLayerBlend output is tangent-space too, so it also needs a Tangent → World Transform before feeding RuntimeVirtualTextureOutput.Normal.
  • After the normal lerp: a Normalize node before output — lerping two unit vectors yields a non-unit vector.
  • Noise gating: multiply the noise contribution by a mask * (1 - mask) * 4 window so noise only perturbs the transition band, not the whole mesh.
  • Procedural noise, not textures: use MaterialExpressionNoise, not a TextureSample, for the dither — avoids visible tiling artifacts.
  • PDO: not used. Python's MaterialProperty enum doesn't expose MP_PIXEL_DEPTH_OFFSET in 5.6 anyway (see UE Python Material API Reference); it would have to be dragged manually. The look was rejected regardless.

Applying to New Meshes

When adding new meshes that should fade into an RVT-enabled landscape: 1. Parent the mesh's MI to the mesh master (M_ORM_RVTBlend or a biome equivalent). 2. Set bUseLandscapeBlend = true. 3. Tune RVT_FadeStart / RVT_FadeRange / noise per-MI. 4. Confirm an RVT volume actually covers the mesh's footprint (no volume = no pages = no blend).


Tuning Guide

Symptom First thing to check
Mesh too dark in lit shading at its base Buffer Visualization → Normal. Non-uniform → RVT.Normal pages are wrong; re-verify Tangent → World on the landscape side
Hard color seam remains Increase RVT_FadeRange
Visible texture-tile artifacts in the blend Ensure noise comes from MaterialExpressionNoise, not a texture sample
Mesh lit fine with blend off, dark with blend on Suspect RVT pages contain wrong/zero data (a probe script may have stomped wires — see Probe scripts stomp wires)


Recent Changes

Date Change Impact
2026-07-03 Documented the pattern reusably from the desert-cliff instance Reference for future biomes
2026-05-24 One-shot authoring scripts deleted (recoverable from git before 5bc8f5b11) Material graph is now the source of truth, not the scripts
2026-05-10 PDO chain removed; z-fight accepted, fade + dither carry the transition Simpler stack