INTRODUZIONE

Original Author: Damiano Vitulli

Translation by: Click here

Nella lezione precedente abbiamo acquisito i dati degli oggetti e li abbiamo memorizzati in una struttura. Nella lezione di oggi introdurremo la libreria grafica OpenGL e la libreria di utilità GLUT che ci permetteranno di disegnare il nostro oggetto sullo schermo. Prima però dobbiamo completare la nostra analisi sulle principali sezioni di un motore 3d erano infatti rimasti incompleti i punti 2 e 3:

  1. Acquisire i dati geometrici dell'oggetto e salvarli in strutture (Completato!)
  2. Trasformazioni per posizionare gli oggetti nel mondo (trasformazioni oggetto e camera).
  3. Disegnare la scena sullo schermo 2d (trasformazioni di proiezione e di viewport, rimozione facce nascoste, buffer colore e profondità)

Poi introdurremo la libreria grafica OpenGL e la libreria di utilità GLUT. Alla fine della lezione saremo in grado di visualizzare un oggetto solido rotante sul nostro schermo!

TRASFORMAZIONI PER POSIZIONARE GLI OGGETTI NEL MONDO (MODELING TRANSFORMATION e VIEWING TRANSFORMATION)

Quello che ci accingeremo a fare sarà nient'altro che prendere i punti che compongono l'oggetto, i vertici, ed applicare ad essi delle opportune trasformazioni che ci porteranno a visualizzarlo su schermo, utilizzando le librerie grafiche. Noi vogliamo creare un mondo, o per essere più esatti (essendo fanatici dei simulatori spaziali) un intero universo.

Per questo la prima trasformazione che andremo ad applicare al nostro oggetto sarà la MODELING TRANSFORMATION. Poichè la nostra intenzione è soprattutto quella di creare delle astronavi che fino a prova contraria non sono oggetti statici, ma si muovono nello spazio circostante, dobbiamo trasformare le coordinare dell' oggetto da locali (cioè relative alla posizione centrale dell'oggetto stesso) ad assolute. Dobbiamo quindi traslare l'oggetto, sommando algebricamente la posizione locale dei vertici alla posizione attuale dell'oggetto nel mondo (che può variare continuamente se è in movimento). Lo stesso procedimento dovrà poi essere applicato per le rotazioni dell'oggetto.

In seguito dobbiamo applicare all'oggetto posizionato nel mondo una ulteriore trasformazione: la VIEWING TRANSFORMATION. La nostra più grande felicità nel creare un mondo è quella di poterlo esplorare muovendoci a nostro piacimento, rendendo il nostro monitor una telecamera. Come effettuare questo tipo di trasformazione? La risposta è relativamente semplice, infatti basterà considerare sempre la nostra telecamera posizionata alle coordinate 0,0 e rotazioni nulle. E poi? Semplice! Qualunque movimento effettuerà la telecamera dovremmo applicare la trasformazione inversa al nostro oggetto anziché muovere la telecamera. Supponiamo ad esempio di muoverci verso il nostro oggetto di +10 punti sull' asse Z e di guardare poi in alto roteando la testa sull'asse x di 40 gradi. In realtà in tutti i motori grafici ciò che realmente accade è che l'oggetto effettuerà un movimento di -10 su Z ed in seguito una rotazione di -40 gradi sull'asse x. Questo semplifica moltissimo la gestione del motore in quanto la telecamera sarà sempre posizionata all'origine. Quindi la prima trasformazione che subiranno i vertici del nostro oggetto sarà quella di "essere traslati" in base ai valori inversi alla nostra posizione nello spazio ed "essere ruotati" in base ai valori inversi della nostra rotazione.

DISEGNARE LA SCENA SULLO SCHERMO 2D (BACK FACE CULLING, PROJECTION TRANSFORMATION e PROJECTION TRANSFORMATION, COLOR BUFFER e DEPTH BUFFER)

Dopo aver effettuato tutti questi calcoli sulla nuova posizione dei triangoli relativa al punto di vista e' necessario eseguire delle operazioni di BACK FACE CULLING. Cioè bisogna escludere tutti i triangoli non visibili nella scena, in modo tale che le successive operazioni non siano appesantite inutilmente. Quali sono i triangoli non visibili? Tutti i triangoli presenti al di fuori della nostra viewport e tutti quelli che si trovano a comporre facce attualmente non visibili degli oggetti, come ad esempio le facce posteriori.

