2
\$\begingroup\$

Could anyone help me set up an intuitive perspective camera matrix using OpenGL and the GLM library?

By "intuitive" I mean that I want the camera to be looking at the middle of the screen down the -Z axis, but I want it to be positioned in front of the camera to the exact degree that would cause a vector placement at (0,0,0) and a vector placement of (screen_width, screen_height, 0) to show up in the bottom-left and top-right corners respectively.

Feel free to ask me to clarify, if need be. This is what I have currently:

 glm::mat4 Projection = glm::perspective(45.0f, (GLfloat) window_width / (GLfloat) window_height, 0.1f, #X#);

 glm::mat4 View = glm::lookAt(
     glm::vec3(window_width/2, window_height/2, #X#), // camera position
     glm::vec3(window_width/2, window_height/2, 0), // look at origin
     glm::vec3(0, 1, 0)  // Head is up
 );


  glm::mat4 Model = glm::mat4(1.0f);

  glm::mat4 MVP = Projection * View * Model;

The #X#s are the arguments that I'm not sure how to calculate.

I don't know how to calculate the correct Z position for the camera, and I also don't know how far out to put my far clipping plane. (Should it be a fixed(large) value, or should it be based on my width/height?)

Also, is 45 degrees what I should be using? I've seen some people use values like 60 or 70.

I'm sure whatever the calculation function is would work based off of the angle/window_width/window_height and I'm also sure its easy if you know trig.


EDIT

In response to user1118321

I tried to implement the solution provided, but it didn't quite give me what I'm looking for.

Here is the code I have now

float fov=45.0f;
glm::mat4 Projection = glm::perspective(fov, (GLfloat) window_width / (GLfloat) window_height, 0.1f, 10000.0f);

float angle1=fov/2.0;
float angle2=180 - (90 + angle1);
float Z = 0.5 * window_height * sin(glm::radians(angle2))/sin(glm::radians(angle1));
glm::mat4 View = glm::lookAt(
    glm::vec3(window_width/2, window_height/2, Z), // camera position
    glm::vec3(window_width/2, window_height/2, 0), // look at origin
    glm::vec3(0, 1, 0)  // Head is up
);                                                                                                                                                                     

 glm::mat4 Model = glm::mat4(1.0f);

 glm::mat4 MVP = Projection * View * Model;

When I try to render a square, who's bottom left corner is at (0,0,0) and who's top right corner is at (window_width, window_height,0), it doesn't span the entire screen.

This is what I get instead:

enter image description here

\$\endgroup\$
1
  • 2
    \$\begingroup\$ 95% of trigonometry is drawing lines until you see a triangle, 4% is memorizing the mnemonic "soh-cah-toa," and the remaining 1% is usually not important. \$\endgroup\$
    – bcrist
    Commented Apr 11, 2015 at 2:39

2 Answers 2

3
\$\begingroup\$

user1118321's answer will provide you the correct answer, though it is more general than necessary. Since we're dealing with a right triangle, the easiest solution is to use the definition of the tangent function:

Tangent diagram

tan(α) = A / B

Substituting half the height of the screen, the z coordinate of the camera, and half the vertical field of view gets us:

tan(22.5°) = 0.5 * height / z

Solving for z gives:

z = height / (2 * tan(22.5°))

Then it's just a matter of picking your near and far planes such that z_near < z and z_far > z.

As for the reason you had trouble using user1118321's solution (which is equivalent to mine), this is because GLM recently switched to using radians everywhere. The glm::perspective function was based on GLU's gluPerspective, and therefore older versions accepted the field of view in degrees by default. Since GLM 0.9.6.0, all functions behave by default as if GLM_FORCE_RADIANS were specified.

I was able to reproduce your screenshot using my engine:

// ...
float fov_y = 45.f;
float z = 0.5 / tan(glm::radians(fov_y / 2.f));

mat4 view = glm::lookAt(
    vec3(0.5, 0.5, z), // camera position
    vec3(0.5, 0.5, 0), // look at origin
    vec3(0, 1, 0));
dd.setView(view);
dd.setProjection(glm::perspective(fov_y, 1.f, 0.5f, 100.f));

dd.point(0, 0);
dd.point(1, 1);
dd.linearGrid(4, 4, vec2(), vec2(1));
dd.rect(be::rect { vec2(), vec2(1) });
// ...

Degrees

By specifying the fov_y to glm::perspective in radians instead of directly in degrees, we solve the problem:

// ...
dd.setProjection(glm::perspective(glm::radians(fov_y), 1.f, 0.5f, 100.f));
// ...

Radians

\$\endgroup\$
5
  • \$\begingroup\$ Do you think you could elaborate slightly on how to choose my z_near and z_far? I was under the impression that z_near/z_far meant that the clip space started z_near in front of the camera and extended out z_far pixels, but this seems not to be the case, because rendering points with -Z values will clip my objects completely, even though my z_far is 10000, which should be more than enough to extend the clip space into -Z \$\endgroup\$
    – Luke
    Commented Apr 11, 2015 at 18:35
  • \$\begingroup\$ Your understanding of the z-clipping planes is mostly correct (technically they're defined in view-space coordinates, not pixels, but in your case you're defining one view-space unit to equal the width of a pixel, so they're equivalent). For a FOV of 45° and window height of 600px, the camera Z will be ~724, so all points with z coordinate between approx. -9275 and 724 should be visible. If that describes your data I suspect you have depth testing enabled and they're being clipped by your rect. But this is really a different problem so you should ask a new question if you can't solve it. \$\endgroup\$
    – bcrist
    Commented Apr 11, 2015 at 18:58
  • \$\begingroup\$ hmm, the only thing Im trying to render is that square and it works perfect with z=0, but changing z to -1 yeilds a blank screen \$\endgroup\$
    – Luke
    Commented Apr 11, 2015 at 19:01
  • \$\begingroup\$ @LukeP rendering at z=-1 using your window size/clipping planes works fine for me, I suggest making a new question with all your relevant rendering code included. \$\endgroup\$
    – bcrist
    Commented Apr 11, 2015 at 19:11
  • \$\begingroup\$ The issue was unrelated and is solved now. I apologize \$\endgroup\$
    – Luke
    Commented Apr 11, 2015 at 19:27
2
\$\begingroup\$

Check out the Law of Cosines. It allows you to calculate any side or angle in a triangle if you have the opposite 2 angles or sides. Or alternately, use the law of sines (described at the bottom of the above link).

In your case, you know that vertical field of view is 45 degrees and that the base side you want is the height of the screen. You can think of the setup of the camera pointing towards the scene as either a single isosceles triangle, or 2 right triangles. Let's look at the 2 right triangles case.

If the eye is at the apex of the focal angle, then we can divide things up like so: at the eye (or camera) are 2 angles, each 22.5°. Directly in front of them are 2 triangle bases, each 1/2 the height of the screen. So you know 2 angle (which means you know 3 angles), and 1 side. The angles are 90° (for the right angle), 22.5° (for 1/2 the field of view), and the 3rd angle must be 180 - (90 + 22.5) = 67.5°. So applying the law of sines:

sine(67.5°)   sine(22.5°)
---------- = -----------
    z         0.5*height

or:

z = 0.5 * height * sine(67.5°)/sine(22.5°)

And if you change your field of view, you simply need to change 22.5° to half your field of view, and 67.5° to [180 - (90 + 0.5*field_of_view)].

\$\endgroup\$
6
  • 1
    \$\begingroup\$ glm::perspective is based on gluPerspective, so 45.0f represents the vertical FOV, not the horizontal FOV. Otherwise a good explanation. \$\endgroup\$
    – bcrist
    Commented Apr 11, 2015 at 5:10
  • 1
    \$\begingroup\$ OK, so replace width with height. It should still come out correct. \$\endgroup\$ Commented Apr 11, 2015 at 5:40
  • \$\begingroup\$ this didn't work for me, see my question edit \$\endgroup\$
    – Luke
    Commented Apr 11, 2015 at 12:46
  • \$\begingroup\$ Hmmm... I'm stumped. I'll need to think on it. \$\endgroup\$ Commented Apr 11, 2015 at 15:53
  • \$\begingroup\$ Note sin(pi/2 - x) / sin (x) is equivalent to cot(x). So you you could just say z = 0.5 * height * cot(22.5°) or z = height / (2 * tan(22.5°)). The latter is also easy to derive from the geometric definition of the tangent function: tan(a) = A/B where A and B are the legs of a right triangle, and a is the angle opposite side A. \$\endgroup\$
    – bcrist
    Commented Apr 11, 2015 at 16:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .