OpenGL è una macchina a stati. Essa viene impostata
in vari stati o modi di funzionamento e resta in quello stato sino a
quando non viene modificato. Come si è già visto, il colore
corrente è una variabile di stato. Si può impostare il colore
corrente in bianco, rosso, e qualsiasi altro colore, quindi ogni
oggetto sarà disegnato in quel colore, sino a quando il colore
impostato non verrà cambiato. Altri controllano cose quali
l'impostazione della vista attuale, e le trasformazioni della
proiezione ortogonale, le trame di linee e poligoni, il modo di
disegnare i poligoni, i metodi per impacchettare i pixel, e le
posizioni e caratteristiche delle luci, le proprietà dei materiali
degli oggetti disegnati. Molti di queste variabili di stato hanno
modalità che possono attivate o disattivate mediante i comandi
glEnable() o glDisable(). Ogni variabile di stato ha
una modalità di default, e in ogni punto del programma si può
chiedere al sistema il valore corrente. Tipicamente, vengono usati
uno dei seguenti 6 comandi per conoscerlo: glGetBooleanv(),
glGetDoublev(), glGetInteger(),glGetPointer(), glIsEnabled(). La
selezione di alcuni di questi comandi dipende dal tipo di dato da
recuperare. Alcune variabili di stato possiedono comandi specifici
(quali glGetLight*(), glGetError() o glGetPolygonStipple().
Inoltre si possono salvare un insieme di variabili di stato su uno
stack di attributi, mediante glPushAttrib() o
glPushClientAttrib()
temporaneamente. Dopo averle modificate, sono recuperate mediante le
istruzioni glPopAttrib(), glPopClientAttrib().
Per cambiamenti di stato temporanei è consigliato utilizzare queste
ultime istruzioni, in quanto sono più efficienti.
In questo blog si parla di programmazione con il linguaggio C++, i post cercheranno di essere molto esplicativi sull'argomento trattato.
lunedì 28 gennaio 2013
sabato 26 gennaio 2013
Animare con OpenGL
Il programma elencato di seguito crea una semplice animazione realizzata utilizzando le funzioni OpenGL.
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
static GLfloat spinta = 1.0;
void inizia(void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
}
void mostra(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glRotatef(spinta, 0.0, 0.0, 1.0);
glColor3f(0.0, 0.0, 1.0);
glRectf(-25.0, -25.0, 25.0, 25.0);
glPopMatrix();
glutSwapBuffers();
}
void spinMostra(void)
{
spinta = spinta + 2.0;
if (spinta > 360.0)
spinta = spinta - 360.0;
glutPostRedisplay();
}
void ridisegna(int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void topo(int bottone, int stato, int x, int y)
{
switch (bottone) {
case GLUT_LEFT_BUTTON:
if (stato == GLUT_DOWN)
glutIdleFunc(spinMostra);
break;
case GLUT_MIDDLE_BUTTON:
if (stato == GLUT_DOWN)
glutIdleFunc(NULL);
break;
default:
break;
}
}
/*
* funzioni di doppio buffer immagine
* funzioni che registrano lo stato del mouse
*/
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize (300, 300);
glutInitWindowPosition (125, 125);
glutCreateWindow (argv[0]);
inizia ();
glutDisplayFunc(mostra);
glutReshapeFunc(ridisegna);
glutMouseFunc(topo);
glutMainLoop();
return 0;
}
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
static GLfloat spinta = 1.0;
void inizia(void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
}
void mostra(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glRotatef(spinta, 0.0, 0.0, 1.0);
glColor3f(0.0, 0.0, 1.0);
glRectf(-25.0, -25.0, 25.0, 25.0);
glPopMatrix();
glutSwapBuffers();
}
void spinMostra(void)
{
spinta = spinta + 2.0;
if (spinta > 360.0)
spinta = spinta - 360.0;
glutPostRedisplay();
}
void ridisegna(int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void topo(int bottone, int stato, int x, int y)
{
switch (bottone) {
case GLUT_LEFT_BUTTON:
if (stato == GLUT_DOWN)
glutIdleFunc(spinMostra);
break;
case GLUT_MIDDLE_BUTTON:
if (stato == GLUT_DOWN)
glutIdleFunc(NULL);
break;
default:
break;
}
}
/*
* funzioni di doppio buffer immagine
* funzioni che registrano lo stato del mouse
*/
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize (300, 300);
glutInitWindowPosition (125, 125);
glutCreateWindow (argv[0]);
inizia ();
glutDisplayFunc(mostra);
glutReshapeFunc(ridisegna);
glutMouseFunc(topo);
glutMainLoop();
return 0;
}
lunedì 21 gennaio 2013
Un assaggio di codice OpenGL II
La prima linea in main() inizializza una
finestra sullo schermo. La funzione InizializzaFinestra() è
pensata come il luogo ove le routine di sistema per la finestra
vengono richiamate, esse non sono funzioni OpenGL. Le due
linee seguenti sono istruzioni OpenGL per rendere lo schermo
nero, glClearColor(), stabilisce il colore con cui la finestra
deve essere pulita; glClear(), ripulisce la finestra. Il
colore della finestra può essere cambiato nuovamente mediante
glClearColor(). In modo simile glColor3f() stabilisce il
colore con il quale gli oggetti devono essere disegnati, in questo
caso il bianco. Tutti gli oggetti disegnati dopo questa istruzione
saranno di quel colore, sino a quando non ne viene richiamato un
altro. Il successivo comando OpenGL, usato dal programma
glOrtho(), specifica le coordinate del systema OpenGL e
come l'immagine debba essere disegnata sullo schermo. Le chiamate
successive sono comprese tra glBegin() e glEnd(),
definiscono l'oggetto da disegnare, in questo caso solo un poligono a
quattro vertici. Gli angoli del poligono sono definiti da
glVertex3f(). Infine glFlush() assicura che i comandi
siano eseguiti piuttosto che immagazzianti in un buffer in attesa di
ulteriori istruzioni OpenGL. La funzione
AggiornaEventiFinestra() gestisce il contenuto della finestra
e l'inizio della gestione dei processi.
Questo codice illustrato non è ben strutturato, in
quanto non permette di cambiare le dimensioni della finestra o
modificare il punto di vista. Successivamente saranno spiegate delle
funzioni che possano rendere il codice più efficiente.
martedì 15 gennaio 2013
Un assaggio di codice OpenGL
Poiché si possono fare molte cose con OpenGL, un
programma di questo tipo può essere molto complicato. Comunque la
struttura di base di un programma utile è semplice; il suo compito è
inizializzare certi stati che controllano come OpenGL calcola e
specifica gli oggetti da calcolare. Prima di guardare del codice
OpenGL, impariamo ad usare alcuni termini. Rendering,
è il termine usato per il processo nel quale le immagini sono create
dai modelli. Questi modelli
sono costituiti da geometrie primitive (punti, linee, poligoni –
dei quali sono specificati i vertici). L'immagine finale calcolata
(rendered) consiste di
un insieme di pixel sullo schermo, l'elemento visibile più piccolo
che uno schermo hardware possa visualizzare. Le informazioni riguardo
ai pixel (ad esempio il colore che devono avere) sono organizzati in
memoria come piani di bit. Un piano di bit è un'area di memoria che
conserva le informazioni di ogni pixel dello schermo; il bit potrebbe
indicare come il pixel in particolare deve essere. I piani di bit
sono a loro volta organizzati in blocchi di trama (framebuffer),
il qual conserva le informazioni grafiche dello schermo e controlla
l'intensità di tutti i pixel. Ora diamo un'occhiata al codice di un
programma OpenGL. Il programma disegna un quadrato bianco su schermo
nero.
#include<CosaServe.h>
main(){
InizializzaFinestra(
);
glClear
(GL_COLOR_BUFFER_BIT);
glColor3f (1.0, 1.0,
1.0);
glOrtho(0.0, 1.0, 0.0,
1.0, -1.0, 1.0);
glBegin(GL_POLYGON);
glVertex3f
(0.3, 0.3, 0.0);
glVertex3f
(0.9, 0.3, 0.0);
glVertex3f
(0.9, 0.9, 0.0);
glVertex3f
(0.3, 0.9, 0.0);
glEnd();
glFlush();
AggiornaEventiFinestra(
);}
Immagine risultante dal programma
sabato 12 gennaio 2013
OpenGL
Introduzione alle OpenGL
OpenGL è una
interfaccia software per hardware di grafica. Questa interfaccia
consiste di circa 150 comandi distinti che possono essere usati per
specifici oggetti e operazioni e produrre applicazione interattive in
tre dimensioni. OpenGL è stato progettato per essere un interfaccia
diretta e hardware indipendente la quale può essere utilizzata su
molte piattaforme hardware. Per raggiungere queste qualità, non vi
sono comandi che effettuano funzioni nelle finestre od ottengano
input dall'utente sono incluse in OpenGL; invece è necessario
integrarle con le istruzioni per il particolare sistema a finestre
che si sta utilizzando o per definire l'input dell'utente a seconda
dell'hardware utilizzato. In maniera simile OpenGL non fornisce
comandi ad alto livello per descrivere i modelli degli oggetti in tre
dimensioni. Questi comandi, che permettono di descrivere oggetti come
parti del corpo, aeroplani o edifici; devono essere costruiti a
partire da elementi semplici, un set di geometrie primitive
(punti, linee, poligoni).
Una sofisticata libreria che
abbia queste funzioni può essere costruita inglobando OpenGL. La
OpenGL Utility Library (GLU) fornisce molte caratteristiche di
modellazione, come superfici quadratiche e curve e superfici NURBS.
GLU è uno standard per ogni implementazione di OpenGL. Esiste
inoltre uno strumento ad alto livello, Open Inventor, costruito sulla
base di OpenGL, il quale rende accessibile molte funzioni separate da
OpenGL.
Cosa
è in grado di fare OpenGL:
- Scena wireframe – Mostra tutti gli oggetti come se fossero costruiti in filo di ferro, ogni linea corrisponde al bordo di un poligono. Un superficie può essere costituita da centinaia di poligoni.
- Scena depth-cued – La scena mostrata in wireframe visualizza linee più sottili per gli oggetti che si trovano più lontani dall'osservatore nella scena come si presenta nella realtà. Inoltre OpenGL permette di simulare effetti atmosferici per raggiungere i risultati della profondità di campo.
- Scena antialiased - Questa tecnica permette di ridurre le linee seghettate sui bordi degli oggetti, creando con approssimazione dei bordi arrotondati mediante pixel, confinati in una griglia rettangolare. Le linee seghettate sono maggiormente visibili se quasi orizzontali o quasi verticali.
- Scena flat shaded, unlit – Gli oggetti sulla scena sono mostrati come solidi. Ma essi appaiono piatti in quanto un solo colore viene usato per poligono, non sono morbidamente arrotondati. Inoltre non vi sono effetti di luce.
- Scena smooth- shaded, lit - Gli oggetti sono molto più realistici in quanto il loro colore risponde agli effetti di luce presenti sulla scena, ombreggiatura morbida. La Texture mapping consente di applicare delle immagini di tessitura a due dimensioni sulla superficie dell'oggetto a tre dimensioni.
- Effetto motion-blur – Gli oggetti presentano un effetto di leggera sfocatura e movimento che lascia una traccia in base alla direzione di movimento.
- Effetto profondità di campo - simula l'inabilità di una lente di telecamera a mantenere tutti gli oggetti sulla scena messi a fuoco. La telecamera focalizza in un solo punto della scena, pertanto gli oggetti più vicini e lontani dalla telecamera sono in qualche modo sfocati.
sabato 5 gennaio 2013
Organizzare il proprio codice
Quando viene programmato un gioco, il codice è
pieno di funzioni e classi, le quali possono diventare ingestibili in
un singolo file. Inoltre è utile riutilizzare parte dei propri
programmi, ad esempio alcune classi e funzioni, come base per
successivi programmi in C++. Bisogna quindi spezzare il codice in
pezzi maneggevoli e utili attraverso file multipli. Generalmente si
separano le funzioni relative o le singole classi in un file proprio.
Il programma Roditore
Il programma Roditore non è composto da un solo
file, è una collezione di tre files che lavorano insieme per creare
una singola applicazione. Si tratta di un progetto molto semplice.
Viene instanziato un solo roditore, il quale dice “ciao”. Questo
esempio dimostra come sia possibile spezzare il programma in file
multipli.
Usare un file
E' possibile creare questo semplice programma con un
singolo file di codice, in quanto piuttosto corto, come mostrato
sotto.
#include<iostream>
using namespace std;
class Roditore
{ public: void Saluto();
};
void Roditore::Saluto()
{ cout << “Ciao, Io
sono un roditore.\n”; }
int main()
{ cout << “Creare un
oggetto Roditore.\n”;
Roditore ctopo;
ctopo.Saluto();
return 0;
}
Comunque, è possibile spezzare questo file in file
multipli che lavorano assieme come un progetto, come viene fatto di
seguito. In questo progetto sono creati 3 files.
- File intestazione. Contiene solo la definizione della classe Roditore.
- File implementazione. Contiene l'implementazione delle funzioni membro della classe Roditore.
- File applicazione. Contiene il programma, con la funzione main(), la quale usa la classe Roditore presente nei file implementazione e intestazione. Questo file viene eseguito dal compilatore.
Creare un file intestazione (Header)
I file
header sono creati per
essere inclusi in altri file. Si è già parlato di file intestazione
in quanto ogni programma già presentato include almeno un file
header proveniente dalla
libreria standard, <iostream>.
Quando si spezza il programma in file multipli, generalmente si
scrive un proprio file intestazione e solitamente lo si fa per ogni
classe. Questo file include solo la definizione della classe, non la
sua implementazione.
//roditore.h
//file
intestazione
#ifndef
RODITORE_H
#define
RODITORE_H
class Roditore
{ public: void Saluto();
};
#endif
La definizione
della classe Roditore
è molto semplice, in quanto dichiara una sola funzione pubblica,
Saluto().
Vi sono alcuni concetti nuovi nel codice. Le tre linee che iniziano
per # sono direttive del
preprocessore, in
pratica istruzioni per il compilatore.
Insieme dicono al compilatore cosa non includere nella definizione di
Roditore del progetto se sono già state incluse in esso. Viene presa
questa precauzione per evitare che la stessa classe sia definita più
di una volta nel codice, questo risulta in un errore. Cosa sta
succedendo? La prima direttiva dice che se il simbolo RODITORE_H non
è stato definito (in una lista di simboli che il compilatore
conserva per compilare il codice), il programma deve andare avanti e
processare tutto il codice che segue, sino al marcatore della
terminazione.
#ifndef
RODITORE_H
Se il simbolo è sulla
lista, il programma deve saltare tutto il codice che segue, sino al
marcatore di terminazione. Il marcatore di fine è l'ultima direttiva
presente sul file.
#endif
La prima volta che roditore.h il file intestazione viene
incluso nel processo, esso viene compilato, RODITORE_H non è nella
lista dei simboli, e vien inclusa la definizione della classe
Roditore. Inoltre il compilatore processa la direttiva, la
quale chiede di includere il simbolo RODITORE_H nella sua lista di
simboli.
#define
RODITORE_H
Questo significa che se vien fatto un altro
tentativo di includere questo file intestazione nel progetto, il
compilatore dopo aver visto il simbolo RODITORE_H nella lista,
salterà la definizione della classe Roditore, senza tentare
di definirla un'altra volta. Comunque il simbolo RODITORE_H che viene
scelto per il file d'intestazione nasce da una convenzione, per la
quale il nome del file viene scritto in maiuscole seguite da _H.
Questa convenzione ci permette di definire un simbolo unico per ogni
file intestazione. Inoltre è quello che ogni altro programmatore si
aspetta di trovare scritto.
Creare il file implementazione
Poiché il file intestazione contiene solo la
definizione della classe, i dati di implementazione della classe
devono essere immagazzinati in un altro file. Questo file deve avere
lo stesso nome del file intestazione, ma la sua estensione è la
familiare .cpp. L'implementazione di roditore.h viene
chiamata roditore.cpp. Di seguito si riporta il codice.
//roditore.cpp
//file implementazione
#include<iostream>
#include “roditore.h”
using namespace std;
void Roditore::Saluto()
{ cout << “Ciao, Io
sono un roditore.\n”; }
Il file contiene l'implementazione di
Roditore::Saluto(). Inoltre include la definizione delle variabili e
dei membri statici. Da notare l'istruzione con la quale viene incluso
il file intestazione:
#include “roditore.h”
Quando si effettua un'inclusione, è come se una
copia venga incollata direttamente dove si trova l'istruzione
include. Includendo roditore.h si completa la
definizione della classe. Questi due file insieme rappresentano un
modo pulito per memorizzare una singola classe. Il passo successivo è
usare la classe in un programma reale.
Creare file applicazione
Si possono
includere i propri file intestazione nell'applicazione usando
l'istruzione include.
Viene incluso roditore.h
in una semplice applicazione. Di seguito viene riportato il codice.
//roditore_app.cpp
//file
applicazione
#include<iostream>
#include
“roditore.h”
using namespace std;
int main()
{ cout << “Creare un
oggetto Roditore.\n”;
Roditore ctopo;
ctopo.Saluto();
return 0;
}
Quando viene
compilato il programma, il compilatore legge la seguente linea di
codice e la definizione della classe Roditore
memorizzata nei file roditore.h
e roditore.cpp.
#include
“roditore.h”
Come se il codice
di definizione fosse stato aggiunto al programma. Pertanto durante
l'esecuzione, l'oggetto Roditore
viene fatta istanza e la funzione dice ciao.
mercoledì 2 gennaio 2013
Usare classi astratte
A volte si vorrebbero definire delle classi che agiscano come base
per altre classi, ma non ha senso istanziare oggetti da questa classe
in quanto troppo generica. Per esempio, si supponga di avere un gioco
nel quale un gruppo di creature si aggirano per l'area. Nonostante
via sia un'ampia varietà di creature, esse hanno due cose in comune:
Possiedono un valore di salute e possono offrire un saluto. Così
possiamo definire una classe, Creatura, come base per derivare
altre classi, ad esempio Fatina, Drago, Troll e così via.
Nonostante Creatura ci aiuta, non ha senso istanziare un
oggetto Creatura. Ma ha più senso se Creatura sia
indicata solo come classe di base e non per istanziare oggetti. Bene
in C++ è possibile definire questo tipo di classi, sono chiamate
classi astratte.
Presentare il programma Creatura astratta
Il programma Creatura astratta dimostra le classi astratte.
Nel programma viene definita una classe astratta, Creatura, la
quale può essere usata come classe di base per uno specifico di
classi creatura. Ne viene definita uno, Troll. Quando viene
istanziato l'oggetto Troll e vi è una funzione che ottiene il
saluto scorbutico del troll, inoltre un'altra funzione ne mostra la
salute.
//Creatura astratta
//Dimostra le classi astratte
#include<iostream>
using namespace std;
class Creatura
{ public: Creatura(int salute = 100):
m_Salute(salute){}
virtual void Saluto() const =0 // funzione
virtuale pura
virtual void MostraSalute() const { cout
<< “Salute: ” << m_Salute << endl; }
protected: int m_Salute;
};
class Troll: public Creatura
{
public: Troll(int salute = 150):
Creatura(salute){}
virtual void Saluto() const
{ cout << “Il Troll sbruffa un
ciao.\n”; }
};
int main()
{
Creatura* pCreatura = new Troll();
pCreatura - > Saluto();
pCreatura - > MostraSalute();
return 0;
}
Iscriviti a:
Post (Atom)