Energy Shield
by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026
Run the shader to adjust these controls.
What it demonstrates
An energy shield is three signals added on top of a sprite: a bright rim where the surface is seen edge-on, a faint pattern across it, and a ripple expanding from the hit. Keeping them separate is the trick, because each answers to a different game event.
Shader inputs
void mainImage(out vec4 fragColor, in vec2 fragCoord)
Called once per pixel. Write the colour to fragColor.
-
iResolutionvec3 - Viewport size in pixels (z is the pixel aspect ratio).
-
iTimefloat - Seconds since the shader started.
-
iTimeDeltafloat - Seconds since the previous frame.
-
iFrameRatefloat - Frames per second, smoothed.
-
iFrameint - Frames rendered since the start.
-
iMousevec4 - Mouse position: xy while held, zw of the last click.
-
iDatevec4 - Year, month, day, and seconds within the day.
-
iChannel0sampler2D - Texture bound to channel 0.
-
iChannel1sampler2D - Texture bound to channel 1.
-
iChannel2sampler2D - Texture bound to channel 2.
-
iChannel3sampler2D - Texture bound to channel 3.
-
iChannelResolutionvec3[4] - Pixel size of each bound channel texture.
-
iChannelTimefloat[4] - Playback time of each channel, in seconds.
-
iSampleRatefloat - Audio sample rate, always 44100.
Main Image4
uniform float uIntensity; // @param 0.0..1.5 = 0.75 "Shield intensity"
uniform vec2 uImpact; // @param -0.5..0.5 = 0.22, 0.12 "Impact point"
uniform vec3 uShieldColor; // @param 0.0..1.0 = 0.35, 0.75, 1.0 color "Shield"
uniform float uRadius; // @param 0.25..0.48 = 0.38 "Shield radius"
float hexPattern(vec2 p, float scale) {
// Hex grid as two offset rectangular lattices; cheaper than a true hex
// tiling and indistinguishable once it is a faint overlay.
p *= scale;
vec2 a = fract(p) - 0.5;
vec2 b = fract(p + vec2(0.5, 0.5)) - 0.5;
float d = min(max(abs(a.x) * 0.866 + abs(a.y) * 0.5, abs(a.y)),
max(abs(b.x) * 0.866 + abs(b.y) * 0.5, abs(b.y)));
return smoothstep(0.34, 0.5, d);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
vec4 sprite = texture(iChannel0, p + 0.5);
vec3 backdrop = mix(vec3(0.03, 0.04, 0.07), vec3(0.07, 0.09, 0.14), p.y + 0.5);
vec3 color = mix(backdrop, sprite.rgb, sprite.a);
// Distance to the shield surface. In two dimensions the stand-in for a
// Fresnel term is proximity to that surface: a sphere seen edge-on presents
// the most glass per pixel exactly where its silhouette is.
float dist = length(p) - uRadius;
float rim = exp(-abs(dist) * 34.0);
float inside = 1.0 - smoothstep(-0.004, 0.004, dist);
// Impact ripple: rings expanding from the hit, decaying with distance and
// repeating on a fixed cycle so the card always shows one.
float hitDist = length(p - uImpact);
float cycle = fract(iTime * 0.55);
float ring = exp(-abs(hitDist - cycle * 0.55) * 26.0) * (1.0 - cycle);
float hex = hexPattern(p, 9.0) * inside * 0.5;
float shield = (rim * 0.9 + hex * 0.35 + ring * 1.4) * uIntensity;
color += uShieldColor * shield;
color += uShieldColor * inside * 0.06 * uIntensity;
fragColor = vec4(color, 1.0);
}
Inputs for this pass
-
iChannel0
textures/sprite-demo-v1.png
- iChannel1 Empty
- iChannel2 Empty
- iChannel3 Empty
Learn from this shader
How it works
The surface is a circle expressed as a distance, and the rim is an exponential falloff either side of zero. That stands in for a Fresnel term: a sphere seen from the side presents the most glass per pixel at its silhouette, so proximity to the boundary is the right proxy in two dimensions. A separate interior term confines the hex pattern to the surface instead of letting it spill into the background. The ripple is a ring whose radius grows on a repeating cycle while its brightness decays, so it reads as a strike fading rather than a pulse looping. Everything is added to the composited sprite rather than mixed with it, which keeps the ship visible through its own shield.
Try changing
Take Shield intensity to zero to see the sprite alone, then raise it past one until the rim blows out. Move Impact point around and note the ripple starts there rather than at the centre. Shrink Shield radius until the shield cuts into the hull, which is the framing bug you get when a shield is authored independently of the model it protects.
Using it in a game
Drive intensity from remaining shield strength and the impact point from the last hit. On a real mesh the rim comes from a proper Fresnel term using the view direction and surface normal, with the shield drawn as additive transparent geometry after the opaque pass.
Explore the techniques
Continue with curated explanations and progressively related examples.
Discussion