Files
updated-radar/build/shaders/sweep.frag
2026-04-23 08:05:03 -07:00

117 lines
4.0 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
*
* sweep.frag — phosphor accumulation update shader.
*
* For each texel in the 1024×1024 phosphor FBO:
* 1. Decay the previous frame's energy by u_decayFactor.
* 2. If the texel's PPI bearing falls within the current sweep arc
* [u_beamAnglePrev, u_beamAngle], add contributions from:
* - range rings (beam-painted per the P7 spec)
* - target echoes
* 3. Output the updated single-channel energy value.
*
* PPI convention: north = +y, east = +x; bearing = atan2(x, y)
* in degrees, clockwise from north.
*/
#version 330 core
in vec2 vTexCoord;
layout(location = 0) out vec4 fragOut; // .r = energy; .gba unused
uniform sampler2D u_prevPhosphor; // previous frame's energy texture (GL_R32F)
uniform float u_decayFactor; // exp(-decay_rate * dt)
uniform float u_beamAngle; // current beam angle, degrees CW from north
uniform float u_beamAnglePrev; // beam angle at previous frame
uniform float u_sweepBg; // ambient sweep-line energy (makes beam visible)
uniform float u_halfBeamDeg; // half-beamwidth for target blobs (display widening)
// Targets: .x = range_norm (0-1), .y = bearing_deg, .z = brightness, .w = size_norm
uniform vec4 u_targets[32];
uniform int u_targetCount;
// Range rings: up to 4 normalised radii
uniform float u_ringRadii[4];
uniform int u_ringCount;
uniform float u_ringWidth; // half-width in normalised range units
uniform float u_ringBrightness;
// ----------------------------------------------------------------
// Smallest unsigned angular distance between two bearings [0,360)
float angleDiff(float a, float b) {
float d = mod(abs(a - b), 360.0);
return (d > 180.0) ? (360.0 - d) : d;
}
// True if bearing b is inside the arc [prev, curr] swept this frame.
// Handles the 0/360 wraparound when the sweep crosses north.
bool inSweep(float b, float prev, float curr) {
if (curr >= prev) {
return (b >= prev && b <= curr);
}
// Wraparound: arc crosses 360→0
return (b >= prev || b <= curr);
}
// ----------------------------------------------------------------
void main() {
vec2 pos = vTexCoord * 2.0 - 1.0; // PPI coords: (-1,-1) SW … (+1,+1) NE
float rng = length(pos);
if (rng > 1.0) {
fragOut = vec4(0.0);
return;
}
// Bearing: clockwise from north — atan2(east, north) = atan2(x, y)
float brg = degrees(atan(pos.x, pos.y));
if (brg < 0.0) brg += 360.0;
// Decay previous value
float energy = texture(u_prevPhosphor, vTexCoord).r * u_decayFactor;
if (inSweep(brg, u_beamAnglePrev, u_beamAngle)) {
float contrib = u_sweepBg; // beam passage gives a faint ambient glow
// ---- Range rings (painted at every bearing as beam sweeps) ----
for (int i = 0; i < u_ringCount; i++) {
float d = abs(rng - u_ringRadii[i]);
if (d < u_ringWidth) {
float w = 1.0 - d / u_ringWidth;
contrib = max(contrib, u_ringBrightness * w * w);
}
}
// ---- Target echoes ----
for (int i = 0; i < u_targetCount; i++) {
float tRng = u_targets[i].x;
float tBrg = u_targets[i].y;
float tBrt = u_targets[i].z;
float tSize = u_targets[i].w;
if (tRng <= 0.0 || tBrt <= 0.0) continue;
// Angular proximity: beam must be sweeping over the target's bearing
float dBrg = angleDiff(brg, tBrg);
if (dBrg >= u_halfBeamDeg) continue;
// Range proximity: pixel must be within the target blob
float dRng = abs(rng - tRng);
if (dRng >= tSize) continue;
float bw = 1.0 - dBrg / u_halfBeamDeg; // angular taper
float rw = 1.0 - dRng / tSize; // range taper
contrib = max(contrib, tBrt * bw * rw);
}
energy = max(energy, contrib);
}
fragOut = vec4(clamp(energy, 0.0, 1.0), 0.0, 0.0, 1.0);
}