Skip to content

Paint finishes

A paint finish draws a car's paint with a shader instead of the game's plain paint. It uses the same sun, street lights, fog and reflections as the rest of the car, and every player sees it.

vehicle.paintFinish = { type: "metallic" };
vehicle.paintFinish = { type: "metallic", flakes: 1, flakeSize: 2, gloss: 1 };   // coarse, glittery, wet look
vehicle.paintFinish = null;                                                      // the game's paint again

Only the paint changes: the parts the game paints in the car's colours. Glass, lights, trim and tyres stay as they are. The mod shops sell finishes too, in their Paint Finish area.


Built in

Metallic

{ type: "metallic", flakes?, flakeSize?, flakeColor?, gloss?, on? }

Flakes in the paint glint as they catch the light, mostly around the highlights. They stay on the car as it moves, and blur into a soft sheen further away. The colour deepens towards the car's edges, as real metallic paint does, under a glossy clear coat.

Option Type Default Description
flakes number 0.7 How much the flakes show, 0 to 1.
flakeSize number 1 Their size: 2 is coarser (glittery), 0.5 finer.
flakeColor number 0xFFFFFF The colour they glint (0xRRGGBB), mixed with the paint's: dark paint shows more of it (silver flakes on black).
gloss number 0.85 The clear coat's shine, 0 (dull) to 1 (wet look).
on "both" \| "primary" \| "secondary" \| "all" "both" Which of the car's colours.

Pearl

{ type: "pearl", pearlColor?, flakes?, flakeSize?, gloss?, on? }

Colour shift's pearl without the shift: the paint keeps its own colour, with a soft pearly glow (faint pastel tints running through it with the angle) and fine pearl flakes under a clear coat.

Option Type Default Description
pearlColor number 0xFFFFFF What the glow and flakes are tinted towards (0xRRGGBB): a gold or blue pearl on white paint, say.
flakes number 0.35 How much the pearl flakes show.
flakeSize number 1 Their size, as metallic's.
gloss number 0.9 The clear coat's shine.
on as metallic "both"

Satin and matte

{ type: "satin" | "matte", sheen?, on? }

A rough finish. The light spreads into a soft, wide glow instead of a sharp highlight, with a faint scatter at the edges and hardly any reflection. Matte is the same with almost no sheen.

Option Type Default Description
sheen number 0.5 (satin), 0.1 (matte) 0 is flat and chalky; 1 is a soft glow.
on as metallic "both"

Colour shift

{ type: "colorShift", hueShift?, shift?, flakes?, flakeSize?, gloss?, on? }

Chameleon paint. Each part's hue turns as the surface turns away from you: one way where it turns to your right (or up), the other way where it turns to your left (or down), with its own colour where it faces you. Colour 1 and colour 2 parts each shift from their own colour, and greys, black and white stay as they are. Fine pearl flakes in the shifting colour sit under a clear coat.

Option Type Default Description
hueShift number 30 How far the hue turns at the edges, in degrees, each way: red runs to orange on one side and to pink on the other. Larger is wilder (120 runs red through green and purple). Negative swaps the sides.
shift number 1 How much of that it reaches, 0 (none) to 1.
flakes number 0.35 How much the pearl flakes show.
flakeSize number 1 Their size, as metallic's.
gloss number 0.9 The clear coat's shine.
on as metallic "both"
vehicle.paintFinish = { type: "colorShift", hueShift: 120 };  // a wide, rainbow-ish shift

---|---|---|---| | shiftColor | number | 0x6A2BD9 (purple) | The colour it turns to (0xRRGGBB). | | edgeColor | number | shiftColor | The colour at the edges, for a three-colour shift. | | shift | number | 1 | How far it turns, 0 (not at all) to 1. | | flakes | number | 0.35 | How much the pearl flakes show. | | flakeSize | number | 1 | Their size, as metallic's. | | gloss | number | 0.9 | The clear coat's shine. | | on | as metallic | "both" | |

