13

A memorable part of the PlayStation 1 experience was “wobbly” 3D graphics, where (supposedly) straight edges would warp, jitter and pulse as the camera panned across a scene. This was especially apparent in 3D-heavy games like Metal Gear Solid, Vagrant Story and Time Crisis.

Why did this happen, and why was the defect completely absent on other platforms of the era (e.g. PC, Nintendo 64)?

  • Are you talking about when playing games on actual PlayStation console or on a emulator? – Ross Ridge Nov 15 '17 at 4:15
  • Both. I’ve just linked a Vagrant Story clip (above) where it’s especially apparent. Note now the character models and scenery pop, shimmer and glitch as the camera pans. – jamesfmackenzie Nov 15 '17 at 6:29
  • Under emulators if you're not playing at the native resolution of the game then things can look jerky because the polygons can only have vertices that are located at the native resolution's integer screen coordinates. If you're not using an emulator then a real PSX doesn't support texture filtering so that will cause textures to shimmer. The later is like the shimmering you can see on the edges of polygons if you don't have AA enabled, but without filtering will appear over the entire surface of the polygon. – Ross Ridge Nov 15 '17 at 7:30
  • Yeah, this is not related to textures or AA. The actual geometry of the models warps and changes both on the original console and in emulators. Can you see the effect in the videos I’ve linked? – jamesfmackenzie Nov 15 '17 at 8:00
  • @jamesfmackenzie it looks like it has nothing to do with 3D rendering. It is more like some post-process filter after rendering. It reminds me on videos taken with a scanline acquisition hardware during its movement (like from a car side window) some parts of the image would got shifted more than others producing skew or similar effects to what your linked video shows. My bet is they use previous frames for rendering current one and use scaling to compensate the scene change, so not as much pixels have to be re-rendered with 3D pipeline. – Spektre Nov 15 '17 at 8:16
17

The transform applied to project geometry from 3d to the 2d coordinates necessary for drawing on a screen is called a perspective projection. It involves calculating 1/z and multiplying x and y by that.

Filling a triangle involves visiting every pixel within it and deciding which colour to put there. To paint a texture that properly obeys perspective you'd need to perform a divide stemming from that 1/z operation for every pixel.

Divides are expensive so the original Playstation doesn't do them. It just squeezes the texture to fit, disregarding any consideration of perspective. That's only mathematically correct if 1/z is constant over the entire triangle, so you tend to see greater problems with geometry as it gets to a greater angle and as it gets closer to the camera.

Genuine perspective: enter image description here

Ignoring z (i.e. affine): enter image description here

Given this constraint, a second issue arises: the standard on the Playstation is to use small triangles because then 1/z can't vary too much across the face. Or, spun another way, texture coordinates are correct anywhere there's an actual vertex, just a bit variable in between, so a higher number of vertices is desirable. A good game will even subdivide larger triangles as they get closer to the camera.

But then clipping the geometry — discarding those parts that fall behind the camera from triangles that are partly in front and partly behind — becomes an expense to do away with. A lot of titles just delete any triangle completely behind the camera, keep any wholly in front, and from the others subdivide to a point but then just push the vertices that should be behind to just in front. Mathematically they should be calculating brand new geometry that is exactly cut by the camera. That adds a sort of liquid effect, where camera motion slightly deforms near geometry by compressing it and moving it around.

So: the very near geometry can be slightly adjusted, and the pixel contents of each triangle are inexact.

There's also a lack of subpixel precision that further reduces precision at triangle edges and more subtly within their fills. The issue there is that the vertices the describe the boundary of a triangle are stored as integers, precise only to the nearest whole pixel. That's enough information to locate them exactly but it affects how precisely you can calculate the on-screen positions of all other edge pixels, and how well you can map back from the screen to the source texture when filling in the middle.

For example, imagine a triangle edge that begins in the pixel at (x, 0) and ends in the pixel at (x+1, 100). You know definitely which column the starting pixel must be in. You know definitely which column the ending pixel should be in. But exactly where should you switch column? Whatever choice you make, it's going to be exactly the same for every frame where the start and end vertices fall at those two locations. But what if the camera is panning very slowly, and, say, every sixth frame the edge moves one pixel to the left? Without subpixel accuracy, the entire edge will appear to be static for six frames and then suddenly leap. Supposing everything else on screen isn't moving at exactly the same rate, it'll appear not to move entirely with the scene. There's an overall shakiness, as the various different vertices all jump from one pixel to the next at different times, making it look like some frames are updating some part of the display, other frames are updating others even though every single pixel was drawn for every single frame.

