converting from ortho to perspective view

I have an app which has used only orthographic projection up to this point. I’d like to add, as an option, perspective projection.

Currently it does something similar to the below.


	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(gLeft, gRight, gBottom, gTop, -gNear, -gFar);

A simple replacement of this with gluPerspective results in the beloved black screen of nothing. So I’m debugging that, but wanted to ask here to see if there were any tips on converting from ortho to perspective from those that have done it.

EDIT: Another way to pose this might be the following. What would be the “equivalent” call to glFrustum given the current call to glOrtho shown above? Of course, the viewing volume shape is different, but the goal is getting a perspective projection (via gluPerspective or glFrustum) which gives roughly the same viewing volume, just with the different shape.

One thing I’m noticing in working this conversion is that with ortho, my viewing volume can include the origin. I can specify a negative near plane and positive far plane and the resultant box will include the origin.

It looks like with perspective, however, the viewing volume must be completely in front of the origin. There doesn’t seem to be a way to specify the eye position and the docs say both near and far must be positive, so it seems the eye must be at the origin and the near and far planes are then out in front of the origin.

So it looks like I may need to create a box approximately the same size as the existing box, figure out where it would be, but then add a translation to the modelview matrix to push everything into the viewing volume as currently my viewing volume contains the origin (near < 0, far > 0).

Or is it just that with glFrustum, it assumes the near and far planes are equal distance in front of and behind the origin? Does it assume the origin is in the middle of the 2 planes?

Correct. An assumption of the OpenGL perspective transformation is that the eyepoint is at the origin looking down the negative Z axis (with X right and Y up). The values you give glOrtho or glPerspective for “near” and “far” are actually negated to come up with the eye-space Z values. So for instance, if you give “near_val” and “far_val” to glFrustum, then the actual eye-space planes for the near clip and far clip planes are Z=-near_val and Z=-far_val. Ditto for orthographic projections (e.g. glOrtho).

There doesn’t seem to be a way to specify the eye position

There sure is. You just don’t do it as part of the PROJECTION transform. That’s the VIEWING transform (part of the MODELVIEW transform).

While in “eye-space” the eye (camera) is always at the origin looking down the negative Z axis, you can use a VIEWING transform (e.g. gluLookAt) on the bottom of the MODELVIEW matrix to position the eye into the world wherever you want. That’s applied first, then the projection matrix. So it just works out.

Conceptually, think of it this way: the eye is out there in WORLD space somewhere looking in some random direction. Conceptually you use rotates and translates to rotate and translate your whole scene to reposition the eye at the origin looking down the negative Z axis. That’s what the VIEWING transform does (gluLookAt being one way to set up this transform).

and the docs say both near and far must be positive, so it seems the eye must be at the origin and the near and far planes are then out in front of the origin.

Yes, where “in front” of the origin means down the negative Z axis in eye-space.

So it looks like I may need to create a box approximately the same size as the existing box, figure out where it would be, but then add a translation to the modelview matrix to push everything into the viewing volume as currently my viewing volume contains the origin (near < 0, far > 0).

Right.

In fact, if you wanted the extents of your visible scene to be exactly the same at the near clip plane as they were before, translate your whole “display region” down to the negative Z axis (looks like it’s currently down the positive Z axis, assuming gNear and gFar are positive values) to “d” units down the axis (i.e. near plane is Z = -d), use the same gLeft, gRight, gBottom, gTop in your glFrustum, and then for near_val use “d”.

Or is it just that with glFrustum, it assumes the near and far planes are equal distance in front of and behind the origin?

No, not at all. The “eyepoint” is always at the origin of eye-space, and the near and var clip planes are down the negative Z axis from it. If you specify glFrustum values near_val and far_val, the near clip plane is Z=-near_val and the far clip plane is Z=-far_fal.

Does it assume the origin is in the middle of the 2 planes? [/QUOTE]

bytebucket,
Mathematically, There’s no camera in OpenGL. gluLookAt() simply consists of some rotations and translations that transforms the vertexes.
The following matrix is applied:
projection * view * model * vertex

For example you specify :

//projection matrix
1)gluPerspective()/glFrustum()/glOrtho()/gluOrtho2D();
//view matrix
2)gluLookAt();
//model matrix
3)glTranslate()/glRotate()/glScale();
//final vertex
4)glVertex();

OpenGL uses post multiplication with column major matrices. So projection matrix is “specified” first(I mean is not applied first, it’s just “specified first” in our code ), then the view matrix, then the model matrices(glTranslate()/glRotate()/glScale() )and then the final vertex. However the matrices are applied in reverse order that they are specified:
( projection * ( view * ( model * vertex ) ) )
So the model matrix is “applied” first, then the view matrix, then the projection matrix. if the model matrix also consists of some matrices such as translation, rotation, scaling, they are also applied from bottom to top. for example with the following order:

glTranslate();
glRotate();
glScale();

