Skip to main content
GameDev.net gamedev.net

Energy Shield

by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026

Use in your engine

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.

Source Revision 1

Author notes are linked to specific lines.

Shader inputs

void mainImage(out vec4 fragColor, in vec2 fragCoord)

Called once per pixel. Write the colour to fragColor.

iResolution vec3
Viewport size in pixels (z is the pixel aspect ratio).
iTime float
Seconds since the shader started.
iTimeDelta float
Seconds since the previous frame.
iFrameRate float
Frames per second, smoothed.
iFrame int
Frames rendered since the start.
iMouse vec4
Mouse position: xy while held, zw of the last click.
iDate vec4
Year, month, day, and seconds within the day.
iChannel0 sampler2D
Texture bound to channel 0.
iChannel1 sampler2D
Texture bound to channel 1.
iChannel2 sampler2D
Texture bound to channel 2.
iChannel3 sampler2D
Texture bound to channel 3.
iChannelResolution vec3[4]
Pixel size of each bound channel texture.
iChannelTime float[4]
Playback time of each channel, in seconds.
iSampleRate float
Audio sample rate, always 44100.
Main Image4
1 uniform float uIntensity; // @param 0.0..1.5 = 0.75 "Shield intensity"
2 uniform vec2 uImpact; // @param -0.5..0.5 = 0.22, 0.12 "Impact point"
3 uniform vec3 uShieldColor; // @param 0.0..1.0 = 0.35, 0.75, 1.0 color "Shield"
4 uniform float uRadius; // @param 0.25..0.48 = 0.38 "Shield radius"
5
6 float hexPattern(vec2 p, float scale) {
7 // Hex grid as two offset rectangular lattices; cheaper than a true hex
8 // tiling and indistinguishable once it is a faint overlay.
9 p *= scale;
10 vec2 a = fract(p) - 0.5;
11 vec2 b = fract(p + vec2(0.5, 0.5)) - 0.5;
12 float d = min(max(abs(a.x) * 0.866 + abs(a.y) * 0.5, abs(a.y)),
13 max(abs(b.x) * 0.866 + abs(b.y) * 0.5, abs(b.y)));
14 return smoothstep(0.34, 0.5, d);
15 }
16
17 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
18 vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
19 vec4 sprite = texture(iChannel0, p + 0.5);
20
21 vec3 backdrop = mix(vec3(0.03, 0.04, 0.07), vec3(0.07, 0.09, 0.14), p.y + 0.5);
22 vec3 color = mix(backdrop, sprite.rgb, sprite.a);
23
24 // Distance to the shield surface. In two dimensions the stand-in for a
25 // Fresnel term is proximity to that surface: a sphere seen edge-on presents
26 // the most glass per pixel exactly where its silhouette is.
27 float dist = length(p) - uRadius;
28 float rim = exp(-abs(dist) * 34.0);
29 float inside = 1.0 - smoothstep(-0.004, 0.004, dist);
30
31 // Impact ripple: rings expanding from the hit, decaying with distance and
32 // repeating on a fixed cycle so the card always shows one.
33 float hitDist = length(p - uImpact);
34 float cycle = fract(iTime * 0.55);
35 float ring = exp(-abs(hitDist - cycle * 0.55) * 26.0) * (1.0 - cycle);
36
37 float hex = hexPattern(p, 9.0) * inside * 0.5;
38 float shield = (rim * 0.9 + hex * 0.35 + ring * 1.4) * uIntensity;
39
40 color += uShieldColor * shield;
41 color += uShieldColor * inside * 0.06 * uIntensity;
42 fragColor = vec4(color, 1.0);
43 }
44

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.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...