En aquest article extendrem la biblioteca d’objectes per a dibuixar objectes poligonals, eliminarem les parts ocultes i les cares de darrere i realçarem les aristes per a donar als objectes aparença de volum.

Primitives poligonals

Fins ara hem dibuixat els objectes usant només un conjunt d’anells. El que anem a fer ara és dibuixar les mateixes primitives però com una superfície formada per polígons.

La idea bàsica és que ara dibuixarem tires de quadrilàters com els de la següent figura:

poligons1

on els vèrtex parells estaran tots a la mateixa altura (en el mateix anell), i els senars a una distinta (en l’anell superior)

Ara utilitzarem cintes en lloc de tires (que utilitzavem per a fer anells), segons el següent codi:

void cinta(float R, float dR, float y, float dy, float N) {
  int i;
  float x, z;

  glBegin(GL_QUAD_STRIP);
  for(i = 0; i <= N; i++) {
    x = dameX(R, N, i);
    z = dameZ(R, N, i);
    glVertex3f(x, y, z);
    x = dameX(R+dR, N, i);
    z = dameZ(R+dR, N, i);
    glVertex3f(x, y+dy, z);
  }
  glEnd();
}

I el codi per a dibuixar una forma sòlida utilitzant tires serà el següent:

void formaSolida(float H, int N, float(*f)(float y)) {
  int i;
  float y, r1, r2;

  for(i = 0; i < N; i++) {
    y = i*H/N-(H/2);
    r1 = f(y);
    r2 = f(y+(H/N));
    cinta(r1, r2-r1, y, H/N, N);
  }
}

En la següent imatge veiem un cilindre sòlid:

cilindrosolido1

Si ens fixem veiem que només hem dibuixat el contorn, necessitem també la tapa superior i la tapa inferior per a que siga un cilindre completament sòlid com aquest:

cilindrosolido2

Per al que hem utilitzat el següent codi:

void tapa(float R, float y, int N) {
	int i;
	float x, z;
	glBegin(GL_POLYGON);
	for(i = 0; i < N; i++) {
		x = dameX(R, N, i);
		z = dameZ(R, N, i);
		glVertex3f(x, y, z);
	}
	glEnd();
}

float fCilindro(float y) {
	return(0.5);
}

void cilindroSolido(int N) {
	formaSolida(1.0f, N, fCilindro);
	tapa(fCilindro(0.5), 0.5, N);
	tapa(fCilindro(0.5), -0.5, N);
}

Eliminació de parts ocultes i cares de darrere

Al dibuixar superfícies és important que les que estiguen més a prop de l’observador oculten les que estan més llunyanes, i que aquesta ocultació siga independent de l’ordre en que es dibuixen les superfícies.

OpenGL implementa un algorisme d’ocultació basat en un buffer de profunditat (z-buffer). Per a utilitzar-lo hem d’aplicar els següents comandaments:

  1. Indicar que l’aplicació OpenGL va a utilitzar el z-buffer:
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    
  2. Activar el test de profunditat:
    glEnable(GL_DEPTH_TEST);
    
  3. Cada vegada que anem a dibuixar l’escena, a més d’esborrar el buffer de color, haurem d’esborrar el buffer de profunditat:
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    

I d’altra banda, podem eliminar les cares de darrere ja que aquestes no les veurem i les estem dibuixant; d’aquesta manera estalviarem un temps de càlcul que no es reflexarà en la imatge final. Aquesta tècnica s’anomena back-face culling.

Només hem d’utilitzar els següents comandaments d’OpenGL:

glFrontFace(GL_CW); /* GL_CW indica que són les cares els vèrtex de les quals estan definits en el sentit de les agulles del rellotge; GL_CCW al contrari */
glCullFace(GL_FRONT); /* També GL_BACK i GL_FRONT_AND_BACK */
glEnable(GL_CULL_FACE);

Realçat d’aristes

Per a que els objectes ens donen sensació de profunditat ara que encara no estem utilitzant la iluminació ni les textures, necessitem dibuixar les cares i també les aristes; simplement dibuixarem els nostres objectes dos vegades, en la primera passada com a polígons i en la segona només les aristes de les cares, com per exemple el següent codi:

