Nella lezione precedente abbiamo visto come sia possibile, tramite OpenGL e la libreria GLUT, ottenere un semplice disegno completo di animazione. Ora vorremmo che il nostro programma disegnasse qualcosa di più complesso che un quadrato e due triangoli.
OpenGL non certo brilla per i suoi strumenti di disegno, l’unico modo per farlo è ricorrere ai punti o meglio vertici che vengono combinati per ottenere tutte le figure che ci possono venire in mente. Due vertici si usano per tracciare una linea, tre per un triangolo e così via. A nostra disposizione sono quindi punti, linee e poligoni che sono poi il cuore della grafica tridimensionale.
La prima cosa da definire è dove disegnare, con OpenGL infatti inseriamo degli oggetti in una scena tridimensionale, ciò che l’osservatore vedrà sarà l’immagine prodotta dal motore grafico. Per specificare un vertice nel nostro ambiente tridimensionale esistono le funzioni della famiglia glVertex che prendono come argomenti le coordinate del vertice che è quello che abbiamo studiato a scuola nella geometria analitica: abbiamo un piano con tre assi perpendicolari tra loro ( x, y e z ) che si intersecano in un punto P(0, 0, 0) chiamato origine. Per disegnare un vertice basta allora richiamare una delle 24 varianti del comando glVertex ed inserire le coordinate dei tre punti, per avere un disegno bidimensionale basta porre z = 0.
Ora sappiamo come inserire un vertice, ma non ancora come costruire qualcosa con esso. Il modo di tracciare le primitive grafiche con OpenGL è un po’ inusuale, che però rispecchia la sua natura Client-Server. Si chiama una funzione che informa il sistema che si inizia a disegnare la primitiva ( glBegin ), successivamente si inviano i vertici che compongono la primitiva grafica e poi si avvisa del completamento del disegno ( glEnd ). Come abbiamo visto nel primo esempio della scorsa lezione, per disegnare un triangolo occorre passare alla funzione glBegin il tipo di primitiva che nel caso di GL_TRIANGLES si chiede che i vertici a tre a tre formino triangoli. Ecco la lista dei possibili valori:
- GL_POINTS – Disegnare dei semplici punti.
- GL_LINES – Disegnare delle linee.
- GL_LINE_STRIP – Disegnare delle linee congiunte.
- GL_LINE_LOOP – Disegnare delle linee a formare figure chiuse.
- GL_TRIANGLES – Disegnare dei triangoli.
- GL_TRIANGLE_STRIP – Disegnare dei triangoli congiunti.
- GL_TRIANGLE_FAN – Disegnare triangoli che partono dallo stesso punto.
- GL_QUADS – Disegnare quadrilateri.
- GL_QUAD_STRIP – Disegnare quadrilateri congiunti.
- GL_POLYGON – Disegnare poligoni.
Occorre precisare che se dovessero esserci dei vertici spaiati, ad esempio se si usa GL_TRIANGLES ed i vertici non sono multipli di tre, questi verranno ignorati. Altra cosa a cui stare attenti è il fatto che non tutti i poligoni vanno bene ad OpenGL: solo quelli convessi verranno disegnati correttamente, quelli concavi provocano risultati a video non corretti, la soluzione è spezzare il poligono in uno o più convessi.
La spiegazione del perché ci siano così tanti parametri di costruzione è legata al risparmio, per esempio un cubo è formato da sei facce ( poligoni ) ed occorre utilizzare 6 chiamate per ognuna con GL_POLYGON, in totale 36; se guardiamo bene però un cubo è formato solo da quadrati ed allora è meglio utilizzare GL_QUADS, in questo caso avremo 26 chiamate totali. Se guardiamo ancora meglio osserviamo che l’area laterale può essere fatta con GL_QUAD_STRIP e poi coprirla con due GL_QUAD, in questo caso le chiamate totali saranno solo 22. Il primo modo di ottimizzare il codice è quindi quello di scegliere la modalità di disegno più adatta all’oggetto da realizzare.
// File FirstWin.cpp per una specie d'uccello #include "GL/glut.h" void Init() { glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_FLAT); glEnable(GL_DEPTH_TEST); } void Draw() { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glColor3f(1.0f, 0.9f, 0.5f); glBegin(GL_QUADS); // testa glVertex3f(10.0f, 30.0f, 0.0f); glVertex3f(10.0f, 40.0f, 0.0f); glVertex3f(-5.0f, 40.0f, 0.0f); glVertex3f(-5.0f, 30.0f, 0.0f); glEnd(); glColor3f(0.0f, 0.5f, 1.0f); glBegin(GL_TRIANGLES); // becco ed ali glVertex3f(-5.0f, 34.0f, 0.0f); glVertex3f(-5.0f, 38.0f, 0.0f); glVertex3f(-28.0f, 15.0f, 0.0f); glColor3f(0.3f, 0.5f, 0.8f); glVertex3f(10.0f, 15.0f, 0.0f); glVertex3f(20.0f, 20.0f, 0.0f); glVertex3f(35.0f, -15.0f, 0.0f); glEnd(); glColor3f(0.1f, 0.5f, 0.0f); glBegin(GL_POLYGON); // corpo glVertex3f(15.0f, -10.0f, 0.0f); glVertex3f(35.0f, 10.0f, 0.0f); glVertex3f(30.0f, 25.0f, 0.0f); glVertex3f(5.0f, 35.0f, 0.0f); glVertex3f(7.0f, 20.0f, 0.0f); glVertex3f(10.0f, 10.0f, 0.0f); glEnd(); glColor3f(0.8f, 0.2f, 0.0f); glBegin(GL_LINES); // piedi glVertex3f(15.0f, -10.0f, 0.0f); glVertex3f(15.0f, -40.0f, 0.0f); glEnd(); glutSwapBuffers(); } void Resize(int iWidth, int iHeight) { glViewport(0, 0, (GLsizei)iWidth, (GLsizei)iHeight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-50.0f, 50.0f, -50.0f, 50.0f, -1.0f, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH); glutInitWindowSize(350, 350); glutInitWindowPosition(100, 100); glutCreateWindow("OpenGL - Primitive Grafiche"); Init(); glutDisplayFunc(Draw); glutReshapeFunc(Resize); glutMainLoop(); }
In questo esempio ho provato a disegnare un improbabile uccellino, ma che rende bene l’idea su come utilizzare le primitive grafiche in OpenGL. Un dato importante per i nostri disegni è senza dubbio il colore che è una variabile di stato, quindi basta specificare quello da usare prima del disegno della primitiva; notate il cambio di colore per il becco e le ali dell’uccello.
Normalmente ai primi esperimenti con OpenGL si ottiene un avvilente schermo nero, questo perché abbiamo posizionato gli oggetti in un punto in cui non stiamo guardando. Per avere un’inquadratura di 100 pixel di diametro ed una profondità di 100 dobbiamo utilizzare la funzione glOrtho in questa maniera:
glOrtho(-50.0f, 50.0f, -50.0f, 50.0f, 0.0f, 100.0f);
in questo modo abbiamo detto di avere uno schermo da -50 a 50 sull’asse x ed y, mentre da 0 a 100 di profondità lungo l’asse z. Vedremo di approfondire la gestione della visuale e le trasformazioni nelle lezioni successive.