If instead you knew that you were drawing from, say, (x + 0.1, 0) to (x + 1.3, 100) in one frame, then from (x + 0.3, 0) to (x + 1.4, 100) in the next, you'd actually factor that into exactly how the edge runs, and exactly how you apply the texture. The user gets enough feedback that the polygon now looks like it is moving entirely with the scene, as there's then a consistent precision applied to the entire pipeline of all rendered pixels, rather than there being a step where an amount of precision is thrown away that is a function of current vertex position.

A secondary problem is that the process of actually assigning pixels to polygons has a bit of a quirk to it. Taking OpenGL as an example because it's well documented, the main rule is that a pixel is considered part of a triangle if its centre falls within the triangle. But that leaves a literal edge case: what do you do with a pixel whose centre lies exactly on the edge of a triangle? If you always draw it then triangles that meet along that edge are both going to draw the pixel and that'll both cost more and ruin any transparency you're applying. If you never draw it then it won't be painted at all and triangles that should meet will show occasional gaps.

There's an easy fix for this, which is to paint pixels exactly on the boundary only if, in terms of the 2d screen, they're on e.g. the left or the top edge of the triangle. But there's a potential precision problem there because you're now testing for an exact match, and hoping to get the same result on at least two separate occasions. So some hardware and some software will sometimes still show individual pixel gaps between geometry that should meet up.

The problem is exacerbated on the Playstation because you might have started with two triangles sharing one edge but then subdivided only one of those polygons. So what was one edge on one side has turned into two edges. And the lack of subpixel precision means that the new vertex in the middle might not exactly be where it should once you've projected it.

Dealing with that is a per-developer problem, but tends to revolve around deliberate more aggressive stripping of precision or stretching geometry to be slightly larger than it would naturally be. In both cases you increase that shaky look (either because precision is worse, or because you did something else directly in projected pixels), and you often also get a greater sense of polygons fighting each other for boundary pixels frame-to-frame.

Finally, the Playstation uses a fixed point 16.16 representation for numbers. That doesn't allow as much localisation of precision as floating point, and means that a developer needs actually to think relatively carefully about game distance units. It's usually not a problem for the sort of draw distances that are realistic on that generation of hardware but a particular developer could deal with that poorly. Shouldn't be an issue for the Metal Gear Solids, Vagrant Stories or Time Crises of the world though.

  • Thanks for the detailed write up! To me, it looks like the polygon instability occurs even when objects don’t bisect the camera or have a large Z-axis range. And it doesn’t seem texture-related either - the actual vertices seem to be moving. Can you help me understand this too? I feel like there must be some kind of imprecision or rounding happening in the vertex coordinates themselves. If it helps, I found a YouTube video that specifically highlights the issue here: youtu.be/nqw2HMUrNiA. – jamesfmackenzie Nov 15 '17 at 17:56
  • 1
    In case it's not obvious, writing software 3d engines was the thing that really got me into programming when I was a teenager, which was approximately contemporaneous with the Playstation era. – Tommy Nov 15 '17 at 18:55
  • 1
    Wikipedia has a pretty good illustration of what it looks like when affine texture mapping (what the PSX does) is used instead of perspective correct mapping: en.wikipedia.org/wiki/Texture_mapping#Affine_texture_mapping – Ross Ridge Nov 15 '17 at 19:10
  • 1
    @wizzwizz4 any software 3d engines on the go? Only in my head. But from the archives here's vector 3d on the Z80-based SAM Coupe: youtube.com/watch?v=j0xN_Mi3B_I , Mode 7 on an Atari Lynx: youtube.com/watch?v=wVGeLCAW4mA , and filled polygons on a Playstation (to ensure 60Hz and therefore use the interlaced mode): youtube.com/watch?v=ZNgUEvLaSpY . All I can offer from my old DOS-era days is screenshots such as: members.allegro.cc/ThomasHarte/shots/RayCaster.png members.allegro.cc/ThomasHarte/shots/Anarchy.png – Tommy Nov 15 '17 at 21:48
  • 1
    @jamesfmackenzie The GTE is mostly irrelevant here. The basic problem is that GPU only accepts integer coordinates, there's no way specify sub-pixel coordinates for a vertex like with modern GPUs. Even if the GTE had full floating-point support, the transformed vertex coordinates would still have to be converted to integers before being drawn by the GPU. (This means the GTE accuracy emulator option does more than just affect how the GTE is emulated, there needs to be changes to the CPU and GPU emulation so the extra precision can actually be passed from the emulated GTE to the emulated GPU.) – Ross Ridge Nov 17 '17 at 23:11

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.