Skip to content

Add the Terrain400 shader family with simultaneous height blending - #7344

Draft
TimMasalme wants to merge 2 commits into
FAForever:developfrom
TimMasalme:feature/terrain400
Draft

TimMasalme wants to merge 2 commits into
FAForever:developfrom
TimMasalme:feature/terrain400

Conversation

@TimMasalme

Copy link
Copy Markdown
Contributor

Description of the proposed changes

Adds a new terrain shader family, Terrain400, next to Terrain200. No existing technique changes, so no existing map changes.

The 2XX shaders lay the strata over each other in order, and splatLerp() compares each layer's height only with its own opacity. Terrain400 blends all layers at once by height instead, after Unity's HeightBasedSplatModify:

w_lower = saturate(1 - sum(w_i))          // masks are weights; a sum above 1 is scaled back to 1
s_i     = h_i * w_i                       // all 8 layers
t       = max(SpecularColor.r, 0.001)     // transition: small cuts hard, ~1.5 is close to a plain blend
w_i'    = (max(s_i + t - max(s), 0) + 0.01 * t) * w_i
w_i''   = w_i' / sum(w')                  // albedo, roughness and normals all blend with w''

ps_2_a decided the texture convention: holding eight albedos plus the atlas gradients does not fit in 22 temp registers, and ps_3_0 would lose the fixed-function terrain fog. So:

  • each stratum carries its height in the alpha of both its albedo and its normal map, so each pass reads only its own textures and stays under 16 samplers
  • the atlas in the upper slot only supplies the roughness, read at mip 0 (tex2Dbias(..., -16)) to avoid seams at tile borders
  • the lower layer has a fixed height of 0.5, so its albedo alpha stays the macrotexture opacity of modes 1 and 2
  • no 30° rotation and no far-scale height

Techniques: Terrain400, Terrain401, Terrain402 (last digit = macrotexture mode, as in Terrain20x) and Terrain400T, Terrain401T, Terrain402T, where Stratum2 and Stratum3 are sampled triplanar from the two strongest of the three projections (biplanar mapping as described by Inigo Quilez), chosen from the map-wide normal in Stratum7.

Pixel shader (ps_2_a) Terrain200 / 200B Terrain400 / 400T
Normals pass 440–473 slots 74 / 103 slots
Albedo pass 478–498 slots 229–232 / 256–259 slots

Testing done on the proposed changes

  • The whole terrain.fx compiles with d3dx9_31, the compiler the game uses.
  • The weight formula was checked on 20,000 random mask, height and transition combinations: no NaN, no negative weights, weights always sum to 1.
  • Runestone (Terrain202B) converted to Terrain402T, compared in game against the original.
  • Two test maps of my own, including a 20 km map with 4096² masks, an 8K map normal and Terrain402/402T.

Not tested yet: other GPUs than mine, and maps made from scratch for this convention by other mappers.

Additional context

Draft, following a short chat with @BlackYps. Open questions:

  • Is the alpha convention (height in the albedo and normal alpha) acceptable? generate-pbr or Ozone could write it.
  • Roughness at mip 0 can alias at long range. Keep it, or a single roughness per layer?
  • The triplanar choice follows the map normal. A map normal that does not match the heightmap (for example one baked from a different Gaea build) picks wrong projections and stretches the triplanar strata; the selection reads the map normal two mip levels coarser to calm high-frequency detail. Worth documenting for mappers.
  • The name: 400, or something else in your numbering?

Checklist

Approval

[ ] The feature is greenlighted through

  • concluded discussion (add link)

Terrain400, 401 and 402 blend all strata at once by height, as Unity's
terrain shaders do, instead of laying them over each other in order.
The stratum masks are weights, the lower layer takes what they leave,
and SpecularColor.r sets the height transition. Each stratum carries its
height in the alpha of its albedo and normal map; the atlas in the upper
slot supplies the roughness.

Terrain400T, 401T and 402T sample Stratum2 and Stratum3 triplanar, using
the two strongest of the three projections chosen from the map-wide
normal in Stratum7.
@coderabbitai

coderabbitai Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant