I seem to have the same problem mentioned here (search “why are my spheres distorted when they get near the edge of the window?“, since there is a lot of irrelevant discussion). The solution in that link was drastically decreasing the FOV to 10, which seems to make my scene be quite shallow in the z direction. Somebody else mentioned that the aspect ratio may be off, but I’m not very sure about that:
gluPerspective(60, (GLfloat)width / (GLfloat)height, 1, -200);
Anyways, at an FOV of 60, my scene looks like this:

Changing it to 45 helps, but the spheres still look distorted near the edges. Why is this? Is this distortion inevitable?
This is the way a affine perspective projection works.
In the case of a affine projection yes.
However using a vertex shader you can implement angle based projections. However those require the geometry to be sufficiently tesselated. Long straigt edges (like those of your background box) must be broken down. A tesselation shader can help. Another way is to render into a cube map 5 quadratic views of 90° FOV, then draw from that cubemap a angular projection.