Thermal Vision False-Color Palette Mapping in GLSL

Thermal vision systems visualize temperature differences by mapping single-channel scalar values into a dynamic spectrum of colors known as a false-color palette. This article examines how to implement thermal vision false-color palette mapping in OpenGL Shading Language (GLSL), detailing the mechanics of converting raw thermal intensity into color coordinates through both texture-based gradient lookups and procedural interpolation techniques.

Data Normalization

Thermal mapping begins by converting input thermal readings—whether sourced from a radiometric sensor texture or calculated from scene luminance—into a normalized scalar value \(t\) ranging strictly from 0.0 to 1.0.

In a GLSL fragment shader, this normalization maps the minimum detectable temperature to 0.0 (typically represented by cool hues like deep blue or black) and the maximum detectable temperature to 1.0 (typically hot hues like bright yellow or white). Clamping is necessary to ensure out-of-bounds artifacts do not occur during lookup evaluation:

float normalizedTemp = clamp((rawTemp - minTemp) / (maxTemp - minTemp), 0.0, 1.0);

Gradient Evaluation Using 1D Texture Lookups

The most efficient and flexible method for thermal false-color evaluation uses a precomputed Look-Up Table (LUT) stored as a 1D texture (sampler1D) or a narrow 2D texture (sampler2D).

The thermal palette (such as Ironbow, Rainbow, or White Hot) is encoded horizontally across the texture. When evaluating the pixel color, the normalized scalar acts directly as the \(u\)-coordinate for sampling:

uniform sampler2D u_paletteTexture;
in vec2 v_texCoord;
out vec4 fragColor;

void main() {
    float temp = texture(u_thermalSource, v_texCoord).r;
    float normalizedTemp = clamp(temp, 0.0, 1.0);
    
    // Sample the 1D gradient stored in a 2D strip
    vec4 thermalColor = texture(u_paletteTexture, vec2(normalizedTemp, 0.5));
    fragColor = thermalColor;
}

Hardware configuration plays a crucial role in gradient evaluation:

  • Filtering: Setting GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER to GL_LINEAR allows the GPU's fixed-function hardware to automatically interpolate between palette colors, providing smooth transitions across scalar steps without extra shader computations.
  • Clamping: Setting GL_TEXTURE_WRAP_S to GL_CLAMP_TO_EDGE prevents color bleeding between the extreme hot and cold ends of the gradient.

Procedural Gradient Evaluation

When external texture samplers are undesirable due to memory bandwidth limits or binding constraints, gradients can be evaluated mathematically directly within the fragment shader.

A common thermal mapping standard is the multi-stop "Ironbow" gradient, which passes sequentially through black, blue/purple, red, orange, yellow, and white. This is evaluated using piecewise linear interpolation via GLSL's mix() and smoothstep() or clamp():

vec3 evaluateThermalGradient(float t) {
    // Define color stops
    const vec3 c0 = vec3(0.0, 0.0, 0.0);       // Black
    const vec3 c1 = vec3(0.19, 0.04, 0.56);   // Dark Blue/Purple
    const vec3 c2 = vec3(0.78, 0.17, 0.28);   // Red
    const vec3 c3 = vec3(0.98, 0.65, 0.05);   // Orange/Yellow
    const vec3 c4 = vec3(1.0, 1.0, 1.0);       // White

    // Piecewise segment selection and interpolation
    if (t < 0.25) {
        return mix(c0, c1, t / 0.25);
    } else if (t < 0.5) {
        return mix(c1, c2, (t - 0.25) / 0.25);
    } else if (t < 0.75) {
        return mix(c2, c3, (t - 0.5) / 0.25);
    } else {
        return mix(c3, c4, (t - 0.75) / 0.25);
    }
}

Branches can be eliminated for improved GPU vector pipeline execution by defining continuous polynomial approximations or calculating step factors via vector arithmetic:

vec3 evaluateContinuousHeat(float t) {
    // Procedural heat approximation without branching
    vec3 color;
    color.r = clamp((t - 0.25) * 4.0, 0.0, 1.0);
    color.g = clamp((t - 0.5) * 4.0, 0.0, 1.0);
    color.b = clamp((t - 0.75) * 4.0, 0.0, 1.0) + clamp(1.0 - t * 4.0, 0.0, 1.0);
    return color;
}

Architectural Trade-offs

Texture-based gradient evaluation is preferred when supporting user-customizable palettes, as swapping gradients only requires binding a different texture without recompiling GLSL programs. It offloads color blending to dedicated texture-sampling units.

Procedural gradient evaluation is ideal for self-contained post-processing passes where texture units are limited or high register availability favors arithmetic computation over memory latency.