Skip to content

Commit 3ddc36f

Browse files
authored
Perform premultiply alpha at last (#2571)
#2567 introduced a bug that non-white stars becomes partially transparent due to because we are using rgb / max(r,g,b) as the color. This PR performs color clamping first before premultiplying alpha for fade out. Also removed a bunch of max/if checks.
1 parent 3d54def commit 3ddc36f

5 files changed

Lines changed: 47 additions & 48 deletions

File tree

shaders/psfstarglow_frag.glsl

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,33 @@ in vec3 v_color;
2222
in float v_alpha;
2323
in float v_peakRadiance;
2424
in float v_psfRadius;
25-
in float v_pointSize;
2625

2726
void main(void)
2827
{
2928
// Pixel offset from the centre of the point sprite (in screen pixels).
30-
vec2 d = (gl_PointCoord.xy - vec2(0.5)) * v_pointSize;
31-
float px = length(d) / max(pointScale, 1e-6);
29+
// gl_PointSize was set to 2 * v_psfRadius * pointScale in the vertex
30+
// shader, so length(gl_PointCoord - 0.5) * 2 * v_psfRadius equals the
31+
// distance from the sprite centre in unscaled pixels (pointScale
32+
// cancels out).
33+
float px = length(gl_PointCoord.xy - vec2(0.5)) * 2.0 * v_psfRadius;
3234

3335
// intensity = clamp(((peak^0.4 / px - a) * b)^2.5, 0, peak)
34-
if (px <= 0.0 || px >= v_psfRadius)
35-
discard;
36-
37-
float base = pow(max(v_peakRadiance, 1e-6), 0.4) / px - psfA;
38-
if (base <= 0.0)
36+
if (px >= v_psfRadius)
3937
discard;
4038

39+
// p04 = pow(v_peakRadiance, 0.4) was already computed in the vertex
40+
// shader as v_psfRadius * psfA -- recover it with a multiply instead
41+
// of paying for a pow per fragment.
42+
float p04 = v_psfRadius * psfA;
43+
float base = p04 / px - psfA;
4144
float val = pow(base * psfB, 2.5);
42-
val = clamp(val, 0.0, v_peakRadiance);
43-
44-
// Cap per-fragment output radiance at v_alpha (hue-preserving): the
45-
// PSF center can otherwise dump arbitrarily large values into the
46-
// additive accumulation, oversaturating any single pixel. Scaling
47-
// by v_alpha is what actually makes the C++-side alpha fade visible
48-
// — without it, premultiplied v_color and the inverse-max clamp
49-
// cancel out, defeating the fade.
50-
float maxCh = max(max(v_color.r, v_color.g), v_color.b);
51-
val = min(val, v_alpha / max(maxCh, 1e-6));
52-
53-
fragColor = vec4(v_color * val, 1.0);
45+
val = min(val, v_peakRadiance);
46+
47+
// Clamp each channel of v_color * val to 1 BEFORE applying the
48+
// fade alpha so the bleached-white centre of a colored (e.g.
49+
// blue) star stays white through the fade, instead of reverting
50+
// to its underlying tint once v_alpha drops the per-channel value
51+
// back below saturation.
52+
vec3 clampedColor = min(vec3(1.0), v_color * val);
53+
fragColor = vec4(clampedColor * v_alpha, 1.0);
5454
}

shaders/psfstarglow_vert.glsl

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,18 @@ out vec3 v_color;
2424
out float v_alpha;
2525
out float v_peakRadiance;
2626
out float v_psfRadius; // PSF cutoff radius in px (>0)
27-
out float v_pointSize;
2827

2928
void main(void)
3029
{
31-
v_color = in_Color.rgb * in_Color.a;
30+
v_color = in_Color.rgb;
3231
v_alpha = in_Color.a;
3332
v_peakRadiance = in_Intensity;
3433

3534
// Glow mode: PSF support radius depends on peak radiance.
3635
// psf_radius = peakRadiance^0.4 / a (px, in unscaled coordinates)
37-
float intensity = max(in_Intensity, 1e-6);
38-
float r = (psfA > 0.0) ? (pow(intensity, 0.4) / psfA) : 1.0;
36+
float r = pow(in_Intensity, 0.4) / psfA;
3937
v_psfRadius = r;
40-
float size = max(2.0 * r * pointScale, 1.0);
4138

42-
v_pointSize = size;
43-
gl_PointSize = size;
39+
gl_PointSize = 2.0 * r * pointScale;
4440
set_vp(in_Position);
4541
}

shaders/psfstarglowlarge_frag.glsl

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,31 @@ uniform float psfB;
1717
in vec2 v_uv;
1818
in vec4 v_color;
1919
in float v_peakRadiance;
20+
in float v_psfRadius;
2021

2122
void main(void)
2223
{
2324
// r = peak^0.4 / a is the PSF support radius in logical pixels; the
2425
// quad spans [-r, +r] so px = length(uv - 0.5) * 2 * r.
25-
float p04 = pow(max(v_peakRadiance, 1e-6), 0.4);
26-
float r = (psfA > 0.0) ? (p04 / psfA) : 1.0;
27-
26+
float r = v_psfRadius;
2827
vec2 d = (v_uv - vec2(0.5)) * 2.0 * r;
2928
float px = length(d);
30-
if (px <= 0.0 || px >= r)
29+
if (px >= r)
3130
discard;
3231

32+
// p04 = pow(v_peakRadiance, 0.4) = r * psfA -- recover with a multiply
33+
// instead of paying for a pow per fragment.
34+
float p04 = r * psfA;
3335
float base = p04 / px - psfA;
34-
if (base <= 0.0)
35-
discard;
36-
3736
float val = pow(base * psfB, 2.5);
38-
val = clamp(val, 0.0, v_peakRadiance);
39-
40-
// Cap per-fragment output radiance at v_color.a (hue-preserving):
41-
// see psfstarglow_frag.glsl for the rationale — scaling by alpha
42-
// is what makes the C++-side alpha fade visible.
43-
float maxCh = max(max(v_color.r, v_color.g), v_color.b);
44-
val = min(val, v_color.a / max(maxCh, 1e-6));
45-
46-
fragColor = vec4(v_color.rgb * val, 1.0);
37+
val = min(val, v_peakRadiance);
38+
39+
// Clamp each channel of v_color * val to 1 BEFORE applying the
40+
// fade alpha (v_color.a) so the bleached-white centre of a
41+
// coloured star stays white through the fade, instead of
42+
// reverting to its underlying tint once alpha drops the
43+
// per-channel value back below saturation. See
44+
// psfstarglow_frag.glsl for the full rationale.
45+
vec3 clampedColor = min(vec3(1.0), v_color.rgb * val);
46+
fragColor = vec4(clampedColor * v_color.a, 1.0);
4747
}

shaders/psfstarglowlarge_vert.glsl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,20 @@ uniform vec2 psfViewportRcp; // (1/width, 1/height)
2626
out vec2 v_uv;
2727
out vec4 v_color;
2828
out float v_peakRadiance;
29+
out float v_psfRadius;
2930

3031
void main(void)
3132
{
3233
v_uv = in_TexCoord0;
33-
v_color = vec4(in_Color.rgb * in_Color.a, in_Color.a);
34+
v_color = in_Color;
3435
v_peakRadiance = in_Intensity;
3536

37+
float r = pow(in_Intensity, 0.4) / psfA;
38+
v_psfRadius = r;
39+
3640
set_vp(vec4(in_Normal, 1.0));
3741

38-
float sizePhys = 2.0 * pow(in_Intensity, 0.4) / psfA * psfPointScale;
42+
float sizePhys = 2.0 * r * psfPointScale;
3943
vec2 extent = sizePhys * psfViewportRcp;
4044
gl_Position.xy += in_Position * extent * gl_Position.w;
4145
}

shaders/psfstarpoint_frag.glsl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ void main(void)
2323
{
2424
// Pixel offset from the centre of the point sprite (in screen pixels).
2525
vec2 d = (gl_PointCoord.xy - vec2(0.5)) * v_pointSize;
26-
float px = length(d) / max(pointScale, 1e-6);
26+
float px = length(d) / pointScale;
2727

2828
// Linear cone of radius `pointRadius`, height min(peakRadiance, 1).
29-
float r = max(pointRadius, 1e-6);
30-
float falloff = clamp(1.0 - px / r, 0.0, 1.0);
29+
float falloff = clamp(1.0 - px / pointRadius, 0.0, 1.0);
3130
float height = min(v_peakRadiance, 1.0);
3231
vec3 radiance = v_color * (falloff * height);
3332

0 commit comments

Comments
 (0)