// Primera passada, dibuixem polígons
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0, 1.0);
Objecte();
// Segona passada, dibuixem aristes
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glDisable(GL_POLYGON_OFFSET_FILL);
Objecte();

El codi que ens quedaria a l’afegir els anteriors comandaments seria el següent:

OpenGL és una biblioteca gràfica per a la creació d’escenes 3D, però des de l’ordinador veiem aquest món 3D a través d’una finestra de dos dimensions, paregut al que fa una càmera fotogràfica.

Quan fem una projecció, qualsevol objecte que estiga totalment fora del camp de visió, per exemple els objectes que es troben a l’esquena de l’observador, no apareixeran en la projecció 2D. I d’aquells objectes que caiguen parcialment dins del camp de visió, només es projectaran les parts que al retallar-se contra el volum de la vista, estiguen dins d’ell.

Projecció ortogonal

El tipus de projecció que hem utilitzat en els exemples dels anteriors articles és una projecció ortogonal. Aquesta projecció defineix un volum de la vista de tipus paralelepipèdic tal que:

La principal característica d’aquesta projecció és que el tamany dels objectes és independent de la distància a la que estan de l’observador; per exemple, dos cilindres del mateix tamany, un a cinc unitats i l’altre a deu unitats de distància de l’observador, es projectaran amb el mateix tamany.

Per a definir una projecció ortogonal en OpenGL utilitzem els següents comandaments:

glMatrix(GL_PROJECTION);
glLoadIdentity();
void glOrtho(esquerra, dreta, baix, dalt, lluny, prop); // ací definim la projecció

I per a definir-la en 2D usarem el següent:

void gluOrtho2D(esquerra, dreta, baix, dalt);

que defineix simplement una projecció ortogonal amb el plà davanter en -1 i el de darrere en 1.

Projecció perspectiva

La projecció ortogonal no dóna sensació de profunditat perquè el tamany dels objectes no depèn de la seua distància a l’observador. Per a aconseguir açò necessitem definir una projecció perspectiva; aquesta defineix un volum de la vista que és un prisma truncat de base rectangular com aquest:

La funció OpenGL que l’estableix és:

void gluPerspective(fovy, aspecte, prop, lluny);

on fovy és l’angle d’apertura de l’objectiu de la càmera (en graus), i aspecte és la relació ample/alt de la base de la piràmide.

Podem provar aquest canvi de perspectiva utilitzant el codi revolucion.c de l’anterior article; només haurem de canviar la línia 133:

glOrtho(-2.5f, 2.5f, -2.5f, 2.5f, -4.0f, 4.0f);

per la següent:

gluPerspective(45.0f, (GLdouble)w/h, 0.1, 10.0);

i a l’executar veurem la diferència entre aquestos dos tipus de projeccions.

OpenGL ens proporciona funcions per a control·lar tres tipus de transformacions geomètriques: translació, rotació i escalat; gràficament resulta senzill d’entendre:

  • Translació:
    translacio
  • Rotació:
    rotacio
  • Escalat:
    escalat

Aquestes transformacions es representen com a matrius 4×4 ordenades com vectors columna tal que:

matriu

No anem a entrar en profunditat en com apliquem matemàticament aquestes transformacions; el codi OpenGL que les realitza és el següent:

glTranslatef(1.0f, 1.0f, 1.0f); // per a translació
glRotatef(45.0f, 1.0f, 0.0f, 0.0f); // per a rotació
glScalef(2.0f, 2.0f, 2.0f);  // per a escalat
objecte();

Amb aquestes transformacions i els objectes que ja haviem creat, podem muntar per exemple una escena com aquesta:

trans1

void escena(){
	//esfera
	glPushMatrix();
	    glTranslatef(0.4f, 0.0f, 0.0f);
		glRotatef(0.0f, 0.0f, 0.0f, 0.0f);
		glScalef(1.4f, 1.4f, 1.4f);
		esferaAlambre(10);
	glPopMatrix();

	//esfera
	glPushMatrix();
	    glTranslatef(-0.4f, 0.0f, 0.0f);
		glRotatef(0.0f, 0.0f, 0.0f, 0.0f);
		glScalef(0.4f, 0.4f, 0.4f);
		esferaAlambre(10);
	glPopMatrix();

	//cilindre
	glPushMatrix();
	    glTranslatef(0.0f, 0.0f, 0.0f);
		glRotatef(45.0f, 0.0f, 0.0f, 1.0f);
		glScalef(2.4f, 2.4f, 2.4f);
		cilindroAlambre(10);
	glPopMatrix();
}

