Gaitkit Sign in Open the lab
Devlog · 24 October 2026

Rebuilding 518 low-poly models: a quality bar you can check in code

How we raised every model in the catalogue from blockout to finished game asset without textures, extra materials or a triangle budget blow-out.

By Ümit Kar, founder of Gaitkit4 min read

Every Gaitkit model is a small program: an ES module that builds a three.js group from typed knobs. That made the catalogue fast to grow, and it also made the first versions look like what they were, clean blockouts made of boxes. In October we rebuilt all 518 models that existed at the time to a written quality bar. The city car went from about 380 triangles to about 3,000; the whole catalogue still uses one vertex-colour material per model and stays under 6,000 triangles at its heaviest settings.

Before and after: car, cottage, knight, spider robot
The pilot: same model, same light, before on the left and after on the right

Why the models, not the lighting

When something looks flat in a 3D viewer, the first instinct is to fix the lighting. We tried that first: softer shadows, ambient occlusion, a better tone mapper. It helped a little. But the pilot above, with the cottage, the knight and the spider robot, uses the same light on both sides, and the difference is all in the geometry: bevelled edges catch the light, recessed windows cast small shadows, trims and frames break up large flat faces. Low-poly does not mean few details; it means few triangles per detail.

The bar, as rules a program can follow

"Make it look better" is not something you can check across 500 files. So we wrote the bar down as rules, and added small helpers to the modelling runtime so each rule is one function call:

  • Edges. Any visible block larger than about 10 cm gets a bevel (chamfer()); round parts use bevelled cylinders and lathed profiles. Plain boxes are only for thin trims, slats and bolts.
  • Silhouettes from profiles. Car bodies, hulls and furniture sides are extruded outlines (slab()), not stacks of boxes, so they taper and overhang the way real objects do.
  • Secondary detail on every large surface. Frames, rims, handles, hinges, seams, sills, vents. Glass and screens sit behind their frames, never flush with them.
  • Two or three tones per colour. A shade() helper derives lighter and darker variants of each base colour, and a per-face tone jitter gives stone, wood and foliage some life. Smooth bodies such as animals get almost none, because too much makes them read as crumpled paper.
  • Budgets by class, measured at the heaviest knob setting: props 2,000 triangles, vehicles 3,500, creatures and characters 4,000, buildings 4,500, and a hard limit of 6,000.

All of this still produces one material per model in the module. The detail lives in the geometry and the vertex colours, with no textures to load, so a scene full of these models stays light.

Keeping what already worked

A rebuild that breaks the API is a regression, however good it looks. Every model kept its id, its knobs, its presets, its moving parts and its night lights, and the existing checks still had to pass: real-world scale, resting on y = 0, deterministic output for the same knobs, and for walkers, planted feet that slide less than 1 mm during the cycle.

The rebuilt spider robot still walks on the same IK legs: the detail is new, the gait data is not

Before a kit was merged we built every model in it, counted triangles across every knob combination small enough to try exhaustively, and compared its knob, preset, rig and night-light lists against the previous version. Anything that changed shape in the API was sent back.

The second wave

After the pilot, the same rules went kit by kit through the rest of the catalogue: medieval villages, mech hangars, underwater scenes, tower defence, seasonal props. Each pair below is before on the left and after on the right.

Before and after from the second wave
Each pair: before (left) and after (right). Haunted house, slide, submarine, griffin, lighthouse, tower-defence turret

Moving parts benefited as much as static ones. The griffin's wings now have layered feathers that fold on the upstroke, and buildings got lanterns and windows that light up at night.

Flight is driven by distance, like walking: the wingbeat speeds up when the griffin does

The cottage at night
Night zones: windows and the lantern glow; your engine can add real lights at the same spots

What we would do differently

Write the rules first. The pilot taught us the rules, but four models is a small sample, and several rules (keep light colours below about 85% lightness, keep ground decals at least 5 mm above the surface) only appeared after dozens of models had broken them. The helpers also came late: the first kits in the wave did by hand what chamfer() and slab() now do in one line, and those kits were the slowest to review.

Every model in the catalogue is at the new bar, including the 60 free ones. Open any of them, turn the knobs, and look at the edges.

Questions

Does more detail make low-poly models slower to render?

Not much, if the detail is geometry with vertex colours: there are no textures to load or sample, and the triangle budgets keep each model small. A model is still several parts, so for crowds of static props merge each one into a single mesh; the guide to low-poly assets for web games shows how.

Why not use textures?

Textures need UVs, image files and extra memory, and they blur at low resolution. Flat-shaded vertex colours stay sharp at any distance, cost nothing to load and can be recoloured by a knob.

Did the rebuild change any asset ids or knobs?

No. Every model kept its id, knobs, presets, moving parts and night lights, so code written against the old versions still works.

More from the blog