Skip to main content

PS1 Rendering: Textures, Vertices, and Draw Order

· 5 min read
Unity developer and creator of Polygon Engine

While working on Polygon Engine's renderer, I needed to understand which visual artifacts associated with PS1 came from the hardware and which could be implementation errors. Texture distortion, small jumps in vertex positions, and incorrectly ordered polygons can appear in the same scene, but they have different causes.

Separating them helped me review each stage of rendering more clearly. To explain how they relate, it helps to start with the process that turns model vertices into an image. From there, I can identify where each problem occurs and what can reduce it.

From model vertices to screen coordinates​

The process begins with the model's vertex positions. Those positions are transformed relative to the camera and projected onto the screen. On PS1, the GTE accelerates transformation and projection operations; the GPU then receives commands describing the primitives to draw in screen coordinates.

Before a primitive reaches the GPU, the engine must therefore prepare its geometry and decide how to organize drawing. I use the GTE reference to check which calculations the coprocessor supports and which the engine's code must handle.

Reviewing these transformations also means considering data precision. A position can be calculated correctly in the model and lose precision when converted to screen coordinates. During slow camera movement, that difference can become visible at polygon edges.

Affine texture mapping​

A tiled floor makes texture distortion easy to observe. The part near the camera occupies more screen space than the part farther away. To represent that perspective correctly, texture-coordinate interpolation would need to account for the change in depth across the surface.

The PS1 GPU uses affine texture interpolation. It interpolates texture coordinates in screen space without the perspective correction a modern renderer normally applies. A large triangle with substantial depth variation can therefore show tiles bending or sliding as the camera moves.

This is independent of whether the triangle was drawn in the correct order. Sorting it perfectly does not change how the texture coordinates are interpolated inside it. The GPU documentation describes the primitive and texture behavior behind this distinction.

Subdivision reduces this effect by dividing the surface into smaller pieces. Each piece spans a smaller change in depth, so the affine approximation often looks better. Integrating it into the engine's workflow also means accounting for the additional vertices and primitive packets that must be prepared and sorted.

Vertex precision and jitter​

Vertex jitter can appear even on an untextured surface. If the outline moves in small steps during slow camera movement, coordinate precision is worth investigating before changing the texture mapping.

Screen coordinates have limited precision, and geometry calculations use finite numeric formats. Motion that is smooth in a higher-precision representation eventually becomes discrete steps on a low-resolution display. Small changes near a rounding boundary can become very noticeable along a high-contrast edge.

That does not make every visible jump an unavoidable hardware feature. An engine can add its own mistakes through inconsistent rounding, poor scaling, or a transform that exceeds its numeric range. I need to separate the expected quantization from errors introduced before the GPU sees the primitive.

This distinction also affects how I use Polygon Engine's preview. Simulating vertex jitter in the editor helps me assess a scene's appearance, but the exported game uses its own transformation path. I need to inspect that result to establish whether the behavior is what I expect on the console.

Polygon sorting and clipping​

The PS1 GPU has no general-purpose depth buffer that resolves the nearest surface for each pixel. Games commonly arrange primitives in an ordering table so farther geometry is submitted before nearer geometry.

The awkward part is assigning a whole polygon to a depth order when different parts of it occupy different depths. Two intersecting surfaces can demand contradictory orders. An average-depth value cannot describe which surface is in front at every point of the intersection.

Making the ordering table more precise can reduce collisions between depth buckets, but it cannot solve that geometric ambiguity. Splitting troublesome surfaces can help. Designing geometry and camera paths so that fewer ambiguous overlaps occur can help too. Neither approach turns an ordering table into a depth buffer.

I also check clipping against the camera's near plane separately. A polygon crossing that plane needs to be handled before projection and drawing. GTE instructions do not replace a complete clipping algorithm, so a surface stretching or disappearing as the camera approaches may point to a problem in that part of the engine.

Testing each rendering issue separately​

To distinguish these cases, a test scene can contain a checkerboard floor, two crossing untextured surfaces, and a slowly moving camera. With only a few elements, it is easier to observe what changes when geometry or rendering is adjusted.

The checkerboard reveals texture distortion. Untextured outlines show coordinate stepping, and the crossing surfaces expose draw-order problems. This makes it possible to check whether subdivision improved the mapping or simply changed how the polygons overlap.

Understanding these differences helped me decide what to address in the renderer and what to support through level creation tools. Some cases require corrected calculations; others can be reduced by splitting a surface or adjusting its geometry. Identifying the cause keeps me from changing parts of the engine that are unrelated to the problem.