Tweaking the projection matrix if viewport resizes

Hi,

usually, if the window size changes, you do two things to adjust the OpenGL rendering:

  • Call glViewport() with the new client area size,
  • set up a new projection matrix, e.g. using gluPerspective().

With this standard approach, the display of the rendering changes differently depending on whether you change the width or height of the window. Download e.g.

http://nehe.gamedev.net/data/lessons/vc/lesson22.zip

and start it in non-fullscreen mode, and shrink the window vertically. You’ll notice that the rendered cube will be displayed smaller in order to still fit into the viewport. On the other side, if you shrink the width, the cube does not change its screen space size, and if the width is small enough, the cube will be cropped.

My question is, how do I need to change the matrix produced by gluPerspective() in order to get the same behavior for shrinking the window width as for shrinking the window height?

By accident, I found out that changing the -1 in the 3rd column, 4th row of the matrix as shown here

http://www.opengl.org/sdk/docs/man/

to S where

A=width/height;
S=-1.0;
if (A<1.0) S=1.0/A;

seems to do the trick, but I have no explanation why this works. So my second question is, if the above is the right thing to do, why does it work?

Thanks.

as far as i go, i update only the ratio. if using normal resolution like 640x480, 800x600, 1024x768, (not widescreen) … they all produce a ratio of 4.0/3.0 = 1.3333.
since windowed mode can be whatever i guess just setting new ratio is enough otherwise the image is somewhat distorted (unproportional).

float aspect_ratio = new_width / new_height // in float.
gluPerspective(fovy,aspect_ratio,near,far);

_NK47, I don’t think it answers the question. This is an interesting question, but I can’t find the answer for now…

hmmm.
eyebex:
My question is, how do I need to change the matrix produced by gluPerspective() in order to get the same behavior for shrinking the window width as for shrinking the window height?

For projections as far as i understand the question, you just need correct aspect ratio of width and height so the image looks proportional. changing the viewport on window resize is enough then.
otheriwise i dont understand the problem.
maybe eyebex can explain it in more detail.

_NK47: Have you downloaded the NeHe demo and played with it? I guess this is the best way to understand my question.

The problem is not that the image does not look proportional, that’s all working fine. I just don’t like that the projection behaves differently depending on whether you change the width or height. This is not (directly) related to the aspect ratio, which I’m calculating correctly.

Yes i checked the sample and understand the problem but don’t really know how to get it. my first guess would be not updating the glViewport??

In the prospective matrix you have

1/(r*tan(fovY/2)     0               0               0
    0            1/(tan(fovY/2)      0               0
    0                0          (zN+zF)/zN-zF) 2*(zN*zF)/(zN-zF)
    0                0             -1          2*(zN*zF)/(zN-zF)

Where
zN is the near clip plane
nF is the far clip plane
fovY is the field of view in the Y axis
r is the screen ratio
and r*fovY is the field of view in the X axis (I’ll call this fovX)
http://www.opengl.org/sdk/docs/man/xhtml/gluPerspective.xml

When you change the width of the window you change R so with a larger window you have a larger fovX. Larger window, larger fov, the image is the same size. :slight_smile:
If you change the height you still changing the R… but you are not changing the fovY… you change fovX… O_O You have an higher windows but the same fov, so your image look zoomed.

To avoid this you have to compute the new fovY as newWindowsHeight/oldWindowHeight.

edit:
sorry… I means
fovY = fovY*newWindowsHeight/oldWindowHeight

Thanks Rosario, you pointed me into the right direction. Here’s the code I finally came up with:

void onResize(int width,int height) {
    static int const init_width=width;
    static double const init_fov=math::Constd::PI()*0.25;

    double fov;

    if (width>=init_width) {
        // Scale the FOV down in the same ration the width increased.
        fov=static_cast<double>(init_width)/width*init_fov;
    }
    else {
        // Scale the FOV so that for a zero window width it becomes PI.
        fov=(init_fov-math::Constd::PI())/init_width*width+math::Constd::PI();
    }

    m_camera.setScreenSpaceDimensions(width,height);
    m_camera.setProjection(math::Mat4d::Factory::PerspectiveProjection(width,height,fov));

    repaint();
}