By Gareth Battensby •

Making of NXGN, an interactive WebGL site

We recently finished up a WebGL project for one of our clients DAZN, an interactive list of the world’s top 50 up and coming football players. You can check it out here.

The brief called for an ’80s inspired neon-lit retro landscape that you scroll through whilst viewing players. This was the perfect job for WebGL and its ability to push lots of graphical effects to the screen quickly, even on mobile devices.

At the heart of WebGL are shaders. These are small programs that run super fast on the graphics card and allow you to manipulate pixels however you wish. Below is a breakdown of the various effects and how we implemented their shaders.

Neon Flame

The first effect we tackled was the neon flame shape that defines each section of the player list. After a bit of experimentation, we decided on using fractal noise. Fractal noise is great as it captures detail at various different scales and looks very natural.

You can read more about the math behind it on Íñigo Quílez’s site.

vec2 p = gl_FragCoord.xy * scale / res.xx;
float q = frac_noise(p - time);
float nx = frac_noise(p + q + time * speed.x - p.x - p.y);
float ny = frac_noise(p + q - time * speed.y);
float n = nx * ny;

We calculate noise for x and y separately so we can scroll the vertical noise at a different rate and make it look like its rising.

The second part of the shader involves revealing the shape by alpha masking it over time. For this we layout out the uvs of the shape onto a straight line with the x coordinate going from zero to one.

Using the GLSL smoothstep function we can create a soft boundary at the transition. By animating the time value from zero to one we can control how much of the shape is visible.

...
float progress = 
  smoothstep(_uv.x-0.01, _uv.x + 0.01, time);
alpha *= progress;
...

Starfield

To help create an extra sense of depth we decided to create the stars as individual particles that would move at different rates. These are drawn as small quads using WebGL’s instanced rendering method. Instanced rendering allows you to draw thousands of items in a single draw-call as long as they share the same geometry. The vertex shader then animates the stars and warps them back to their start position once they fly past the camera. Doing the animation in the shader this way is very fast as it means you don’t need to re-upload thousands of animation values to the GPU from JavaScript every frame.

void main()
{
	...
	vec3 p = instance_position + (position * size);
	p.z += time;
	p.z = mod(p.z, 12.0); //wrap around at 12 'units'
	...
}

To draw the light of the star itself we used a simple circle distance field. Distance fields are a cool procedural way of creating textures in a shader, check out the book of shaders for many great examples.

float circle(vec2 st, float r, float soft) {
    vec2 dist = st - vec2(0.5);
    return 1.0 - smoothstep(r-(soft), r+(soft), dot(dist,dist) * 4.0);
}
void main()
{
    float alpha = circle(_uv, _size, 0.1);
    gl_FragColor = vec4(1.0,1.0,1.0, alpha);
}

Glitch Effects

No ’80s video game inspired site would be complete without glitch effects! There are many places online (such as shadertoy) where you can find glitch shader examples. The general idea is to render the scene to a texture then use the glitch function to adjust that image in various ways before pushing it to the screen.

One type of glitch is colour channel separation. This involves extracting the separate RGB values from the input image and shifting them across horizontally.

...
float sx = (channel_shift + rand(st) * dispersion) * intensity;
result = extract_r(texture2D(image, shift_x(st,  sx))) +
	    extract_b(texture2D(image, shift_x(st, -sx))) +
	    extract_g(texture2D(image, st));
...

Reflective Logo

The first thing you see on the page is the chrome NXGN logo. To create the reflections we use the scene texture from before and distort it spherically.

Reflections become more pronounced the more edge-on a surface is to the viewer (often referred to as Fresnel). By blending the reflection colour with pure white using fresnel as the blend value the reflection looks a little more natural.

...
vec2 scene_uv = env_map_equirect(reflect(-_eye, _normal));
vec3 scene_sample = texture2D(scene, scene_uv).rgb;
float f = fresnel(_eye, _normal);
vec3 rgb = mix(env_sample, vec3(1.0), f);
...

We had a lot of fun on this project and are really pleased with the result. Shaders are pretty math heavy but open up the web to lots of interesting possibilities. WebGL has great browser support and WebGPU is on the horizon so now is a great time to use these technologies to bring projects to life.