# webgl-wind: a million wind particles on a map, drawn entirely on the GPU

> webgl-wind is a JavaScript and WebGL library that renders global wind as a field of particles, capable of drawing up to a million of them at 60fps. It suits anyone building a weather map or a particle simulation demo, and the interesting part is that the simulation runs in a fragment shader rather than on the CPU.

**mapbox/webgl-wind** — Wind power visualization with WebGL particles

- Repository: https://github.com/mapbox/webgl-wind
- Website: https://mapbox.github.io/webgl-wind/demo/
- Stars: 1,112 · Forks: 230
- Language: JavaScript
- License: ISC
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mapbox-webgl-wind

## A million particles, and why the GPU is the point

webgl-wind is a WebGL-powered visualization of wind power, and the headline claim is specific: capable of rendering up to 1 million wind particles at 60fps.

That number is a statement about the architecture rather than about the hardware, because a million particles updated sixty times a second is sixty million particle updates a second, and doing that on the CPU with a JavaScript object per particle would be hopeless. The way the library avoids it is the interesting design decision.

The wind field itself lives in a texture. A particle's position is not stored in JavaScript, it is encoded in the particle texture, and the update step runs as a fragment shader that reads the current position, samples the wind texture for the velocity at that position, advances the position, and writes it back. The fragment shader also handles the wrap-around at the edges of the map, so a particle that leaves one side reappears on the other.

Because the position is a value in a texture rather than a vertex attribute, the draw call is the same for one particle and for a million. The CPU does nothing per particle. That is the difference between a wind map that stutters and one that does not, and it is why the design generalises to any particle simulation with a velocity field.

Rendering the particles is a separate pass, a fragment shader that draws a small texture at each particle's position and fades it in and out over its lifetime, with the colour determined by the wind speed. The combination gives the characteristic look of streaked lines that follow the flow.

The library is written in JavaScript and WebGL, the licence is ISC, and the last push was on 2026-08-06 with no GitHub releases and a package version of 0.0.0.

## It is a library with a demo attached, and the demo is the documentation

The repository is small and the top level says what it is: a `src/` directory, a `demo/` directory, a `data/` directory, a `dist/` directory, a `rollup.config.js`, a `package.json` and a `CODEOWNERS` file.

The demo runs locally with three commands:

```bash
npm install
npm run build
npm start
# open http://127.0.0.1:1337/demo/
```

The demonstration lives at mapbox.github.io/webgl-wind/demo/ and the repository ships the demo's own source under `demo/`, with `demo/index.html`, `demo/index.js` and a `demo/wind/` directory. The `dist/` directory is checked in, so the built library is in the repository rather than only produced at publish time.

The package manifest shows the build system, and it is notable for how old it is. The build script is `rollup -c`, the watch script is `rollup -c -w`, and the rollup version in the dev dependencies is `^0.41.4` with `rollup-plugin-buble` at `^0.15.0`. Buble is a transpiler for older browsers, and rollup 0.41 predates most of the current tooling. The eslint is at `^3.15.0` with the `eslint-config-mourner` ruleset.

The lint configuration is worth reading because it is a small statement of the author's priorities. `no-var` and `prefer-const` are both set to error, so the codebase is modern JavaScript in style even though the toolchain is old, and the parser is set to `sourceType: module`. The globals list names `dat` and `WindGL`, so the demo relies on two globals, one external and one from the library.

The dev dependencies also include `pngjs` at `^3.0.0`, which is how the data tooling reads the wind images, and `npm-run-all` with the `start` script running `run-p serve watch`, so the serve and watch tasks run in parallel.

The other thing to note is that the documentation is minimal. There is a demo link, a description, a list of inspirations and a data download section. The API documentation, such as it is, is in the source and in the demo's use of it. This is a project that expects you to read the code.

## The data pipeline starts from a GRIB file

Getting wind data is a three step procedure and it is the part of the project most likely to be unfamiliar.

First, install ecCodes, the ECMWF library, for example with `brew install eccodes`. Second, edit the constants in `data/download.sh` for the desired date, time and resolution. Third, run `./data/download.sh <dir>` to generate wind data files, `png` and `json`, for use with the library.

The ecCodes dependency is the key to the whole thing. The raw data for global weather is distributed in GRIB format, which is a binary format that is compact and efficient for gridded atmospheric data, and notoriously awkward to read directly. ecCodes is the reference toolkit for decoding it, and it is a substantial native dependency with its own data files.

The `download.sh` script is therefore doing the real work: it fetches a GRIB file for the date and resolution you set, decodes it with ecCodes, and renders the wind components into a PNG that becomes the wind texture the shader samples, plus a JSON file carrying the metadata such as the bounds and the timestamp. That is why the output is a PNG, because an image is a convenient way to get a texture onto the GPU with the right filtering, and the per-pixel encoding of speed and direction is what the shader decodes.

The `png` and `json` pairing is a nice separation. The PNG holds the field and the JSON holds the parameters needed to interpret it, so the same wind field can be used with different render settings without regenerating the data.

The manifest shows a `data/prepare.js` file being linted alongside the sources, and the `data/` directory also holds the download script. So the data tooling is JavaScript that shells out to the ecCodes tooling, with the shell script driving the download and the JavaScript preparing the result.

The resolution setting matters for performance as well as accuracy. A higher resolution means a larger wind texture and more detail, and the particle count is independent of it, so resolution affects how precisely a particle's velocity is sampled rather than how many particles there are.

## The lineage, which explains the design

The project credits its inspirations explicitly, and reading that list is a better guide to the design than the documentation is.

The first is Cameron Beccario and his Earth project, along with its open-source version, and the description calls it a wonderful project. The Earth project is what made animated global wind visualisation a familiar thing on the web, and webgl-wind is explicitly an open WebGL implementation of that idea.

The second is Fernanda Viégas and Martin Wattenberg and their US Wind Map project. That one is different in kind, because it is a data visualisation in the sense of a designed piece rather than a technical demo, and it established the visual language, the particle streaks and the colour ramp that read as wind speed.

The third is Chris Wellons and his WebGL tutorials, in particular A GPU Approach to Particle Physics. This is the one that matters most for the architecture. It is the technique of storing particle state in a texture and updating it with a fragment shader, and it is why the library can draw a million particles. The credit here is a pointer to the paper-like write-up that a reader should follow if they want to understand the technique rather than just copy the code.

The fourth is Greggman and his WebGL Fundamentals guide, which is the general grounding for the WebGL side.

The author is Vladimir Agafonkin, who is listed in the package manifest.

That history explains the scope. The library is the rendering engine, not the application. The demo at mapbox.github.io/webgl-wind/demo/ is a sample that wires the library to a map and a layer, and the actual product decision of what basemap to use, what controls to offer, and what the map interaction should be, is left to whoever embeds it.

## Where webgl-wind stops being the right tool

There are four limits, and they are the sort you hit when you try to build a product rather than a demo.

The first is that the library is not a map. It renders particles over whatever you give it, and the demo is built around a specific setup, so a real deployment has to supply a basemap, a layer, and the usual map interaction, which is work the library does not do for you.

The second is the data pipeline. The ecCodes dependency and the GRIB decoding are a native toolchain and a set of data files, and the download script expects the data to be fetched from a source you configure. A deployment that wants fresh weather every few minutes needs that running on a schedule and somewhere to host the output, and the library itself has no opinion about any of it. The demo data in the repository is fine for looking at, and fine for developing against, and not fine for shipping.

The third is the version state. The package version is 0.0.0, there are no GitHub releases, and the toolchain is old, with rollup 0.41 and eslint 3. That does not mean the code does not work, since a WebGL shader is a WebGL shader, but it does mean nobody is upgrading the dependencies for you, and a build chain this old is a liability when a transitive dependency needs a bump. If you adopt this, expect to take over the build.

The fourth is that the 60fps and one million particle figures are a claim about a capability, not a promise about your hardware. The cost is dominated by the particle count and the size of the wind texture, and the number of particles you can push depends on the GPU and on the resolution of the particle texture. There is also no mention in the documentation of how the library behaves on a device with a software renderer, which is a real concern if you are shipping to a browser on a low-end machine.

The alternative approaches are worth naming. A server-rendered tile approach, where the wind field is baked into animated images on a server, is what most production weather maps do, because it costs the client nothing and works everywhere. A 2D canvas particle system is simpler but limited to a few thousand particles. And a fully custom WebGL implementation, which is what this library already is, so the real comparison is with the tile approach.

The difference in approach is where the work happens. webgl-wind puts the simulation in the browser and makes the data pipeline your problem, while the tile approach puts the rendering on the server and makes the network your problem. For an interactive map with a lot of users, the tile approach is usually the better trade, and for an educational demo, a research visualisation, or a data product where the wind field is static, the client side simulation is better because it is interactive by construction.

## Conclusion

webgl-wind fits a team building a weather or climate map that wants the animated wind layer people recognise from the big streaming weather services, because the whole particle field is simulated in a fragment shader and the library exposes the pieces rather than shipping one fixed map. Skip it if you need your own basemap and controls, because the demo expects a specific setup, and note that the package version is 0.0.0 with no GitHub releases, so pin a commit rather than a version range. Before you build on it, read the source rather than the README, because the documentation is a demo plus a data download script, and the `src/` directory and the `demo/wind/` directory are where the actual API lives.

## FAQ

### What is webgl-wind?

It is a WebGL-powered visualization of wind power that renders up to 1 million wind particles at 60fps, with the particle positions stored in a texture and the simulation run in a fragment shader.

### How do I run the webgl-wind demo locally?

Run `npm install`, then `npm run build`, then `npm start`, and open http://127.0.0.1:1337/demo/. The start script runs the static server and the rollup watch task in parallel.

### How do I get wind data for webgl-wind?

Install ecCodes, for example with `brew install eccodes`, edit the constants in `data/download.sh` for the date, time and resolution you want, then run `./data/download.sh <dir>` to generate the png and json files the library uses.

### Who did webgl-wind take inspiration from?

Cameron Beccario and the Earth project, Fernanda Viégas and Martin Wattenberg with the US Wind Map, Chris Wellons and his GPU particle physics work, and Greggman with the WebGL Fundamentals guide.

### What licence is webgl-wind released under?

ISC. The package manifest declares the ISC licence and names Vladimir Agafonkin as the author, and the version field is 0.0.0 with no GitHub releases.

## Sources

- [Issues](https://github.com/mapbox/webgl-wind/issues)
- [License: ISC](https://github.com/mapbox/webgl-wind/blob/master/LICENSE)
- [mapbox/webgl-wind on GitHub](https://github.com/mapbox/webgl-wind)
- [Project website](https://mapbox.github.io/webgl-wind/demo/)
- [README](https://github.com/mapbox/webgl-wind/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mapbox-webgl-wind
