nanite-webgpu: Nanite's meshlet pipeline rebuilt in a browser tab
UE5's Nanite implementation using WebGPU. Includes the meshlet LOD hierarchy, software rasterizer and billboard impostors. Culling on both per-instance and per-meshlet basis.
At a glance
- What is it?
- A TypeScript and WebGPU reimplementation of the geometry side of Unreal Engine 5's Nanite, with the meshlet LOD hierarchy, a software rasterizer, billboard impostors and culling at the instance, meshlet and triangle level. The interesting constraint is that WebGPU has no 64-bit atomics, so depth and normals get packed into 32 bits.
- Who is it for?
- nanite-webgpu fits a graphics engineer who wants to understand how a Nanite style pipeline works by reading it, who needs the meshlet decomposition, the impostor scheme and the culling decisions in one readable repository rather than in an engine's source, and who is willing to work inside what WebGPU exposes.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 166 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The geometry half of Nanite, in WebGPU
nanite-webgpu is a Nanite implementation in a web browser using WebGPU, and the README is unusually honest about what that means in practice.
The project contains the meshlet LOD hierarchy, a software rasterizer, at least as far as possible given the current state of WGSL, and billboard impostors. Culling happens on both a per-instance and a per-meshlet basis, with frustum and occlusion culling in both cases, and textures and per-vertex normals are supported. The README also says there is a slider or a checkbox for every setting imaginable, and the last line of the feature list is a warning as much as a boast: there is a lot of WebGPU and WGSL code that you can copy into your own project, and if you want to do something, the author has either attempted it or discovered that it does not work.
That last point is the reason to read the repository rather than just look at the demo. The two demo scenes are the Jinx scene at 640 million triangles and a scene with many objects at 1.7 billion triangles, and in the Jinx scene the white triangles are the software-rasterized ones. The model is a third party asset by a Sketchfab user, and the README is candid that the best simplification available takes the model from 44 thousand triangles to 3 thousand.
There is one hard environmental constraint that colours everything else, and the README puts it in bold: WebGPU is only available on Chrome.
The project is MIT licensed, the primary language is TypeScript, the default branch is `master`, the last push was on 2026-05-09, and the package version in the repository is 1.0.0. The author is Marcin Matuszczyk, and there is a separate repository for the related Frostbitten hair WebGPU work.
The meshlet hierarchy is built in the browser
The first implemented component is the meshlet LOD hierarchy, and the way it is produced is the interesting part.
Mesh preprocessing executes in the browser, using WebAssembly for meshoptimizer and METIS, and there is a file exporter too, for anyone who does not want to wait between page refreshes. So the decomposition of a mesh into clusters is not a build step and not an asset that has to be shipped pre-baked, it happens when the page loads, using two well known libraries compiled to WebAssembly.
meshoptimizer is the cluster-building side of it, and METIS is the graph partitioning, which is what decides how clusters are grouped into a hierarchy where a parent cluster can be substituted for its children. Running both in the browser is what makes the demo interactive, because a change to the cluster size or the error threshold re-runs the whole decomposition and the result is visible immediately.
The error threshold is exposed as a runtime parameter, and the demo URLs in the README carry it, along with the impostor threshold and the software rasterizer threshold. So the entire quality and performance trade-off of the technique is a set of sliders, and the difference between a scene that looks like a low poly model and one that looks like the original is a number in the query string.
There is also a custom file format, so the mesh does not have to be preprocessed every time, and that is optional, because an OBJ file works as well. The repository has the custom format for the cases where the preprocessing is expensive, and the OBJ path for the cases where it is not.
The 32-bit problem shapes the whole rasterizer
The software rasterizer exists because of a single missing WebGPU feature, and the README explains the consequence in more detail than most engine documentation does.
WebGPU does not have the `atomic<u64>` needed to implement this feature efficiently, so the depth is packed as `u16` and the octahedron-encoded normals as two `u8` values, together making 32 bits. The author is blunt about what that costs: with only 32 bits the precision is being butchered, the only concern is to show that the rasterization works, and if the software rasterized bunny model in the background is white and has reasonable shading, that is the expected result. Reprojecting depth and compressing the normals is enough to get something that is, in the words used, not offending.
The limitation does not stay in the rasterizer. It also affects the depth pyramid used for occlusion culling, which means the occlusion decisions are made on a compressed history as well.
The alternatives are named, which makes the design decision readable. There are other algorithms to do this: PPLL, or something with tiles, or double rasterization, where the first pass writes depth and the second does a `compareExchange()`. The author chose to stick to UE5's solution instead, on the grounds that the 32-bit limitation is only in WebGPU, so the approach is not wrong in general, only constrained here.
That is the right way round for a reimplementation. The point of the project is to reproduce the engine's structure, so borrowing the engine's algorithm and making the browser's limitation visible is more useful than inventing a different algorithm that cannot be compared against the original.
Impostors, twelve images and a dithered blend
The billboard impostors are the third implemented component, and they are the cheapest way to represent geometry that is too small to be worth drawing.
There are 12 images around the up axis, blended with dithering based on the camera position, and the implementation does not handle up or down views. Each impostor contains both diffuse and normals, so the shading is reasonable at runtime rather than flat.
UE5 uses a more advanced version, integrated with a visibility buffer, and the README links the Nanite presentation for that. The difference in approach is instructive. The engine's version blends impostors with the geometry that survived culling, using the visibility buffer to know which fragments are already covered, so an impostor does not draw over something in front of it. The browser version blends by dithering against the camera position, which is a simplification that works for a scene where the impostor is the only representation of that object at that distance.
The demo pages expose the impostor threshold and the impostor texture size, and there is a dedicated impostors preview scene with the texture size increased so that more detail is visible, which is the practical way to judge the quality of the impostor atlas.
Impostors are where a Nanite style pipeline earns its keep, because they are the reason a distant object costs a few dozen textured quads rather than a cluster hierarchy that has to be traversed. The cost is a fixed atlas per mesh and the restriction that the camera has to stay roughly level with the object.
Culling at three levels, and the feature that was cut
The culling is where most of the triangles go away, and the README breaks it down by level.
Per instance there is frustum and occlusion culling. Per meshlet there is frustum and occlusion culling as well. Per triangle there is hardware backface culling and the depth buffer, with the note that WebGPU does not have early-z.
Occlusion culling is the simplest thing that could work and the README is clear about it. It is a depth pyramid from the previous frame's depth buffer, with no reprojection and no two-pass. The claim is that the current implementation is enough to cull a lot of triangles, a lot, and to judge the performance impact, which the author describes as a big improvement. The stated reason for keeping it simple is instructive too: the author expects somebody will want to read the code, and they will be grateful this feature was not added.
The cut feature is per-meshlet backface cone culling. The author tried it, it worked fine, and it was cut from the final release. The README mentions it in the culling list with a pointer to the FAQ for the details, which is a small example of a project documenting its own rejected experiments as carefully as its accepted ones.
The other culling-related feature is a freeze control. It allows the camera to move and inspect only what was drawn last frame, which is a debugging aid rather than a rendering mode, and it is the sort of thing that only exists in a project whose author is actively looking for bugs in their own pipeline.
A CPU path, a profiler and an offline Deno run
The project ships a second rendering path and a set of tools for looking at what the first one is doing.
There is a switch between GPU-driven rendering and a naive CPU implementation, and the README is honest that not much time went into optimising the CPU version. It works, and you can step through it with the debugger, which is the point: a CPU implementation of the same pipeline is the way to find out whether a bug is in the algorithm or in the WebGPU code.
The inspection tooling is extensive. There are controls to change parameters at runtime, debug views, and a lot of stats: memory, geometry, scene meshlet and triangle count, drawn meshlets and triangles split between the hardware and the software rasterizer, impostor count, and a dedicated profiler button for timings.
The build is deliberately plain. The package scripts are `node esbuild-script.js` for a build and `node esbuild-script.js --dev` for development, with a `clean` step before each, a `serve` script that runs http-server with a certificate and a key from the build directory, and yarn 4.2.2 as the package manager. The runtime dependencies are three: dat.gui for the parameter controls, webgl-obj-loader for loading OBJ files, and wgpu-matrix for the matrix maths. There is no framework, and there is a `deno.json` and a `deno.lock` beside them, because the whole application also runs offline in Deno, and the README says the shader unit tests were written that way.
That last point is the one to keep. A project that can run its shaders in a Deno test, away from the browser, is a project whose rendering code is testable rather than only demonstrable, and it is the difference between an experiment and something a team can build on.
Where nanite-webgpu is the wrong tool
Three limits are worth naming, and the first one is a matter of what the project is.
The first is that this is a reimplementation for study, not a renderer. The software rasterizer exists to show that the concept works in a browser without the atomic operations it would want, and the author says so repeatedly. A team that needs to draw hundreds of millions of triangles in a browser today should not route them through a rasterizer that is deliberately losing precision.
The second is the browser surface. WebGPU is only available on Chrome, and even there the set of operations is what the specification exposes today, so a design that assumes 64-bit atomics or early-z has to be rewritten, as the 32-bit packing and the note about the missing early-z show. A project that has to run in several browsers cannot use this as a base, and the fork of the rasterizer would be a rewrite rather than a configuration change.
The third is that the meshlet hierarchy is a different thing from Nanite as shipped. Nanite in Unreal Engine is a whole rendering architecture, with its own shading model, its own material system and its own streaming, and what this repository reproduces is the geometry and culling half. Reading it tells you how the cluster hierarchy and the impostors work, and it does not tell you how the rest of the engine fits together.
The alternative for most teams is the obvious one: a standard glTF asset with pre-built level of detail chains, rendered by a WebGPU or WebGL engine, which gives a smaller, more portable result with none of the interesting problems. The difference in approach is that nanite-webgpu is a faithful port of a specific technique so that it can be read and copied, and the alternative optimises for shipping a scene rather than for understanding a technique. A team that wants to ship should take the alternative, and a team that wants to understand what Nanite does, or wants the WGSL for their own experiments, should read this code.
Editorial conclusion
nanite-webgpu fits a graphics engineer who wants to understand how a Nanite style pipeline works by reading it, who needs the meshlet decomposition, the impostor scheme and the culling decisions in one readable repository rather than in an engine's source, and who is willing to work inside what WebGPU exposes. It is the wrong choice as a production renderer, because the software rasterizer is explicitly a demonstration of a concept rather than an efficient path, and because the browsers that implement WebGPU do not yet expose the atomic operations the design would need. Before depending on it, read the section on the 32-bit packing and the author's own notes on what the implementation gets wrong, and treat the demo scenes, with hundreds of millions of triangles, as a measurement of the pipeline's structure rather than of its performance.
Frequently asked questions
What does nanite-webgpu actually implement?
The geometry side of Unreal Engine 5's Nanite in the browser using WebGPU: the meshlet LOD hierarchy, a software rasterizer, billboard impostors, and culling on a per-instance, per-meshlet and per-triangle basis, with textures and per-vertex normals supported. The mesh preprocessing runs in the browser using WebAssembly for meshoptimizer and METIS.
Why does nanite-webgpu need a software rasterizer?
Because WebGPU does not have the atomic<u64> the feature would need, so depth is packed as u16 and normals as two octahedron-encoded u8 values into 32 bits, which costs precision. The same limitation affects the depth pyramid used for occlusion culling, and the author notes that PPLL, tiled approaches or double rasterization would be the alternatives.
What are the billboard impostors in nanite-webgpu?
Twelve images around the up axis, blended with dithering based on the camera position, each carrying diffuse and normals so shading works at runtime. The implementation does not handle up and down views, and UE5 uses a more advanced version integrated with a visibility buffer.
How is nanite-webgpu built and tested?
With esbuild through a script: `node esbuild-script.js` builds and `node esbuild-script.js --dev` runs the development build, with yarn 4.2.2 as the package manager. The whole application also runs offline in Deno, and the shader unit tests were written that way.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/scthe-nanite-webgpu)Was this useful?