Related
I'm new to OpenGL and I'm trying to understand how the projection matrix works in it.
To create a simple case, I define a triangle in the world space and its coordinates are:
(0,1,0), (1,0,0), (-1,0,0)
I set the modelview matrix and projection matrix as below:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(
0, 0, 2,
0, 0, 0,
0, 1, 0);
glMatrixMode(GL_PROJECTION);
glOrtho(-2, 2, -2, 2, -0.1, -2.0); // does not work
// glOrtho(-2, 2, -2, 2, 0.1, 2.0); // works
From my understanding, gluLookAt() is used to set the viewing matrix. Since OpenGL does not have a concept of "camera", and thus it transforms the entire world to reach the effect of a camera. In the above code, I assume the "camera" is at (0,0,2), looking at (0,0,0). So OpenGL internally moves the triangle backwards along z axis to z=-2.
To define a view frustum, glOrtho() get 6 parameters. To make the triangle visible in the frustum, I set the near and far value to -0.1 and -2.0 respectively and this should indicate that the frustum include [-0.1, -2.0] on z axis.
I searched for similar questions and found out someone states that the last two parameters of glOrtho() is in fact -near and -far. But if this is correct, the following code should work(but it doesn't):
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(
0, 0, -2, // changed 2 to -2, thus the triangle should be transformed to z=2?
0, 0, 0,
0, 1, 0);
glMatrixMode(GL_PROJECTION);
glOrtho(-2, 2, -2, 2, -0.1, -2.0); // -near=-0.1, -far=-2.0, thus the frustum should include [0.1, 2.0], thus include the triangle
If I'm correct, the triangle should be drawn on the screen, so there must be something wrong with my code. Can anyone help?
First of all note, that the fixed function pipeline matrix stack and drawing by glBegin/glEnd sequences is deprecated since more than 10 years.
Read about Fixed Function Pipeline and see Vertex Specification for a state of the art way of rendering.
If you use a view matrix like this:
gluLookAt(0, 0, 2, 0, 0, 0, 0, 1, 0);
Then the values for the near and the far plane have to be positive when you set up the the projection matrix,
glOrtho(-2, 2, -2, 2, 0.1, 2.0);
because, gluLookAt transforms the vertices to view space (in view space the z axis points out of the viewport), but the projection matrix inverts the z-axis.
But be careful, since the triangle is at z=0
(0,1,0), (1,0,0), (-1,0,0)
and the distance from the camera to the triangle is 2, because of the view matrix, the triangle is placed exactly on the far plane (which is 2.0 too). I recommend to increase the distance to the far plane from 2.0 to (e.g.) 3.0:
glOrtho(-2, 2, -2, 2, 0.1, 3.0);
If you change the view matrix,
gluLookAt(0, 0, -2, 0, 0, 0, 0, 1, 0);
then still the (view space) z-axis points out of the viewport, but you look at the "back" side of the triangle. The triangle is still in the center of the view (0, 0, 0), but the camera position has changed. The triangle is still in front of the camera.
If you would do
gluLookAt(0, 0, 2, 0, 0, 4, 0, 1, 0);
then you would look away from the triangle. You would have to project the backside of the view to the viewport to "see" the triangle (glOrtho(-2, 2, -2, 2, -0.1, -3.0);).
Further note, that glOrtho multiplies the current matrix by the orthographic projection matrix. This means you should set the identity matrix, before you use glOrtho, as you do it with the model view matrix:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-2, 2, -2, 2, 0.1, 2.0);
Explanation
The projection, view and model matrix interact together to present the objects (meshes) of a scene on the viewport.
The model matrix defines the position orientation and scale of a single object (mesh) in the worldspace of the scene.
The view matrix defines the position and viewing direction of the observer (viewer) within the scene.
The projection matrix defines the area (volume) with respect to the observer (viewer) projected onto the viewport.
At orthographic projection, this area (volume) is defined by 6 distances (left, right, bottom, top, near and far) to the viewer's position.
View matrix
The view coordinates system describes the direction and position from which the scene is looked at. The view matrix transforms from the wolrd space to the view (eye) space.
If the coordiante system of the view space is a Right-handed system, then the X-axis points to the left, the Y-axis up and the Z-axis out of the view (Note in a right hand system the Z-Axis is the cross product of the X-Axis and the Y-Axis).
Projection matrix
The projection matrix describes the mapping from 3D points of the view on a scene, to 2D points on the viewport. It transforms from eye space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) by dividing with the w component of the clip coordinates. The NDC are in range (-1,-1,-1) to (1,1,1). Every geometry which is out of the clippspace is clipped.
At Orthographic Projection the coordinates in the view space are linearly mapped to clip space coordinates and the clip space coordinates are equal to the normalized device coordinates, because the w component is 1 (for a cartesian input coordinate).
The values for left, right, bottom, top, near and far define a box. All the geometry which is inside the volume of the box is "visible" on the viewport.
The Orthographic Projection Matrix looks like this:
r = right, l = left, b = bottom, t = top, n = near, f = far
2/(r-l) 0 0 0
0 2/(t-b) 0 0
0 0 -2/(f-n) 0
-(r+l)/(r-l) -(t+b)/(t-b) -(f+n)/(f-n) 1
The z-axis is inverted by the projection matrix.
I am a beginner in openGL. I am currently working on a program which take in inputs the width and the length of a board. Given those inputs i want to dynamically position my camera so that i can have a view on the whole board. Let' s say that my window size is 1024x768.
Are there any mathematical formula to compute the different parameters of the opengl function glookat to make it possible ?
the view i want to have on the board should look like this.
It doesn't matter if a board too big will make things look tiny. What matters the most here is to position the camera in a way that the view on the whole board is made possible
So far i am hopelessly randomly changing the parameters of my glookat function till i ran into something decent for a X size width and and Y size Height.
my gluperpective function :
gluPerspective(70 ,1024 / 768,1,1000)
my glooatfunction for a 40 * 40 board
gluLookAt(20, 20, 60, 20, -4, -20, 0, 1, 0);
how i draw my board (plane):
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
gluLookAt(20, 20, 60, 20, -4, -20, 0, 1, 0);
glBindTexture(GL_TEXTURE_2D, texture_sol);
glBegin(GL_QUADS);
glTexCoord2i(0, 0); glVertex3i(width, 0, height);
glTexCoord2i(10, 0); glVertex3i(0, 0, height)
glTexCoord2i(10, 10); glVertex3i(0, 0, 0);
glTexCoord2i(0, 10); glVertex3i(width, 0, 0);
glEnd();
the output looks as follow :
gluLookAt takes 2 points and a vector; the eye and centre positions and the up vector. There's no issue with the last parameter. The first two are relevant to your question.
I see that your board in the world space is extending on the positive X and Y axes with some arbitrary width and height values. Lets take width = height = 1.0 for instance. So the board spans from (0, 0), (1, 0), (1, 1), (0, 1); the Y value is ignored here since the board lies on the Y = 0 plane and have the same value for all vertices; these are just (X, Z) values.
Now coming to gluLookAt, eye is where the camera is in world space and centre is the point where you want the camera to be looking at (in world space)
Say you want the camera to look at centre of the board I presume, so
eye = (width / 2.0f, 0, height/2.0f);
Now you've to position the camera at its vantage point. Say somewhere above the board but towards the positive Z direction since there's where the user is (assuming your world space is right handed and positive Z direction is towards the viewer), so
centre = (width / 2.0f, 5.0f, 1.0f);
Since the farthest point on Z is 0, I just added one more to be slightly father than that. Y is how much above you want to see the board from, I just chose 5.0 as an example. These are just arbitrary values I can come up with, you'll still have to experiment with these values. But I hope you got the essence of how gluLookAt works.
Though this is written as an XNA tutorial, the basic technique and math behind it should carry over to OpenGL and your project:
Positioning the Camera to View All Scene Objects
Also see
OpenGL FAQ
8.070 How can I automatically calculate a view that displays my entire model? (I know the bounding sphere and up vector.)
Edit in response to the comment question
A bounding sphere is simply a sphere that completely encloses your model. It can be described as:
A bounding sphere, S, of a point set P with n points is described by
a center point, c, and a radius, r.
So,
P = the vertices of your model (the board in this case)
c = origin of your model
r = distance from origin of the vertex, in P, farthest from the origin
So the Bounding Sphere for your board would be composed of the origin location (c) and the distance from one corner to the origin (r) assuming the board is a square and all points are equidistant.
For more complicated models, you may employ pre-created solutions [1] or implement your own calculations [2] [3]
I have a scene which is basically a square floor measuring 15x15 (a quad with coordinates (0,0,0) (0,0,15) (15,0,15) (15,0,0) ).
I 've set the center-of-scene to be at (7.5,0,7.5). Problem is I can't figure out how to rotate the camera horizontally around that center of scene (aka make the camera do a 360 horizontal circle around center-of-scene). I know you need to do something with sin and cos, but don't know what exactly.
Here is the code (plain C):
//set camera position
//camera height is 17
GLfloat camx=0, camy=17, camz=0;
//set center of scene
GLfloat xref=7.5, yref=0, zref=7.5;
gluLookAt(camx, camy, camz, xref, yref, zref, 0, 1, 0);
//projection is standard gluPerspective, nothing special
gluPerspective(45, (GLdouble)width/(GLdouble)height, 1, 1000);
You need to modify the camx and camz variables.
The points you want to walk through lie on the circle and their coordinates are determined by x = r*sin(alpha) + 7.5, z = r*cos(alpha) + 7,5, where r is the radius of the circle and alpha is the angle between xy plane and the current position of your camera.
Of course the angle depends on the rotation speed and also on the time from the beginning of the animation. Basically, the only thing you need to do is to set the right angle and then calculate the coordinates from the expressions above.
For more info about the circle coordinates, see Wiki : http://en.wikipedia.org/wiki/Unit_circle
I think there are two ways you can use:
You can use sin/cos to compute your camx and camz position. This picture is a good example how this works.
An alternative would be to move the camera to 7.5, 0, 7.5, then rotate the camera with the camera angle you want. After that you move the camera by -7.5, 0, -7.5.
I am using gluLookAt() to set the "camera" position and orientation
GLU.gluLookAt(xPosition, yPosition, zPosition,
xPosition + lx, yPosition, zPosition + lz
0, 1, 0);
my lz and lx variables represent my forward vector
lz = Math.cos(angle);
lx = -Math.sin(angle);
When turn around in the 3D world, it appears that I am rotating around an axis that is always infront of me
I know this because my xPosition and yPosition variables stay the same, but I appear to spin around an object when im close to it and I turn.
I know there is not a problem with the maths that I have used here, because I have tried using code from past projects that have worked properly but the problem still remains.
This is what I am doing in the rendering loop
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//draw scene from user perspective
glLoadIdentity();
GLU.gluLookAt(camera.getxPos(), camera.getyPos(), camera.getzPos(),
camera.getxPos()+camera.getLx(), camera.getyPos(), camera.getzPos()+p1.getLz(),
0, 1, 0);
glBegin(GL_QUADS);
glVertex3f(-dim, dim, 0);
glVertex3f(dim, dim, 0);
glVertex3f(dim, 0, 0);
glVertex3f(-dim, 0, 0);
glEnd();
pollInput();
camera.update();
I have tried rendering a box where the player coordinates are and I got this result. The camera appears to be looking from behind the player coordinates. To use an analogy right now its like a 3rd Person game and It should look like a first person game
The small box here is rendered in the camera coordinates, to give some perspective the bigger box is infront.
Solved!
The problem was that I was initially calling gluLookAt() while the matrix mode was set to GL_PROJECTION.
I removed that line and moved it to just after I had set the matrix mode to GL_MODELVIEW and that solved the problem.
I am wondering if gluLookAt together with glFrustum is distorting the rendered picture.
This is how a scene is rendered:
And here's the code that rendered it.
InitCamera is called once and should, as I understand it now, set up a matrix so as if I looked from a position 2 units above and 3 units in front of the origin towards the origin. Also glFrustum is used in order to create a perspective`.
void InitCamera() {
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt (
0, 2 , 3,
0, 0 , 0,
0, 1 , - 0
);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum (- 1, 1,
- 1, 1,
1,1000.0);
glMatrixMode(GL_MODELVIEW);
}
Then TheScene is what actually draws the picture:
void TheScene() {
glClear(
GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT
);
glMatrixMode(GL_MODELVIEW);
// Draw red circle around origin and radius 2 units:
glColor3d(1,0,0);
glBegin(GL_LINE_LOOP);
for (double i = 0; i<=2 * M_PI; i+=M_PI / 20.0) {
glVertex3d(std::sin(i) * 2.0, 0, std::cos(i) * 2.0);
}
glEnd();
// draw green sphere at origin:
glColor3d(0,1,0);
glutSolidSphere(0.2,128, 128);
// draw pink sphere a bit away
glPushMatrix();
glColor3d(1,0,1);
glTranslated(8, 3, -10);
glutSolidSphere(0.8, 128, 128);
glPopMatrix();
SwapBuffers(hDC_opengl);
}
The red ball should be drawn in the origin and at the center of the red circle around it. But looking at it just feels wierd, and gives me the imprssion that the green ball is not in the center at all.
Also, the pink ball should, imho, be drawn as a perfect circle, not as an ellipse.
So, am I wrong, and the picture is drawn correctly, or am I setting up something wrong?
Your expectations are simply wrong
The perspective projection of a 3d circle (if the circle is fully visible) is an ellipse, however the projection of the center of the circle is NOT in general the center of the ellipse.
The outline of the perspective projection of a sphere is in general a conic section i.e. can be a circle, an ellipse, a parabola or an hyperbola depending on the position of viewpoint, projection plane and sphere in 3D. The reason is that the outline of the sphere can be imagined as a cone starting from the viewpoint and touching the sphere being intersected with the projection plane.
Of course if you're looking at a circle with a perfectly perpendicular camera the center of the circle will be projected to the center of the circle projection. In the same manner if your camera is pointing exactly to a sphere the sphere outline will be a circle, but those are special cases, not the general case.
These differences between the centers are more evident with strong perspective (wide angle) cameras. With a parallel projection instead this apparent distortion is absent (i.e. the projection of the center of a circle is exactly the center of the projection of the circle).
To see the green sphere in the centre of the screen with a perfect circle around it you need to change the camera location like so:
gluLookAt (
0, 3, 0,
0, 0, 0,
0, 0, 1
);
Not sure what's causing the distortion of the purple sphere though.
The perspective is correct, it just looks distorted because that's how things fell together here.
try this for gluLookAt, and play around a bit more.:
gluLookAt (
0, 2 , 10,
0, 0 , 0,
0, 1 , 0
);
The way I tried it out was with a setup that allows me to adjust the position and view direction with the mouse, so you get real time motion. Your scene looks fine when I move around. If you want I can get you the complete code so you can do that too, but it's a bit more than I want to shove into an answer here.