📄 chapter02.html
字号:
the CPU to wait for each drawing command to complete before issuing the
next one. While the CPU is sending a vertex down the pipeline, the transformation
hardware is working on transforming the last one sent, the one before that
is being clipped, and so on. In such a system, if the CPU waited for each
command to complete before issuing the next, there could be a huge performance
penalty.
<P>In addition, the application might be running on more than one machine.
For example, suppose that the main program is running elsewhere (on a machine
called the client), and that you're viewing the results of the drawing
on your workstation or terminal (the server), which is connected by a network
to the client. In that case, it might be horribly inefficient to send each
command over the network one at a time, since considerable overhead is
often associated with each network transmission. Usually, the client gathers
a collection of commands into a single network packet before sending it.
Unfortunately, the network code on the client typically has no way of knowing
that the graphics program is finished drawing a frame or scene. In the
worst case, it waits forever for enough additional drawing commands to
fill a packet, and you never see the completed drawing.
<P>For this reason, OpenGL provides the command <B>glFlush()</B>, which
forces the client to send the network packet even though it might not be
full. Where there is no network and all commands are truly executed immediately
on the server, <B>glFlush()</B> might have no effect. However, if you're
writing a program that you want to work properly both with and without
a network, include a call to <B>glFlush()</B> at the end of each frame
or scene. Note that <B>glFlush()</B> doesn't wait for the drawing to complete
- it just forces the drawing to begin execution, thereby guaranteeing that
all previous commands execute in finite time even if no further rendering
commands are executed.
<P>A few commands - for example, commands that swap buffers in double-buffer
mode - automatically flush pending commands onto the network before they
can occur. void <B>glFlush</B>(void);
<P>Forces previously issued OpenGL commands to begin execution, thus guaranteeing
that they complete in finite time.
<P>If <B>glFlush()</B> isn't sufficient for you, try <B>glFinish()</B>.
This command flushes the network as <B>glFlush()</B> does and then waits
for notification from the graphics hardware or network indicating that
the drawing is complete in the framebuffer. You might need to use <B>glFinish()</B>
if you want to synchronize tasks - for example, to make sure that your
three-dimensional rendering is on the screen before you use Display PostScript
to draw labels on top of the rendering. Another example would be to ensure
that the drawing is complete before it begins to accept user input. After
you issue a <B>glFinish()</B> command, your graphics process is blocked
until it receives notification from the graphics hardware (or client, if
you're running over a network) that the drawing is complete. Keep in mind
that excessive use of <B>glFinish()</B> can reduce the performance of your
application, especially if you're running over a network, because it requires
round-trip communication. If <B>glFlush()</B> is sufficient for your needs,
use it instead of <B>glFinish()</B>.void <B>glFinish</B>(void);
<P>Forces all previously issued OpenGL commands to complete. This command
doesn't return until all effects from previous commands are fully realized.
<H3>
Hidden-Surface Removal Survival Kit</H3>
When you draw a scene composed of three-dimensional objects, some of them
might obscure all or parts of others. Changing your viewpoint can change
the obscuring relationship. For example, if you view the scene from the
opposite direction, any object that was previously in front of another
is now behind it. To draw a realistic scene, these obscuring relationships
must be maintained. If your code works something like this
<PRE>while (1) {
get_viewing_point_from_mouse_position();
glClear(GL_COLOR_BUFFER_BIT);
draw_3d_object_A();
draw_3d_object_B();
}</PRE>
it might be that for some mouse positions, object A obscures object B,
and for others, the opposite relationship might hold. If nothing special
is done, the preceding code always draws object B second, and thus on top
of object A, no matter what viewing position is selected.
<P>The elimination of parts of solid objects that are obscured by others
is called <I>hidden-surface removal</I>. (Hidden-line removal, which does
the same job for objects represented as wireframe skeletons, is a bit trickier,
and it isn't discussed here. See "Hidden-Line Removal," for details.) The
easiest way to achieve hidden-surface removal is to use the depth buffer
(sometimes called a z-buffer). (Also see Chapter 10 .)
<P>A depth buffer works by associating a depth, or distance from the viewpoint,
with each pixel on the window. Initially, the depth values for all pixels
are set to the largest possible distance using the <B>glClear()</B> command
with GL_DEPTH_BUFFER_BIT, and then the objects in the scene are drawn in
any order.
<P>Graphical calculations in hardware or software convert each surface
that's drawn to a set of pixels on the window where the surface will appear
if it isn't obscured by something else. In addition, the distance from
the eye is computed. With depth buffering enabled, before each pixel is
drawn, a comparison is done with the depth value already stored at the
pixel. If the new pixel is closer to the eye than what's there, the new
pixel's color and depth values replace those that are currently written
into the pixel. If the new pixel's depth is greater than what's currently
there, the new pixel would be obscured, and the color and depth information
for the incoming pixel is discarded. Since information is discarded rather
than used for drawing, hidden-surface removal can increase your performance.
<P>To use depth buffering, you need to enable depth buffering. This has
to be done only once. Each time you draw the scene, before drawing you
need to clear the depth buffer and then draw the objects in the scene in
any order.
<P>To convert the preceding program fragment so that it performs hidden-surface
removal, modify it to the following:
<PRE>glEnable(GL_DEPTH_TEST);
...
while (1) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
get_viewing_point_from_mouse_position();
draw_3d_object_A();
draw_3d_object_B(); }</PRE>
The argument to <B>glClear()</B> clears both the depth and color buffers.
<P>
<HR>
<H2>
Describing Points, Lines, and Polygons</H2>
This section explains how to describe OpenGL geometric primitives. All
geometric primitives are eventually described in terms of their <I>vertices</I>
- coordinates that define the points themselves, the endpoints of line
segments, or the corners of polygons. The next section discusses how these
primitives are displayed and what control you have over their display.
<H3>
What Are Points, Lines, and Polygons?</H3>
You probably have a fairly good idea of what a mathematician means by the
terms point, line, and polygon. The OpenGL meanings aren't quite the same,
however, and it's important to understand the differences. The differences
arise because mathematicians can think in a geometrically perfect world,
whereas the rest of us have to deal with real-world limitations.
<P>For example, one difference comes from the limitations of computer-based
calculations. In any OpenGL implementation, floating-point calculations
are of finite precision, and they have round-off errors. Consequently,
the coordinates of OpenGL points, lines, and polygons suffer from the same
problems.
<P>Another difference arises from the limitations of a bitmapped graphics
display. On such a display, the smallest displayable unit is a pixel, and
although pixels might be less than 1/100th of an inch wide, they are still
much larger than the mathematician's infinitely small (for points) or infinitely
thin (for lines). When OpenGL performs calculations, it assumes points
are represented as vectors of floating-point numbers. However, a point
is typically (but not always) drawn as a single pixel, and many different
points with slightly different coordinates could be drawn by OpenGL on
the same pixel.
<H4>
Points</H4>
A point is represented by a set of floating-point numbers called a vertex.
All internal calculations are done as if vertices are three-dimensional.
Vertices specified by the user as two-dimensional (that is, with only <I>x</I>
and <I>y</I> coordinates) are assigned a <I>z</I> coordinate equal to zero
by OpenGL.
<P><B>Advanced</B>
<P>OpenGL works in the homogeneous coordinates of three-dimensional projective
geometry, so for internal calculations, all vertices are represented with
four floating-point coordinates (<I>x</I>, <I>y</I>, <I>z</I>, <I>w</I>).
If <I>w</I> is different from zero, these coordinates correspond to the
euclidean three-dimensional point (<I>x/w, y/w, z/w</I>). You can specify
the <I>w</I> coordinate in OpenGL commands, but that's rarely done. If
the <I>w</I> coordinate isn't specified, it's understood to be 1.0. For
more information about homogeneous coordinate systems, see Appendix G .
<H4>
Lines</H4>
In OpenGL, <I>line</I> means <I>line segment</I>, not the mathematician's
version that extends to infinity in both directions. There are easy ways
to specify a connected series of line segments, or even a closed, connected
series of segments (see Figure 2-1 ). In all cases, though, the lines comprising
the connected series are specified in terms of the vertices at their endpoints.
<P><IMG SRC="figures/fig02-01.gif" ALT="[IMAGE]" NOSAVE HEIGHT=145 WIDTH=449>
<P><B>Figure 2-1 : </B>Two Connected Series of Line Segments
<BR>
<BR>
<H4>
Polygons</H4>
Polygons are the areas enclosed by single closed loops of line segments,
where the line segments are specified by the vertices at their endpoints.
Polygons are typically drawn with the pixels in the interior filled in,
but you can also draw them as outlines or a set of points, as described
in "Polygon Details."
<P>In general, polygons can be complicated, so OpenGL makes some strong
restrictions on what constitutes a primitive polygon. First, the edges
of OpenGL polygons can't intersect (a mathematician would call this a <I>simple
polygon</I>). Second, OpenGL polygons must be convex, meaning that they
cannot have indentations. Stated precisely, a region is convex if, given
any two points in the interior, the line segment joining them is also in
the interior. See Figure 2-2 for some examples of valid and invalid polygons.
OpenGL, however, doesn't restrict the number of line segments making up
the boundary of a convex polygon. Note that polygons with holes can't be
described. They are nonconvex, and they can't be drawn with a boundary
made up of a single closed loop. Be aware that if you present OpenGL with
a nonconvex filled polygon, it might not draw it as you expect. For instance,
on most systems no more than the convex hull of the polygon would be filled,
but on some systems, less than the convex hull might be filled.
<P><IMG SRC="figures/fig02-02.gif" ALT="[IMAGE]" NOSAVE HEIGHT=190 WIDTH=640>
<P><B>Figure 2-2 : </B>Valid and Invalid Polygons
<BR>
<BR>
<P>For many applications, you need nonsimple polygons, nonconvex polygons,
or polygons with holes. Since all such polygons can be formed from unions
of simple convex polygons, some routines to describe more complex objects
are provided in the GLU. These routines take complex descriptions and <I>tessellate</I>
them, or break them down into groups of the simpler OpenGL polygons that
can then be rendered. (See Appendix C for more information about the tessellation
routines.) The reason for OpenGL's restrictions on valid polygon types
is that it's simpler to provide fast polygon-rendering hardware for that
restricted class of polygons.
<P>Since OpenGL vertices are always three-dimensional, the points forming
the boundary of a particular polygon don't necessarily lie on the same
plane in space. (Of course, they do in many cases - if all the <I>z</I>
coordinates are zero, for example, or if the polygon is a triangle.) If
a polygon's vertices don't lie in the same plane, then after various rotations
in space, changes in the viewpoint, and projection onto the display screen,
the points might no longer form a simple convex polygon. For example, imagine
a four-point quadrilateral where the points are slightly out of plane,
and look at it almost edge-on. You can get a nonsimple polygon that resembles
a bow tie, as shown in Figure 2-3 , which isn't guaranteed to render correctly.
This situation isn't all that unusual if you approximate surfaces by quadrilaterals
made of points lying on the true surface. You can always avoid the problem
by using triangles, since any three points always lie on a plane.
<P><IMG SRC="figures/ch02-5.gif" ALT="[IMAGE]" NOSAVE HEIGHT=106 WIDTH=509>
<P><B>Figure 2-3 : </B>Nonplanar Polygon Transformed to Nonsimple Polygon
<BR>
<BR>
<H4>
Rectangles</H4>
Since rectangles are so common in graphics applications, OpenGL provides
a filled-rectangle drawing primitive, <B>glRect*()</B>. You can draw a
rectangle as a polygon, as described in "OpenGL Geometric Drawing Primitives,"
but your particular implementation of OpenGL might have optimized <B>glRect*()</B>
for rectangles.void <B>glRect</B>{sifd}(<B>TYPE</B><I>x1</I>,
<B>TYPE</B><I>y1</I>,
<B>TYPE</B><I>x2</I>, <B>TYPE</B><I>y2</I>);
<BR>void <B>glRect</B>{sifd}<B>v</B>(<B>TYPE</B><I>*v1</I>, <B>TYPE</B><I>*v2</I>);
<P>Draws the rectangle defined by the corner points (<I>x1, y1</I>) and
(<I>x2, y2</I>). The rectangle lies in the plane <I>z</I>=0 and has sides
parallel to the <I>x</I>- and <I>y</I>-axes. If the vector form of the
function is used, the corners are given by two pointers to arrays, each
of which contains an (<I>x, y</I>) pair.
<P>Note that although the rectangle begins with a particular orientation
in three-dimensional space (in the <I>x-y</I> plane and parallel to the
axes), you can change this by applying rotations or other transformations.
See Chapter 3 for information about how to do this.
<H4>
Curves</H4>
Any smoothly curved line or surface can be approximated - to any arbitrary
degree of accuracy - by short line segments or small polygonal regions.
Thus, subdividing curved lines and surfaces sufficiently and then approximating
them with straight line segments or flat polygons makes them appear curved
(see Figure 2-4 ). If you're skeptical that this really works, imagine
subdividing until each line segment or polygon is so tiny that it's smaller
than a pixel on the screen.
<P><IMG SRC="figures/ch02-6.gif" ALT="[IMAGE]" NOSAVE HEIGHT=102 WIDTH=509>
<P><B>Figure 2-4 : </B>Approximating Curves
<BR>
<BR>
<P>Even though curves aren't geometric primitives, OpenGL does provide
some direct support for drawing them. See Chapter 11 for information about
how to draw curves and curved surfaces.
<H3>
Specifying Vertices</H3>
With OpenGL, all geometric objects are ultimately described as an ordered
set of vertices. You use the <B>glVertex*()</B> command to specify a vertex.
void <B>glVertex</B>{234}{sifd}[v](<B>TYPEcoords</B>);
<P>Specifies a vertex for use in describing a geometric object. You can
supply up to four coordinates (<I>x, y, z, w</I>) for a particular vertex
or as few as two (<I>x, y</I>) by selecting the appropriate version of
the command. If you use a version that doesn't explicitly specify <I>z</I>
or <I>w</I>, <I>z</I> is understood to be 0 and <I>w</I> is understood
to be 1. Calls to <B>glVertex*()</B> should be executed between a <B>glBegin()</B>
and <B>glEnd()</B> pair.
<P>Here are some examples of using <B>glVertex*()</B>:
<PRE>glVertex2s(2, 3);
glVertex3d(0.0, 0.0, 3.1415926535898);
glVertex4f(2.3, 1.0, -2.2, 2.0);
GLdouble dvect[3] = {5.0, 9.0, 1992.0};
glVertex3dv(dvect);</PRE>
The first example represents a vertex with three-dimensional coordinates
(2, 3, 0). (Remember that if it isn't specified, the <I>z</I> coordinate
is understood to be 0.) The coordinates in the second example are (0.0,
0.0, 3.1415926535898) (double-precision floating-point numbers). The third
example represents the vertex with three-dimensional coordinates (1.15,
0.5, -1.1). (Remember that the <I>x, y</I>, and <I>z</I> coordinates are
eventually divided by the <I>w</I> coordinate.) In the final example, <I>dvect</I>
is a pointer to an array of three double-precision floating-point numbers.
<P>On some machines, the vector form of <B>glVertex*()</B> is more efficient,
since only a single parameter needs to be passed to the graphics subsystem,
and special hardware might be able to send a whole series of coordinates
in a single batch. If your machine is like this, it's to your advantage
to arrange your data so that the vertex coordinates are packed sequentially
in memory.
<H3>
OpenGL Geometric Drawing Primitives</H3>
Now that you've seen how to specify vertices, you still need to know how
to tell OpenGL to create a set of points, a line, or a polygon from those
vertices. To do this, you bracket each set of vertices between a call to
<B>glBegin()</B>
and a call to <B>glEnd()</B>. The argument passed to <B>glBegin()</B> determines
what sort of geometric primitive is constructed from the vertices. For
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -