If you are a curious person, for a long time you may have been wondering the purpose of the following function, always called in
ortho 0.0 1.0 0.0 1.0 (-1.0) 1.0
The only thing you know about it is that it is related to how we are going to "view" the objects in our HOpenGL world, as I told you in Your First (and Simple) Program lesson.
This is exactly the topic of this lesson: viewing. Let's starting talking about projection.
Projection Matrix determines how objects are projected onto the screen, as its name suggests. It defines a viewing volume, and objects outside this volume are clipped so that they're not drawn in the final scene. "But what is a viewing volume?", you may ask. The answer is in the following picture:
HOpenGL tries to imitate the human eye. The viewing volume (the region you effectively see), in this case, corresponds to all of the volume (inside the pyramid) between the two gray planes. Although (part of) a primitive or a 3D object you drew may lie outside of the viewing volume, HOpenGL will display into the screen only what's inside of it. That's why you should define your viewing volume (in other words, the world's projection) correctly.
Keep in mind that the viewing volume must not necessarily be a pyramidal one. It can be a rectangular parallelepiped, or more informally, a box (the differences will be explained). To choose and set the type of your viewing volume, you first need to change the current matrix to the
Projection Matrix and them initialize it (make it equal to the identity matrix). This is done by the following code:
matrixmode Projection loadIdentity
Now you are ready to define the viewing rules of your world. You may choose between two of them: one type is the perspective projection, which matches how you see things in daily life. Perspective makes objects that are farther away appear smaller; for example, it makes railroad tracks appear to converge in the distance. If you're trying to make realistic pictures, you'll want to choose perspective projection.
The other type of projection is orthographic, which maps objects directly onto the screen without affecting their relative size. Orthographic projection is used in architectural and computer-aided design applications where the final image needs to reflect the measurements of objects rather than how they might look. Architects create perspective drawings to show how particular buildings or interior spaces look when viewed from various vantage points; the need for orthographic projection arises when blueprint plans or elevations are generated, which are used in the construction of buildings. [These two last paragraphs were taken from the OpenGL Programming Guide (The Red Book).]
The perspective projection corresponds to the pyramidal viewing volume, while the orthographic projection corresponds to the "box" volume. If you'd like to use the perspective projection, then you should use function
frustum (after changing the current matrix to the projection one and initializing it, of course). Here you have the signature of this function:
frustum :: GLdouble -> GLdouble -> GLdouble -> GLdouble -> GLdouble -> GLdouble -> IO ()
The usage of this function is:
frustum :: left right botton top near far
The following picture [also taken from the OpenGL Programming Guide (The Red Book)] may make it easier to understand:
The first four parameters describe the size of the near clipping plane, while the two last parameters give the distances from the viewpoint to the near and far clipping planes. Notice that all of them needs to be positive.
If you find
frustum not very intuitive to use, you may try to use function
perspective, from the Utility Library (GLU). Here you have its signature:
perspective :: GLdouble -> GLdouble -> GLdouble -> GLdouble -> IO ()
And its usage:
perspective :: (field of view) aspect near far
field of view (or just
fov) is the angle of the field of view in the x-z plane, in degrees. It must range from 0.0 to 180.0. The
aspect is the aspect ratio of the frustum: its width divided by its height. For a square portion of the screen, the aspect ratio is 1.0. Finally,
far are still the same, and again they must be positive. Check out the following picture [you already know from where it was taken]:
If you wish to use a orthographic projection, in which the distance from the viewpoint doesn't affect how large an object appears, you'll have to use the function
ortho (now you finally know its use!):
ortho :: GLdouble -> GLdouble -> GLdouble -> GLdouble -> GLdouble -> GLdouble -> IO ()
Its usage is:
ortho left right bottom top near far
The difference here is that
far can be negative, the direction of projection is always parallel to the z-axis, and the viewpoint faces toward the negative z-axis (into the screen). The following picture says everything about
ortho [including from where it was taken]:
We have been using an orthographic projection so far because our examples until now were all bi-dimensional (2D).If you are going to make a program in which you are sure that you'll always work with two-dimensional images onto a two-dimensional screen, you can also use another Utility Library (GLU) routine, called
ortho2D :: GLdouble -> GLdouble -> GLdouble -> GLdouble -> IO ()
Its usage is:
ortho2D left right bottom top
The difference from
ortho2D is that all the z coordinates for objects in the scene are assumed to lie between
ortho2D. In other words, if you always use
Vertex2 rather than
Vertex3 to declarate the vertices of your primitives, you'll never get objects clipped (not shown) because of their z values.
ortho functions are defined in module GL_CoordTrans, which is automatically imported if your program already imports module GL, while
ortho2D are defined in module GLU_Matrix, which is automatically imported if your program already imports module GLU.
Now I'm going to introduce you to a new type of callback procedure: the
reshapeFunc. Every time your program window is reshaped, the function declared by
reshapeFunc is called. The interesting thing about this is that this function will automatically receive the new width and height of the window.
Translating this to code, suppose a function called
reshape will be your
reshapeFunc callback procedure. Declare it in the
reshapeFunc (Just reshape)
Its signature must be:
reshape :: ReshapeAction
ReshapeAction is defined as follows:
type ReshapeAction = WindowSize -> IO ()
All of this stuff is defined in module GLUT_CBWindow, which is automatically imported if your program already imports module GLUT.
Now you may ask what is the relation between a reshape callback with viewing. The answer is simple: viewport.
What is a viewport? The answer is simple: the viewport is the rectangular region of the window where the image is drawn. To set this, call function
viewport :: WindowPosition -> WindowSize -> IO ()
WindowPosition parameter specifies the lower-left corner of the viewport, and the
WindowSize parameter specifies the width and height of the viewport (the size of the viewport rectangle).
Suppose now that you defined your viewport and then the window is reshaped. The new window may not correspond to the old viewport. For example, if you use the whole window as the viewport, two things can happen when the window is reshaped:
That's why the best place to call
viewport is in the reshape callback procedure (because you have the current size of the window). Observe the following code:
reshape :: ReshapeAction reshape screenSize@(WindowSize w h) = do viewport (WindowPosition 0 0) screenSize ...
This sets the viewport to the whole window. The
@ here says that every time this function refers to
screenSize, actually it is referring to
(WindowSize w h). The following code, without the
@, has the same effect, but is less elegant:
reshape :: ReshapeAction reshape (WindowSize w h) = do viewport (WindowPosition 0 0) (WindowSize w h) ...
viewport function is defined in module GL_CoordTrans, which is automatically imported if your program already imports module GL.
reshape :: ReshapeAction reshape screenSize@(WindowSize w h) = do viewport (WindowPosition 0 0) screenSize matrixMode Projection loadIdentity let near = 1 far = 80 fov = 90 ang = (fov*pi)/(360 :: Double) top = near / ( cos(ang) / sin(ang) ) aspect = fromIntegral(w)/fromIntegral(h) right = top*aspect frustum (-right) right (-top) top near far matrixMode Modelview
Modelview Matrix. This is a wise thing to be done, since we only need to use the
Projection Matrixwhen setting the projection type. We'll talk more about the
Modelview Matrixin the next lesson.
loadIdentitybefore setting the projection! Weird things can happen otherwise...