Finalment, utilitzant el següent codi podem usar el mouse per a moure la figura de diferents maneres, usant les transformacions que hem explicat anteriorment. Tampoc anem a entrar en detalls sobre el codi que hem afegit per a que açò funcione, però si n’esteu interessats, podeu analitzar revolucion.c.

El primer pas per a obtindre el modelatge d’un objecte consisteix en definir objectes de revolució.

Un objecte de revolució el definim amb un perfil i l’eix sobre el que gira el perfil. En el nostre cas, l’eix de revolució serà sempre l’eix ‘Y’ i el perfil el definirem com una funció que ens tornarà el valor de ‘X’ per als valors de ‘Y’ entre l’interval [-0.5, 0.5].

Gràficament la idea seria la següent:

rev1

Per exemple, per a modelar un cilindre la funció que haurem d’utilitzar serà la de la recta, i obtindriem un objecte de revolució com aquest:

cilindre

Per a obtindre l’anterior objecte de revolució hem utilitzat el següent codi:

Els objectes es dibuixen com un conjunt d’anells, i el radi de cada anell depèn de l’altura a la que es dibuixe. Depenent de la funció particular que ens torne el valor del radi com a funció de l’altura obtindrem unes figures o unes altres.

D’aquesta manera, anem a analitzar una mica de codi, que és el que realment ens interessa. El que anem a analitzar es troba en el fitxer modelado.c.

En primer lloc, el codi que dibuixa la figura de revolució és el següent:

/*--------------------------------------------------------------------------
 Algorisme de dibuix per a la figura de revolució
 H: altura de la figura
 N: nombre d'anells i divisions en cada anell
  --------------------------------------------------------------------------*/
void formaAlambre(float H, int N, float(*f)(float y)) {
	int i;
	float y, r;

	for(i = 0; i < N; i++) { // per a cada nivell d'altura
		y = i*H/N-(H/2);
		r = f(y);            // obtenim el radi
		anillo(r, y, N);     // i dibuixem un anell
	}
}

Per a canviar l’altura o el nombre d’anells només haurem de canviar els valors de H i N respectivament.

El codi que ens definix el perfil, és a dir, la figura de revolució que anem a obtindre, és el següent:

float fCilindro(float y) {
	return(0.5); // funció lineal
}

Per a obtindre un con només haurem de canviar aquest codi pel següent:

float fCon(float y) {
	return ((y+0.5f)/2); //funció del con
}

con

O una esfera:

float fEsfera(float y) {
	return((float)sqrt(fabs(0.25-(y*y)))); // funció de l'esfera
}

esfera

Podem provar diferents codis per a obtindre diferents figures geomètriques, com per exemple:

float f1(float y) {
	return((float)sqrt(fabs((((0.4-sqrt(fabs((0.04)-(0.25))))*(0.4-sqrt(fabs((0.04)-(0.25)))))-(y*y)))));
}

f1

float f2(float y) {
	return((float)sqrt(((0.015-(0.25-(sqrt(fabs(0.25+(y*y)))))*(0.25-(sqrt(fabs(0.25+(y*y))))))*(0.015-(0.25-(sqrt(fabs(0.25+(y*y)))))*(0.25-(sqrt(fabs(0.25+(y*y)))))))));
}

f2

float f3(float y) {
	return((float)sqrt(fabs(((0.5)*(0.5))-((y-0.4)*(y-0.4)))));
}

f3

És senzill veure que les tres figures anteriors tenen diferent nombre d’anells que les primeres; simplement modificant el nombre d’anells (valor de N) podem obtindre objectes com aquestos:

Amb 50 anells:

esfera1

Amb 100 anells:

esfera2

Amb 1000 anells:

esfera3

Podreu comprovar que el temps de resposta és molt diferent a l’executar aquestes figures canviant el nombre d’anells, sempre depenent del vostre hardware.