La prossima operazione da effettuare sarà la PROJECTION TRANSFORMATION. Infatti noi abbiamo bisogno di visualizzare uno spazio tridimensionale su di uno schermo bidimensionale. Dobbiamo simulare l'asse Z perchè il nostro povero monitor ha solo due assi X e Y. Se ci pensate bene l'unico modo per effettuare questo tipo di transformazione è quello di suddividere tutte le coordinate X e Y in base alla loro componente Z. L'effetto di questo procedimento è quello di comprimere i punti distanti avvicinandoli al punto centrale x=0 e y=0. Ed è quello che accade in realtà e che molti di voi avranno studiato a scuola con il termine di proiezione prospettica.

L'ultima trasformazione da fare è la PROJECTION TRANSFORMATION. Questa operazione si occuperà di adattare la scena alla corrente risoluzione dello schermo.

Per buffer si intende una zona di memoria nella quale possiamo salvare o leggere dei dati. I nostri buffer sono delle zone grandi esattamente quanto la nostra viewport. Ad esempio se abbiamo aperto una finestra di 640 x 480 abbiamo allocato un buffer grande 640 x 480 = 307200 pixel, che significa, nel caso stiamo usando 16 bit di profondità di colore: 307200*16 = 4915200 bit. Che corrispondono a circa 614 kbyte di memoria video!

OpenGL usa due buffer principali per poter disegnare: il COLOR BUFFER (che può essere singolo o doppio) ed il DEPTH BUFFER.

Il primo è ciò che noi vediamo su schermo, dove vanno a finire tutte le operazioni di disegno. Dopo che sono stati effettuati tutti i calcoli geometrici OpenGL inizia a riempire questo buffer pixel per pixel riempiendo tutti i nostri poligoni e mostrandoceli su schermo una volta che l'operazione è stata completata. Se la scena è animata il color buffer viene disegnato e cancellato ogni frame. Spesso questo buffer viene utilizzato in modalita duale, è la tecnica del DOUBLE BUFFER, che noi useremo. Questa consiste nel visualizzare un buffer mentre l'altro buffer si pulisce e si riempie con il frame successivo, una volta che questa operazione è stata completata si scambiano i buffer. In questo modo l'animazione risultante è praticamente priva di sfarfallii e si ottimizzano le attese.

Supponiamo adesso che nella nostra scena ci siano due triangoli uno dietro l'altro, entrambi rivolti verso di noi e quindi visibili. In questo caso l'ordine con cui si disegnano i triangoli è molto importante, se noi disegnamo prima il triangolo più vicino e poi quello più lontano i pixel che compongono quest'ultimo si sovrapporrebbero al triangolo più vicino creando effetti non realistici. Una tecnica per evitare questo inconveniente è quella di ordinare tutti i triangoli visibili e poi disegnarli in ordine dai più lontani ai più vicini, questa si chiama "tecnica del pittore". Noi non utilizzeremo questa tecnica perchè OpenGL ci fornisce uno strumento ben più potente: il DEPTH BUFFER. Questo buffer ha le stesse dimensioni del color buffer ma invece di contenere i colori dei pixel è utilizzato come contenitore di informazioni che riguardano la profondità sull'asse Z di ogni pixel. Salvare queste informazioni è molto importante per un semplice motivo, quando andiamo a disegnare i nostri triangoli pixel per pixel sullo schermo facciamo un test per vedere se il pixel che andiamo a stampare è più vicino del pixel che già si trova su quel punto, se è più vicino allora aggiorniamo il depth buffer con il nuovo valore ed abilitiamo anche la scrittura sul color buffer altrimenti scartiamo il pixel. Questo produce risultati ottimi senza tuttavia pesare troppo sulla velocità di esecuzione nelle moderne schede video

Noi non ci dovremmo preoccupare di effettuare tutte queste operazioni a mano in quanto la nostra libreria OpenGL si occuperà di svolgere gran parte di questo lavoro e si occuperà anche della parte di stampa su schermo, disegnando i nostri triangoli ed applicando ad essi gli effetti di colorazione, di luce e di mappatura.

Voi non dovrete fare proprio niente! Quindi che cosa stiamo facendo qui? Stiamo perdendo tempo? No! Il nostro compito infatti è quello di fornire ad OpenGL tutte le informazioni di cui ha bisogno per svolgere il suo lavoro a basso livello interfacciandosi anche con la nostra scheda video, sfruttando se possibile l'accelerazione hardware.

FINALMENTE OPENGL!

OpenGL è una libreria grafica che ci consente di interfacciarsi all'hardware grafico, Ci mette a disposizione una serie di funzioni per disegnare punti, linee e poligoni, effettua tutti i calcoli necessari per l'illuminazione, lo shading, la trasformazione dei vertici ecc. Glut invece e' una libreria di utilita' usata per interfacciarsi con il window system, per questo indipendente dalla piattaforma utilizzata (Windows o Linux), ci permette di creare una finestra, cosi' da lasciarci disegnare, e si occupa anche di acquisire lo stato della tastiera.

La struttura di un programma OpenGL si divide in varie sezioni, che spiegheremo in dettaglio:

  1. Funzione di inizializzazione, usata per inizializzare OpenGL e le matrici di modeling/viewing e di projection e per effettuare tutte le inizializzazioni di cui necessitiamo.
  2. Funzione di resize, richiamata ogni volta che l'utente avvia il programma o cambia le dimensioni della finestra di output, ogni volta che accadono queste cose infatti occorre comunicare ad OpenGL la nuova viewport.
  3. Funzione di acquisizione da tastiera, richiamata ogni volta che l'utente preme un tasto
  4. Funzione di disegno della scena, nella quale si puliscono i buffer di colore e di profondita, si effettuano tutte le trasformazioni di modeling e di viewing, si disegna la scena tramite i comandi sulle primitive grafiche e si applicano le trasformazioni di projection, infine si invertono i 2 buffer di colore, nel caso si stia usando il double buffering
  5. Ciclo principale, praticamente un loop infinito racchiuso nel main, il quale richiama ad ogni frame tutte le funzioni necessarie.

Una tipica funzione OpenGL ha questa sintassi: glFunctionName(GL_TYPE arguments). Le funzioni Glut invece hanno questa sintassi glutFunctionName(arguments). OpenGL ha anche tipi di dati proprietari per aiutare la portabilità del codice. Questi tipi iniziano con il prefisso "GL" a sono seguiti da "u" (per i tipi senza segno) e dal tipo (float, int ecc.). Per esempio possiamo utilizzare GLfloat oppure GLuint per definire variabili compatibili rispettivamente con i tipi float e int.

HEADERS

La prima cosa da fare e' di includere gli header necessari, windows.h (per gli utilizzatori di windows) e glut.h.

#include <windows.h>
#include <GL/glut.h>

Tramite l'inclusione di glut.h abbiamo anche incluso implicitamente gl.h e glu.h gli header di OpenGL. Occorre inoltre includere nelle opzioni di compilazione anche le librerie opengl32.lib glu32.lib e glut32.lib

Dopo aver definito l'oggetto tramite il codice della scorsa lezione dichiariamo una funzione che nella quale inizializzeremo OpenGL tramite alcune funzioni.

FUNZIONE DI INIZIALIZZAZIONE

void init(void)
{
   glClearColor(0.0, 0.0, 0.2, 0.0);
   glShadeModel(GL_SMOOTH);
   glViewport(0,0,screen_width,screen_height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(45.0f,(GLfloat)screen_width/(GLfloat)screen_height,1.0f,1000.0f);
   glEnable(GL_DEPTH_TEST);
   glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
}

Cominciamo ora ad analizzare il codice:

  • void glClearColor( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) questa funzione specifica le componenti rossa, verde, blu ed alpha usate da glClear per pulire il color buffer (lo schermo). Come colore di sfondo abbiamo scelto un blu scuro, avendo assegnato alla componente blu il valore 0.2. Dimenticavo di ricordarvi che di solito in OpenGL il range valido per effettuare qualunque tipo di parametrazione va da 0 a 1.
  • void glShadeModel( GLenum mode ) specifica la modalità di shading, cioè il modo con cui verranno riempiti i triangoli. Se in mode si inserisce il valore GL_FLAT allora ogni singolo triangolo sarà disegnato in modalita flat shading, cioè il colore sarà uniforme. Invece usando GL_SMOOTH si usa un' altra modalità di shading, chiamata anche gouraud shading, che effettua delle interpolazioni lineari tra i colori dei vertici in modo da ottenere degli effetti di colore graduale.
  • void glViewport( GLint x, GLint y, GLsizei width, GLsizei height) imposta le dimensioni della viewport per la viewport transformation.
  • void glMatrixMode( GLenum mode) Questa funzione seleziona il tipo di matrice per le successive operazioni con le matrici. Cosa è una matrice? Fino ad ora non abbiamo parlato delle matrici, in realtà il discorso è troppo complesso e dedicheremo un tutorial completo per affrontarlo in dettaglio. Per adesso vi può bastare sapere che una matrice è un oggetto utilizzato da OpenGL per effettuare i calcoli necessari per le trasformazioni di cui abbiamo parlato. Possiamo utilizzare due matrici in mode: la GL_PROJECTION per effettuare trasformazioni prospettiche e GL_MODEL per effettuare trasformazioni di vista e di modello. Una volta specificata la matrice attiva è possibile modificarla a piacimento, OpenGL successivamente la utilizzerà per disegnare la scena e disporre gli oggetti secondo il nostro volere.
  • void glLoadIdentity( void ) Resetta la matrice corrente caricando la matrice identità (questi concetti ci saranno più chiari in seguito).
  • void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble near,GLdouble far) Vi ricordate la projection transformation? Questa è la fase in cui concretizziamo quanto detto, infatti con questa funzione comunichiamo ad OpenGL di caricare la matrice GL_PROJECTION con i valori relativi ai parametri che abbiamo inserito a gluPerspective. In fovy dobbiamo specificare the angle of the field of view, in aspect l'aspect ratio, in near e far la distanza minima e massima dei piani di clipping nella nostra scena. OpenGL userà questa matrice ogni volta che renderizzerà la scena, usandola per effettuare la trasformazione prospettica.
  • void glEnable( GLenum cap ) Questo comando abilità varie opzioni. In questa situazione lo utilizzeremo per abilitare lo Z Buffer (DEPTH BUFFER).
  • void glPolygonMode( GLenum face, GLenum mode ) Tramite questa funzione possiamo decidere di disegnare i poligoni come punti, linee o riempiti (usando rispettivamente GL_POINT,GL_LINES or GL_FILL come parametri).

FUNZIONE DI RESIZE

Questa funzione è molto simile alla "init": pulisce i buffer ridefinisce la viewport e disegna nuovamente la scena.

void resize (int width, int height)
{
   screen_width=width; 
   screen_height=height; 
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glViewport(0,0,screen_width,screen_height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(45.0f,(GLfloat)screen_width/(GLfloat)screen_height,1.0f,1000.0f);
   glutPostRedisplay ();
}

Queste sono le funzioni che ancora non abbiamo descritto:

  • void glClear( GLbitfield mask ); pulisce i buffer specificati nel campo "mask". Possiamo inserire più di un buffer separando i campi con l'operatore logico OR "|". Nel nostro caso abbiamo cancellato il color buffer ed il depth buffer.
  • La chiamata a glViewport è necessaria qui per ridefinire la nuova viewport con i valori salvati in screen_width e screen_height.
  • void glutPostRedisplay(void); Questa è una funzione della libreria GLUT e si occupa di richiamare la routine inserita in glutDisplayFunc per disegnare e/o aggiornare la scena.

FUNZIONI PER LA TASTIERA

Definiremo ora due funzioni per la tastiera: una per gestire gli eventi corrispondenti alla pressione di tasti ASCII ("r" and "R" and a blank character ' ') ed un'altra per gestire i tasti direzione:

void keyboard (unsigned char key, int x, int y)
{
   switch (key)
   {
      case ' ':
         rotation_x_increment=0;
         rotation_y_increment=0;
         rotation_z_increment=0;
      break;
      case 'r': case 'R':
         if (filling==0)
         {
            glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
            filling=1;
         } 
         else 
         {
            glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
            filling=0;
         }
      break;
   }
}

Utilizziamo tre variabili per far ruotare il nostro oggetto intorno all'asse desiderato: rotation_x_increment, rotation_y_increment and rotation_z_increment. Possiamo resettare queste variabili utilizzando la barra spaziatrice per fermare l'oggetto nella sua posizione corrente. Possiamo anche cambiare la modalità di disegno per i poligoni (wireframe o solidi) utilizzando i tasti "r" o "R".

void keyboard_s (int key, int x, int y)
{
   switch (key)
   {
      case GLUT_KEY_UP:
         rotation_x_increment = rotation_x_increment +0.005;
      break;
      case GLUT_KEY_DOWN:
         rotation_x_increment = rotation_x_increment -0.005;
      break;
      case GLUT_KEY_LEFT:
         rotation_y_increment = rotation_y_increment +0.005;
      break;
      case GLUT_KEY_RIGHT:
         rotation_y_increment = rotation_y_increment -0.005;
      break;
   }
}

Questa funzione è molto simile alla precedente ma gestisce i tasti direzione. Vi prego di notare le costanti GLUT: GLUT_KEY_UP, GLUT_KEY_DOWN, GLUT_KEY_LEFT e GLUT_KEY_RIGHT che aumentano e diminuiscono le velocità di rotazione dell'oggetto secondo gli assi che identificano.

FUNZIONE DI DISEGNO

Signore e signori ecco ciò che stavate attendendo con ansia: la funzione di disegno!

void display(void)
{
   int l_index;
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0,0.0,-50);
   rotation_x = rotation_x + rotation_x_increment;
   rotation_y = rotation_y + rotation_y_increment;
   rotation_z = rotation_z + rotation_z_increment;
   if (rotation_x > 359) rotation_x = 0;
   if (rotation_y > 359) rotation_y = 0;
   if (rotation_z > 359) rotation_z = 0;
   glRotatef(rotation_x,1.0,0.0,0.0);
   glRotatef(rotation_y,0.0,1.0,0.0);
   glRotatef(rotation_z,0.0,0.0,1.0);

La prima parte di questa funzione pulisce i buffer colore e profondità ed applica le trasformazioni oggetto e vista. Per prima cosa selezioniamo come matrice correntemente attiva la modelview matrix tramite la funzione glMatrixMode (GL_MODELVIEW). Poi, inizializziamo questa matrice ogni frame tramite una chiamata a glLoadIdentity.

  • void glTranslatef( GLfloat x, GLfloat y, GLfloat z ); muove il nostro oggetto nello spazio 3D. Questa funzione moltiplica la matrice oggetto per la matrice di traslazione definita utilizzando i parametri x,y,z. La lettera "f" dopo glTranslate indica che stiamo utilizzando valori float, utilizzando invece la lettera "d" avremmo utilizzato valori double. Utilizziamo glTranslate per muovere l'oggetto di 50 punti in avanti. Vi ricordate la videocamera e la viewing transformation? Bene, possiamo considerare questa operazione come una traslazione di -50 applicata alla videocamera. Questo movimento è necessario poichè ci dobbiamo spostare di una piccola distanza lontano dall'oggetto in modo tale da vederlo pienamente. Potete provare a variare a piacimento il valore Z in modo da notare l'infuenza che esso ha sulla distanza.
  • void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z ); questa funzione moltiplica la matrice corrente per una matrice rotazione. Questa è utilizzata ruotare l'oggetto di una quantità di gradi corrispondente al campo "angle" intorno al vettore x,y,z. Le variabili rotation_x, rotation_y e rotation_z hanno un range che va da 0.0 a 1.0 ed assegnano il livello di influenza della rotazione su ogni signolo asse. Consideriamo questa trasformazione una trasformazione oggetto poichè il punto di vista non ne è apparentemente influenzato, in realtà senza punti di riferimento fissi le trasformazioni applicate all'oggetto o alla telecamera sono praticamente equivalenti, ciascuna può essere vista la complementare dell'altra. Comunque per ora non dobbiamo preoccuparci di questo, affronteremo questo problema in futuro durante il tutorial che avrà come argomento proprio la videocamera.
   glBegin(GL_TRIANGLES);
   for (l_index=0;l_index<12;l_index++)
   {
      glColor3f(1.0,0.0,0.0);
      glVertex3f( cube.vertex[ cube.polygon[l_index].a ].x, cube.vertex[ cube.polygon[l_index].a ].y, cube.vertex[ cube.polygon[l_index].a ].z);
      glColor3f(0.0,1.0,0.0);
      glVertex3f( cube.vertex[ cube.polygon[l_index].b ].x, cube.vertex[ cube.polygon[l_index].b ].y, cube.vertex[ cube.polygon[l_index].b ].z);
      glColor3f(0.0,0.0,1.0);
      glVertex3f( cube.vertex[ cube.polygon[l_index].c ].x, cube.vertex[ cube.polygon[l_index].c ].y, cube.vertex[ cube.polygon[l_index].c ].z);
   }
   glEnd();
   glFlush();
   glutSwapBuffers();
}

