3D to Pixel Art
There are many techniques out there for rendering 3D objects as pixel art. It usually looks very bad. I’ve developed my own method.
Why
Pixels in pixel art art are hand-placed. Therefore, pixel art is:
- Clear
- Imperfect
I want complicated structures like gears and spiral drills in my game. I do not want to animate gears turning in Aseprite, so I needed a 3D method.
Dead Cells uses 3D models for pixel graphics. The game is fun and the animations themselves are great, but I feel it suffers from a lack of clarity during important moments that the art team tries to rectify with additive blending. There’s a lot of bloom and soft gradients here.
It was inspirational. I wanted something similar but without having to deal with normal maps, toon shaders, and lighting.
You can read their blog post about their art pipeline here.
Clarity
To get around lights and toon shaders, I drew a normal lookup texture.
Think of it as how I’d shade a sphere in the game’s world.
When applying the shader to the model, I use an optional noise texture to roughen the model surface and artificially add detail. You can see that in action on the sphere, which would otherwise look just like the normal lookup image.
fixed4 frag (v2f i) : SV_Target {
half4 color;
// offset normal.xy by the noise texture's r and b values
half2 noiseOffset = (tex2D(_NoiseTex, i.texcoord.xy * _NoiseTex_ST.xy).rb);
// convert to -1, 1 so it's not just offset positively
noiseOffset = noiseOffset * 2 - 1;
// apply noise slider
noiseOffset *= _NoiseStr;
color = tex2D(_ColorMap, ((i.normal.xy + 1) / 2) + noiseOffset);
color = lerp(color, _FlashColor, _FlashColor.a);
return color;
}
Moving the roughness slider to demonstrate how it works. I use a standard tiled smooth noise texture to modify the vertex normals.
Imperfection
3D art looks too perfect rendered as pixels. Its smooth movement and consistency stands out too much. I used two methods to disguise it.
Toon Motion
I’ve touched on this method before. I have a script that cuts objects’ movement in Unity into 12 or 16 FPS.
World Space Vertex Jitter
I mess up the models just a tiny bit as they move through space to mimic the inconsistencies of rendering a 3D object as pixel art.
The sphere stays unchanged because its vertices aren’t moving through space.
Here’s the vertex shader:
v2f vert (appdata_base v) {
v2f o;
v.vertex.xyz += noise(mul(unity_ObjectToWorld, v.vertex).xyz) * _VertJitter * 0.001;
o.pos = UnityObjectToClipPos(v.vertex);
// use worldspace to avoid jitter when the camera moves
o.texcoord = v.texcoord;
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
In-Game
Here’s a boss arena with gears turning as a backdrop.
These are terrible gears that look fine but would break in the real world. See this post for more.