About Store Forum Documentation Contact



Post Reply 
Terrain Anti-Tiling: distance dual-scale + stochastic "by-example" no asset pipeline
Author Message
Fex Offline
Gold Supporter

Post: #1
Terrain Anti-Tiling: distance dual-scale + stochastic "by-example" no asset pipeline
Terrain Anti-Tiling: distance dual-scale + stochastic “by-example” (no asset pipeline)

Before anti-tile:
[Image: Screenshot-From-2026-06-18-23-39-08.png]

After anti-tile:
[Image: Screenshot-From-2026-06-18-23-38-58.png]

Works best when looking out over the terrain, looking straight down at terrain it doesn't help as much.



Written by AI:

Repeating ground textures betray themselves two different ways, and they need two different fixes:

  1. The far-field grid — from a hill or a grazing camera, the regular periodicity of the texture reads as a checkerboard receding to the horizon.
  2. The close/mid feature repeat — that one distinctive grass clump / rock / crack showing up again every few meters right in front of you.

Two complementary techniques in the deferred terrain shader (
Code:
Engine/Source/Shaders/Deferred.cpp
) address each. Both run on the single- and multi-material terrain paths, up to 5 blended materials, and both are gated to terrain only (
Code:
#if HEIGHTMAP && SET_POS
), so buildings, props, and characters are completely untouched.

1. Distance-based dual-scale: kills the far grid

Sample the same ground texture at two UV scales and blend in the lower-frequency, larger-tile one with camera distance:

Code:
tex = lerp(near, RTex(Col, uv * TileFarScale), fade(distance));

Near the camera you get full detail. Far away, the tight repeat dissolves into a coarser, non-aligned pattern. This is the standard Unreal-landscape-style trick, and it is mip-safe for free, because the two samples use implicit derivatives, so no
Code:
SampleGrad
is needed.

2. Stochastic “by-example”: kills the near/mid feature repeat

This is Heitz–Neyret’s by-example noise, minus the histogram LUT, which means it needs no precomputed assets and no per-texture bake.
  • Triangular / “hex” grid — 3 samples, no axis-aligned blend seams.
  • Per-cell random offset only, no mirror — the mirror in the cheaper grid variants is what causes those directional scratches/scuffs on grass. Dropping it removes them.
  • Variance-preserving blend — naive averaging washes out contrast, so instead of
    Code:
    sum(w_i * c_i)
    , we blend the deviation from the mean and renormalize by
    Code:
    1 / sqrt(sum(w_i^2))
    . The mean is read from the texture’s smallest mip, approximately its average color, so we recover contrast with zero precomputation.
  • Code:
    SampleGrad
    keeps mipmapping correct.

The result: a single tiling texture stops repeating and reads as a natural, non-periodic surface — no scratches, no muddy blend blobs.

How to use it

Everything is driven by a single
Code:
TerrainTile
shader cbuffer, set from the public shader-param API:

Code:
SPSet("TerrainTileEnable", 1.0f);  // distance effect
SPSet("TileFarScale", 0.22f);
SPSet("TileFadeStart", 5.0f);      // ...TileFadeRange, TileFarMax

SPSet("TerrainStochastic", 1.0f);  // stochastic de-tiling

It is off by default. The cbuffer is zero-initialized, so nothing changes unless your app sets the params. The editor and all other content render exactly as before.

The params only exist once the terrain shader has loaded, so set them each frame before drawing the world.
Code:
SPSet
safely no-ops until then. There is no engine-wide default yet; you opt in from your render loop.

Parameters
  • Code:
    TerrainTileEnable
    — 0/1, distance dual-scale on/off.
  • Code:
    TileFarScale
    — coarse UV multiplier. Smaller means bigger far tiles and a stronger frequency break.
  • Code:
    TileFadeStart
    — meters: distance where the coarse blend begins.
  • Code:
    TileFadeRange
    — meters: distance over which it ramps to
    Code:
    TileFarMax
    .
  • Code:
    TileFarMax
    — max fraction of the coarse sample blended in at far distance.
  • Code:
    TerrainStochastic
    — 0/1, stochastic de-tiling on/off.
  • Code:
    StochMaxDist
    — performance limit: only de-tile within this distance.
    Code:
    <=0
    means no limit.
  • Code:
    StochMinWeight
    — performance limit: skip materials whose blend weight is below this. means de-tile all.

Performance limits: opt-in, runtime

Multi-material stochastic can hit up to 5 materials × 4 taps = 20 albedo taps, so there are two runtime knobs:
  • Code:
    StochMaxDist
    — only run the stochastic taps within this distance. The far grid is handled by the distance blend anyway, so far pixels drop straight back to 1 tap.
  • Code:
    StochMinWeight
    — skip stochastic for a material whose per-pixel blend weight is below a threshold, so a barely-visible layer does not cost 4 taps.

Both default to “no practical limit.”

Large-world / floating-origin

Terrain UVs are world-continuous, built from the absolute area index, so far out in a big world they grow large.

A
Code:
Frac(sin(...))
hash loses precision there and the de-tiling starts aliasing, so the stochastic cell hash is a PCG2D integer hash on the integer cell ID, exact at any magnitude.

The pattern stays locked to the ground and is transparent to floating-origin rebasing. UVs deliberately do not rebase. Shifting them would move the non-integer-aligned stochastic grid and pop the pattern every rebase.

Tutorials

Code:
Tutorial_14_TerrainTiling

Code:
Tutorials/Source/14 - Game Basics/36 - Terrain Tiling.cpp

The live A/B demo. It loads a textured world and lets you toggle and tune everything in real time:
  • T — distance anti-tiling on/off.
  • Y — stochastic de-tiling on/off.
  • 1–8 — distance params: strength, fade start, fade range, coarse scale.
  • G / H — stochastic max distance, for performance.
  • J / K — stochastic min weight, for performance.
  • 9 — reset all to defaults.
  • Hold RMB — look.
  • Mouse wheel — zoom.
  • ESC — quit.

The HUD shows every value live, and the
Code:
EE_TILE*
env vars let you drive it headless for A/B captures:

Code:
EE_TILE
EE_TILE_STOCH
EE_TILE_CAM_*
EE_TILE_FARMAX
EE_TILE_STOCHDIST

Since the effect lives in the shader, it shows up on any terrain. The other world tutorials are good places to see it on different content once an app enables the params:
  • Code:
    Tutorial_14_World
  • Code:
    Tutorial_14_WorldWithCharacter
  • Code:
    Tutorial_14_ProceduralWorld

Caveats / future work
  • The only thing the full Heitz histogram LUT would add over this is slightly sharper blend zones. It carries a per-texture asset-bake cost, so it is left out. The variance-preserving approximation looks clean in practice.
  • One residual issue: the triangular-grid barycentric weights lose a little float precision at extreme UV. The cell hash is exact, so the de-tiling holds; only the blend transitions get marginally coarser very far out.
  • Backends: Vulkan and GL shader paks are regenerated. DX still needs a Windows tree to recompile, only tested on Linux+Vulkan.

in this repo: https://github.com/DrewGilpin/EsenthelEngine
06-19-2026 04:57 AM
Find all posts by this user Quote this message in a reply
Post Reply