Ho deciso di scrivere un po 'sull'aspetto della programmazione e su come i componenti si parlano. Forse farà luce su alcune aree.
La presentazione
Cosa serve per avere quella singola immagine, che hai pubblicato nella tua domanda, disegnata sullo schermo?
Esistono molti modi per disegnare un triangolo sullo schermo. Per semplicità, supponiamo che non siano stati utilizzati buffer di vertici. (Un buffer di vertici è un'area di memoria in cui vengono memorizzate le coordinate.) Supponiamo che il programma abbia semplicemente detto alla pipeline di elaborazione grafica di ogni singolo vertice (un vertice è solo una coordinata nello spazio) in una riga.
Ma , prima di poter disegnare qualsiasi cosa, dobbiamo prima eseguire delle impalcature. Vedremo perché dopo:
// Clear The Screen And The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Reset The Current Modelview Matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Drawing Using Triangles
glBegin(GL_TRIANGLES);
// Red
glColor3f(1.0f,0.0f,0.0f);
// Top Of Triangle (Front)
glVertex3f( 0.0f, 1.0f, 0.0f);
// Green
glColor3f(0.0f,1.0f,0.0f);
// Left Of Triangle (Front)
glVertex3f(-1.0f,-1.0f, 1.0f);
// Blue
glColor3f(0.0f,0.0f,1.0f);
// Right Of Triangle (Front)
glVertex3f( 1.0f,-1.0f, 1.0f);
// Done Drawing
glEnd();
Quindi cosa ha fatto?
Quando si scrive un programma che desidera utilizzare la scheda grafica, di solito si sceglie una sorta di interfaccia per il driver. Alcune interfacce ben note al driver sono:
Per questo esempio rimarremo con OpenGL. Ora, la tua interfaccia con il driver è ciò che ti offre tutti gli strumenti necessari per far dialogare il tuo programma con la scheda grafica (o con il driver, che quindi comunica con la scheda).
Questa interfaccia è destinata a fornirti determinati strumenti . Questi strumenti prendono la forma di un'API che puoi chiamare dal tuo programma.
Quell'API è ciò che vediamo essere utilizzato nell'esempio sopra. Diamo un'occhiata più da vicino.
L'impalcatura
Prima di poter davvero eseguire qualsiasi disegno reale, dovrai eseguire una configurazione . Devi definire il tuo viewport (l'area che verrà effettivamente renderizzata), la tua prospettiva (la telecamera nel tuo mondo), quale anti-aliasing userai (per appianare il bordo del tuo triangolo) ...
Ma non vedremo nulla di tutto ciò. Daremo solo un'occhiata alle cose che dovrete fare in ogni fotogramma . Piace:
Svuotare lo schermo
La pipeline grafica non cancella lo schermo per ogni frame. Dovrai dirlo. Perché? Ecco perché:
Se non cancelli lo schermo, disegnerai semplicemente su di esso ogni fotogramma. Ecco perché chiamiamo glClear
con il GL_COLOR_BUFFER_BIT
set. L'altro bit ( GL_DEPTH_BUFFER_BIT
) dice a OpenGL di cancellare il buffer di profondità . Questo buffer viene utilizzato per determinare quali pixel sono davanti (o dietro) altri pixel.
Trasformazione
Fonte immagine
La trasformazione è la parte in cui prendiamo tutte le coordinate di input (i vertici del nostro triangolo) e applichiamo la nostra matrice ModelView. Questa è la matrice che spiega come il nostro modello (i vertici) viene ruotato, ridimensionato e tradotto (spostato).
Successivamente, applichiamo la nostra matrice di proiezione. Questo sposta tutte le coordinate in modo che siano rivolte correttamente verso la nostra fotocamera.
Ora ci trasformiamo ancora una volta, con la nostra matrice Viewport. Facciamo questo per adattare il nostro modello alle dimensioni del nostro monitor. Ora abbiamo una serie di vertici che sono pronti per essere renderizzati!
Torneremo alla trasformazione un po 'più tardi.
Disegno
Per disegnare un triangolo, possiamo semplicemente dire a OpenGL di iniziare un nuovo elenco di triangoli chiamando glBegin
con la GL_TRIANGLES
costante.
Ci sono anche altre forme che puoi disegnare. Come una striscia triangolare o una ventola triangolare . Queste sono principalmente ottimizzazioni, in quanto richiedono meno comunicazioni tra la CPU e la GPU per disegnare la stessa quantità di triangoli.
Successivamente, possiamo fornire un elenco di insiemi di 3 vertici che dovrebbero costituire ciascun triangolo. Ogni triangolo usa 3 coordinate (dato che siamo nello spazio 3D). Inoltre, fornisco anche un colore per ciascun vertice, chiamando glColor3f
prima di chiamare glVertex3f
.
La tonalità tra i 3 vertici (i 3 angoli del triangolo) viene calcolata automaticamente da OpenGL . Interpolerà il colore su tutta la faccia del poligono.
Interazione
Ora, quando fai clic sulla finestra. L'applicazione deve solo acquisire il messaggio della finestra che segnala il clic. Quindi è possibile eseguire qualsiasi azione nel programma desiderato.
Questo diventa molto più difficile quando vuoi iniziare a interagire con la tua scena 3D.
Devi prima sapere chiaramente a quale pixel l'utente ha fatto clic sulla finestra. Quindi, tenendo conto della tua prospettiva , puoi calcolare la direzione di un raggio, dal punto del mouse fai clic sulla tua scena. È quindi possibile calcolare se qualsiasi oggetto nella scena si interseca con quel raggio . Ora sai se l'utente ha fatto clic su un oggetto.
Quindi, come si fa a farlo ruotare?
Trasformazione
Sono a conoscenza di due tipi di trasformazioni che vengono generalmente applicate:
- Trasformazione basata su matrice
- Trasformazione basata sull'osso
La differenza è che le ossa influenzano i singoli vertici . Le matrici influenzano sempre tutti i vertici disegnati allo stesso modo. Diamo un'occhiata a un esempio.
Esempio
In precedenza, abbiamo caricato la nostra matrice di identità prima di disegnare il nostro triangolo. La matrice identità è quella che semplicemente non fornisce alcuna trasformazione . Quindi, qualunque cosa io disegni, è influenzata solo dalla mia prospettiva. Quindi, il triangolo non verrà ruotato affatto.
Se voglio ruotarlo ora, potrei fare io stesso la matematica (sulla CPU) e semplicemente chiamare glVertex3f
con altre coordinate (che sono ruotate). Oppure potrei lasciare che la GPU faccia tutto il lavoro, chiamando glRotatef
prima di disegnare:
// Rotate The Triangle On The Y axis
glRotatef(amount,0.0f,1.0f,0.0f);
amount
è, ovviamente, solo un valore fisso. Se vuoi animare , dovrai tenerne traccia amount
e aumentarlo ad ogni fotogramma.
Quindi, aspetta, cosa è successo a tutti i discorsi sulla matrice prima?
In questo semplice esempio, non dobbiamo preoccuparci delle matrici. Chiamiamo semplicemente glRotatef
e si occupa di tutto ciò per noi.
glRotate
produce una rotazione di angle
gradi attorno al vettore xyz. La matrice corrente (vedi glMatrixMode ) viene moltiplicata per una matrice di rotazione con il prodotto che sostituisce la matrice corrente, come se glMultMatrix fosse chiamato con la seguente matrice come argomento:
x 2 1 - c + cx y 1 - c - z sx z 1 - c + y s 0 y x 1 - c + z sy 2 1 - c + cy z 1 - c - x s 0 x z 1 - c - y sy z 1 - c + x sz 2 1 - c + c 0 0 0 0 1
Bene, grazie per quello!
Conclusione
Ciò che diventa ovvio è che si parla molto con OpenGL. Ma non ci sta dicendo niente. Dov'è la comunicazione?
L'unica cosa che OpenGL ci sta dicendo in questo esempio è quando ha finito . Ogni operazione richiederà un certo periodo di tempo. Alcune operazioni richiedono incredibilmente tempo, altre sono incredibilmente veloci.
Inviare un vertice alla GPU sarà così veloce, non saprei nemmeno come esprimerlo. L'invio di migliaia di vertici dalla CPU alla GPU, ogni singolo frame, è molto probabilmente nessun problema.
La cancellazione dello schermo può richiedere un millisecondo o peggio (tieni presente che di solito hai solo 16 millisecondi di tempo per disegnare ogni fotogramma), a seconda della dimensione del tuo viewport. Per cancellarlo, OpenGL deve disegnare ogni singolo pixel con il colore che vuoi cancellare, che potrebbe essere milioni di pixel.
Oltre a ciò, possiamo praticamente chiedere a OpenGL solo le funzionalità del nostro adattatore grafico (risoluzione massima, anti-aliasing massimo, profondità di colore massima, ...).
Ma possiamo anche riempire una trama di pixel che hanno ciascuno un colore specifico. Ogni pixel contiene quindi un valore e la trama è un "file" gigante pieno di dati. Possiamo caricarlo nella scheda grafica (creando un buffer di trama), quindi caricare uno shader , dire a quello shader di utilizzare la nostra trama come input ed eseguire calcoli estremamente pesanti sul nostro "file".
Possiamo quindi "rendere" il risultato del nostro calcolo (sotto forma di nuovi colori) in una nuova trama.
È così che puoi far funzionare la GPU per te in altri modi. Suppongo che CUDA si esibisca in modo simile a quell'aspetto, ma non ho mai avuto l'opportunità di lavorarci.
Abbiamo toccato solo leggermente l'intero argomento. La programmazione grafica 3D è un inferno di una bestia.
Fonte immagine