Night Vision Green Phosphor Formula in GLSL
Simulating a realistic night vision device requires converting standard RGB scene data into the characteristic monochrome luminescence of an electro-optical phosphor screen. This article explains the mathematical formula used to calculate light amplification, extract perceptual luminance, and remap the resulting values to match physical green phosphors (such as P20 or P43) using an efficient GLSL fragment shader implementation.
The Mathematical Formula
The color remapping process consists of three mathematical steps: calculating perceptual luminance, applying light amplification and contrast, and remapping the result to a phosphor target vector.
Perceptual Luminance Extraction: Human vision perceives green as significantly brighter than red or blue. The standard ITU-R BT.709 luminance formula calculates perceived brightness (\(Y\)): \[Y = 0.2126R + 0.7152G + 0.0722B\]
Amplification and Contrast Curve: Night vision devices amplify ambient photons, often introducing non-linear response curves and highlight saturation: \[Y' = \text{clamp}(Y \times \text{gain}, 0.0, 1.0)\] To add contrast and simulate dynamic range compression, an exponential curve is applied: \[Y'' = (Y')^{\gamma}\]
Phosphor Remapping: The calculated intensity is multiplied by the spectral emission color of the phosphor screen: \[\vec{C}_{\text{final}} = Y'' \times \vec{P}_{\text{color}}\]
Typical normalized RGB values for common night vision phosphors:
- P20 (Yellow-Green):
vec3(0.2, 0.95, 0.15) - P43 (Slightly cooler peak green):
vec3(0.1, 1.0, 0.25)
- P20 (Yellow-Green):
GLSL Implementation
The following GLSL snippet implements this formula inside a post-processing fragment shader:
// Uniforms
uniform sampler2D u_SceneTexture;
uniform float u_Gain; // Amplification factor, typically 2.0 to 5.0
uniform float u_Gamma; // Gamma adjustment, typically 0.8 to 1.2
in vec2 v_TexCoord;
out vec4 FragColor;
// Coefficients for ITU-R BT.709 perceptual luminance
const vec3 LUMA_COEFFS = vec3(0.2126, 0.7152, 0.0722);
// Target P20/P43 phosphor color signature
const vec3 PHOSPHOR_COLOR = vec3(0.15, 1.0, 0.25);
void main()
{
vec3 sceneColor = texture(u_SceneTexture, v_TexCoord).rgb;
// 1. Calculate luminance
float luma = dot(sceneColor, LUMA_COEFFS);
// 2. Amplify and clamp
float amplified = clamp(luma * u_Gain, 0.0, 1.0);
// 3. Apply contrast curve
float finalIntensity = pow(amplified, u_Gamma);
// 4. Remap to phosphor color
vec3 finalColor = finalIntensity * PHOSPHOR_COLOR;
// Optional highlight blowout (whitens extremely bright areas)
finalColor += vec3(pow(amplified, 4.0) * 0.5);
FragColor = vec4(finalColor, 1.0);
}Key Parameters
u_Gain: Controls the light amplification. Low-light environments require higher gain values, though high values can lead to overexposure in bright areas.u_Gamma: Modifies the mid-tone distribution. A value below 1.0 lifts the shadow details, mimicking the automatic gain control (AGC) found in modern image intensifier tubes.- Highlight Desaturation: Real phosphors shift toward
white when completely saturated. The term
pow(amplified, 4.0) * 0.5introduces this effect by bleeding green saturation into pure white highlights.