Force field shader

The hexagonal force field is a staple of video game science fiction. It’s made of pure energy and glows with its emission color. How it’s projected is up to your game’s lore, but to create a convincing effect, everything you need is here.

Scene setup

All you need is a sphere in a MeshInstance and a ShaderMaterial, but you can add floors, walls, or other geometry that intersect with the force field. To make the colors glow, add a WorldEnvironment node and enable the Glow property with the Additive blending mode. Enable Bicubic Upscaling to prevent sharp glow artifacts.

We’ll make the energy shield in three steps: the inner glow, the intersection with the world, and details like pulsing and a scanline.

Inner glow

We achieve the inner glow using a fresnel effect. It’s an effect achieved by pretending the camera is a directional light source. On round objects, the middle is bright while the edges are dark, but by reversing it, we achieve the opposite with the edges light and the middle dark. If we plug that value into the ALPHA of the shader, then the dark middle vanishes, and the light edges light up.

The world environment and EMISSION property with an HDR color greater than 1 take care of the glowing.

First, let’s look at the render modes.

shader_type spatial;
render_mode depth_draw_opaque, cull_disabled, ambient_light_disabled, shadows_disabled, blend_add;

For the glow effect, we add uniforms: the shield’s color, how strong the fresnel effect is (how thin the border), and how much of the color to put back into the transparent parts after the fresnel.

uniform vec4 color : hint_color;
uniform float fresnel_power = 1.0;
uniform float edge_intensity = 2.0;
uniform float fill_amount : hint_range(0, 1) = 0.1;

We make sure that the ALBEDO is black, so light information does not interfere and pollute the shield.

void fragment() {
    ALBEDO = vec3(0);
}

We calculate the fresnel effect by taking the dot product of the fragment NORMAL (in view space) and the VIEW vector to the camera. The result is -1 when pointing in opposite directions, 0 when perpendicular, and 1 when parallel. 1 minus this value reverses the effect and gives an effect where the edges are light, and the middle is dark.

We use pow to make the edges thinner and multiplication after makes them brighter.

We plug the fresnel into the ALPHA, and we use smoothstep to clamp it to the [0..1] range and soften it. We give the EMISSION the color.

float fresnel = pow(1.0 - dot(NORMAL, VIEW), fresnel_power) * edge_intensity;

EMISSION = color.rgb;
ALPHA = smoothstep(0, 1, fresnel);

Why aren’t back faces 2 given their normal would face opposite the view and end up being opaque (1–1, clamped to 1)? When disabling culling, Godot uses two-sided lighting. The normals of the inside of the mesh reverse to face the camera, making it look like a front face.

Add back opaqueness

The edges are lovely and bright, but the middle is looking empty. We can add back some color by using the fill_amount uniform.

fresnel = fresnel + fill_amount;

Blend texture pattern

To give it that well known hexagonal sci-fi look, we can introduce a texture pattern. The texture is a seamless square of black outlined hexagons on a white background. We use the UV offset parameter to manually make the hexagonal mesh denser across the horizontal and vertical positions.

uniform vec2 pattern_uv_offset = vec2(6.0, 3.0);

uniform sampler2D pattern_texture : hint_albedo;

We sample from that pattern and multiply the fresnel with it. The fresnel ends up visible inside the shapes and disappears in the outlines.

vec4 pattern = texture(pattern_texture, (UV * pattern_uv_offset));
fresnel *= pattern.r;

Making the pattern move

The pattern looks a little too static right now, but we can make it move by adding an element of ongoing time to the UVs of the sample the pattern. We can use the TIME built-in.

To control the speed of the scrolling, we introduce the uniform we’ll multiply by time.

uniform float pattern_scroll_speed = 0.025;

The final scrolling speed is TIME multiplied by the scroll speed. We then create a UV offset that we add onto the ones sampling the texture.

float scrolling_time = TIME * pattern_scroll_speed;
vec4 pattern = texture(pattern_texture, (UV * pattern_uv_offset) + vec2(scrolling_time));

The pattern is now animated, scrolling leisurely in a diagonal way. You could add a new Vector2 uniform to control the direction and multiply it by scrolling_time instead.