|Download Files |
From: mjk@fangio.asd.sgi.com (Mark Kilgard)
Newsgroups: comp.graphics.api.opengl
Subject: Achieving Quality PostScript output for OpenGL
Date: Fri, 18 Apr 97 03:08:43 1997
Organization: Silicon Graphics, Inc.
OpenGL programmers,
This posting discusses how to use OpenGL's feedback mechanism to
generate resolution independent versions of scenes rendered by OpenGL
programs. As an example application for feedback, you can feedback to
generate PostScript descriptions of a 3D scene that are actually
geometric, not simply dumps of an OpenGL rendered image. At the end of
this posting, a complete working GLUT-based
OpenGL program is provided that can generate Encapsulated PostScript
for a useful class of OpenGL scenes. I also discuss other applications
for OpenGL's feedback mechanism, many specifically targeted at 3D
games.
Before the full explanation of OpenGL's feedback mode, let's see what
the example code can do. First, here's a simple torus rendered
on-screen with OpenGL by rendereps:

Now, here's what ghostview (a GNU PostScript previewer) shows when we
write out the rendering as a resolution-independent Encapsulated
PostScript file:

You can verify for yourself that the PostScript generated really is
resolution independent by printing the same render.eps Encapsulated
PostScript file to a high-resolution PostScript printer. Download render.ps.gz (GNU zipped). (Note that a
PostScript showpage directive has been added to the end of the
file generated by rendereps so that the file can be printed.)
First, a bit of high-level theory about OpenGL's rendering process. The
OpenGL rendering process actually has two main steps: First, OpenGL
"transforms" your 3D primitives into screen space. The
transformation step includes 3D modeling, culling, and per-vertex
lighting. The second step is rasterization. OpenGL
"rasterizes" the transformed primitives so that they properly
update pixels in the frame buffer.
The rasterization step is where your 3D scene becomes
"pixelized". Pixel-level operations such as blending and
depth testing (for hidden service removal) all get done as part of
rasterization.
Rasterization is where your 3D scene gets mapped to some fixed
pixelized resolution. All the points, lines, and polygons in your scene
get mapped to onto the finite set of pixels to form your final rendered
image. An unfortunate, unsightly, but largely inevitable part of
rasterization is that lines and edges of polygons end up with jagged
edges (when ideally the edges should be exactly straight). Yes,
rasterization gives you a nice display-able image, but you also give up
the resolution independence of your abstract 3D scene.
The difficulties that comes from mapping idealized geometry onto a
discrete grid of pixels are commonly called "rasterization
aliasing artifacts".
Consider what happens when you try to "screen capture" an
OpenGL-rendered image on your computer's screen and print it out on a
high-resolution laser printer. You'll generally find that the printed
version looks "blocky" or "blurry" if the image
gets printed large on a piece of paper. This is because the spatial
resolution of a piece of paper is substantially higher than
resolution of your computer monitor.
Wouldn't it be great if there was some way to take your resolution
independent abstract 3D scene and render it with OpenGL into some
format that could be printed out to a laser printer so that it would be
at the resolution of the laser printer instead of your lower-resolution
computer screen?
A naive solution is to simply render the image with OpenGL at the
higher resolution of the laser printer. In general, rasterizing at a
higher resolution is often too memory intensive and not really
practical. The space required to store the resulting image is directly
proportional to its pixel area. Such high resolution images can take up
huge amounts of space. Plus, even a higher-resolution image still has a
finite resolution so you would still get a blocky look on an even
higher resolution laser printer.
A better solution would be to render your scene with OpenGL but instead
of both transforming and rasterizing your scene, stop after
transformation step and return the results of the transformed
primitives back to the application. OpenGL provides exactly this
capability with its feedback mechanism. Essentially, you can ask OpenGL
to "feedback" the results of OpenGL's transformation step
back to your application. Then, you can do what you like with the
results.
One thing you could do is write out the transformed scene in some
resolution independent format such as PostScript that most laser
printers understand. The complete program below shows exactly how to do
just that.
In feedback mode, OpenGL does all its standard modeling, culling, and
lighting calculations just like it was going to render the scene into a
framebuffer except OpenGL skips the rasterization step that
would actually update the framebuffer. Be warned that operations such
as depth testing (for hidden surface removal) and texturing and
blending are therefore not available in feedback mode. This shouldn't
be too surprising since these OpenGL steps inherently manipulate
pixels, the very thing we are trying to avoid with feedback mode.
The lack of depth buffering for hidden surface removal sounds quite
limiting, but in fact since OpenGL feedback gives you back all the
polygons in the scene projected into window space including depth
values, you can sort the polygons back to front. Then you can use the
"painter's" algorithm for hidden surface removal. After
sorting the primitives, render them back to front.
Here's the way to use OpenGL feedback:
GLfloat buffer[1024];
glFeedbackBuffer(1024, GL_3DCOLOR, buffer);
glRenderMode(GL_FEEDBACK);
renderScene();
used = glRenderMode(GL_RENDER); /* Switch back to render mode. */
After these calls (assuming the feedback buffer didn't overflow),
you'll have in the array buffer all unculled primitives. Each vertex
will have both a 3D coordinate and a color value. See the glFeedbackBuffer
and glRenderMode
function descriptions for more details.
Now, you can post-process the feedback buffer and translate it's
contents into PostScript or do whatever else you want to do with the
results. The format of the feedback buffer is a bit involved. The best
explanation of the feedback buffer format is in the OpenGL
specification. See: ../glspec/node122.html
The rendereps.c program below shows how to decode the feedback
buffer and translate it into Encapsulated PostScript. You can then
embed the Encapsulated PostScript in some other document and not lose
any unnecessary resolution in your scene when you go to print it.
The included demonstration program can either sort the primitives back
to front or leave them in the order rendered. Sorting adds a bit of
overhead, but is almost certainly required for any scene that uses
depth testing for hidden surface removal. The unsorted option is useful
when generating wire frame drawings.
I want to make sure that you appreciate that OpenGL feedback is
amazingly useful for a whole range of tasks, not just generating
PostScript for OpenGL rendered scenes. Here are some other uses:
-
Output to plotters. It is straightforward to turn a wire-frame
rendering using OpenGL into plotter commands.
-
Determining the back to front (or front to back) ordering of primitives
in arbitrary scenes. Here are a couple applications:
-
You can then use the painter's algorithm for hidden surface removal and
avoid the substantial cost of depth testing.
Imagine a game where a ninja wanders around in a 3D town. When the
ninja encounters an enemy, the viewpoint becomes fixed while the fight
ensues. Rendering time should then be spent adding detail to the
fighters, not wasted on depth testing the town's backdrop with depth
testing.
Since the town backdrop is fixed for the duration of the fight, use
OpenGL feedback to transform the town backdrop (culling polygons
outside the view frustum) and then sort the polygons so you can then
draw them without depth testing. This leaves you more performance to
render the fight!
When the fight is over and the ninja begins roaming the town again
looking for another victim, you'd render the town backdrop with depth
testing again so you can allow the viewport to roam.
-
OpenGL's standard polygon anti-aliasing method requires a front to back
rendering order to work optimally. Say you want a nicely rendered scene
with no "jaggies" and don't mind having this high-quality
rendering be slow. Perhaps you are creating a static scene for glossy
publication.
First, render the scene with OpenGL feedback and sort the polygons
front to back. Enable OpenGL's standard polygon anti-aliasing mode like
this:
glEnable(GL_POLYGON_SMOOTH);
glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE);
glEnable(GL_BLEND);
Now, render the sorted polygons front to back. Watch your jaggies
disappear.
-
Use OpenGL feedback to "linearize" a hierarchical model and
coalesce vertices to take advantage of vertex arrays. This can be
helpful to optimize frequently rendered geometry. For example:
-
Say you were writing a "destruction derby" game where players
were allowed to "outfit" their cars with accessories like
spiked bumpers, monster tires, front-mounted machine guns, etc. You'd
like the cars to have detailed geometry to look extra cool, but it is
expensive to hierarchically traverse the structures as modeled and
combined.
After out-fitting the car, render the car in feedback mode
hierarchically (tons of convenient but expensive
push/pop/translate/scale/rotate calls). Capture all the vertices and
their colors. Now coalesce the vertices into a vertex array. Reuse
shared vertices.
Now you have an optimized set of linearize geometry that you can put
in an OpenGL vertex array for fastest possible rendering.
The best part of OpenGL's feedback mechanism is that OpenGL
implementations can actually use the same dedicated transformation
engine used for rendering to do feedback calculations. A well-designed
piece of OpenGL hardware can off-load all the OpenGL transformation
operations onto specialized floating point engines for maximum
performance. SGI's RealityEngine and InfiniteReality graphics
subsystems are both examples demonstrating hardware acceleration of
feedback.
Also note that OpenGL's feedback mechanism is a fairly novel 3D
rendering API feature. Other 3D APIs such as Direct3D Immediate Mode
completely lack any equivalent to OpenGL's feedback mode.
Here's the code for rendereps.c (or
download it from previous link).
- Mark
/* Copyright (c) Mark J. Kilgard, 1997. */
/* This program is freely distributable without licensing fees
and is provided without guarantee or warrantee expressed or
implied. This program is -not- in the public domain. */
/* Example showing how to use OpenGL's feedback mode to capture
transformed vertices and output them as Encapsulated PostScript.
Handles limited hidden surface removal by sorting and does
smooth shading (albeit limited due to PostScript). */
/* Compile: cc -o rendereps rendereps.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm */
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>
/* OpenGL's GL_3D_COLOR feedback vertex format. */
typedef struct _Feedback3Dcolor {
GLfloat x;
GLfloat y;
GLfloat z;
GLfloat red;
GLfloat green;
GLfloat blue;
GLfloat alpha;
} Feedback3Dcolor;
int blackBackground = 0; /* Initially use a white background. */
int lighting = 0; /* Initially disable lighting. */
int polygonMode = 1; /* Initially show wireframe. */
int object = 1; /* Initially show the torus. */
GLfloat angle = 0.0; /* Angle of rotation for object. */
int moving, begin; /* For interactive object rotation. */
int size = 1; /* Size of lines and points. */
/* How many feedback buffer GLfloats each of the three objects need. */
int objectComplexity[3] =
{6000, 14000, 380000}; /* Teapot requires ~1.5 megabytes for
its feedback results! */
/* render gets called both by "display" (in OpenGL render mode)
and by "outputEPS" (in OpenGL feedback mode). */
void
render(void)
{
glPushMatrix();
glRotatef(angle, 0.0, 1.0, 0.0);
switch (object) {
case 0:
glutSolidSphere(1.0, 10, 10);
break;
case 1:
glutSolidTorus(0.5, 1.0, 15, 15);
break;
case 2:
glutSolidTeapot(1.0);
break;
}
glPopMatrix();
}
void
display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
render();
glutSwapBuffers();
}
void
updateBackground(void)
{
if (blackBackground) {
/* Clear to black. */
glClearColor(0.0, 0.0, 0.0, 1.0);
} else {
/* Clear to white. */
glClearColor(1.0, 1.0, 1.0, 1.0);
}
}
void
updateLighting(void)
{
if (lighting) {
glEnable(GL_LIGHTING);
} else {
glDisable(GL_LIGHTING);
}
}
void
updatePolygonMode(void)
{
switch (polygonMode) {
case 0:
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
break;
case 1:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
break;
case 2:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
}
}
/* Write contents of one vertex to stdout. */
void
print3DcolorVertex(GLint size, GLint * count,
GLfloat * buffer)
{
int i;
printf(" ");
for (i = 0; i < 7; i++) {
printf("%4.2f ", buffer[size - (*count)]);
*count = *count - 1;
}
printf("\n");
}
void
printBuffer(GLint size, GLfloat * buffer)
{
GLint count;
int token, nvertices;
count = size;
while (count) {
token = buffer[size - count];
count--;
switch (token) {
case GL_PASS_THROUGH_TOKEN:
printf("GL_PASS_THROUGH_TOKEN\n");
printf(" %4.2f\n", buffer[size - count]);
count--;
break;
case GL_POINT_TOKEN:
printf("GL_POINT_TOKEN\n");
print3DcolorVertex(size, &count, buffer);
break;
case GL_LINE_TOKEN:
printf("GL_LINE_TOKEN\n");
print3DcolorVertex(size, &count, buffer);
print3DcolorVertex(size, &count, buffer);
break;
case GL_LINE_RESET_TOKEN:
printf("GL_LINE_RESET_TOKEN\n");
print3DcolorVertex(size, &count, buffer);
print3DcolorVertex(size, &count, buffer);
break;
case GL_POLYGON_TOKEN:
printf("GL_POLYGON_TOKEN\n");
nvertices = buffer[size - count];
count--;
for (; nvertices > 0; nvertices--) {
print3DcolorVertex(size, &count, buffer);
}
}
}
}
GLfloat pointSize;
static char *gouraudtriangleEPS[] =
{
"/bd{bind def}bind def /triangle { aload pop setrgbcolor aload pop 5 3",
"roll 4 2 roll 3 2 roll exch moveto lineto lineto closepath fill } bd",
"/computediff1 { 2 copy sub abs threshold ge {pop pop pop true} { exch 2",
"index sub abs threshold ge { pop pop true} { sub abs threshold ge } ifelse",
"} ifelse } bd /computediff3 { 3 copy 0 get 3 1 roll 0 get 3 1 roll 0 get",
"computediff1 {true} { 3 copy 1 get 3 1 roll 1 get 3 1 roll 1 get",
"computediff1 {true} { 3 copy 2 get 3 1 roll 2 get 3 1 roll 2 get",
"computediff1 } ifelse } ifelse } bd /middlecolor { aload pop 4 -1 roll",
"aload pop 4 -1 roll add 2 div 5 1 roll 3 -1 roll add 2 div 3 1 roll add 2",
"div 3 1 roll exch 3 array astore } bd /gouraudtriangle { computediff3 { 4",
"-1 roll aload 7 1 roll 6 -1 roll pop 3 -1 roll pop add 2 div 3 1 roll add",
"2 div exch 3 -1 roll aload 7 1 roll exch pop 4 -1 roll pop add 2 div 3 1",
"roll add 2 div exch 3 -1 roll aload 7 1 roll pop 3 -1 roll pop add 2 div 3",
"1 roll add 2 div exch 7 3 roll 10 -3 roll dup 3 index middlecolor 4 1 roll",
"2 copy middlecolor 4 1 roll 3 copy pop middlecolor 4 1 roll 13 -1 roll",
"aload pop 17 index 6 index 15 index 19 index 6 index 17 index 6 array",
"astore 10 index 10 index 14 index gouraudtriangle 17 index 5 index 17",
"index 19 index 5 index 19 index 6 array astore 10 index 9 index 13 index",
"gouraudtriangle 13 index 16 index 5 index 15 index 18 index 5 index 6",
"array astore 12 index 12 index 9 index gouraudtriangle 17 index 16 index",
"15 index 19 index 18 index 17 index 6 array astore 10 index 12 index 14",
"index gouraudtriangle 18 {pop} repeat } { aload pop 5 3 roll aload pop 7 3",
"roll aload pop 9 3 roll 4 index 6 index 4 index add add 3 div 10 1 roll 7",
"index 5 index 3 index add add 3 div 10 1 roll 6 index 4 index 2 index add",
"add 3 div 10 1 roll 9 {pop} repeat 3 array astore triangle } ifelse } bd",
NULL
};
GLfloat *
spewPrimitiveEPS(FILE * file, GLfloat * loc)
{
int token;
int nvertices, i;
GLfloat red, green, blue;
int smooth;
GLfloat dx, dy, dr, dg, db, absR, absG, absB, colormax;
int steps;
Feedback3Dcolor *vertex;
GLfloat xstep, ystep, rstep, gstep, bstep;
GLfloat xnext, ynext, rnext, gnext, bnext, distance;
token = *loc;
loc++;
switch (token) {
case GL_LINE_RESET_TOKEN:
case GL_LINE_TOKEN:
vertex = (Feedback3Dcolor *) loc;
dr = vertex[1].red - vertex[0].red;
dg = vertex[1].green - vertex[0].green;
db = vertex[1].blue - vertex[0].blue;
if (dr != 0 || dg != 0 || db != 0) {
/* Smooth shaded line. */
dx = vertex[1].x - vertex[0].x;
dy = vertex[1].y - vertex[0].y;
distance = sqrt(dx * dx + dy * dy);
absR = fabs(dr);
absG = fabs(dg);
absB = fabs(db);
#define Max(a,b) (((a)>(b))?(a):(b))
#define EPS_SMOOTH_LINE_FACTOR 0.06 /* Lower for better smooth
lines. */
colormax = Max(absR, Max(absG, absB));
steps = Max(1.0, colormax * distance * EPS_SMOOTH_LINE_FACTOR);
xstep = dx / steps;
ystep = dy / steps;
rstep = dr / steps;
gstep = dg / steps;
bstep = db / steps;
xnext = vertex[0].x;
ynext = vertex[0].y;
rnext = vertex[0].red;
gnext = vertex[0].green;
bnext = vertex[0].blue;
/* Back up half a step; we want the end points to be
exactly the their endpoint colors. */
xnext -= xstep / 2.0;
ynext -= ystep / 2.0;
rnext -= rstep / 2.0;
gnext -= gstep / 2.0;
bnext -= bstep / 2.0;
} else {
/* Single color line. */
steps = 0;
}
fprintf(file, "%g %g %g setrgbcolor\n",
vertex[0].red, vertex[0].green, vertex[0].blue);
fprintf(file, "%g %g moveto\n", vertex[0].x, vertex[0].y);
for (i = 0; i < steps; i++) {
xnext += xstep;
ynext += ystep;
rnext += rstep;
gnext += gstep;
bnext += bstep;
fprintf(file, "%g %g lineto stroke\n", xnext, ynext);
fprintf(file, "%g %g %g setrgbcolor\n", rnext, gnext, bnext);
fprintf(file, "%g %g moveto\n", xnext, ynext);
}
fprintf(file, "%g %g lineto stroke\n", vertex[1].x, vertex[1].y);
loc += 14; /* Each vertex element in the feedback
buffer is 7 GLfloats. */
break;
case GL_POLYGON_TOKEN:
nvertices = *loc;
loc++;
vertex = (Feedback3Dcolor *) loc;
if (nvertices > 0) {
red = vertex[0].red;
green = vertex[0].green;
blue = vertex[0].blue;
smooth = 0;
for (i = 1; i < nvertices; i++) {
if (red != vertex[i].red || green != vertex[i].green || blue != vertex[i].blue) {
smooth = 1;
break;
}
}
if (smooth) {
/* Smooth shaded polygon; varying colors at vetices. */
int triOffset;
/* Break polygon into "nvertices-2" triangle fans. */
for (i = 0; i < nvertices - 2; i++) {
triOffset = i * 7;
fprintf(file, "[%g %g %g %g %g %g]",
vertex[0].x, vertex[i + 1].x, vertex[i + 2].x,
vertex[0].y, vertex[i + 1].y, vertex[i + 2].y);
fprintf(file, " [%g %g %g] [%g %g %g] [%g %g %g] gouraudtriangle\n",
vertex[0].red, vertex[0].green, vertex[0].blue,
vertex[i + 1].red, vertex[i + 1].green, vertex[i + 1].blue,
vertex[i + 2].red, vertex[i + 2].green, vertex[i + 2].blue);
}
} else {
/* Flat shaded polygon; all vertex colors the same. */
fprintf(file, "newpath\n");
fprintf(file, "%g %g %g setrgbcolor\n", red, green, blue);
/* Draw a filled triangle. */
fprintf(file, "%g %g moveto\n", vertex[0].x, vertex[0].y);
for (i = 1; i < nvertices; i++) {
fprintf(file, "%g %g lineto\n", vertex[i].x, vertex[i].y);
}
fprintf(file, "closepath fill\n\n");
}
}
loc += nvertices * 7; /* Each vertex element in the
feedback buffer is 7 GLfloats. */
break;
case GL_POINT_TOKEN:
vertex = (Feedback3Dcolor *) loc;
fprintf(file, "%g %g %g setrgbcolor\n", vertex[0].red, vertex[0].green, vertex[0].blue);
fprintf(file, "%g %g %g 0 360 arc fill\n\n", vertex[0].x, vertex[0].y, pointSize / 2.0);
loc += 7; /* Each vertex element in the feedback
buffer is 7 GLfloats. */
break;
default:
/* XXX Left as an excersie to the reader. */
printf("Incomplete implementation. Unexpected token (%d).\n", token);
exit(1);
}
return loc;
}
void
spewUnsortedFeedback(FILE * file, GLint size, GLfloat * buffer)
{
GLfloat *loc, *end;
loc = buffer;
end = buffer + size;
while (loc < end) {
loc = spewPrimitiveEPS(file, loc);
}
}
typedef struct _DepthIndex {
GLfloat *ptr;
GLfloat depth;
} DepthIndex;
static int
compare(const void *a, const void *b)
{
DepthIndex *p1 = (DepthIndex *) a;
DepthIndex *p2 = (DepthIndex *) b;
GLfloat diff = p2->depth - p1->depth;
if (diff > 0.0) {
return 1;
} else if (diff < 0.0) {
return -1;
} else {
return 0;
}
}
void
spewSortedFeedback(FILE * file, GLint size, GLfloat * buffer)
{
int token;
GLfloat *loc, *end;
Feedback3Dcolor *vertex;
GLfloat depthSum;
int nprimitives, item;
DepthIndex *prims;
int nvertices, i;
end = buffer + size;
/* Count how many primitives there are. */
nprimitives = 0;
loc = buffer;
while (loc < end) {
token = *loc;
loc++;
switch (token) {
case GL_LINE_TOKEN:
case GL_LINE_RESET_TOKEN:
loc += 14;
nprimitives++;
break;
case GL_POLYGON_TOKEN:
nvertices = *loc;
loc++;
loc += (7 * nvertices);
nprimitives++;
break;
case GL_POINT_TOKEN:
loc += 7;
nprimitives++;
break;
default:
/* XXX Left as an excersie to the reader. */
printf("Incomplete implementation. Unexpected token (%d).\n",
token);
exit(1);
}
}
/* Allocate an array of pointers that will point back at
primitives in the feedback buffer. There will be one
entry per primitive. This array is also where we keep the
primitive's average depth. There is one entry per
primitive in the feedback buffer. */
prims = (DepthIndex *) malloc(sizeof(DepthIndex) * nprimitives);
item = 0;
loc = buffer;
while (loc < end) {
prims[item].ptr = loc; /* Save this primitive's location. */
token = *loc;
loc++;
switch (token) {
case GL_LINE_TOKEN:
case GL_LINE_RESET_TOKEN:
vertex = (Feedback3Dcolor *) loc;
depthSum = vertex[0].z + vertex[1].z;
prims[item].depth = depthSum / 2.0;
loc += 14;
break;
case GL_POLYGON_TOKEN:
nvertices = *loc;
loc++;
vertex = (Feedback3Dcolor *) loc;
depthSum = vertex[0].z;
for (i = 1; i < nvertices; i++) {
depthSum += vertex[i].z;
}
prims[item].depth = depthSum / nvertices;
loc += (7 * nvertices);
break;
case GL_POINT_TOKEN:
vertex = (Feedback3Dcolor *) loc;
prims[item].depth = vertex[0].z;
loc += 7;
break;
default:
/* XXX Left as an excersie to the reader. */
assert(1);
}
item++;
}
assert(item == nprimitives);
/* Sort the primitives back to front. */
qsort(prims, nprimitives, sizeof(DepthIndex), compare);
/* Understand that sorting by a primitives average depth
doesn't allow us to disambiguate some cases like self
intersecting polygons. Handling these cases would require
breaking up the primitives. That's too involved for this
example. Sorting by depth is good enough for lots of
applications. */
/* Emit the Encapsulated PostScript for the primitives in
back to front order. */
for (item = 0; item < nprimitives; item++) {
(void) spewPrimitiveEPS(file, prims[item].ptr);
}
free(prims);
}
#define EPS_GOURAUD_THRESHOLD 0.1 /* Lower for better (slower)
smooth shading. */
void
spewWireFrameEPS(FILE * file, int doSort, GLint size, GLfloat * buffer, char *creator)
{
GLfloat clearColor[4], viewport[4];
GLfloat lineWidth;
int i;
/* Read back a bunch of OpenGL state to help make the EPS
consistent with the OpenGL clear color, line width, point
size, and viewport. */
glGetFloatv(GL_VIEWPORT, viewport);
glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor);
glGetFloatv(GL_LINE_WIDTH, &lineWidth);
glGetFloatv(GL_POINT_SIZE, &pointSize);
/* Emit EPS header. */
fputs("%!PS-Adobe-2.0 EPSF-2.0\n", file);
/* Notice %% for a single % in the fprintf calls. */
fprintf(file, "%%%%Creator: %s (using OpenGL feedback)\n", file, creator);
fprintf(file, "%%%%BoundingBox: %g %g %g %g\n",
viewport[0], viewport[1], viewport[2], viewport[3]);
fputs("%%EndComments\n", file);
fputs("\n", file);
fputs("gsave\n", file);
fputs("\n", file);
/* Output Frederic Delhoume's "gouraudtriangle" PostScript
fragment. */
fputs("% the gouraudtriangle PostScript fragement below is free\n", file);
fputs("% written by Frederic Delhoume (delhoume@ilog.fr)\n", file);
fprintf(file, "/threshold %g def\n", EPS_GOURAUD_THRESHOLD);
for (i = 0; gouraudtriangleEPS[i]; i++) {
fprintf(file, "%s\n", gouraudtriangleEPS[i]);
}
fprintf(file, "\n%g setlinewidth\n", lineWidth);
/* Clear the background like OpenGL had it. */
fprintf(file, "%g %g %g setrgbcolor\n",
clearColor[0], clearColor[1], clearColor[2]);
fprintf(file, "%g %g %g %g rectfill\n\n",
viewport[0], viewport[1], viewport[2], viewport[3]);
if (doSort) {
spewSortedFeedback(file, size, buffer);
} else {
spewUnsortedFeedback(file, size, buffer);
}
/* Emit EPS trailer. */
fputs("grestore\n\n", file);
fputs("%Add `showpage' to the end of this file to be able to print to a printer.\n",
file);
fclose(file);
}
void
outputEPS(int size, int doSort, char *filename)
{
GLfloat *feedbackBuffer;
GLint returned;
FILE *file;
feedbackBuffer = calloc(size, sizeof(GLfloat));
glFeedbackBuffer(size, GL_3D_COLOR, feedbackBuffer);
(void) glRenderMode(GL_FEEDBACK);
render();
returned = glRenderMode(GL_RENDER);
if (filename) {
file = fopen(filename, "w");
if (file) {
spewWireFrameEPS(file, doSort, returned, feedbackBuffer, "rendereps");
} else {
printf("Could not open %s\n", filename);
}
} else {
/* Helps debugging to be able to see the decode feedback
buffer as text. */
printBuffer(returned, feedbackBuffer);
}
free(feedbackBuffer);
}
void
choice(int value)
{
switch (value) {
case 0:
glutSetCursor(GLUT_CURSOR_WAIT);
outputEPS(objectComplexity[object], 1, "render.eps");
glutSetCursor(GLUT_CURSOR_INHERIT);
break;
case 1:
glutSetCursor(GLUT_CURSOR_WAIT);
outputEPS(objectComplexity[object], 0, "render.eps");
glutSetCursor(GLUT_CURSOR_INHERIT);
break;
case 2:
/* Try to start GNU "ghostview" to preview the EPS. */
system("ghostview render.eps &");
break;
case 3:
glutSetCursor(GLUT_CURSOR_WAIT);
outputEPS(objectComplexity[object], 0, NULL);
glutSetCursor(GLUT_CURSOR_INHERIT);
break;
case 4:
blackBackground = 1 - blackBackground;
updateBackground();
glutPostRedisplay();
break;
case 5:
lighting = 1 - lighting;
updateLighting();
glutPostRedisplay();
break;
case 6:
polygonMode = (polygonMode + 1) % 3;
updatePolygonMode();
glutPostRedisplay();
break;
case 7:
size = (size % 5) + 1;
glLineWidth(size);
glPointSize(size);
glutPostRedisplay();
break;
case 8:
object = (object + 1) % 3;
glutPostRedisplay();
break;
case 666:
exit(0);
break;
}
}
/* ARGSUSED2 */
void
mouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
moving = 1;
begin = x;
}
if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
moving = 0;
}
}
/* ARGSUSED1 */
void
motion(int x, int y)
{
if (moving) {
angle = angle + (x - begin);
begin = x;
glutPostRedisplay();
}
}
GLfloat light_diffuse[] =
{0.0, 1.0, 0.0, 1.0}; /* Green light. */
GLfloat light_position[] =
{1.0, 1.0, 1.0, 0.0};
int
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
glutCreateWindow("rendereps");
glutDisplayFunc(display);
glutMouseFunc(mouse);
glutMotionFunc(motion);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHT0);
glMatrixMode(GL_PROJECTION);
gluPerspective( /* field of view in degree */ 22.0,
/* aspect ratio */ 1.0,
/* Z near */ 5.0, /* Z far */ 10.0);
glMatrixMode(GL_MODELVIEW);
gluLookAt(0.0, 0.0, 5.0, /* eye is at (0,0,5) */
0.0, 0.0, 0.0, /* center is at (0,0,0) */
0.0, 1.0, 0.); /* up is in postivie Y direction */
glTranslatef(0.0, 0.0, -3.0);
/* Give the object an "interesting" orientation. */
glRotatef(25, 1.0, 0.0, 0.0);
glutCreateMenu(choice);
glutAddMenuEntry("Write out Encapsulated PS (sorted)", 0);
glutAddMenuEntry("Write out Encapsulated PS (UNsorted)", 1);
glutAddMenuEntry("Spawn ghostview to view EPS", 2);
glutAddMenuEntry("Display feedback buffer", 3);
glutAddMenuEntry("Toggle black/white background", 4);
glutAddMenuEntry("Toggle lighting", 5);
glutAddMenuEntry("Switch fill mode (line, poly, point)", 6);
glutAddMenuEntry("Switch line/point size", 7);
glutAddMenuEntry("Switch object", 8);
glutAddMenuEntry("Quit", 666);
glutAttachMenu(GLUT_RIGHT_BUTTON);
updateBackground();
updateLighting();
updatePolygonMode();
glEnable(GL_DEPTH_TEST);
glColor3f(1.0, 0.0, 0.0); /* Geometry should appear red. */
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}
|