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,68 +120,56 @@ internal static class DrawListExtensions
|
|||||||
dl.AddRectFilledMultiColor(min, max, top, top, bottom, bottom);
|
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
|
// Banding is quantisation: an alpha ramp has as many steps as it has
|
||||||
// only has 26 distinct values, so across 500 pixels each one owns a stripe
|
// distinct values, and across a tall pane each step owns a stripe tens of
|
||||||
// roughly 20px tall -- read as horizontal scanlines. Raising the contrast
|
// pixels high. The eye then sharpens those edges into scanlines.
|
||||||
// just gives more, thinner stripes.
|
|
||||||
//
|
//
|
||||||
// The fix is to dither. Instead of one ramp, several at a fraction of the
|
// Splitting the ramp into faint layers makes it worse, not better -- each
|
||||||
// alpha each, every one offset by a sub-step of the height. Their stripe
|
// layer carries a fraction of the alpha and therefore a fraction of the
|
||||||
// boundaries land at different rows, so where one layer steps up its
|
// steps, so the stripes get coarser and their blend adds interference.
|
||||||
// neighbours have not yet, and the blend between them lands between two
|
//
|
||||||
// quantised values. Same total tint, layers times the effective resolution,
|
// What actually works is running the whole ramp over a short distance
|
||||||
// for one extra draw call per layer.
|
// instead of a tall one. The same handful of steps then falls within a
|
||||||
public static void DrawDitheredVTint(
|
// 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,
|
this ImDrawListPtr dl,
|
||||||
Vector2 min,
|
Vector2 min,
|
||||||
Vector2 max,
|
Vector2 max,
|
||||||
uint topAbgr,
|
uint edgeAbgr,
|
||||||
uint bottomAbgr,
|
float fadeHeight,
|
||||||
int layers = 4
|
bool fromBottom = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (layers < 1)
|
|
||||||
layers = 1;
|
|
||||||
|
|
||||||
var height = max.Y - min.Y;
|
var height = max.Y - min.Y;
|
||||||
if (height <= 0f)
|
if (height <= 0f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Every layer starts at the top edge and ends slightly lower than the
|
var fade = MathF.Min(fadeHeight, height);
|
||||||
// last, so they share a starting colour but run at different slopes.
|
var clear = edgeAbgr & 0x00FFFFFFu;
|
||||||
// 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);
|
|
||||||
|
|
||||||
for (var i = 0; i < layers; i++)
|
if (fromBottom)
|
||||||
{
|
dl.AddRectFilledMultiColor(
|
||||||
var stretch = band * (i / (float)layers);
|
new Vector2(min.X, max.Y - fade),
|
||||||
|
max,
|
||||||
|
clear,
|
||||||
|
clear,
|
||||||
|
edgeAbgr,
|
||||||
|
edgeAbgr
|
||||||
|
);
|
||||||
|
else
|
||||||
dl.AddRectFilledMultiColor(
|
dl.AddRectFilledMultiColor(
|
||||||
min,
|
min,
|
||||||
new Vector2(max.X, max.Y + stretch),
|
new Vector2(max.X, min.Y + fade),
|
||||||
top,
|
edgeAbgr,
|
||||||
top,
|
edgeAbgr,
|
||||||
bottom,
|
clear,
|
||||||
bottom
|
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
|
// A rule that fades out along its length instead of stopping dead. A hard
|
||||||
// line boxes content in; a fading one suggests a boundary without drawing a
|
// line boxes content in; a fading one suggests a boundary without drawing a
|
||||||
|
|||||||
@@ -51,13 +51,14 @@ internal sealed class SurfaceBackdrop
|
|||||||
// visible underneath, the ramp only tints it, and the game showing
|
// visible underneath, the ramp only tints it, and the game showing
|
||||||
// through breaks up any step that is left.
|
// through breaks up any step that is left.
|
||||||
var opacity = ((ImGui.GetColorU32(ImGuiCol.WindowBg) >> 24) & 0xFFu) / 255f;
|
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
|
// Two short fades rather than one tall ramp: light from the top edge,
|
||||||
// dozen distinct values, so each owns a stripe tens of pixels tall, seen
|
// shadow gathering at the bottom, flat in between. A ramp stretched over
|
||||||
// as horizontal scanlines across the pane.
|
// the full height is exactly the case that bands, because its handful of
|
||||||
dl.DrawDitheredVTint(min, max, lift, drop);
|
// 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)
|
if (darken > 0f)
|
||||||
dl.AddRectFilled(min, max, (uint)(0xFF * darken * opacity) << 24);
|
dl.AddRectFilled(min, max, (uint)(0xFF * darken * opacity) << 24);
|
||||||
|
|||||||
Reference in New Issue
Block a user