La seconda parte della funzione display utilizza le funzioni glBegin e glEnd. Questi due comandi delimitano i vertici che definiscono una primitiva grafica.

  • void glBegin( GLenum mode ); indica l'inizio di una lista di dati di vertici che definiscono una primitiva grafica. Nel parametro "mode" dobbiamo inserire il tipo di primitiva che vogliamo disegnare. Ci sono 10 differenti tipologie di primitive che OpenGL ci permette di specificare (GL_TRIANGLES, GL_POYLGON, GL_LINES etc.). Noi utilizzeremo GL_TRIANGLES per disegnare il nostro cubo utilizzando 12 triangoli. Per disegnare un singolo triangolo utilizzeremo tre chiamate a glVertex3f e glColor3f.
  • void glVertex3f( GLfloat x, GLfloat y, GLfloat z ); specifica le coordinate x,y,z di un vertice. Il suffisso "3f" indica che la funzione accetta come parametri in ingresso tre valori float. In caso avreste avuto bisogno di utilizzare valori bidimensionali in doppia precisione il comando corretto sarebbe stato: void glVertex2d( GLdouble x, GLdouble y ). Inseriamo quindi i dati geometrici dell'oggetto dentro ogni singolo parametro.
  • void glColor3f( GLfloat red, GLfloat green, GLfloat blue ); specifica il colore correntemente attivo. Definiamo un colore differente (rosso 1,0,0 - verde 0,1,0 - blu 0,0,1) per ogni vertice. Vi prego di notare come la modalità GL_SMOOTH influenza il colore finale del triangolo. Vediamo infatti che i colori sono interpolati gradualmente da vertice a vertice in modo da cambiare gradualmente ed armoniosamete. E' molto bello da vedere!
  • void glEnd( void ); termina la definizione dell'oggetto.
  • void glFlush( void ); utilizzata per forzare ad OpenGL un aggiornamento dello schermo. I buffer colore e profondità sono riempiti e la scena è finalmente visibile!
  • void glutSwapBuffers(void); Quando ci troviamo nella modalità double buffer questa funzione scambia il buffer nascosto (back buffer), il luogo nel quale tutte le funzioni di disegno lavorano, con il buffer visibile (front buffer), ciò che vediamo nella finestra video. Se non ci troviamo nella modalità double buffer questa funzione non ha effetto.

