Files
updated-radar/shaders/phosphor.frag
2026-04-24 00:25:29 -07:00

102 lines
3.7 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* MIT License
* Author: Mark Allyn
*
* phosphor.frag — maps the two-channel phosphor energy texture to the
* P7 colour sequence (blue → green → yellow-green → dark) and applies
* a simple inline bloom (box-filter glow) to bright pixels.
*
* The phosphor FBO is GL_RG32F:
* R channel — signal energy (target echoes, sweep background)
* multiplied by u_gain before display
* G channel — range ring energy, gain-independent; mixed with signal
* after gain is applied so rings never dim with gain
*
* Coordinate system: gl_FragCoord.xy in GL viewport pixels (origin
* bottom-left). Scope centre is passed as u_scopeCenter in the same
* coordinate system.
*/
#version 330 core
out vec4 fragColor;
uniform sampler2D u_phosphor; // GL_RG32F phosphor energy FBO
uniform vec2 u_scopeCenter; // scope centre in GL viewport pixels (bottom-left origin)
uniform float u_scopeRadius; // scope radius in pixels
uniform float u_gain; // receiver gain [0,1] — scales signal (R) channel only
uniform float u_bloomStep; // UV step for bloom sample (≈ 2.5 / FBO_SIZE)
uniform float u_bloomStrength; // additive blend weight for bloom
// P7 energy thresholds — MUST match settings.h P7_THRESH_* constants.
// T_YGREE is intentionally low (0.05) to keep most of the decay in the
// GREEN zone; see the comment in settings.h for the full rationale.
const float T_BLUE = 0.82;
const float T_GREEN = 0.55;
const float T_YGREE = 0.05;
const float T_DARK = 0.03;
// P7 colour anchors
const vec3 C_BLUE = vec3(0.30, 0.70, 1.00);
const vec3 C_GREEN = vec3(0.05, 1.00, 0.30);
const vec3 C_YGREE = vec3(0.50, 1.00, 0.05);
const vec3 C_YELLW = vec3(0.70, 0.70, 0.00);
const vec3 C_BLACK = vec3(0.00, 0.00, 0.00);
// P7 colour ramp: hue selected by energy level, then scaled by energy so
// brightness decreases monotonically from fresh strike (peak) to dark.
// This prevents intermediate decay colours (yellow-green) from appearing
// brighter than the initial blue flash.
vec3 p7Color(float e) {
if (e < T_DARK) return C_BLACK;
vec3 hue;
if (e >= T_BLUE)
hue = C_BLUE;
else if (e >= T_GREEN)
hue = mix(C_GREEN, C_BLUE, (e - T_GREEN) / (T_BLUE - T_GREEN));
else if (e >= T_YGREE)
hue = mix(C_YGREE, C_GREEN, (e - T_YGREE) / (T_GREEN - T_YGREE));
else
hue = mix(C_YELLW, C_YGREE, (e - T_DARK) / (T_YGREE - T_DARK));
return hue * e;
}
void main() {
// Fragment position relative to scope centre
vec2 delta = (gl_FragCoord.xy - u_scopeCenter) / u_scopeRadius;
float dist = length(delta);
if (dist > 1.0) {
fragColor = vec4(0.0); // outside scope circle — transparent black
return;
}
// Map from PPI delta [-1,+1] to phosphor texture UV [0,1]
vec2 uv = delta * 0.5 + 0.5;
vec2 rg = texture(u_phosphor, uv).rg;
// Signal (R): gain-scaled received echoes.
// Ring (G): gain-independent timing reference; always at full brightness.
float energy = max(rg.r * u_gain, rg.g);
// Inline bloom: weighted box-filter over a 5×5 neighbourhood
float bloom = 0.0;
float wsum = 0.0;
for (int dx = -2; dx <= 2; dx++) {
for (int dy = -2; dy <= 2; dy++) {
float w = exp(-float(dx*dx + dy*dy) * 0.45);
vec2 srg = texture(u_phosphor, uv + vec2(dx, dy) * u_bloomStep).rg;
float e = max(srg.r * u_gain, srg.g);
bloom += e * w;
wsum += w;
}
}
bloom = (bloom / wsum) * u_bloomStrength;
float finalE = clamp(energy + bloom, 0.0, 1.0);
vec3 col = p7Color(finalE);
// Soft-edge vignette at the scope boundary
float edge = smoothstep(1.0, 0.97, dist);
fragColor = vec4(col * edge, 1.0);
}