PSX color - CTR-tools/CTR-tools GitHub Wiki
This article covers various PSX color handling peculiarities. This isn't CTR exclusive, it's about PSX hardware and is relevant to any PSX game.
Vertex color
You may have noticed that PSX models imported in modern editors are bland and darker than originally intended. This is due to vertex color calculated differently in modern pipelines. These days we calculate the average value, but on PSX you just add two colors, clipping the result.
texture_color = 255; \\white
vertex_color = 255; \\white
final_pixel_now = (texture_color + vertex_color) / 2;
final_pixel_psx = texture_color + vertex_color > 255 ? 255 : texture_color + vertex_color;
This essentially means that a grey texture pixel (128) mapped to a grey vertex (128) equals white (255) on PSX. The easiest method to replicate this behavior in a shader that supports diffuse color intensity (sometimes referred to as material color) is to set its value to 2.0f. This way you naturally get the original PSX color value (you simply negate that division by 2).
Color blending modes
When you are drawing primitives on the backbuffer, you basically have 2 colors - target backbuffer pixel color and current primitive pixel color. PSX can blend these colors in 4 different ways:
- 0.5 x Back + 0.5 x Front (50% transparent)
- 1.0 x Back + 1.0 x Front (additive)
- 1.0 x Back - 1.0 x Front (subtractive)
- 1.0 x Back + 0.25 x Front (additive translucent)
Transparency modes
PSX doesn't support transparency in its modern form (as in full 8 bit alpha mask). Instead it has 1 bit semi-transparency mask and 2 modes to treat this mask. Color is represented in 16 bits 5-5-5-1 format: 5 bits per color channel, 1 bit - stp mask.
- If semi-transparency is disabled:
- full black (0,0,0,0) is treated as a hardcoded color key, always transparent
- any other color value is not transparent
- if semi-transparency is enabled:
- full black without stp is transparent
- any color without stp bit is not transparent
- any color with stp bit set (including full black) is semitransparent (alpha = 127)