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.
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.
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.
depth_draw_opaque
does not draw the shield on the depth buffer since it’s going to be transparent. That way, it doesn’t obscure objects behind it.cull_disabled
prevents the GPU from removing faces that face away from the camera. The shield is transparent, and the user should see it interacting with the world through it.ambient_light_disabled
and shadows_disabled
keep light pollution and shadows from hitting the shield. We use EMISSION
so we can’t use unshaded
, but it’s a light source of pure energy, and we want to control its color ourselves.blend_add
makes the transparent shield add its color to anything it stands over, making the result brighter instead of replacing it.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.
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;
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;
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.