LA FUNZIONE PRINCIPALE

int main(int argc, char **argv)
{

Le seguenti quattro funzioni della libreria glut ci permettono di creare una finestra grafica senza troppi problemi.

   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(screen_width,screen_height);
   glutInitWindowPosition(0,0);
   glutCreateWindow("www.spacesimulator.net - 3d engine tutorials: Tutorial 2");
  • void glutInit(&argc, argv); inizializza la libreria glut. Dobbiamo chiamare questa funzione prima di chiamare qualunque altra funzione glut.
  • void glutInitDisplayMode(unsigned int mode); imposta la modalità di visualizzazione. Le costanti glut GLUT_DOUBLE, GLUT RGB, GLUT DEPTH definiscono la modalità double buffer, la modalità RGB color mode e la modalità depth buffer.
  • void glutInitWindowSize(int width, int height); e void glutInitWindowPosition(int x, int y); utilizzate per impostare le dimensioni e la posizione iniziale della finestra di disegno.
  • int glutCreateWindow(char *name); crea la nostra finestra di disegno.

Per definire le funzioni di callback utilizziamo:

   glutDisplayFunc(display);
   glutIdleFunc(display);
   glutReshapeFunc (resize);
   glutKeyboardFunc (keyboard);
   glutSpecialFunc (keyboard_s);
   init();
   glutMainLoop();
}
  • void glutDisplayFunc(void (*func) (void)); specifica la funzione da chiamare quando la finestra deve essere disegnata, cioè ad esempio quando c'è una chiamata a glutPostRedisplay.
  • void glutIdleFunc(void (*func) (void)); imposta la funzione di idle cioè la funzione richiamata quando non ci sono altri eventi attivi. Questo significa che la nostra funzione idle è praticamente chiamata sempre, a meno che non ci siano altri eventi del window system in corso. Questa manterrà la fluidità della nostra animazione.
  • void glutReshapeFunc(void (*func) (void)); imposta la funzione di resizing
  • void glutKeyboardFunc(void (*func) (void)); imposta la funzione da chiamare quando vengono permuti caratteri ASCII.
  • void glutSpecialFunc(void (*func) (void)); imposta la funzione da chiamare per i tasti non ASCII.
  • void glutMainLoop(void); avvia un ciclo infinito con processing di eventi.

CONCLUSIONI

Ed ora? Si, abbiamo finalmente terminato! Lo so, è stato un lavoro duro ma guardate un pò... ora siamo capaci di creare un vero oggetto rotante 3d! Questo significa che abbiamo creato il nostro primo motore 3d!

Nella prossima lezione studieremo come realizzare il texture mapping.

SOURCE CODE

The Source Code of this lesson can be downloaded from the Tutorials Main Page