I want vertical lines to be drawn as vertical lines regardless of their position relative to the camera (distance/offset). I want the projection to be perspective so the furthest objects from camera look smaller while keeping their respective proportions.

What am I trying to achieve: A two (vanishing) point perspective like this one http://insidetheoutline.com/wp-content/uploads/2019/07/2_Point_Perspective_Cityscape.jpg

Why am I at it: I'm really bad at 3D (obviously) therefore I'm trying to replace character models with 2D sprites like in Populous 3, or "The Game" quest in Fable 3. I also want to have a top-downish view like in isometric games. Alas, perspective distortion totally kills the effect, making sprites look skewed whenever they are off-center.

What am I doing right now:

  gluPerspective(FOV, wndWidth / wndHeight, zNear, zFar);
  // Googling lead me to this MV matrix, alas it's not working for me
  // {
  //  1, 0, 0, 0, 
  //  0, 1, 0, 0, 
  //  0, -camY/camZ, -camZ, 0, 
  //  0, 0, 0, 1
  // }
  // camX = camY = camZ = 50
    camX, camY, camZ,
    0, 0, 0,
    0, 0, 1
  // then I draw a uniform grid -5..5 by -5..5
  // and slightly off-center wireframe box to test if they are drawn correctly

Does it work? No, it doesn't. Provided matrix produce no result at all, so I had to change it to { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -camY/camZ, -camZ, 1}. Just like any text on a two-point perspective says: "Set one of the values in the last column to zero". I tried zero in any position, except the last one, to no avail. Generally, I get something like this: example output

The rightmost vertical line--the one that aligns with screen center--is vertical, just as I want all of them to be, but the others are skewed away.

What else I managed to google: Not much, really. I found an example that kinda looks like something I want to achieve, but they place the camera at ground level with zero Z delta, effectively eliminating the third vanishing point. In some other example, they just narrowed FOV to some absurdly low value, making it look like a two-point perspective to some extent.

I'm afraid I'm missing or misunderstanding something. I also not sure if the desired output will, in fact, look like I want, and I won't discard it if it's not.

  • \$\begingroup\$ "Does it work? No, it doesn't" It may be easier for other users to understand the particular way in which this fails to work if you include a screenshot illustrating the problem. Especially if you can compare it against a target or mocked-up example of what you want it to look like instead, so we can identify the specific deltas that need adjustment. \$\endgroup\$ – DMGregory Jan 19 '20 at 13:33
  • \$\begingroup\$ @DMGregory Shure, hope this helps. Please, let me know if any other clarification is required. \$\endgroup\$ – badunius Jan 19 '20 at 13:57
  • \$\begingroup\$ It looks like you want an oblique frustum. Here your camera's axis is actually looking flat out toward the horizon, so any vertical lines stay vertical. Instead of angling the camera downward to fill the frame with your ground, you take the projected image and slide it upwards in image space, similar to doing tilt-shift photography. There's a good article about the technique in the Unity documentation, which you may be able to apply to your OpenGL case. \$\endgroup\$ – DMGregory Jan 19 '20 at 15:50
  • \$\begingroup\$ @DMGregory Your suggestion is option 1 in the answer I've posted. \$\endgroup\$ – Magma Jan 22 '20 at 1:09
  • \$\begingroup\$ @DMGregory the article you pointed at suggests something weird. First, the projection matrix happens to be really sensitive to these adjustments, to the point where 0.001 makes difference between clipping half of the screen and none of it. Second, I clipped half of the screen, then compressed the other half 1:2, the stretched output 2:1. And now I have the same picture as before, but with 3 more transformations =) \$\endgroup\$ – badunius Jan 22 '20 at 2:58

For true undistorted two-point perspective, the camera must always face horizontally, otherwise vertical lines will not project to vertical lines. So you need to edit your 'gluLookAt' call to make the camera face horizontally.

Unfortunately, this means that the horizon line will be at eye level with the observer, i.e. in the center of the screen. If most of the action happens below that horizon line, you'll end up with half a screen full of sky. To fix that, you have a few options:

1. Keep the camera as it is, but shift the entire view up.

This will cause the horizon to move up on screen, but also changes the projection center, so the viewer will have to move his head up as far as the horizon moves to see an undistorted view again. Note that people are actually rather used to viewing screens at an angle, so this might be tolerable in certain cases.

To do this, modify your code as follows:

glTranslatef(0, 0.5, 0); // Change 0.5 to the amount of half-screens you want to shift the horizon by
//glScalef(1, 0.97, 1); // If perspective distortion becomes too great, try this to mitigate it.
gluPerspective(FOV, wndWidth / wndHeight, zNear, zFar);

This is a rather unorthodox position for glTranslatef to be called at, but it's perfectly legal and should do exactly the right thing. The effect of this is essentially to "scroll down" on the screen.

2. Tilt the camera slightly down, and tilt all your sprites by the same angle.

This one is a pretty cheap trick to make verticals look vertical again, but it can be very effective if the art style supports it. Try it and see if you like it.

3. Tilt the camera down and draw sprites untilted.

Yes, vertical lines will not be parallel to the screen borders, but you'll deal with it, because that's how perspective works in real life. Take a photo at an angle, verticals will be crooked too. If it really looks that wrong to you, try adjusting your FOV setting to match your real life FOV as determined by your chair and computer screen, and move the camera farther away if necessary.

4. Put the entire UI on the top half of the screen.

This solution is perfect from a perspective math standpoint, but its applicability severely depends on the type of game you're making. If you have a UI-heavy game with lots of menus and buttons and windows and stuff, the sky is free real estate. If your game is a minimalist RPG, then this solution is not for you.

5. Combine several of the previous options.

All these options are more or less compatible with one another. It's up to you to determine by experiment which combination suits your game best.

  • \$\begingroup\$ I'll give it a try and let you know how it turned out \$\endgroup\$ – badunius Jan 22 '20 at 2:59
  • \$\begingroup\$ Ok, I tried the first one and it looks nice, but when the camera is looking straight forward it becomes rather hard to a) find corresponding focus point (camera target) and b) zoom towards and away from it. \$\endgroup\$ – badunius Jan 23 '20 at 13:37
  • \$\begingroup\$ By camera target do you mean what's at the center of the screen? \$\endgroup\$ – Magma Jan 23 '20 at 17:28
  • \$\begingroup\$ In addition to and as an alternative to #1, you can use an asymmetrical FOV (less degrees on the top edge, more degrees on the bottom edge), commonly known as a tilt-shift effect (this is literally what IRL tilt-shift camera lenses do, although they're better known for the side effect of shallow depth of field). To accomplish this in OpenGL, start with a taller-than-needed FOV, then use glClipPlane to clip off the top 1/3 or so move the horizon up (and an appropriate amount off the sides to maintain your screen's aspect ratio). \$\endgroup\$ – Slipp D. Thompson Apr 9 '20 at 3:45
  • \$\begingroup\$ You can also accomplish this with custom frustrum calculation. The libCinder source code has an example of how to do this. \$\endgroup\$ – Slipp D. Thompson Apr 9 '20 at 3:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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