Presentation only. Nothing here touches Topography, the noise, or any generation constant; the renderer is handed a height field LOADED from a .f32 dump and has no way to produce one, so a look change provably cannot move the terrain. Hillshade: Horn 3x3, cell size 1, edges clamped (never wrapped — the Trench border is a wall, not a seam). ZExaggeration is required rather than optional: measured median land slope is 0.22 degrees unexaggerated, so the island shades as a flat plane. Chosen from a measured slope table — zex 75 gives 16/28/37 deg at land median/p90/p99, which reads as natural relief. It is a look dial; raw units are not metres and no code may read it as if they were. Palette: stops placed on the MEASURED height distribution, not spread linearly. Land is bottom-heavy (median 0.456, p99 1.113, max 1.415 over 8.6M land columns across four seeds), so a linear ramp would spend 99% of its range on 1% of the land and the map would read as green with a few white dots. Bathymetry is compressed by d/(d+0.35) so the shallow shelf gets the range and the featureless abyss flattens. Anchors are absolute and fixed across the batch — a map coloured on its own min/max cannot be compared with its neighbour, and comparison is the point of a batch. Blend: the naive tint x hillshade darkens everything by 29% before any slope is involved, because flat ground shades to sin(altitude). So shade is normalized by its flat-ground value first — flat terrain keeps its true tint and only SLOPE moves the colour — then shadows multiply while highlights screen toward white. That asymmetry is the difference between a colour ramp and a map you would frame. Relief fades to zero with depth below sea. Not only taste: the deep floor is the Trench, a synthetic wall whose gradient is ~1.5x the p99 land gradient, so shading it faithfully draws a bright rim around the map and lights the abyss with noise mottle. The fade puts relief on the near-shore shelf where the bathymetry is real. Three named looks (atlas / relief / dusk), gated like any other change and kept deliberately few — iteration fatigue on a subjective gate is a real failure mode. Each pair isolates one question. HeightMapRenderer.cs is retired: its raw I/O moved to HeightField.cs and its ramp to ReliefPalette.cs, so there is one palette everywhere and the generator's own quick-look is coloured identically to a beauty render. No biome words, no classification, no water. The 0.15 line is a colour boundary.
99 lines
4.2 KiB
C#
99 lines
4.2 KiB
C#
namespace IslaApocalypse.Tools
|
||
{
|
||
/// <summary>
|
||
/// The presentation dials — everything about how a heightmap is SHOWN, and nothing about how it
|
||
/// is MADE. → `Design - Rendering - Roughness Is Presentation.md`.
|
||
///
|
||
/// ⚠ Config-gated for the same reason every shaping change is: a look decision should be an A/B
|
||
/// pair, not a memory of last week's render, and a rejected look should cost a flipped default
|
||
/// rather than a reverted commit. → `Design - Tooling - Iteration and Batching.md`.
|
||
///
|
||
/// ⚠ Kept DELIBERATELY SMALL. This is a taste gate, and iteration fatigue on a subjective gate
|
||
/// is a real failure mode — a handful of variants the eye can actually judge beats a wall of
|
||
/// near-duplicates. Three named looks, chosen so each pair isolates one question.
|
||
/// </summary>
|
||
public sealed class LookConfig
|
||
{
|
||
/// <summary>Short label, used in output filenames.</summary>
|
||
public string Name = "atlas";
|
||
|
||
/// <summary>Which hypsometric palette. → <see cref="ReliefPalette.Kind"/>.</summary>
|
||
public ReliefPalette.Kind Palette = ReliefPalette.Kind.Atlas;
|
||
|
||
/// <summary>
|
||
/// Vertical exaggeration for the relief normal. ⚠ A LOOK DIAL, NOT A PHYSICAL CLAIM — the
|
||
/// raw height units are not metres. See <see cref="Hillshade"/> for the measured slope
|
||
/// table this was chosen from.
|
||
/// </summary>
|
||
public float ZExaggeration = 75f;
|
||
|
||
/// <summary>Compass degrees clockwise from north. 315° (NW) is the cartographic standard.</summary>
|
||
public float LightAzimuth = 315f;
|
||
|
||
/// <summary>Degrees above the horizon. 45° standard; lower = longer, more dramatic shadows.</summary>
|
||
public float LightAltitude = 45f;
|
||
|
||
/// <summary>
|
||
/// How much the relief moves the tint. 0 = flat hypsometric tint, no shading at all;
|
||
/// 1 = full relief. Above ~0.85 the tint starts washing out and the map reads as a
|
||
/// greyscale DEM wearing colour.
|
||
/// </summary>
|
||
public float HillshadeStrength = 0.55f;
|
||
|
||
/// <summary>
|
||
/// Relief multiplier BELOW sea level.
|
||
///
|
||
/// ⚠ Deliberately much weaker than on land, and the reason is the deferral: the seabed is
|
||
/// RAW — no coast shelf, no islets, both Phase-2 items — so it is steep and noisy in a way
|
||
/// the finished terrain will not be. Shading it at full strength drags the eye to the one
|
||
/// part of the map that is knowingly unfinished. Cartographic bathymetry is conventionally
|
||
/// shown flat or lightly shaded anyway, so the convention and the deferral agree.
|
||
/// </summary>
|
||
public float SeaHillshadeFactor = 0.35f;
|
||
|
||
/// <summary>
|
||
/// How much of the lit side is allowed to lift toward white. Highlights SCREEN rather than
|
||
/// multiply, so sunlit slopes brighten without blowing out to paper.
|
||
/// </summary>
|
||
public float HighlightGain = 0.5f;
|
||
|
||
/// <summary>The colour boundary. NOT a water surface — no water is modelled (Phase 2).</summary>
|
||
public float SeaLevel = 0.15f;
|
||
|
||
/// <summary>
|
||
/// The three looks offered to the taste gate. Each pair isolates one question:
|
||
/// atlas vs relief → how strong should the relief be? (same palette and light)
|
||
/// atlas vs dusk → which palette, and how low a sun? (comparable relief)
|
||
/// </summary>
|
||
public static LookConfig[] Variants() => new[]
|
||
{
|
||
// Classic physical-atlas plate: tint-forward, relief present but polite.
|
||
new LookConfig
|
||
{
|
||
Name = "atlas", Palette = ReliefPalette.Kind.Atlas,
|
||
ZExaggeration = 75f, LightAzimuth = 315f, LightAltitude = 45f,
|
||
HillshadeStrength = 0.55f,
|
||
},
|
||
|
||
// Shape-forward: the same palette and light, harder relief. Isolates the relief dial.
|
||
new LookConfig
|
||
{
|
||
Name = "relief", Palette = ReliefPalette.Kind.Atlas,
|
||
ZExaggeration = 120f, LightAzimuth = 315f, LightAltitude = 45f,
|
||
HillshadeStrength = 0.80f,
|
||
},
|
||
|
||
// Warmer palette, lower sun, longer shadows. Isolates palette + light.
|
||
new LookConfig
|
||
{
|
||
Name = "dusk", Palette = ReliefPalette.Kind.Dusk,
|
||
ZExaggeration = 100f, LightAzimuth = 300f, LightAltitude = 35f,
|
||
HillshadeStrength = 0.70f,
|
||
},
|
||
};
|
||
|
||
public override string ToString() =>
|
||
$"{Name}: palette={Palette} zex={ZExaggeration:F0} light={LightAzimuth:F0}°/{LightAltitude:F0}° " +
|
||
$"strength={HillshadeStrength:F2} sea×{SeaHillshadeFactor:F2}";
|
||
}
|
||
}
|