Per a accelerar aquest temps d’execució quan tenim molts objectes, OpenGL ens proporciona les llistes de visualització, una espècie de macros on podem emmagatzemar la geometria dels objectes que anem a dibuixar en la nostra escena, entre d’altres.

Encara que no anem a entrar en detalls sobre açò, un exemple d’utilització seria aquest:

escenario = glGenLists(1); /* Solicitamos un identificador */
if(escenario != 0) {              /* es valido si distinto de 0 */
    glNewList(escenario, GL_COMPILE); /* Inicia la definicion */
    escena();                         /* nuestros objetos */
    glEndList();                      /* finaliza la definición */
}

I per a dibuixar un objecte a través d’aquesta llista, utilitzariem:

glCallList(escenario);

Com ja hem comentat, podem usar OpenGL en qualsevol plataforma, de manera que podem utilitzar qualsevol entorn de desenvolupament que treballe amb C++.

Per exemple proposem Anjuta per a Linux, Dev-C++ en Windows i XCode per a Mac OS.

A part de l’entorn de desenvolupament, necessitarem un compilador de codi C++ i les llibreries OpenGL i glut.

Linux

Proposem l’entorn Anjuta, es tracta de software lliure que ve integrat en les distribucions ubuntu, entre d’altres.

anjuta.png

Normalment el sistema operatiu ja inclou el compilador, només haureu de buscar les llibreríes.

Mac OS

Podem usar les XCode tools en Mac OS.

xcode.png

En aquest cas, al crear un nou projecte, haurem d’afegir els frameworks de Cocoa, GLUT i OpenGL, inclosos en Existing Frameworks.

En aquest cas serà important substituïr els headers habituals en el codi pels següents:

CODE:
  1. #include <string.h>
  2. #include <stdio.h>
  3. #include <math.h>
  4. #include <stdlib.h>
  5. #include <time.h>
  6. #include <GLUT/glut.h>
  7. #include <OpenGL/glext.h>
  8. #include <OpenGL/gl.h>
  9. #include <OpenGL/glu.h>

Més informació ací i ací.

Windows

Podem utilitzar per exemple l’entorn lliure Dev-C++.

devc.jpg

Normalment la llibreria glut no ve instal·lada, així que podem descarregar el Pack Manager de Dev-c ací.

Inici

A continuació ja podem començar. Només hem de crear un nou projecte on ja podem provar per exemple el següent codi:

#ifndef OCUADRADO_H
#define OCUADRADO_H

/* Abre una ventana OpenGL */
void AbreVentana (int numeroArgumentos, char ** listaArgumentos);

/* Funcion de dibujado */
void Dibuja(void);

/* Establece el area visible */
void TamanyoVentana (int alto, int ancho);

/* Inicia las propiedades de la vista */
void IniciaVista (void);

#endif
#include "GL/glut.h"
#include "stdio.h"
#include "cuadrado.h"

/******************************************************************/
/* Funcion de dibujado                                            */
/* Parametros: Ninguno                                            */
/* Salida: Ninguna                                                */
/******************************************************************/
void Dibuja(void)
{
	/* Establece el color de borrado */
	glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

	/* Borra el buffer de color */
	glClear (GL_COLOR_BUFFER_BIT);

	/* Establece el color de dibujo */
	glColor3f (1.0f, 1.0f, 1.0f);

	/* Crea un poligono 2D (cuadrado) */
	glBegin (GL_POLYGON);
		glVertex2f (-0.5f, -0.5f);
		glVertex2f (-0.5f, 0.5f);
		glVertex2f (0.5f, 0.5f);
		glVertex2f (0.5f, -0.5f);
	glEnd ();

	/* Se asegura de que se ejecutan todas las ordenes */
	glFlush ();
}

/******************************************************************************************/
/* Establece el area visible                                                              */
/* Parametros: int ancho --> Ancho del area visible                                       */
/*             int alto --> Alto del area visible                                         */
/* Salida: Ninguna                                                                        */
/******************************************************************************************/
void TamanyoVentana (int ancho, int alto)
{
	glViewport (0, 0, ancho, alto);
}

