Opengl view, projections and orthographic aspect ratio

aspect-ratiocopenglviewport

There's a lot of great tutorials out there on opengl projection matrices for 3D but I am not doing 3D. I am really having a tough time getting orthographic projection setup to my liking.

int width = 320;
int height = 480;

I create a view projection matrix with these settings.

//eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz//
matrix view = (0, 0, -20, 0, 0, -1, 0, 1, 0);

matrix projection = (0, width, 0, height, 1, 100);
glViewport(0, width, 0, -height);

After creating this view and projection matrix and passing them to the gpu.

Then I create a quad going from -1,-1 to 1, 1 so that it's origin is at the center.

then I make a scale matrix for the quad so that I can actually see it on the screen. It's rendering as a perfect square but of course the glViewport or the perspective matrix shouldn't be square. It should be rectangle.

How can I setup the glViewport as well as the perspective matrix so that I can maintain aspect ratio.

for example I now have the aspect ratio which is width/height. How do I use that with the projection matrix and the glviewport?

@Reto is probably correct that I am overthinking this but opengl application interface is a bit tricky for me.

edit
I drew an image in hopes to help clarify things.

Let's say that I want my viewport to be 320×480 pixels. I'd like two different scaling modes that I can choose from. Either keep a fixed height
where the width will grow to show more horizontal view or a fixed width with a growing height to show more vertically.

here's the imageenter image description here

Let's say I design a scene around 320×480 and I lay everything out and I know that I would like to scale the width larger but keep a fixed height.

How can I achieve that with the glViewport and orthographic projection matrix from my aspect ratio?

Best Answer

When using an onthogonal projection, you can simply think of the ortho matrix as defining some axis-aligned 2D rectangle in the xy-plane, which describes the area of the scene which is mapped to the viewport. If the aspect ratio of that rectangle does not match the aspect ratio of the viewport, the image will be distored accordingly.

Let us use the following definitions:

V: aspect of the viewport:
  V = viewport_width / viewport_height
P: aspect of the ortho projection:
  P = (right - left) / (top - bottom)
O: aspect of some axis-aligned rectangle which is drawn
  O = (x_max - x_min) / (y_max - y_min)

When the transformations are applied, the object will appear with aspect ratio O / P * V on the screen.

Usually, when speaking of "keeping aspect ratio", we set P == V so that V / P cancels each other out in the above formula, and objects appear with exactly that aspect ratio they are drawn in eye space.

And this is already exactly what you get with your code.:

Then I create a quad going from -1,-1 to 1, 1 so that it's origin is at the center.

That is a square, and I wonder how you ever can expect this to come out as a rectangle when the aspect ratio is preserved.

From your images, it is clear that your object of interest is a rectangle with aspect ratio 2/3. So you should also draw it as a rectangle with such an aspect ratio. There are multiple ways to achieve this. Since the viewport size seems to be a given, you can sill tweak O or P or both.

However, to me it seems like you are completely overcomplicating things. If I got you right, you have some "design space" of 320x480 pixels, which is your "region of interest" which you always want to be visible on the screen, no matter what the viewport size is. To achieve that, you could do the following:

float target_width = 320.f;
float target_height = 480.f;
float A = target_width / target_height; // target aspect ratio 
// ... calculate V as above
if (V >= A) {
    // wide viewport, use full height
    ortho(-V/A * target_width/2.0f, V/A * target_width/2.0f, -target_height/2.0f, target_height/2.0f, ...);
} else {
    // tall viewport, use full width
    ortho(-target_width/2.0f, target_width/2.0f, -A/V*target_height/2.0f, A/V*target_height/2.0f, ...);
}

Now, you can work in your "design range" in pixels. In that example, the always visible range will be from (-160, -240) to (160, 240)`, and if you draw an rectangle exactly with that coordinates, it will match the blue box in your image, in any of the viewports. If you don't want the origin in the center, you can of course translate it.

Related Topic