I’m not 100% sure of exactly why it does that, I tested it too and it did the same thing. I think it may have to do with the distance the lookAt point is from the camera.

if you want, here is some code that will calculate the lookAt point given an X and Y angle in degrees.
Alternatively you can delete all the piover180 references to specify and angle in radians if you want.

#define piover180 0.0174532925
float pointX, pointY, pointZ, temp;
float cameraAngleX = SET THIS TO THE X ANGLE
float cameraAngleY = SET THIS TO THE Y ANGLE
pointY = sin(cameraAngleX*piover180)+cameraPositionY;
temp = cos(cameraAngleX*piover180);
pointX = sin(cameraAngleY*piover180)*temp+cameraPositionX;
pointZ = cos(cameraAngleY*piover180)*temp+cameraPositionZ;
gluLookAt(cameraPositionX, cameraPositionY, cameraPositionZ,
pointX, -pointY, pointZ,
0.0, 1.0, 0.0);

I use that for camera rotation. But if you just use it once it’ll give you correct values that you can use.

I am so sorry, I made a mistake in that code.
The lookAt point shout be (pointX, pointY, pointZ) I accidentally had -pointY. And then the calculation for pointY should have a negative in front:
pointY = -sin(cameraAngleX*piover180)+cameraPositionY;

My appoligies. It doesn’t seem to allow me to edit my first post.

If you get vertically mirrored projection with glFrustum, you have probably swapped the bottom and top parameters, which is further suggested by the order in which the parameters occur on the screen shots. The order is left, right, bottom, top, near and far. My guess is that you pass top instead of bottom, and bottom instead of top, like in the screen shots.