/******************************************************************************************/
/* Inicia las propiedades de la vista                                                     */
/* Parametros: Ninguno                                                                    */
/* Salida: Ninguna                                                                        */
/******************************************************************************************/
void IniciaVista (void)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
}

/******************************************************************************************/
/* Abre una ventana OpenGL                                                                */
/* Parametros: int numeroArgumentos --> El numero de argumentos en la llamada al programa */
/*             char ** listaArgumentos --> Vector de cadenas con cada argumento           */
/* Salida: Ninguna                                                                        */
/******************************************************************************************/
void AbreVentana (int numeroArgumentos, char ** listaArgumentos)
{
	glutInit(&numeroArgumentos, listaArgumentos);
	glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
	glutInitWindowSize (500, 500);
	glutInitWindowPosition (100, 100);
	glutCreateWindow (listaArgumentos[0]);
	glutDisplayFunc (Dibuja);
	glutReshapeFunc (TamanyoVentana);
	IniciaVista ();
}

/******************************************************************************************/
/* Funcion principal                                                                      */
/* Parametros: int numeroArgumentos --> El numero de argumentos en la llamada al programa */
/*             char ** listaArgumentos --> Vector de cadenas con cada argumento           */
/* Salida: Un entero que se devuelve al sistema al acabar la ejecucion del programa       */
/******************************************************************************************/
int main(int numArgumentos, char ** listaArgumentos)
{
	/* Crea la ventana de la aplicaci¢n */
	AbreVentana (numArgumentos, listaArgumentos);

	/* Establece el bucle principal de control de OpenGL */
	glutMainLoop();

	return (0);
}

Després de compilar i executar, obtindríem una finestra com la següent:

cuadrado1.png

Des que ho vaig estudiar a la universitat la meua visió sobre els videojocs, animació per computador, simuladors… ha canviat considerablement. Informàtica gràfica només era una assignatura optativa que els primers cursos no m’atreia però després de conèixer l’opinió d’alguns companys vaig decidir cursar-la. I realment em va fascinar i va canviar la manera de veure aquest món.

Com que ja fa un temps que estic interessat en xarrar una mica sobre l’assumpte en plan pràctic i tal, per exemple en alguna Lan Party i pareix ser que no hi haja massa seguidors, he decidit postejar setmanalment un conjunt de xicotets articles sobre el tema (també per a donar vida a aquest moribund bloc…). Bàsicament consistents en les diferents pràctiques que vaig fer en el seu dia per a superar l’assignatura.

La llibreria que usarem és OpenGL i la biblioteca de funcions glut. OpenGL es una llibreria formada per uns cent vint comandaments aproximadament; entre les principals accions que podem realitzar s’inclouen l’especificació dels objectes i operacions necessàries per a produïr aplicacions interactives en 3D. S’ha dissenyat de forma independent del hardware de suport per a ser implementat sobre diferents plataformes; per això no hi han comandaments per a executar tasques dependents del hardware com per exemple la gestió de finestres o la lectura de dades d’entrada de l’usuari; tampoc proveeix comandaments d’alt nivell per a descriure models en 3D, de manera que el model ha de construïr-se a partir de primitives geomètriques simples com punts, línies i polígons.

Per exemple, amb aquest codi que podeu baixar d’ací (dof.c)


#include "GL/glut.h"
#include "stdlib.h"
#include "math.h"
#include "jitter.h"

#ifdef WIN32
#define near zNear
#define far zFar
#endif

#define PI_ 3.14159265358979323846

GLuint teapotList;

void accFrustum(GLdouble left, GLdouble right, GLdouble bottom,
   GLdouble top, GLdouble near, GLdouble far, GLdouble pixdx,
   GLdouble pixdy, GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
   GLdouble xwsize, ywsize;
   GLdouble dx, dy;
   GLint viewport[4];

   glGetIntegerv (GL_VIEWPORT, viewport);

   xwsize = right - left;
   ywsize = top - bottom;

   dx = -(pixdx*xwsize/(GLdouble) viewport[2] + eyedx*near/focus);
   dy = -(pixdy*ywsize/(GLdouble) viewport[3] + eyedy*near/focus);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glFrustum (left + dx, right + dx, bottom + dy, top + dy, near, far);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef (-eyedx, -eyedy, 0.0);
}

