How to shade PS1 models - scurest/duckstation-3D-Screenshot GitHub Wiki

This is a summary of what you need to know to shade PS1 models. It also explains how we represent PS1 models in OBJ/MTL.

My reference for this is DuckStation's ShadePixel function. Also the PlayStation specification documents.

Vertex colors

Every vertex has an RGB vertex color.

Polys can be either textured or untextured. For textured polys, the vertex color is combined with the texture color using the component-wise formula

2 * TextureColor * VertexColor

The vertex color #808080 is "fully bright" in the sense that it does not change the texture color. Vertex colors above this will brighten the texture; colors below it will darken it.

Note that this is different than the more common TextureColor * VertexColor formula, which only allows darkening.

(Note: If using a linear-light color space instead of sRGB (like Blender), use 4.6325 * TextureColor * VertexColor instead.)

Semitransparency

Semitransparency is a feature where the color of the fragment being drawn, the foreground F, is combined with the color of the background behind it, B, instead of simply replacing it.

There are four semitransparency modes which use different formulas for the color of the resulting pixel:

  • B/2 + F/2

    This is the same as normal alpha-over with 50% opacity. Used for translucent surfaces, like water or colored glass.

  • B + F

    Brightens the background. Used for fire, light, and glowy magic effects.

  • B - F

    Darkens the background. Used for shadows, darkness magic, and fade-to-black screen effects.

  • B + F/4

    Same as B + F, but less bright.

Semitransparency is enabled by a semitransparent bit stored on each polygon.

For textures, each texel also has a semitransparent bit. For textured polys, semitransparency is enabled for a fragment ONLY if BOTH the poly's semitransparent bit AND the texel's semitransparent bit are set.

(Note that there is no alpha channel on the PS1, either for textures or for vertex colors.)

Transparent black

In addition to, and separate from, semitransparency, for textured polys only, if the texel color for a fragment is black with the semitransparent bit clear, that fragment is immediately discarded. That is, black with semitransparent bit clear is always treated as fully transparent.

Summary of PS1 transparency

if poly is textured
    if texel color is black and texel's semitransparent bit is clear
        discard (do not draw)
    else if both poly's and texel's semitransparent bits are set
        draw semitransparent
    else
        draw normal
    end
else if poly is untextured
    if poly's semitransparent bit is set
        draw semitransparent
    else
        draw normal
    end
end

Conversion to OBJ/MTL

Our philosophy is to represent models in OBJ in a way that will look as accurate as possible in a generic OBJ loader (eg. converting PS1 transparency to standard alpha blending), while still exposing all information necessary for PS1-accurate rendering to anyone who cares to consume it.

  1. Vertex colors are written at the end of vertex lines as v x y z r g b, following a common extension to the OBJ format.
  2. Textures have Alpha = 0% wherever there was transparent black.
  3. For semitransparent polys, the semitransparency mode they use is written in the material name (eg usemtl B/2+F/2,...).
  4. The model has Alpha = 50% wherever semitransparency was enabled. This will look correct for B/2 + F/2 mode. Other modes can't be represented correctly in OBJ/MTL, but you will be able to tell it should be semitransparent from the fact it has Alpha = 50% and can look up the intended mode in the material name.
    1. For untextured polys, Alpha = 50% is set with d 0.5 in the MTL file.

    2. For textured polys, Alpha = 50% is set in the alpha channel of the texture.

      Note that because semitransparency needs to be enabled both by the poly and by the texel, we may need two versions of a texture: one to use for non-semitransparent polys that ignores the texel's semitransparent bit, and one to use for semitransparent polys, that doesn't.

      You can recognize the one for semitransparent polys from the filename. It ends with a "t".

Note about UVs

Texture coordinates on the PS1 have no subpixel precision. That is, we know which texel the UV goes in, but not where in that texel the UV should go.

Our choice is to always put the UV at the exact texel center.