# Pixel art from a 3D hero, no dithering: eight ways compared

Materia's heroes are 3D models: rigged, dressed, and with hair that sways. In the game we bake them to sprite sheets, and the sailor girl is drawn at about 86 px tall. At that size the obvious question comes up: could she be real pixel art instead of a small render?

The one rule for this study: **no dithering anywhere**. No Bayer, no noise, no checkerboard to fake in-between colours. Every pixel has to be a flat colour that a pixel artist could have chosen.

## Setup

I added a small lab page that loads the live hero and draws each pose at 768 px. Besides the normal inked frame, it saves the buffers a renderer has and a pixel artist doesn't:

- the colour without lines,
- a **region id** for every mesh (hair, face, shirt, skirt, socks, boots, and so on),
- **depth**,
- view normals and the flat texture colour.

A Python script then draws each pose small with each method. I used 23 poses: idle in five directions, a run, a punch, and all 16 frames of the walk, so I could measure how steady each method is in motion.

One bug cost an hour. The depth was packed into RGBA, and the canvas premultiplied colour by alpha when it saved the PNG, which scattered zeros across the depth. The fix was to pack depth into RGB only and keep alpha at 1.

## Eight methods

![](https://cdn.hashnode.com/uploads/covers/682665f051e3d254b7cd5062/38c861e2-af9e-494a-a286-a1be4ce9790b.png align="center")

**A, shrink.** Average the baked frame down and fit a 32-colour palette. This is what we had been doing. It reads as a shrunk render: soft gradients cut into palette steps, mushy edges.

**B, point.** Take the colour at each block's centre. It is sharper, but each pixel rests on a single sample, so it shimmers when the pose moves under the grid.

**C, mode.** Put the big frame in the palette first, then give each block its most common colour, not the average. There are no in-between colours at borders. This is the crispest of the post-process family, but it still reads as a render.

**D, native.** Sample colour, ids and depth at block centres and work out 1 px lines at the small size. It has the most flicker of all: 60 % of figure pixels change per walk frame.

**E, cel.** This is where it turned. Each part gets its own small ramp: four shades found by k-means over that part's pixels. Each block then decides in two steps: first **which part** most of the block shows, then **which of that part's shades** is commonest. Lines are worked out at the small size, one pixel wide, where the region id or the depth jumps.

**F, cel+.** E, plus cleanup: a lone pixel takes its neighbours' shade, and L-shaped corners in lines are cut so the lines stay one pixel wide.

**G, cel+dark.** F, with dark marks such as eyes and folds weighted up to 4× in the vote so they don't get outvoted. Inside one part, only big depth steps get a line.

**H, cel 32.** G with three shades per part and one line colour per part, then all colours cut to a global 32.

![](https://cdn.hashnode.com/uploads/covers/682665f051e3d254b7cd5062/0c58f14a-1e27-4cb4-bf57-46c5143928a8.png align="center")

## Numbers

|  | method | colours | pixels changed per walk frame |
| --- | --- | --- | --- |
| A | shrink | 32 | 58 % |
| B | point | 32 | 58 % |
| C | mode | 32 | 50 % |
| D | native | 64 | 60 % |
| E | cel | 90 | 44 % |
| F | cel+ | 89 | 44 % |
| G | cel+dark | 89 | 42 % |
| H | cel 32 | 32 | 42 % |

The last column is the share of figure pixels that change between two walk frames. Lower means the sprite holds its shapes instead of boiling. The part-aware methods are both the most "pixel art" and the steadiest.

![](https://cdn.hashnode.com/uploads/covers/682665f051e3d254b7cd5062/052e4082-8c34-4d9c-a782-e5aaa3434c74.webp align="center")

## What I learned

**Deciding beats averaging.** Every method that averages or samples asks "what colour is here?". The cel methods ask two smaller questions, "which part is this?" and then "which of its few shades?", and that is much closer to how a pixel artist works. Flat clusters, clean borders between parts, and 1 px lines come out of that for free.

**Lines belong at the target size.** Lines drawn at 768 px and then shrunk turn into two-pixel smudges or broken dots. Lines worked out from ids and depth at 128 px are exactly one pixel wide.

**The face can't be voted in.** At this size an eye is 2–3 pixels. In the cel methods the eyes either vanish or become brown blots, because the head's k-means lumps the iris together with the hair's shadow. A pixel artist doesn't render eyes; they draw them. The next step is probably to give the eyes their own part and stamp a small hand-drawn eye per direction.

**Hair cards are noisy.** Every card edge is a depth step, so hair collects the most stray lines. G's "only big steps inside one part" helps. Hair still needs its own rule: two or three shades, and a line only at the outer silhouette.

## Next

1. Eyes as their own part, with per-direction stamps.
2. A hair rule.
3. Run H as a pass in our bake tool, so every motion gets a pixel-art sheet, and try her in play next to the current pixel hero.

AI pixelizers were left out on purpose. They are known to be weak at frame-to-frame consistency, and steadiness in motion was the thing I wanted to measure.