void accPerspective(GLdouble fovy, GLdouble aspect,
   GLdouble near, GLdouble far, GLdouble pixdx, GLdouble pixdy,
   GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
   GLdouble fov2,left,right,bottom,top;

   fov2 = ((fovy*PI_) / 180.0) / 2.0;

   top = near / (cos(fov2) / sin(fov2));
   bottom = -top;

   right = top * aspect;
   left = -right;

   accFrustum (left, right, bottom, top, near, far,
               pixdx, pixdy, eyedx, eyedy, focus);
}

void init(void)
{
   GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
   GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };

   GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
   GLfloat local_view[] = { 0.0 };

   glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
   glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
   glLightfv(GL_LIGHT0, GL_POSITION, position);

   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
   glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

   glFrontFace (GL_CW);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_AUTO_NORMAL);
   glEnable(GL_NORMALIZE);
   glEnable(GL_DEPTH_TEST);

   glClearColor(0.0, 0.0, 0.0, 0.0);
   glClearAccum(0.0, 0.0, 0.0, 0.0); 

   teapotList = glGenLists(1);
   glNewList (teapotList, GL_COMPILE);
   glutSolidTeapot (0.5);
   glEndList ();
}

void renderTeapot (GLfloat x, GLfloat y, GLfloat z,
   GLfloat ambr, GLfloat ambg, GLfloat ambb,
   GLfloat difr, GLfloat difg, GLfloat difb,
   GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine)
{
   GLfloat mat[4];

   glPushMatrix();
   glTranslatef (x, y, z);
   mat[0] = ambr; mat[1] = ambg; mat[2] = ambb; mat[3] = 1.0;
   glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
   mat[0] = difr; mat[1] = difg; mat[2] = difb;
   glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
   mat[0] = specr; mat[1] = specg; mat[2] = specb;
   glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
   glMaterialf (GL_FRONT, GL_SHININESS, shine*128.0);
   glCallList(teapotList);
   glPopMatrix();
}

void display(void)
{
   int jitter;
   GLint viewport[4];

   glGetIntegerv (GL_VIEWPORT, viewport);
   glClear(GL_ACCUM_BUFFER_BIT);

   for (jitter = 0; jitter < 8; jitter++) {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      accPerspective (45.0,
         (GLdouble) viewport[2]/(GLdouble) viewport[3],
         1.0, 15.0, 0.0, 0.0,
         0.33*j8[jitter].x, 0.33*j8[jitter].y, 5.0);

/*	ruby, gold, silver, emerald, and cyan teapots	*/
      renderTeapot (-1.1, -0.5, -4.5, 0.1745, 0.01175,
                    0.01175, 0.61424, 0.04136, 0.04136,
                    0.727811, 0.626959, 0.626959, 0.6);
      renderTeapot (-0.5, -0.5, -5.0, 0.24725, 0.1995,
                    0.0745, 0.75164, 0.60648, 0.22648,
                    0.628281, 0.555802, 0.366065, 0.4);
      renderTeapot (0.2, -0.5, -5.5, 0.19225, 0.19225,
                    0.19225, 0.50754, 0.50754, 0.50754,
                    0.508273, 0.508273, 0.508273, 0.4);
      renderTeapot (1.0, -0.5, -6.0, 0.0215, 0.1745, 0.0215,
                    0.07568, 0.61424, 0.07568, 0.633,
                    0.727811, 0.633, 0.6);
      renderTeapot (1.8, -0.5, -6.5, 0.0, 0.1, 0.06, 0.0,
                    0.50980392, 0.50980392, 0.50196078,
                    0.50196078, 0.50196078, .25);
      glAccum (GL_ACCUM, 0.125);
   }
   glAccum (GL_RETURN, 1.0);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB
			| GLUT_ACCUM | GLUT_DEPTH);
   glutInitWindowSize (400, 400);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutDisplayFunc(display);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}

Amb l’anterior codi obtindriem uns teapots tant xulos com aquestos:

dof.jpg

Amb el codi que us proposo ací podem muntar una escena com aquesta (podeu baixar l’executable ací, funciona amb windows amb les llibreríes correctament instalades):