Six stylised looks for Three.js, running on real models on this page rather than described in a code block. Toon and flat shading, an inverted-hull outline, ordered Bayer dithering, pixelation, palette reduction and PS1 vertex wobble. Pick them from the dock and drag the model. Two of the six rewrite the mesh and travel inside a .glb; the other four are shaders and only exist while a page is drawing, which this page is unusually specific about because it is the thing that decides whether they are any use to you.
Rows of orange, blue and green cans behind glass, a red cabinet and yellow price tags: separate, saturated hues on an object where you already know what colour everything should be. Reduction snaps every one of them to the nearest entry in a small palette, matched in OKLab so "nearest" means what an eye would say rather than what subtracting RGB says. It rewrites the colour buffer, so it travels inside a .glb. Reduced keeps the machine's own palette and simply uses less of it; the named ones are fixed hardware palettes, which is why something this colourful can come out looking wrong in them. Game Boy has four greens and no reds at all, so the cabinet has nowhere to go.
Running on Vending Machine. Drag to orbit.
Toon bakes three hard light bands into the vertex colours and then drops the lighting model; Flat drops it with no bands at all. Both export as KHR_materials_unlit, so the look survives into Unity, Godot and Blender. Watch the lantern glass: unlit means the model stops reacting to scene lights entirely.
Running on Lantern Post, a free model rebuilding live in your browser. Drag to orbit.
An inverted hull: a copy of the mesh pushed out along smoothed normals and drawn back-faces-only. Smoothed matters — these models are hard-edged, and extruding along the per-face normal tears the hull open at every corner. The width is in real pixels, derived from the projection, so it holds at any distance and on any size of model.
Running on Toy Rocket, a free model rebuilding live in your browser. Drag to orbit.
A Bayer threshold pattern in the fragment shader, quantising each pixel to a few levels per channel. It trades spatial resolution for colour resolution, so the average comes out right while no individual pixel does. Pair it with pixelation below: at full resolution the cells are one pixel wide and read as noise rather than as a style.
Running on Tree Stump, a free model rebuilding live in your browser. Drag to orbit.
No post-processing pass and no render target: the scene is drawn into a buffer a fraction of the size and the browser scales it back with nearest-neighbour. That makes it the one effect here that is cheaper than not using it, since a quarter-scale buffer is a sixteenth of the fragments.
Running on Picnic-table, a free model rebuilding live in your browser. Drag to orbit.
The console had no sub-pixel precision in its rasteriser, so projected vertices snapped to a coarse grid and geometry visibly swam as the camera moved. Snapping in clip space after the projection is what reproduces the motion; snapping the mesh once in world space just gives you a mangled mesh. Orbit it to see the effect at all.
Running on Log Plank Bridge, a free model rebuilding live in your browser. Drag to orbit.
Every free model is hotlinkable. This is the real snippet for Hanging Signboard:
// GLB: works in three.js, Unity, Godot, Blender
new GLTFLoader().load(
'https://polyfork.dev/cdn/hanging-signboard-c10e01.glb',
(gltf) => scene.add(gltf.scene)
);
// or the drop-in ES module (three.js, zero loaders):
import { createAsset } from 'https://polyfork.dev/cdn/hanging-signboard-c10e01.mjs';
scene.add(createAsset());
This is the distinction worth taking away. Palette and Shading rewrite the vertex colours and the material, so they travel inside a .glb and open correctly anywhere glTF does. Outline, Dither, Pixelate and Wobble live in shaders and a renderer setting, and no glTF can carry one: a file has nowhere to put a fragment program. Everything on this page says which it is, on the menu itself, because finding out at download time is the wrong moment.
Cel shading normally quantises the light term into bands. On flat-shaded geometry every face already renders as a single flat tone, so banding a mosaic that is already a mosaic changes very little, and mostly removes the tonal separation that was making the forms readable. Going the other way is what reads: drop the lighting model entirely and the model becomes coloured shapes with an outline round them. That is why Shading offers Flat as well as Toon, and why the outline matters more than the bands.
Ordered Bayer dithering trades spatial resolution for colour resolution, quantising each pixel to a few levels and using a screen-space threshold pattern so the average comes out right. At full resolution the pattern is one pixel wide and reads as noise rather than as a style. Pair it with Pixelate and the cells become big enough to see, which is the combination that actually looks like the hardware it is imitating.
A stylised look that only holds in the bind pose is not much use. Palette, Shading, Dither, Outline and Wobble all survive skinning: the outline is a hull bound to the same skeleton as the model, so it walks with it rather than hanging behind in a T-pose. Pick a character and play a clip to see it.
These looks restyle whatever mesh they are given. The models underneath are parametric as well: typed knobs that change the geometry rather than the paint, documented on the parametric 3D assets page.
Most of these effects are well known and none of them are hard to write. What differs is how much you build, what it costs at runtime, and whether the result is something you can hand to Unity or Blender afterwards:
| Approach | Best for | What it costs you | Does it leave the browser |
|---|---|---|---|
| Write your own ShaderMaterial | Anything specific, and learning how it works | GLSL, plus keeping up with Three.js chunk changes between releases | No. A shader is not a file format |
| pmndrs/postprocessing or EffectComposer | Full-frame effects: bloom, depth of field, chromatic aberration | A render target and an extra pass per effect, and it applies to the whole frame | No, and it styles the scene rather than the model |
| MeshToonMaterial with a gradient map | A quick cel look with no custom code | A gradient texture, and it fights flat shading (see below) | Partly: the material exports, the gradient map has to travel with it |
| Stylise in Blender and export | A look baked once, into an asset you already own | A modelling round trip for every change | Yes, but the look is frozen at export |
| Polyfork looks | Restyling any model in this catalogue without writing shader code | Nothing to build. Palette and shading can be baked server-side from a URL | Two of the six do, as a plain .glb. The rest are honest about not |
A toon or cel shader quantises lighting into a few hard bands instead of a smooth gradient, usually with an outline around the silhouette. Three.js ships MeshToonMaterial for the banding, and outlines are normally done with an inverted hull: a copy of the mesh pushed out along its normals and drawn back-faces-only. On flat-shaded low-poly models the outline carries most of the effect, because the faces are already flat tones.
No. glTF describes geometry and materials, not fragment programs, so a custom shader cannot travel in a .glb. What can travel is anything a shader would have computed that you bake into the file first: vertex colours and material settings. That is why palette reduction and unlit shading export cleanly here and dithering, pixelation and outlines do not.
No, and deliberately so. There is no EffectComposer involved: the shading effects patch the material rather than adding a full-frame pass, and pixelation is done by rendering into a smaller buffer and letting the browser scale it back up with nearest-neighbour, which is cheaper than drawing at full size rather than more expensive.
Yes. All of them survive skinning, including the outline, which is bound to the same skeleton as the model it traces so it deforms with the animation instead of staying in the bind pose.
Yes, for the two that bake. /cdn/{id}-remix.glb?look=palette:gameboy,shading:toon returns that exact variant as a .glb, and it composes with the asset's own knobs through ?p=. That endpoint is public and metered, and it is what an agent or a build script should call.
Trying them is, on every model, with no account. They are applied in your browser, so nothing is metered. The two that bake into a .glb can be requested from the CDN within the usual free allowance, and the shader code that produces the other four ships with a paid download.