Polyfork BrowseKitsPricingBlog

Three.js shaders: six stylised looks, running live

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.

6 lookslive, not screenshotsworks on rigged characters2 of 6 bake into a GLB

Palette reduction

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 and flat shading

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.

Outline

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.

Ordered dithering

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.

Pixelation

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.

PS1 vertex wobble

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.

Use a model in one line

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());

How do I add a toon shader to a Three.js model?

  1. Pick a model on this page and open the Palette or Shading menu on the right of the viewer. Shading gives you Toon (three hard bands) or Flat (no lighting at all).
  2. Combine it with an outline. Toon shading and an outline are what most people mean by cel shading; on flat-shaded low-poly geometry the outline is doing most of the work, because every face is already one flat tone.
  3. To get it as a file, ask the CDN to bake it: /cdn/{id}-remix.glb?look=palette:gameboy,shading:toon returns a .glb with the palette rewritten and an unlit material, ready for Unity, Godot or Blender.

Which of these end up in your file, and which do not

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.

Flat shading changes what a toon shader even means

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.

Dithering wants to be pixelated

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.

They work on rigged characters

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.

Want the model itself to be parametric too?

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.

Ways to get a stylised look in Three.js

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:

ApproachBest forWhat it costs youDoes it leave the browser
Write your own ShaderMaterialAnything specific, and learning how it works GLSL, plus keeping up with Three.js chunk changes between releasesNo. A shader is not a file format
pmndrs/postprocessing or EffectComposerFull-frame effects: bloom, depth of field, chromatic aberration A render target and an extra pass per effect, and it applies to the whole frameNo, and it styles the scene rather than the model
MeshToonMaterial with a gradient mapA 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 exportA look baked once, into an asset you already own A modelling round trip for every changeYes, but the look is frozen at export
Polyfork looksRestyling any model in this catalogue without writing shader code Nothing to build. Palette and shading can be baked server-side from a URLTwo of the six do, as a plain .glb. The rest are honest about not

FAQ

What is a toon shader in Three.js?

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.

Can a shader be exported to GLB?

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.

Do I need a postprocessing library for these?

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.

Do these work on animated characters?

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.

Can I get a styled model from a URL?

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.

Are the shader looks free?

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.

Polyfork

Sign in

One account for your purchases and downloads.

Continue with Google
or

No password needed: the link signs you in directly.