vehicle.paintFinish = { type: "colorShift", shiftColor: 0x1e7f3a, edgeColor: 0x2050ff };  // to green, then blue

Liveries

A livery is an image over a car's paint, such as a sticker bomb wrap or a paint job of your own. It shows under any finish, so a car can have a metallic sticker bomb.

vehicle.livery = { texture: "liveries/stickerbomb.jpg" };                          // any car
vehicle.livery = { texture: "game:savanna1/savanna92body256a", mapping: "uv", blend: "multiply" };  // the game's Savanna flames
vehicle.livery = null;
Option Type Default Description
texture string An image client file (PNG or JPG, up to 2048 square), or one of the game's textures, "game:<txd>/<texture>".
mapping "projected" \| "uv" "projected" "projected" lays it onto any car from the sides, top and front, tiled scale metres across. "uv" uses the paint's own layout, for an image drawn for one model (as the game's own paint jobs are); only the parts the game's paint jobs cover get it.
scale number 4 Projected: how many metres one copy of the image covers.
opacity number 1 0 to 1.
blend "over" \| "multiply" "over" "over" puts the image on top as it is. "multiply" tints it by the car's colour, as the game's paint jobs are (mid grey keeps the paint as it is).
on as metallic "both" Which of the car's colours.

The game's own paint jobs are in its TXDs, named after the car with 1, 2 or 3 on the end: game:savanna1/savanna92body256a, game:savanna2/savanna92body256b, and so on. Freeroam has a sticker bomb texture to start from (examples/freeroam/client-files/liveries/stickerbomb.jpg).


Your own

vehicle.paintFinish = { shader: "shaders/paint_chrome.hlsl", params: { tint: [1, 0.95, 0.8] } };

shader is an HLSL client file with a shade function, which gives the paint's colour at each point. params holds up to 8 values by name, each a number or 2 to 4 numbers, which the shader reads by those names.

// client-files/shaders/paint_chrome.hlsl
float3 shade(Surface s) {
  float3 n = s.normal;
  float3 mirror = reflection(s, n) * 1.6 * tint;
  return mirror * s.texel.rgb + specularLight(s, n, 250) * 1.5;
}

Surface

Field Type Description
color float3 The paint colour.
alpha float Its alpha.
normal float3 The surface's direction, in world space.
view float3 The direction to the camera.
position float3 Where the point is, in world space.
local float3 Where it is on the car, in metres (model space). Use this for patterns that stay on the car, such as flakes or stripes.
localNormal float3 The surface's direction on the car.
uv float2 Its texture coordinates.
texel float4 Its texture (dirt and details), or white if it has none. Multiply by it to keep the car's dirt.
distance float Its distance from the camera.
ambient float3 The ambient light.

Helpers

Function Description
diffuseLight(s, n) The ambient light plus every light shining on a surface facing n. Multiply your colour by it.
specularLight(s, n, power) The lights' highlights off a surface facing n; a higher power gives a tighter highlight.
reflection(s, n) The surroundings reflected off a surface facing n (the game's car reflection map), dimmed at night.
fresnel(nDotV, f0) How much more a surface reflects towards its edges; f0 is the amount head on.
hueRotate(c, angle) A colour with its hue turned by angle (radians); greys stay grey.
iridescence(t) A soft rainbow colour that runs with t, for pearly sheens.
hash3(p) Three random numbers, 0 to 1, for a cell (floor of a position).
time Seconds, for animation.

Fog is added for you afterwards. A shader that doesn't compile is reported as a clientScriptError, with where set to "paint <file>". Up to three lights reach a point: the sun first, then the two nearest lamps.

Keep shaders modest: shader model 3 has 32 temporary registers, and Wine's shader compiler (Linux players, Proton, Lutris) needs more than Windows' for the same code. A shader that needs more than 32 is reported as an error (and the paint drawn plainly) rather than drawn as garbage. Fewer values held at once, one pass over the lights instead of several, and fewer texture reads all help.

examples/freeroam/client-files/shaders/paint_chrome.hlsl is a starting point.