fix(style): shorten the ramp instead of layering it
The dithering attempt made it worse and the reasoning was backwards. Four layers at a quarter alpha each carry a quarter of the steps each, so every layer bands more coarsely than the single ramp did, and blending four coarse ramps adds interference on top. Reverted. The real variable is distance, not layer count. A ramp has as many steps as it has distinct alpha values, so stretching it over the full height of a pane gives each step a stripe twenty-odd pixels tall, which the eye sharpens into scanlines. Running the same ramp over 190 pixels puts those steps a few pixels apart, where they read as a falloff. So: light fading down from the top edge, shadow gathering at the bottom, and a flat surface in between. Constant alpha cannot band at all, which leaves most of the pane immune by construction.
This commit is contained in:
@@ -120,67 +120,55 @@ internal static class DrawListExtensions
|
||||
dl.AddRectFilledMultiColor(min, max, top, top, bottom, bottom);
|
||||
}
|
||||
|
||||
// A vertical tint that does not band.
|
||||
// A vertical tint that stays under the banding threshold.
|
||||
//
|
||||
// The problem is quantisation, not the ramp: an alpha sweep from 26 to 0
|
||||
// only has 26 distinct values, so across 500 pixels each one owns a stripe
|
||||
// roughly 20px tall -- read as horizontal scanlines. Raising the contrast
|
||||
// just gives more, thinner stripes.
|
||||
// Banding is quantisation: an alpha ramp has as many steps as it has
|
||||
// distinct values, and across a tall pane each step owns a stripe tens of
|
||||
// pixels high. The eye then sharpens those edges into scanlines.
|
||||
//
|
||||
// The fix is to dither. Instead of one ramp, several at a fraction of the
|
||||
// alpha each, every one offset by a sub-step of the height. Their stripe
|
||||
// boundaries land at different rows, so where one layer steps up its
|
||||
// neighbours have not yet, and the blend between them lands between two
|
||||
// quantised values. Same total tint, layers times the effective resolution,
|
||||
// for one extra draw call per layer.
|
||||
public static void DrawDitheredVTint(
|
||||
// Splitting the ramp into faint layers makes it worse, not better -- each
|
||||
// layer carries a fraction of the alpha and therefore a fraction of the
|
||||
// steps, so the stripes get coarser and their blend adds interference.
|
||||
//
|
||||
// What actually works is running the whole ramp over a short distance
|
||||
// instead of a tall one. The same handful of steps then falls within a
|
||||
// couple of hundred pixels, each stripe is a few pixels tall rather than
|
||||
// twenty, and they read as a smooth falloff. Below the fade the surface is
|
||||
// flat, where a constant alpha cannot band at all.
|
||||
public static void DrawEdgeTint(
|
||||
this ImDrawListPtr dl,
|
||||
Vector2 min,
|
||||
Vector2 max,
|
||||
uint topAbgr,
|
||||
uint bottomAbgr,
|
||||
int layers = 4
|
||||
uint edgeAbgr,
|
||||
float fadeHeight,
|
||||
bool fromBottom = false
|
||||
)
|
||||
{
|
||||
if (layers < 1)
|
||||
layers = 1;
|
||||
|
||||
var height = max.Y - min.Y;
|
||||
if (height <= 0f)
|
||||
return;
|
||||
|
||||
// Every layer starts at the top edge and ends slightly lower than the
|
||||
// last, so they share a starting colour but run at different slopes.
|
||||
// Shifting them instead would leave a gap at the top where fewer layers
|
||||
// overlap, which reads as a bright band -- trading one artefact for a
|
||||
// worse one.
|
||||
//
|
||||
// The stagger is a fraction of one quantisation band, so the steps
|
||||
// interleave rather than landing on top of each other.
|
||||
var steps = MathF.Max(1f, ((topAbgr >> 24) & 0xFFu) + ((bottomAbgr >> 24) & 0xFFu));
|
||||
var band = height / steps;
|
||||
var share = 1f / layers;
|
||||
var top = ScaleAlpha(topAbgr, share);
|
||||
var bottom = ScaleAlpha(bottomAbgr, share);
|
||||
var fade = MathF.Min(fadeHeight, height);
|
||||
var clear = edgeAbgr & 0x00FFFFFFu;
|
||||
|
||||
for (var i = 0; i < layers; i++)
|
||||
{
|
||||
var stretch = band * (i / (float)layers);
|
||||
if (fromBottom)
|
||||
dl.AddRectFilledMultiColor(
|
||||
new Vector2(min.X, max.Y - fade),
|
||||
max,
|
||||
clear,
|
||||
clear,
|
||||
edgeAbgr,
|
||||
edgeAbgr
|
||||
);
|
||||
else
|
||||
dl.AddRectFilledMultiColor(
|
||||
min,
|
||||
new Vector2(max.X, max.Y + stretch),
|
||||
top,
|
||||
top,
|
||||
bottom,
|
||||
bottom
|
||||
new Vector2(max.X, min.Y + fade),
|
||||
edgeAbgr,
|
||||
edgeAbgr,
|
||||
clear,
|
||||
clear
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint ScaleAlpha(uint abgr, float factor)
|
||||
{
|
||||
var a = (uint)Math.Clamp(MathF.Round(((abgr >> 24) & 0xFFu) * factor), 0f, 255f);
|
||||
return (abgr & 0x00FFFFFFu) | (a << 24);
|
||||
}
|
||||
|
||||
// A rule that fades out along its length instead of stopping dead. A hard
|
||||
|
||||
@@ -51,13 +51,14 @@ internal sealed class SurfaceBackdrop
|
||||
// visible underneath, the ramp only tints it, and the game showing
|
||||
// through breaks up any step that is left.
|
||||
var opacity = ((ImGui.GetColorU32(ImGuiCol.WindowBg) >> 24) & 0xFFu) / 255f;
|
||||
var lift = 0x00FFFFFFu | ((uint)(0x1E * strength * opacity) << 24);
|
||||
var drop = 0x00000000u | ((uint)(0x34 * strength * opacity) << 24);
|
||||
|
||||
// Dithered, because a plain ramp bands: the alpha sweep has only a few
|
||||
// dozen distinct values, so each owns a stripe tens of pixels tall, seen
|
||||
// as horizontal scanlines across the pane.
|
||||
dl.DrawDitheredVTint(min, max, lift, drop);
|
||||
// Two short fades rather than one tall ramp: light from the top edge,
|
||||
// shadow gathering at the bottom, flat in between. A ramp stretched over
|
||||
// the full height is exactly the case that bands, because its handful of
|
||||
// alpha steps each cover twenty-odd pixels.
|
||||
var fade = MathF.Min(190f * Metrics.Scale, (max.Y - min.Y) * 0.45f);
|
||||
dl.DrawEdgeTint(min, max, 0x00FFFFFFu | ((uint)(0x22 * strength * opacity) << 24), fade);
|
||||
dl.DrawEdgeTint(min, max, (uint)(0x30 * strength * opacity) << 24, fade, fromBottom: true);
|
||||
|
||||
if (darken > 0f)
|
||||
dl.AddRectFilled(min, max, (uint)(0xFF * darken * opacity) << 24);
|
||||
|
||||
Reference in New Issue
Block a user