we get this equation:
(translate * ( Rotate *( Scale * vertex ) ) );
(So scale matrix is applied first, then the rotation matrix is applied , then the translation is applied .

In this case, we have a fixed coordinate system and the projection matrix looks at the negative z axis. After the model and view matrix are applied to the vertex, the projection matrix is applied and then those vertexes that are outsied the box are clipped with the rules of clipping.
So
1)Camera is always at(0,0,0) pointing to the negative z
2)matrices are applied( in reverse order that they are specified in OpenGL ) to the vertexes and normal vectors and change their position and orientation respectively, but the coordinate system is always fixed.

However how we can simplify it? We can just “suppose” ( I mean it’s not correct, but it works, its just an imagination ), that there’s a camera ( specified with gluLookAt()), and each vertex ( or simply each object ) is specified at “local coordinate system”. “local coordinate is not fixed”. So in this simpler case:
"Model matrices including translation, rotation and scaling are applied from “top to bottom” to the “local coordinate system” and “transform it”.Then the camera is translated/rotated with the gluLookAt function and then the final projection is applied “relative” to the camera position and orientation.
For example the following code:

glTranslate();
glRotate();
glScale();

Now translates the local coordinate system( translation “applied” first here ), then rotates it and finally scales it( The transformed local coordinate system transforms every vertex too )
Then the camera which is inside the "world coordinate system is also rotated/translated.
Then its projection is applied.

This imagination is like taking a photo by a photographer in real world.At the beginning, his camera is at(0,0,0) and points to the negative z direction.He moves objects first, Then he moves his camera too, then he takes the photo.

Note that these rules are just correct for column major matrices( As specified with OpenGL). For row major matrices( DirectX uses row major matrices ) we have to use another rule )

Make sense?

Note that with the second manner, the “camera”(view matrix) and “objects” (model matrices ) are independent of each other and each of them have their own spaces ).
Again… The second manner is just an imagination :slight_smile:

Thanks to both of you for your replies! I think I’m understanding it a little better now. Here’s what I’ve come up with.

The existing ortho viewing projection is setup like the below. The vv (viewing volume) variables are all set based on the objects we want to display, but I’ve removed the specifics of how they get set to keep it simple. An example of rendering a simple box at the origin results in negative left, bottom, and near planes and positive right, top, and far planes so that the ortho viewing volume contains the origin and the box.


	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	GLdouble vvLeft = ...
	GLdouble vvRight = ...
	GLdouble vvBottom = ...
	GLdouble vvTop = ...
	GLdouble vvNear = ...
	GLdouble vvFar = ...

	glOrtho(vvLeft, vvRight, vvBottom, vvTop, vvNear, vvFar);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// render scene

In order to provide the same approximate viewing volume, except shaped as a frustum instead of a box and accounting for the fact that the eye is now at the origin instead of the viewing volume enclosing the origin (conceptually), I’ve done the below.


	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	GLdouble vvLeft = ...
	GLdouble vvRight = ...
	GLdouble vvBottom = ...
	GLdouble vvTop = ...
	GLdouble vvNear = ...
	GLdouble vvFar = ...

	GLdouble vvDepth = vvFar - vvNear;
	GLdouble vvHeight = vvTop - vvBottom;

	const GLdouble vvFovDegs = 45.0;
	GLdouble vvFovRads = ConvertDegsToRads(vvFovDegs);

	vvNear = (vvHeight / 2.0) / tan(vvFovRads / 2.0);
	vvFar = vvNear + vvDepth;

	glFrustum(vvLeft, vvRight, vvBottom, vvTop, vvNear, vvFar);

	glMatrixMode(GL_MODELVIEW);

	glTranslated(0,0,-(vvNear + (vvDepth / 2.0)));

	glLoadIdentity();

	// render scene

This results in the near plane being sized the same as the original ortho box. The size of course increases toward the far plane as expected. The translation pushes everything into the view.

This seems to be working. Of course, before, view rotation was simply a matter of applying the rotation. Now, I think we need to orbit around the center of the scene since we’re (conceptually) observing the scene from afar instead of being in the scene (to state it informally).

Also, with ortho projection, we could use a very deep volume. We would add extra to the near and far distances and it didn’t matter. Now, with frustum, I think its much more sensitive and the distance between the near and far planes have a big effect on how you see the scene.

Looks about right. If not that, then pretty close.

Though I can’t quite convince myself that your glTranslate offset is right. Instead of vvDepth / 2.0 I’d have though you’d want the original vvNear – to push the entire region out in front of the new near clip plane, not just half of it. But maybe I’m just not looking at it right.

The original ortho viewing volume box is centered about the origin. If I move it by just vvNear, then the box will now be centered about the near plane. So I added the (vvDepth/2.0) so the new box will be aligned (near planes matching) as the old.

I’m not 100% certain its correct, but that was the rationale I used to come up with the translation :slight_smile:

