Lighting and Services s.r.l. - impianti elettrici, allarme, antincendio, domotica, climatizzazione, web designischia impianti e servizi Lighting and Services
Tradotto dal celebre tutorial di NeHe, con quanto scritto di seguito potete iniziare a stuzzicarvi con la programmazione grafica...
Benvenuti ai miei tutorial sulle OpenGL. Io ho una grande passione per le OpenGL! La prima volta che ho sentito parlare delle OpenGL risale a quando la 3Dfx ha rilasciato i driver OpenGL per la sua scheda video accelerata Voodoo 1. Ho immediatamente visto le OpenGL come qualcosa che dovevo imparare. Sfortunatamente, era veramente molto difficile trovare qualche informazione riguardo le OpenGL nei libri o sulla rete. Ho speso molte ore provando a fare del codice funzionante e molte altre cercando persone per un aiuto in e-mail o su IRC. Ho visto che queste persone che conoscono le OpenGL si considerano superiori, e non hanno interesse a condividere le loro conoscenze. E’ veramente frustrante!
Io ho creato questo sito web in modo che le persone interessate ad imparare le OpenGL avessero un posto dove andare se avevano bisogno d’aiuto. In ognuno dei miei tutorial provo a spiegare, il più dettagliatamente possibile, cosa fa ogni linea di codice. Ho provato a tenere il mio codi semplice (non c’è da imparare le MFC!). Un newbie assoluto nell’uso di Visual C++ e OpenGL sarà capace di andare avanti nel codice, ed avere una buona idea di ciò che il codice sta facendo. Il mio sito è solo uno dei molti siti che offrono tutorials sulle OpenGL. Se tu sei un programmatore OpenGL avanzato, il mio sito potrebbe essere troppo semplicistico, ma se tu stai iniziando, sono sicuro che il mio sito ha molto da offrirti!
Questo tutorial è stato completamento riscritto nel Gennaio del 200. Il tutorial v’insegnerà come settare una finestra OpenGL. La finestra può essere in modalità finestra (windowed) o in modalità a schermo intero (fullscreen), di qualunque dimensione vogliate, di qualunque risoluzione, e con qualunque profondità di colore. Il codice è molto flessibile e può essere usato per i vostri progetti OpenGL. Tutti i miei tutorial sono basati su questo codice! Ho scritto il codice in modo che sia flessibile, e potente allo stesso tempo. Sono riportati tutti gli errori. Non ci sono perdite di memoria, e il codice è facile da leggere e da modificare. Ringrazio Fredric Echols per le sue modifiche al codice!
Inizierò questo tutorial andando direttamente al codice. Il primo pensiero che dovete avere è quello di creare un progetto in Visual C++. Se non sapete come fare, non dovete imparare le OpenGL, dovete imparate il Visual C++. Il codice scaricabile è per l’ambiente Visual C++ 6.0. Alcune versioni di VC++ richiedono che “bool” sia cambiato in “BOOL”, “true” in “TRUE”, e “false” in “FALSE”. Tramite le modifiche menzionate, ho potuto compilare il codice in Visual C++ 4.0 e 5.0 senza altri problemi.
Dopo aver creato un nuovo progetto Win32 Application in Visual C++, avete bisogno di linkare le librerie OpenGL. In Visual C++ andate al menu Project, Settings, e cliccate sulla pagina LINK. Sotto “Object/Library Modules” all’inizio della riga (prima di kernel32.lib) aggiungete OpenGL32.lib GLu32.lib e GLaux.lib. Poi premete OK.
Le prime quattro righe includono gli header files per ogni libreria che dobbiamo usare. Eccole:
#include // Header File per Windows
#include // Header File per la libreria OpenGL32
#include // Header File per la libreria GLu32
#include // Header File per la libreria GLaux
Dopo avrete bisogno di dichiarare le variabili che dovete usare nel programma. Questo programma creerà una finestra OpenGL vuota, quindi avete bisogno di settare poche variabili. Le poche variabili che setterete sono molto importanti, e saranno usate in tutti i programmi OpenGL che scriverete usando questo codice.
La prima riga setta il Rendering Context. Ogni programma OpenGL è linkato a un Rendering Context. Un Rendering Context è quello che collega le chiamate OpenGL al Device Context. Il Rendering Context di OpenGL è definito come hRC. In modo che il vostro programma possa disegnare su una finestra avete bisogno di creare un Device Context, che è fatto nella seconda riga. Il Device Context di Windows è definito come hDC. Il DC collega la finestra alla GDI (Graphics Device Interface). Il RC (Rendering Context) collega OpenGL al DC (Device Context).
Nella terza riga la variabile hWnd memorizzerà l’handle assegnato da Windows alla finestra creata, e infine, la quarta riga crea una istanza (occorrenza) per il programma.
La prima riga qui sotto setta un array che useremo per monitorare i tasti premuti sulla tastiera. Ci sono diversi modi per vedere i tasti premuti, ma questo è il metodo che io utilizzo. Esso può tenere conto di più tasti premuti contemporaneamente.
La variabile “active” è usata per dire al programma se la finestra è minimizzata oppure no. Se la finestra è stata minimizzata (ridotta ad icona) noi possiamo o sospendere il programma, oppure uscire da esso. Io preferisco sospenderlo.
La variabile “fullscreen” si spiega da sola. Se il programma sta girando in modalità schermo intero, “fullscreen” varrà TRUE, altrimenti varrà FALSE. E’ importante dichiarare questa variabile in modo globale in modo che ogni procedura sappia se il programma sta girando in modalità schermo intero oppure in finestra.
bool keys[256]; // Array usato per la routine della tastierabool active=TRUE; // Flag di finestra attiva, settato a TRUE per defaultbool fullscreen=TRUE; // Flag di Fullscreen settato a TRUE per default
Ora dobbiamo definire WndProc(), poiché in CreateGLWindow() c’è un riferimento a WndProc().
Il compito del prossimo pezzo di codice è di ridimensionare la scena OpenGL quando la finestra (ammettendo che voi stiate usando una finestra e non la modalità schermo interno) viene ridimensionata. Quando non è possibile ridimensionare la finestra (ad esempio in modalità schermo intero), questa routine verrà richiamata soltanto all’inizio del programma per settare la perspective view. La scena OpenGL viene ridimensionata in base all’altezza e alla larghezza della finestra.
// Ridimensiona ed Inizializza la finestra GL
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
{
if (height==0) // Evita una divisione per zero
{
height=1; // Imposta l’altezza uguale a uno
}
glViewport(0, 0, width, height); // Resetta la Viewport corrente
Le righe seguenti settano lo schermo per una vista prospettica. Questo significa che gli oggetti lontani sembreranno più piccoli. Questo crea una scena molto realistica. La prospettiva è calcolata con un angolo di vista di 45 gradi basato sulla larghezza e sull’altezza della finestra. I punti 0.1f, 100.0f sono i punti iniziale e finale per la profondità di quello che deve essere disegnato nello schermo.
glMatrixMode(GL_PROJECTION) indica che le successive due righe di codice avranno effetto sulla matrice di proiezione. La matrice prospettica è responsabile dell’aggiunta della prospettiva alla scena. glLoadIdentity() è simile ad un reset. Essa ripristina la matrice selezionata al suo stato originale. Dopo che glLoadIdentity() è stata chiamata noi settiamo la perspective view per la scena. glMatrixMode(GL_MODELVIEW) indica che ogni nuova trasformazione avrà effetto sulla matrice modelview. La modelview matrix è quella dove sono immagazzinate le informazioni degli oggetti. Infine resettiamo la modelview matrix. Non vi preoccupate se ora non capite queste cose, spiegherò tutto meglio nei prossimi tutorial. Per ora vi basti sapere che questo va fatto se volete una buona scena prospettica.
glMatrixMode(GL_PROJECTION); // Seleziona la Projection Matrix
glMatrixMode(GL_MODELVIEW); // Seleziona la Modelview Matrix
glLoadIdentity(); // Resetta la Modelview Matrix
}
Nella prossima sezione di codice faremo tutti i settagli per OpenGL. Noi setteremo con quale colore deve essere cancellato lo schermo, attiveremo il depth buffer, abiliteremo lo smooth shading, ecc. Questa routine non deve essere chiamata finché la finestra OpenGL non è stata creata. Questa procedura restituisce un valore ma poiché l’inizializzazione non è complessa noi possiamo non preoccuparci del valore per adesso.
int InitGL(GLvoid) // Tutti i settaggi per OpenGL vanno fatti qui
{
La riga seguente abilita lo smooth shading. Spiegherò lo smooth shading in dettaglio in un altro tutorial.
glShadeModel(GL_SMOOTH); // Abilita lo Smooth Shading
La riga seguente setta il colore dello schermo quando viene pulito. Se non sapete come funzionano i colori, lo spiego velocemente. Il colore varia da 0.0f a 1.0f. 0.0f è il più scuro e 1.0f è il più chiaro. Il primo parametro dopo glClearColor è l’intensità di Rosso, il secondo parametro è per il Verde e il terzo è per il Blu. Più il numero è vicino a 1.0f, più il colore specifico sarà accentuato. L’ultimo numero è un valore Alpha. Quando dovete pulire lo schermo, non dovete preoccuparvi del quarto parametro. Per ora lasciatelo a 0.0f. Spiegherò il suo uso in un altro tutorial.
Potete creare colori differenti mischiando i tre colori principali (rosso, verde, blu). Come avete imparato a scuola. Quindi, se scrivete glClearColor(0.0f,0.0f,1.0f,0.0f) volete cancellare lo schermo con un blu acceso. Se scrivete glClearColor(0.5f,0.0f,0.0f,0.0f) volete cancellare lo schermo con un rosso medio. Non accesso e non spento, una via di mezzo. Per fare uno sfondo bianco, dovete settare tutti i colori al massimo possibile (1.0f). Per fare uno sfondo nero, dovete settare tutti i colori al minimo possibile (0.0f).
Le prossime tre righe hanno a che fare con il Depth Buffer. Pensate al depth buffer come a dei piani nello schermo. Il depth buffer tiene traccia di come sono messi gli oggetti nello schermo. Noi non vogliamo veramente usare il depth buffer in questo programma, ma ogni programma OpengGL che disegna qualcosa in 3D sullo schermo userà il depth buffer. Il depth buffer è una parte molto importante di OpenGL.
glClearDepth(1.0f); // Settaggio del Depth Buffer glEnable(GL_DEPTH_TEST); // Abilitazione del Depth TestingglDepthFunc(GL_LEQUAL); // Il tipo di Depth Test da fare
Poi dobbiamo dire ad OpenGL quale correzione della prospettiva vogliamo. Questo causa una perdita di performance, ma rende la vista prospettiva molto migliore.
// Calcolo della Prospettiva veramente buono
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
Infine dobbiamo restituire TRUE. Se volete vedere se l’inizializzazione è andata a buon fine, dovete testare se viene restituito TRUE o FALSE. Potete aggiungere del codice per restituire FALSE se si verifica un errore. Per ora non faremo questo.
return TRUE; // Inizializzazione OK
}
Questa sezione è dove viene scritto tutto il vostro codice di disegno. Ogni cosa desideriate appaia sullo schermo va in questa sezione di codice. Ogni tutorial dopo questo aggiungerà codice in questa sezione del programma. Se avete già un po’ di conoscenze di OpenGL, potete provare a creare figure base aggiungendo codice sotto glLoadIdentity() e prima di return TRUE. Se siete nuovi di OpenGL, aspettate il prossimo tutorial. Per ora tutto quello che faremo sarà di pulire lo schermo con il colore che abbiamo scelto prima, pulire il depth buffer e resettare la scena. Per ora non vogliamo disegnare nulla.
Il return TRUE dice al programma che non ci sono stati problemi. Se volete che il programma si fermi per qualunque motivo, aggiungete un return FALSE in un punto prima di return TRUE per dire al programma che l’operazione di disegno è fallita. Il programma sarà chiuso.
int DrawGLScene(GLvoid) // Qui è dove vengono fatti tutti i disegni
glLoadIdentity(); // Resetta la Modelview Matrix corrente
return TRUE; // Tutto è OK
}
La prossima sezione di codice è chiamata poco prima che il programma sia chiuso. Il lavoro di KillGLWindow() è di rilasciare il Rendering Context, il Device Context e finalmente il Window Handle. Ho aggiunto anche molti test sugli errori. Se il programma non riesce a distruggere una parte della finestra, apparirà una finestra con un messaggio di errore, dicendovi cosa è fallito. Questo renderà più facile trovare degli errori nel codice.
GLvoid KillGLWindow(GLvoid) // Chiude in modo appropriato la finestra
{
La prima cosa da fare in KillGLWindow() è vedere se siamo in modalità schermo intero. Se ci siamo, dobbiamo cambiare la modalità in finestra. Noi possiamo anche distruggere la finestra prima di aver disabilitato la modalità schermo intero, ma con molte schede video se noi distruggiamo la finestra prima di aver disabilitato la modalità schermo intero, il desktop non sarà visualizzato in modo corretto. Quindi disabilitiamo prima la modalità schermo intero. Questo evita che il desktop venga disegnato in modo errato, e funziona bene sia con le schede Nvidia sia con le 3dfx.
if (fullscreen) // Siamo in modalità schermo intero?
{
Usiamo ChangeDisplaySettings(NULL,0) per ritornare al desktop originale. Passando NULL come primo parametro e 0 come secondo forziamo Windows ad usare i valori correnti memorizzati nel registro di Windows (la risoluzione di default, il bit depth, la frequenza, ecc.) per ripristinare veramente il desktop originale. Dopo esser tornati al desktop dobbiamo rendere il cursore del mouse nuovamente visibile.
ChangeDisplaySettings(NULL,0); // Torna al desktop
ShowCursor(TRUE); // Mostra il puntatore del Mouse
}
Il codice qui sotto testa se esiste un Rendering Context (hRC). Se non c’è, il programma salta la sezione di codice che testa la presenza di un Device Context.
if (hRC) // Esiste un Rendering Context?
{
Se esiste un Rendering Context, il codice sotto testa se è possibile rilasciarlo (distaccare il hRC dal hDC). Notate il modo in cui testo gli errori. Dico semplicemente al programma di provare a rilasciare (con wglMakeCurrent(NULL,NULL)), quindi testo per vedere se il rilascio ha avuto successo o no. Quindi combino più righe di codice in una sola.
// E’ possibile rilasciare il DC e l’RC Context?
if (!wglMakeCurrent(NULL,NULL))
{
Se è impossibile rilasciare il DC e l’RC context, MessageBox() mostrerà un messaggio d’errore. NULL significa che la message box non ha una finestra genitore. Il testo alla destra di NULL è il testo che appare nella message box. “SHUTDOWN ERROR” è il titolo della message box. Poi ci sono MB_OK, che significa che vogliamo una message box con un pulsante chiamato “OK”. MB_ICONINFORMATION fa in modo che appaia come icona il simbolo di informazione.
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",
MB_OK | MB_ICONINFORMATION);
}
Qui proviamo a cancellare il Rendering Context. Se questo non va a buon fine apparirà un messaggio d’errore.
if (!wglDeleteContext(hRC)) // E’ possibile cancellare il RC?
{
Se non è possibile cancellare il Rendering Context il codice sotto fa apparire una message box che informa che non è stato possibile cancellare l’RC. hRC viene settato a NULL.
Ora testiamo se il programma principale ha un Device Context e se c’è, tentiamo di cancellarlo. Se non è possibile rilasciare il Device Context apparirà un messaggio d’errore e hDC sarà settato a NULL.
if (hDC && !ReleaseDC(hWnd,hDC)) // E’ possibile rilasciare il DC?
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",
MB_OK | MB_ICONINFORMATION);
hDC=NULL; // Setta il DC a NULL
}
Ora testiamo se c’è un Window Handle e se c’è, cerchiamo di distruggere la finestra utilizzando DestroyWindow(hWnd). Se non è possibile distruggere la finestra, apparirà un messaggio d’errore e hWnd sarà settato a NULL.
if (hWnd && !DestroyWindow(hWnd)) // Si può distruggere la finestra?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",
MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // Setta hWnd a NULL
}
L’ultima cosa da fare è deregistrare la Windows Class. Questo permette di distruggere in modo corretto la finestra, e permette di riaprire un’altra finestra senza ricevere il messaggio d’errore “Windows Class already registered”.
// E’ possibile deregistrare la classe?
if (!UnregisterClass("OpenGL",hInstance))
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",
MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Setta hInstance a NULL
}
}
La prossima sezione di codice crea la finestra OpenGL.
Come potete vedere la procedure restituisce BOOL (TRUE o FALSE), e prende 5 parametri: titolo della finestra, larghezza della finestra, altezza della finestra, bits (16/24/32), e infine il flag di fullscreen (TRUE schermo intero, FALSE finestra). Restituiamo un valore booleano che indica se la finestra è stata creata o meno.
BOOL CreateGLWindow(char* title, int width, int height, int bits,
bool fullscreenflag)
{
Quando chiedete a Windows di trovare un pixel format che combaci con quello richiesto, il numero il numero della modalità che Windows trova alla fine viene memorizzato nella variabile PixelFormat.
GLuint PixelFormat; // Memorizza il risultato dopo la ricerca
La variabile wc sarà usata per memorizzare la struttura Window Class. La struttura Window Class tiene le informazioni sulla finestra. Cambiando i campi della classe noi possiamo cambiare il comportamento e l’aspetto della finestra. Ogni finestra ha una Window Class. Prima di creare una finestra, voi dovete prima registrare una classe per la finestra.
WNDCLASS wc; // Struttura Windows Class
Le variabili dwExStyle e dwStyle memorizzano le informazioni di Extended e Normal Window Style. Io uso le variabili per memorizzare gli stili in modo che posso cambiare questi stili dipendentemente dal tipo di finestra di cui ho bisogno (una finestra popup per la modalità schermo intero o una finestra con il bordo per la modalità finestra).
Le seguenti 5 righe di codice settano i valori degli angoli superiore sinistro e inferiore destro di un rettangolo. Noi useremo questi valori per aggiustare le dimensioni della finestra. Normalmente se noi creiamo una finestra di 640x480, i bordi prendono un po’ di risoluzione.
// Valori Rettangolo Superiore Sinistro / Inferiore DestroRECT WindowRect; WindowRect.left=(long)0; // Setta il valore Sinistro a 0// Setta il valore Destro alla larghezza richiestaWindowRect.right=(long)width; WindowRect.top=(long)0; // Setta il valore Sopra a 0// Setta il valore Sotto all’altezza richiestaWindowRect.bottom=(long)height;
Nella prossima linea di codice impostiamo la variabile globale fullscreen uguale a fullscreenflag. Quindi se volete fare la finestra in modalità schermo intero, la variabile fullscreenflag deve essere TRUE. Se non impostate la variabile fullscreen uguale a fullscreenflag, la variabile fullscreen sarà FALSE. Se voi volete distruggere la finestra, e siete in modalità schermo intero, ma la variabile fullscreen è FALSE invece di TRUE come dovrebbe essere, il computer non tornerà al desktop, perché penserà di essere già in modalità windows.
fullscreen=fullscreenflag; // Setta il flag globale Fullscreen
Nella prossima sezione di codice, noi fisseremo una istanza per la finestra, quindi definiremo la Window Class.
Lo stile CS_HREDRAW e CS_VREDRAW forza la finestra a ridisegnare l’area cliente ogni volta che viene ridimensionata. CS_OWNDC crea un DC privato per la finestra. Questo significa che il DC non è condiviso in tutta l’applicazione. WndProc è la procedura che gestisce i messaggi del programma. Non vengono usati dati extra per la finestra quindi i due campi sono a zero. Quindi settiamo l’istanza. Dopo settiamo hIcon a NULL perché non vogliamo un’icona per la finestra, e per il puntatore del mouse usiamo una freccia standard. Il colore di sfondo non importa (viene settato in GL). Noi non vogliamo un menù in questa finestra quindi lo settiamo a NULL, e il nome della classe può essere un qualsiasi nome. Io ho usato “OpenGL” per semplicità.
hInstance = GetModuleHandle(NULL); // Crea un’istanza per la finestra
// Ridisegna se mossa o ridimensionata, e DC privato per la finestra
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc per gestire i messaggi
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Setta l’istanza
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Carica l’icona di Default
// Carica la freccia per il puntatore
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL; // Non è richiesto sfondo per GL
wc.lpszMenuName = NULL; // Non vogliamo un menu
wc.lpszClassName = "OpenGL"; // Setta il nome della classe
Ora registriamo la classe. Se qualcosa va storto, apparirà un messaggio d’errore. Cliccando su OK si uscirà dal programma.
if (!RegisterClass(&wc)) // Prova a registrare la Window Class
{
MessageBox(NULL,"Failed To Register The Window
Class.",
"ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Esce e restituisce FALSE
}
Ora testiamo se il programma deve girare in modalità schermo intero o finestra. Se deve girare in modalità schermo intero, cercheremo di settare la modalità schermo intero.
if (fullscreen) // Si vuole la modalità schermo intero?
{
La prossima sezione di codice è quella con la quale molte persone hanno un po’ di problemi… passare alla modalità schermo intero. Ci sono diverse cose importanti da tenere a mente quando si passa alla modalità schermo intero. Assicuratevi che la larghezza e l’altezza che usate in modalità schermo intero sono le stesse che avete pianificato di usare per la vostra finestra, e molto importante, settate la modalità schermo intero prima di creare la finestra. In questo codice, voi non dovete preoccuparvi della larghezza e dell’altezza, perché le dimensioni dello schermo intero e della finestra sono settati ugualmente alla dimensione richiesta.
DEVMODE dmScreenSettings; // Modalità del dispositivo// Si assicura che la memoria si pulitamemset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // Dimensione della struttura DevmodedmScreenSettings.dmSize=sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = width; // Seleziona la larghezza schermodmScreenSettings.dmPelsHeight = height; // Seleziona l’altezza schermodmScreenSettings.dmBitsPerPel = bits; // Seleziona i Bits per PixeldmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
Nel codice sopra noi puliamo l’area per memorizzare i settaggi video. Settiamo la larghezza, l’altezza e i bits in cui vogliamo che cambi lo schermo. Nel codice sotto proviamo a settare la modalità schermo intero richiesta. Memorizziamo tutte le informazioni riguardo la larghezza, l’altezza e i bits in dmScreenSettings. Nella riga sotto ChangeDisplaySettings prova a cambiare in una modalità che combacia con i dati memorizzati in dmScreenSettings. Ho usato il parametro CDS_FULLSCREEN quando cambio la modalità, poiché si suppone di rimuovere la barra delle applicazioni in fondo alla schermo, e in più non si vuole muovere o ridimensionare la finestra sul desktop quando si passa allo schermo intero.
// Proviamo a settare la modalità selezionata e prendiamo i risultati.
// NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
if (ChangeDisplaySettings(&dmScreenSettings,
CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
Se non si riesce a settare la modalità viene eseguito il codice qui sotto. Se non esiste una modalità schermo intero che combacia, apparirà un messaggio che offre due opzioni. L’opzione di far girare il programma in modalità finestra e l’opzione di chiudere il programma.
// Se la modalità fallisce, si offrono due opzioni.
// Chiudere o eseguire in modalità finestra.
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported
By\nYour Video Card. Use Windowed Mode Instead?",
"NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
Se l’utente decide di usare la modalità finestra, la variabile fullscreen torna a FALSE, e il programma continua a girare.
fullscreen=FALSE; // Seleziona la modalità finestra (Fullscreen=FALSE)
}
else
{
Se l’utente decide di chiudere, appare un messaggio che avverte l’utente che il programma sta per essere chiuso. Viene restituito FALSE per dire al programma che la finestra non è stata creata. Quindi il programma viene chiuso.
// Fa apparire un messaggio per dire all’utente
// che si sta chiudendo il programma.
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE; // Esci e restituisce FALSE
}
}
}
Poiché il codice di schermo intero sopra potrebbe fallire e l’utente potrebbe scegliere di far girare il programma in modalità finestra, dobbiamo testare ancora fullscreen per vedere se è TRUE o FALSE prima di settare il tipo di schermo / finestra.
if (fullscreen) // Siamo in modalità schermo intero?
{
Se siamo in modalità schermo intero dobbiamo settare lo stile esteso WS_EX_APPWINDOW, che forza una finestra in primo piano sopra la barra delle applicazioni. Per lo stile della finestra useremo WS_POPUP. Questo tipo di finestra non ha bordo, ed è quindi perfetta per la modalità schermo intero.
Infine, disabilitiamo il puntatore del mouse. Se il vostro programma non è interattivo, di norma è meglio disabilitare il puntatore del mouse quando si è in modalità schermo intero.
ShowCursor(FALSE); // Nasconde il puntatore del Mouse
}
else
{
Se stiamo usando una finestra invece della modalità schermo intero, dobbiamo aggiungere WS_EX_WINDOWEDGE allo stile esteso. Questo dà alla finestra un aspetto 3D. Per lo stile useremo WS_OVERLAPPEDWINDOW al posto di WS_POPUP. WS_OVERLAPPEDWINDOW crea una finestra con il titolo, bordi ridimensionabili, menu di sistema, e pulsanti di riduzione a icona e ingrandimento.
La riga sotto aggiusta la finestra in dipendenza di quale stile di finestra si sta creando. L’aggiustamento rende la finestra dell’esatta risoluzione che abbiamo richiesto. Normalmente i bordi sovrascrivono parte della finestra. Usando il comando AdjustWindowRectEx niente della scena OpenGL verrà coperto dai bordi, perché la finestra sarà creata più larga di tanti pixel quanti ne servono per disegnare i bordi. Nella modalità schermo intero, questo comando non ha effetto.
// Aggiusta la finestra alle reali dimensioni richieste
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
Nella prossima sezione di codice, andiamo a creare la finestra e controlliamo se è stata creata correttamente. Noi passiamo a CreateWindowEx() tutti i parametri che richiede. Lo stile esteso che abbiamo deciso di usare. Il nome della classe (che deve avere lo stesso nome che avete usato per registrare la Window Class). Il titolo della finestra. Lo stile della finestra. La posizione superiore sinistra della finestra. La larghezza e l’altezza della finestra. Noi non vogliamo una parent window, e non vogliamo un menu quindi entrambi questi parametri sono NULL. Noi passiamo l’istanza della finestra, e finalmente mettiamo a NULL l’ultimo parametro.
Notate che abbiamo incluso gli stili WS_CLIPSIBLINGS e WS_CLIPCHILDREN oltre allo stile della finestra che abbiamo deciso di usare. WS_CLIPSIBLINGS e WS_CLIPCHILDREN sono entrambi fondamentali per far sì che OpenGL funzioni bene.Questi stili evitano che altre finestre disegnino dentro o sopra la finestra OpenGL.
if (!(hWnd=CreateWindowEx( dwExStyle, // Stile Esteso per la finestra
"OpenGL", // Nome della Classe
title, // Titolo della Finestra
WS_CLIPSIBLINGS | // Stile della Finestra Obbligatorio
WS_CLIPCHILDREN | // Stile della Finestra Obbligatorio
dwStyle, // Stile della Finestra scelto
0, 0, // Posizione della finestra
WindowRect.right-WindowRect.left, // Larghezza aggiustata finestra
WindowRect.bottom-WindowRect.top, // Altezza aggiustata finestra
NULL, // No Parent Window
NULL, // Nessun Menu
hInstance, // Instanza
NULL))) // Non passiamo nulla a WM_CREATE
Ora vediamo se la finestra è stata creata in modo corretto. Se la finestra è stata creata, hWnd terrà l’handle della finestra. Se la finestra non è stata creata il codice sotto farà apparire un messaggio d’errore e il programma sarà chiuso.
{
KillGLWindow(); // Resetta il Display
MessageBox(NULL,"Window Creation Error.",
La prossima parte di codice descrive il Pixel Format. Noi sceglieremo un formato che supporta OpenGL e il double buffering, insieme a RGBA (rosso, verde, blu, e canale alpha). Proveremo a trovare un pixel format che coincide con i bits che abbiamo deciso (16bit, 24bit, 32bit). Finalmente setteremo uno Z-Buffer a 16bit. I parametri rimanenti sono o non usati o non importanti.
// pfd dice a Windows come noi pensiamo che debba essere
static PIXELFORMATDESCRIPTOR pfd=
{
// Dimensione di questo Pixel Format Descriptor
sizeof(PIXELFORMATDESCRIPTOR),
1, // Numero di Versione
PFD_DRAW_TO_WINDOW | // Il Formato deve supportare finetra
PFD_SUPPORT_OPENGL | // Il Formato deve supportare OpenGL
PFD_DOUBLEBUFFER, // Deve supportare il Double Buffering
PFD_TYPE_RGBA, // Richiesta di un formato RGBA
bits, // Selezione della profondità del Colore
0, 0, 0, 0, 0, 0, // Bits del Colore (ignorato)
0, // No Alpha Buffer
0, // Shift Bit (ignorato)
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits (ignorato)
16, // 16Bit Z-Buffer (Depth Buffer)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Riservato
0, 0, 0 // Layer Masks (ignorato)
};
Se non ci sono errori quando si crea la finestra, dobbiamo provare a ottenere un Device Context di OpenGL. Se non possiamo ottenere un DC apparirà un messaggio d’errore sullo schermo, e il programma sarà chiuso (restituisce FALSE).
if (!(hDC=GetDC(hWnd))) // Possiamo ottenere un Device Context?
{
KillGLWindow(); // Resetta il Display
MessageBox(NULL,"Can't Create A GL Device Context.",
Se abbiamo ottenuto un Device Context per la finestra OpenGL possiamo provare a trovare un pixel format che coincide con quello che abbiamo descritto sopra. Se Windows non può trovare un pixel format, apparirà un messaggio d’errore sullo schermo e il programma sarà chiuso.
// Windows può trovare un Pixel Format?
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))
{
KillGLWindow(); // Resetta il Display
MessageBox(NULL,"Can't Find A Suitable PixelFormat.",
"ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Restituisce FALSE
}
Se Windows trova un pixel format proveremo a settare il pixel format. Se il pixel format non può essere settato, apparirà un messaggio d’errore e il programma sarà chiuso.
// E’ possibile settare il Pixel Format?
if(!SetPixelFormat(hDC,PixelFormat,&pfd))
{
KillGLWindow(); // Resetta il Display
MessageBox(NULL,"Can't Set The PixelFormat.",
"ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Restituisce FALSE
}
Se il pixel format viene settato correttamente proviamo ad ottenere un Rendering Context. Se non possiamo ottenere un Rendering Context apparirà un messaggio d’errore e il programma sarà chiuso.
// E’ possibile ottenere un Rendering Context?
if (!(hRC=wglCreateContext(hDC)))
{
KillGLWindow(); // Resetta il Display
MessageBox(NULL,"Can't Create A GL Rendering Context.",
"ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Restituisce FALSE
}
Se non ci sono stati errori, e siamo riusciti a creare sia il Device Context sia il Rendering Context dobbiamo rendere attivo il Rendering Context. Se non riusciamo a rendere attivo il Rendering Context apparirà un messaggio d’errore e il programma sarà chiuso.
if(!wglMakeCurrent(hDC,hRC)) // Proviamo ad attivare il Rendering Context
{
KillGLWindow(); // Resetta il Display
MessageBox(NULL,"Can't Activate The GL Rendering Context.",
Quando tutto è pronto, e la finestra OpenGL è stata creata dobbiamo mostrare la finestra, settarla come finestra in primo piano e quindi settare il focus sulla finestra. Quindi chiamiamo ReSizeGLScene passandogli la larghezza e l’altezza della finestra per settare la prospettiva OpenGL.
ShowWindow(hWnd,SW_SHOW); // Mostra la finestra
SetForegroundWindow(hWnd); // Da la priorità massima alla finestra
SetFocus(hWnd); // Setta il focus sulla finestra
ReSizeGLScene(width, height); // Setta lo prospettiva
Finalmente chiamiamo InitGL() dove settiamo le luci, le textures, e qualsiasi altra cosa che ha bisogno di essere settata. Voi potete fare il vostro controllo degli errori in InitGL() e restituire TRUE (se tutto è andato bene) o FALSE (se si è verificato qualche errore). Per esempio, se caricate delle texture in InitGL() e si verifica un errore vorrete che il programma si fermi. Se restituite FALSE da InitGL() le righe di codice qui sotto mostreranno un messaggio d’errore e chiuderanno il programma.
Se il programma è errivato a questo punto vuol dire che tutto è andato bene. Restituiamo TRUE a WinMain() dicendo a WinMain() che non ci sono errori. Questo evita che il programma sia chiuso.
return TRUE; // Tutto OK
}
Questa è la funzione dove tutti i messaggi vengono elaborati. Quando noi registriamo la Window Class gli diciamo di saltare a questa parte di codice per gestire i messaggi.
LRESULT CALLBACK WndProc( HWND hWnd, // Handle per questa finestra
UINT uMsg, // Messaggio per questa finestra
WPARAM wParam, // Informazioni in più del messaggio
LPARAM lParam) // Informazioni in più del messaggio
{
Il codice sotto setta uMsg come valore per tutte le istruzioni case che compaiono sotto. uMsg contiene il nome del messaggio che si deve elaborare.
switch (uMsg) // Test per i messaggi di Windows
{
Se uMsg è WM_ACTIVE noi dobbiamo vedere se la finestra è attiva. Se la finestra è stata minimizzata la variabile active viene messa a FALSE. Se la finestra è attiva, la variabile active deve essere TRUE.
case WM_ACTIVATE: // Test per il messaggio Windows Activate
{
if (!HIWORD(wParam)) // Test per lo stato minimizzato
{
active=TRUE; // Il programma è attivo
}
else
{
active=FALSE; // Il programma non è attivo
}
return 0; // Ritorna al ciclo dei messaggi
}
Se il messaggio è WM_SYSCOMMAND (comando di sistema) noi confrontiamo wParam con le istruzioni case. Se wParam è SC_SCREENSAVE o SC_MONITORPOWER significa che sta partendo lo screensaver o che il monitor sta cercando di andare in modalità risparmio. Restituendo 0 noi evitiamo che queste cose avvengano.
case WM_SYSCOMMAND: // Intercetta i Comandi di Sistema
{
switch (wParam) // Testa la chiamata di sistema
{
case SC_SCREENSAVE: // Lo Screensaver sta partendo?
case SC_MONITORPOWER: // Il Monitor prova a entrare in risparmio?
Se uMsg è WM_CLOSE la finestra deve essere chiusa. Noi mandiamo un messaggio di chiusura che il ciclo principale deve intercettare. La variabile done viene settata a TRUE, il ciclo principale in WinMain() viene fermato, e il programma viene chiuso.
case WM_CLOSE: // Abbiamo ricevuto un messaggio di chiusura?
{
PostQuitMessage(0); // Manda un messaggio di chiusura
return 0; // Torna indietro
}
Se un tasto viene premuto possiamo capire quale tasto è vedendo il valore di wParam. Quindi mettiamo la cella nell’array keys[] a TRUE. In questo modo possiamo vedere anche in un secondo tempo quale tasto è stato premuto. Questo fa sì che più di un tasto possa essere premuto contemporaneamente.
case WM_KEYDOWN: // E’ stato premuto un tasto?
{
keys[wParam] = TRUE; // Mette la cella giusta a TRUE
return 0; // Torna indietro
}
Se un tasto viene rilasciato possiamo capire quale tasto è vedendo il valore di wParam. Quindi mettiamo la cella nell’array keys[] a FALSE. Così leggendo la cella dell’array possiamo capire se quel tasto è premuto o è stato rilasciato. Ogni tasto sulla tastiera rappresenta un numero da 0 a 255. Quando premo il tasto che rappresenta il numero 40 per esempio, keys[40] viene messo a TRUE. Quando lo lascio, torna a FALSE. Questo è il modo che uso per memorizzare i tasti premuti.
case WM_KEYUP: // E’ stato rilasciato un tasto?
{
keys[wParam] = FALSE; // Mette la cella giusta a FALSE
return 0; // Torna indietro
}
Ogni volta che ridimensioniamo la finestra uMsg contiene il messaggio WM_SIZE. Noi possiamo leggere i valori LOWORD e HIWORD di lParam per trovare la nuova larghezza e altezza della finestra. Poi passiamo questi nuovi valori a ReSizeGLScene(). La scene OpenGL viene quindi ridimensionata.
case WM_SIZE: // Ridimensinata la finestra OpenGL
{
// LoWord=Larghezza, HiWord=Altezza
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));
return 0; // Torna indietro
}
}
Ogni messaggio che non viene gestito deve essere passato a DefWindowProc in modo che Windows possa gestirlo.
// Passiamo tutti i messaggi non gestiti a DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
Questo è il punto d’entrata dell’applicazione Windows. Qui è dove noi chiamiamo la routine di creazione della finestra, abbiamo il loop di gestione dei messaggi, e testiamo per un’interazione dell’utente.
int WINAPI WinMain( HINSTANCE hInstance, // Istanza
HINSTANCE hPrevInstance, // Istanza precedente
LPSTR lpCmdLine, // Parametri della linea di comando
int nCmdShow) // Stato di Window Show
{
Dichiariamo due variabili. msg sarà usata per testare se ci sono messaggi che aspettano di essere gestiti. La variabile done è inizializzata a FALSE. Questo significa che il programma non ha finito di girare. Fino a quando done rimane FALSE, il programma continuerà a girare. Quando done cambierà da FALSE a TRUE il programma sarà chiuso.
MSG msg; // Struttura per i messaggi di Windows Message
BOOL done=FALSE; // Variabile Bool per uscire dal loop
Questa sezione di codice è completamente opzionale. Essa mostra una message box che chiede se volete che il programma giri in modalità schermo intero. Se l’utente clicca sul pulsante NO, la variabile fullscreen cambia da TRUE (che è il default) a FALSE e il programma girerà in modalità finestra.
// Chiede all’utente quale modalità preferisce
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?",
"Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // Modalità finestra
}
Questo è dove viene creata la finestra OpenGL. Noi passiamo il titolo, la larghezza, l’altezza, la profondità di colore e TRUE (schermo intero) o FALSE (modalità finestra) per CreateGLWindow. Se la finestra non viene creata per qualunque motivo, viene restituito FALSE è il programma viene chiuso immediatamente.
// Crea la finestra OpenGL
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Chiude il programma se la finestra non è stata creata
}
Questo è l’inizio del ciclo. Finché done è uguale a FALSE il ciclo verrà ripetuto.
while(!done) // Ciclo che si itera finché done=TRUE
{
La prima cosa che dobbiamo fare è testare se c’è qualche messaggio in attesa. Usando PeekMessage() cerchiamo i messaggi senza bloccare il programma. Molti programmi usano GetMessage(). Funziona bene, ma con GetMessage() il programma non fa nient’altro finché non riceve un messaggio di ridisegno o un qualunque altro messaggio.
// Ci sono messaggi in attesa?
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
Nella prossima sezione vediamo se è stato mandato un messaggio di chiusura. Se il messaggio corrente è WM_QUIT, mandato da PostQuitMessage(0) la variabile done è settata a TRUE, causando l’uscita dal programma.
if (msg.message==WM_QUIT) // Si è ricevuto un messaggio di chiusura?
{
done=TRUE; // Imposta done=TRUE
}
else // Altrimenti, continua con la gestione dei messaggi
{
Se il messaggio non è un messaggio di chiusura noi traduciamo il messaggio e lo mandiamo a WndProc() in modo che venga gestito da noi o da Windows.
TranslateMessage(&msg); // Traduce il Messaggio
DispatchMessage(&msg); // Invia il Messaggio
}
}
else // se non ci sono Messaggi
{
Se non ci sono messaggi noi disegniamo la scena OpenGL. La prima riga di codice sotto testa se la finestra è attiva. Se è stato premuto il tasto ESC la variabile done è messa a TRUE, causando la chiusura del programma.
// Disegna la scena. Testa per ESC e chiude
if (active) // Il programma è attivo
{
if (keys[VK_ESCAPE]) // E’ stato premuto ESC?
{
done=TRUE; // ESC indica la chiusura
}
else // Se non si esce, aggiorna lo schermo
{
Se il programma è attivo e non è stato premuto ESC si renderizza la scena e si swappa il buffer (usando il double buffering si ottengono migliori animazioni). Usando il double buffering, possiamo disegnare qualunque cosa in uno schermo nascosto che non si vede. Quando swappiamo il buffer, lo schermo che vediamo diventa nascosto, e quello nascosto diventa visibile. Questo metodo fa in modo che non si vedano le operazioni di disegno. Tutto appare in modo istantaneo.
DrawGLScene(); // Disegna la scena
SwapBuffers(hDC); // Swappa i Buffers (Double Buffering)
}
}
L’ultimo pezzo di codice è nuovo ed è stato aggiunto di recente. Esso permette di premere F1 per cambiare dalla modalità schermo intero alla modalità finestra e viceversa.
if (keys[VK_F1]) // E’ stato premuto F1?
{
keys[VK_F1]=FALSE; // Rimette il tasto a FALSE
KillGLWindow(); // Distrugge la finestra corrente
fullscreen=!fullscreen; //Cambia da Fullscreen a finestra e viceversa// Ricrea la finestra OpenGL if(!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen)){
return 0; // Chiude se la finestra non è stata creata
}
}
}
}
Se la variabile done è diversa da FALSE, il programma viene chiuso. Noi distruggiamo la finestra OpenGL in modo corretto e liberiamo tutto, quindi usciamo dal programma.
// Shutdown
KillGLWindow(); // Distrugge la finestra
return (msg.wParam); // Esce dal programma
}
In questo tutorial ho tentato di spiegare tutto in dettaglio, ogni passo per settare, e creare un programma OpenGL a schermo intero, che esce quando si preme ESC e che monitorizza se la finestra è attiva o meno. Ho speso circa due settimane scrivendo il codice, una settimana correggendo i bug e parlando con i guru della programmazione, e due giorni (circa 22 ore per scrivere questo file HTML). Se avete commenti o domande, per favore scrivetemi. Se pensate che ho commentato in modo non corretto qualcosa o che il codice possa essere migliore in qualche sezione, per favore fatemelo sapere. I voglio fare i migliori tutorial OpenGL che posso e sono interessato ad ascoltare i vostri pareri.