Abdullah Ali — Portfolio

Building software that solves real-world problems.

000
Skip to content
Signal Field WebGL visualisation showing a displaced terrain of seismic data

Signal Field

Ten years of seismic data, rendered as one continuous surface.Experiment2024live
01

A weekend question that became a six-week piece: what does a decade of earthquake data look like if you stop plotting it and start sculpting it? Two million points rendered as a single displaced mesh you can fly through at 60fps.

Role
Creative Developer
Duration
6 weeks
Team
Solo
Year
2024
Category
Experiment
Status
live

Stack

WebGLThree.jsGLSLTypeScriptViteWeb Workers
02

What it moved.

2.1M

Points rendered

Single draw call

60fps

Desktop

Integrated graphics

1

Draw call

For the entire field

6

Weeks

Concept to publish

03

The problem.

The source dataset is 2.1 million records. Every conventional charting approach either aggregates the interesting detail away or collapses under the point count.

The story is in the relationship between depth, magnitude and time, which is three dimensions plus geography — more than a flat chart can hold honestly.

It had to run on a laptop in a browser tab, not a workstation, or nobody would ever look at it.

04

What I built.

Data is baked into a floating-point texture at build time. The vertex shader reads it and displaces a plane, so the GPU does all the work and the CPU does almost none.

Magnitude drives displacement, depth drives colour through a perceptually uniform ramp, and time drives a travelling wave you can scrub.

One draw call for the entire field. Camera movement is decoupled from data updates, so scrubbing a decade never drops a frame.

05

How it fits together.

A deliberately small piece of software. One scene, one mesh, one material, and a build step that turns CSV into a texture.

01

Build

Node script parsing the source CSV, normalising it and packing values into an RGBA float texture.

Node.jsTypeScriptsharp
02

Render

Three.js scene with a custom ShaderMaterial. Displacement, colour and the time wave all live in GLSL.

Three.jsGLSLWebGL2
03

Interaction

Damped orbit controls plus a scrub timeline. GPU picking resolves the point under the cursor.

TypeScriptLenisGSAP
06

What it does.

01

Two million points, one draw call

The whole dataset is a single mesh, displaced entirely on the GPU.

02

Scrubbable decade

Drag through ten years and watch the surface breathe as events accumulate.

03

GPU picking

Hover any peak for the exact record behind it, resolved by rendering ids.

04

Perceptual colour ramp

Depth uses a uniform ramp so equal colour steps mean equal depth steps — the data is not lied about.

05

Graceful fallback

No WebGL2 gets a static rendered still and the underlying data table, not a blank canvas.

08

A look at the code.

Displacement vertex shader

glslshaders/field.vert
precision highp float;

uniform sampler2D uData;   // rg = magnitude, depth | ba = time, id
uniform float uTime;       // 0..1 scrub position
uniform float uAmplitude;

varying float vDepth;
varying float vIntensity;

void main() {
  vec4 record = texture2D(uData, uv);

  float magnitude = record.r;
  float depth     = record.g;
  float when      = record.b;

  // Events fade in as the scrub head passes them, then settle.
  float age     = clamp((uTime - when) * 14.0, 0.0, 1.0);
  float settle  = 1.0 - 0.25 * smoothstep(0.0, 1.0, age);
  float lift    = magnitude * uAmplitude * age * settle;

  vDepth     = depth;
  vIntensity = magnitude * age;

  vec3 displaced = position + normal * lift;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(displaced, 1.0);
}
Every point of the surface is positioned here. The CPU only ever updates uTime.
09

Where it got hard.

01

Texture precision

What broke

Packing magnitude into eight bits produced visible banding — the surface looked terraced where it should have been smooth.

How it was fixed

Moved to a half-float texture and normalised per channel against the real data range. Banding gone, memory cost acceptable.

02

Mobile GPUs

What broke

The full mesh melted mid-range phones, dropping to about eight frames per second and heating the device.

How it was fixed

Detect the renderer and drop to a quarter-resolution mesh with a simplified fragment shader. Visually close, and comfortably 60fps.

03

Scrubbing recomputed everything

What broke

The first version rebuilt geometry on every timeline change, which made the scrubber unusable.

How it was fixed

Time became a uniform. Scrubbing now changes one float, and the shader does the rest.

10

What I owned.

01

Everything — data pipeline, shaders, interaction and the writing that accompanies it.

11

How it ran.

Data

Week 1

Sourcing, cleaning and working out what the dataset could honestly support.

Shader

Weeks 2–4

Displacement, colour ramp and the time wave.

Interaction

Week 5

Camera damping, GPU picking and the scrub timeline.

Polish

Week 6

Mobile tier, fallbacks and the written piece.

12

What I took from it.

Moving work to the GPU is often an architecture decision, not an optimisation. Time-as-uniform was the whole project.

Perceptual colour matters. The first ramp looked better and misrepresented the data, which makes it worse.

A fallback that shows the underlying table respects the reader more than a 'browser not supported' card.

Constraints made this finishable. One scene, one mesh, six weeks, done.

13

What I'd do next.

WebGPU compute pass for on-the-fly filtering by magnitude band.

A guided narrative mode that flies the camera through notable events.

Next case study

RespiraScan AI

AI-powered respiratory bacteria classification using Deep Learning.