I was just looking through your code and noticed you call glLoadIdentity(); after you call glTranslated(0,0,-(vvNear + (vvDepth / 2.0)));. This essentially undos the translation since it clears the matrix and your image will not show up where it should.


	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	GLdouble vvLeft = ...
	GLdouble vvRight = ...
	GLdouble vvBottom = ...
	GLdouble vvTop = ...
	GLdouble vvNear = ...
	GLdouble vvFar = ...

	GLdouble vvDepth = vvFar - vvNear;
	GLdouble vvHeight = vvTop - vvBottom;

	const GLdouble vvFovDegs = 45.0;
	GLdouble vvFovRads = ConvertDegsToRads(vvFovDegs);

	vvNear = (vvHeight / 2.0) / tan(vvFovRads / 2.0);
	vvFar = vvNear + vvDepth;

	glFrustum(vvLeft, vvRight, vvBottom, vvTop, vvNear, vvFar);

	glMatrixMode(GL_MODELVIEW);

	glTranslated(0,0,-(vvNear + (vvDepth / 2.0)));

	glLoadIdentity();

	// render scene

that’s correct. it was a typo when I posted the code :slight_smile: I’m calling glLoadIdentity first, then glTranslate.

I have the set the ortho for my data with these boundaries and can view view it.I followed the steps to convert ortho to perspective suggested in this thread and I get blank screen.Can you suggest the soultion…

Xmin=398291.437500
Xmax=400033.000000
Ymin=135049.078125
Ymx=137133.796875
Zmin=-5.624723
Zmax=84.187180

Ortho

void resizeGL(int w, int h)
{
if(h<=0) h=1 ;

//To Preserve Aspect Ratio
float fWorldW=sBoundingBox.fMaxX-sBoundingBox.fMinX;
float fWorldH=sBoundingBox.fMaxY-sBoundingBox.fMinY;
float fWorldR=fWorldW/fWorldH;
float fViewportR=w/h;
if(fWorldR&gt;fViewportR)
{
	glViewport(10,50,w,w/fWorldR);
}
else
{
	glViewport(10,50,h*fWorldR,h);
}

glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
//Assign Bounding Box Coordinates of Shapefile to glOrtho()
glOrtho(sBoundingBox.fMinX, sBoundingBox.fMaxX,sBoundingBox.fMinY,sBoundingBox.fMaxY,sBoundingBox.fMinZ,sBoundingBox.fMaxZ);

printf("xmin= %f,xmax=%f,ymin= %f,ymax=%f,zmin= %f,zmax=%f",sBoundingBox.fMinX, sBoundingBox.fMaxX,sBoundingBox.fMinY,sBoundingBox.fMaxY,sBoundingBox.fMinZ,sBoundingBox.fMaxZ);
glMatrixMode(GL_MODELVIEW);

}

void GLDraw()
{
glClear (GL_COLOR_BUFFER_BIT);
glLoadIdentity ();
glColor4f(1.0,0.0,0.0,1.0);
float xdelta=(sBoundingBox.fMinX+sBoundingBox.fMaxX)/2;
float ydelta=(sBoundingBox.fMinY+sBoundingBox.fMaxY)/2;
float zdelta=(sBoundingBox.fMinZ+sBoundingBox.fMaxZ)/2;

    glPushMatrix();
    glTranslatef(xdelta,ydelta,zdelta);
glRotatef(-45.0f,1,0,0);
glTranslatef(-xdelta,-ydelta,-zdelta);
DrawPolygonShapefile1();//Rendering logic
glPopMatrix();	
    glFlush();

}

Perspective

double ConvertDegsToRads (double d)
{
return d * M_PI/180.0;
}

void resizeGL(int w, int h)
{
if(h<=0) h=1 ;
//glViewport (0, 0, (GLsizei) w, (GLsizei) h);

//To Preserve Aspect Ratio
float fWorldW=sBoundingBox.fMaxX-sBoundingBox.fMinX;
float fWorldH=sBoundingBox.fMaxY-sBoundingBox.fMinY;
float fWorldR=fWorldW/fWorldH;
float fViewportR=w/h;
if(fWorldR&gt;fViewportR)
{
	glViewport(10,50,w,w/fWorldR);
}
else
{
	glViewport(10,50,h*fWorldR,h);
}

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

GLdouble vvLeft = sBoundingBox.fMinX;
GLdouble vvRight = sBoundingBox.fMaxX;
GLdouble vvBottom =sBoundingBox.fMinY;
GLdouble vvTop = sBoundingBox.fMaxY;
GLdouble vvNear = sBoundingBox.fMinZ;
GLdouble vvFar = sBoundingBox.fMaxZ;

GLdouble vvDepth = vvFar - vvNear;
GLdouble vvHeight = vvTop - vvBottom;

const GLdouble vvFovDegs = 45.0;
GLdouble vvFovRads = ConvertDegsToRads(vvFovDegs);

vvNear = (vvHeight / 2.0) / tan(vvFovRads / 2.0);
vvFar = vvNear + vvDepth;

glFrustum(vvLeft, vvRight, vvBottom, vvTop, vvNear, vvFar);

glMatrixMode(GL_MODELVIEW);

glTranslated(0,0,-(vvNear + (vvDepth / 2.0)));

glLoadIdentity();

}

void GLDraw()
{

glClear (GL_COLOR_BUFFER_BIT);
glLoadIdentity ();	
DrawPolygonShapefile1();		
glFlush();

}