Come configurare smartphone e PC. Portale informativo

Telecamera virtuale. Proiezione prospettica

Uscita grafica solitamente eseguito in un'area rettangolare dello schermo o della finestra. Nel rendering OpenGL, quest'area è chiamata porta di output. È in quest'area rettangolare che la libreria posizionerà l'immagine generata. Le sue dimensioni sono determinate rispetto a sinistra angolo superiore finestre e sono misurati in pixel. Per determinare la porta di output, l'applicazione deve ascoltare l'evento di ridimensionamento della finestra e determinare la porta di output utilizzando la funzione:

void glViewport(GLint x, GLint y, GLsizei larghezza, GLsizei altezza);

Gli argomenti (x, y) specificano la posizione dell'angolo superiore sinistro della porta di output e larghezza e altezza sono le sue dimensioni. Per impostazione predefinita, la libreria estende la porta di output per coprire l'intera finestra OpenGL.

Sistema di coordinate

Prima che un vertice definito nel sistema di coordinate della scena possa essere visualizzato sullo schermo, deve essere sottoposto a un processo di proiezione. Per descrivere ed eseguire trasformazioni di sistemi di coordinate nella libreria OpenGL, viene utilizzato un apparato a matrice. Il sistema di coordinate stesso e le sue trasformazioni sono descritte da matrici nelle cosiddette coordinate omogenee.

Innanzitutto, il vertice viene convertito in una matrice 1X4 in cui i primi tre elementi rappresentano le coordinate x, y, z. Il quarto numero è il fattore di scala w, che solitamente è 1,0. Il vertice viene moltiplicato per la matrice della vista, che descrive la trasformazione del sistema di coordinate della vista. Otteniamo il vertice in coordinate visibili. A sua volta lo moltiplichiamo per la matrice di proiezione e otteniamo il vertice in coordinate di proiezione. In questa fase, alcuni vertici vengono scartati (perché non rientrano nel volume di visualizzazione). I vertici vengono quindi normalizzati per trasmettere la prospettiva (a meno che la coordinata w non sia 1.0). La proiezione finale del vertice sulla superficie bidimensionale dello schermo viene eseguita dalla libreria OpenGL stessa e non è possibile interferire in questo processo.

Matrice di proiezione

La matrice di proiezione è responsabile di quanto spazio verrà visualizzato, di come i vertici delle primitive grafiche verranno proiettati sulla superficie bidimensionale dello schermo del monitor. Le trasformazioni della matrice di proiezione provocano la modifica dell'intera immagine (ridimensionamento, spostamento o rotazione). OpenGL supporta due modalità di matrice di proiezione: prospettiva e ortografica.

La proiezione prospettica sfrutta il fatto che l'occhio umano lavora con un oggetto di tipo distante le cui dimensioni hanno dimensioni angolari. Più un oggetto è lontano, più ci appare piccolo. Pertanto, il volume di spazio visualizzato è una piramide.

La matrice di proiezione prospettica è definita mediante la funzione:

void glFrustum(GLdoppio sinistra, GLdoppio destra, GLdoppio fondo, GLdoppio alto, GLdoppio vicino, GLdoppio lontano);

(left, bottom, -near) e (right, top, -near) definiscono le coordinate del riquadro di ritaglio più vicino; vicino e lontano hanno sempre valori positivi e determinano la distanza dal punto di vista ai fotogrammi di ritaglio vicino e lontano.

Per impostare la matrice di proiezione prospettica, puoi anche utilizzare la funzione gluPerspective(), che ha diversi argomenti

void gluPerspective(GLdouble fovy, GLdouble aspetto, GLdouble vicino, GLdouble lontano);

L'argomento fovy (campo visivo) specifica il campo visivo e l'aspetto è il rapporto tra la larghezza del riquadro di ritaglio e la sua altezza. vicino e lontano hanno sempre valori positivi e determinano la distanza dal punto di vista ai fotogrammi di ritaglio vicino e lontano.

La proiezione prospettica viene solitamente utilizzata in giochi e applicazioni in cui è necessario realizzare oggetti altamente realistici che possano essere visualizzati dall'occhio. Per bidimensionale e Visualizzazione 3D I dati scientifici e tecnici utilizzano solitamente una proiezione ortografica. Esaminiamo prima l'impostazione della proiezione ortografica per il rendering 2D. Con la proiezione ortografica, il volume dello spazio visualizzato è un parallelepipedo:

La particolarità della proiezione ortografica è che la distanza tra la telecamera e gli oggetti non influisce sull'immagine finale.

Per impostare e successivamente modificare la matrice di proiezione, eseguire la funzione glMatrixMode(GL_PROJECTION). Per cominciare, dovresti anche annullare tutte le impostazioni e trasformazioni precedenti, rendendo unica la matrice di proiezione utilizzando la funzione glLoadIdentity(). La matrice di proiezione ortografica viene impostata utilizzando una funzione che ha il seguente prototipo:

void glOrtho(GLdoppio sinistro, GLdoppio destra, GLdoppio fondo, GLdoppio alto, GLdoppio vicino, GLdoppio lontano);

(sinistra, basso, -vicino) e (destra, alto, -vicino) sono punti che definiscono il fotogramma di ritaglio più vicino. (sinistra, basso, -far) e (destra, alto, -far) sono punti che definiscono il fotogramma di ritaglio lontano. Dopo aver applicato questo comando, la direzione di proiezione è parallela all'asse z verso valori negativi

//Funzione per ridimensionare e impostare le coordinate
void Reshape(int larghezza, int altezza)
{
//Imposta la porta di uscita
glViewport(0, 0, larghezza, altezza);

//Modalità matrice di proiezione
glMatrixMode(GL_PROJECTION);
//Matrice identità
glLoadIdentity();

//Impostazione di un sistema di coordinate ortografiche bidimensionali
glOrto(-50., 50., -50., 50., -1., 1.);

//Visualizza la modalità matrice
glMatrixMode(GL_MODELVIEW);
}

Nello specifico per il rendering 2D, puoi utilizzare una funzione che ha il seguente prototipo:

void gluOrtho2D(GLdoppio sinistro, GLdoppio destra, GLdoppio fondo, GLdoppio alto);

Questa funzione è simile a glOrtho(), che chiama l'argomento vicino=-1.0 e far=1.0. Durante il rendering 2D, la coordinata z ai vertici ha un valore pari a 0, il che significa che gli oggetti si trovano sul piano medio.

Se è necessario mantenere le proporzioni, il sistema di coordinate deve essere impostato tenendo conto del rapporto tra larghezza e altezza della finestra.

//Impostazione del sistema di coordinate ortografiche
doppio aspetto=larghezza/doppio(altezza);
se(larghezza>=altezza)
{
gluOrto2D(-50.*aspetto, 50.*aspetto, -50., 50.);
}
altro
{
gluOrto2D(-50., 50., -50./aspetto, 50./aspetto);
}

Visualizza matrice

La matrice di visualizzazione è responsabile del sistema di coordinate del modello tridimensionale creato. Durante il processo di creazione di un modello, la matrice di visualizzazione può essere cambiata più volte per modificare l'immagine delle singole primitive grafiche (trasformare un quadrato in un rettangolo, un cubo in un parallelepipedo, una sfera in un ellissoide). Per installare e successivamente modificare la matrice di visualizzazione, eseguire la funzione glMatrixMode(GL_MODELVIEW). Per cominciare, dovresti anche annullare tutte le impostazioni e trasformazioni precedenti, rendendo unica la matrice di proiezione utilizzando la funzione glLoadIdentity(). Se le coordinate dei vertici di un oggetto vengono specificate al momento della sua creazione, allora ulteriori trasformazioni non sono richiesti sistemi di coordinate. Tuttavia, questo è spesso difficile o semplicemente impossibile.

OpenGL fornisce tre funzioni per eseguire trasformazioni del sistema di coordinate: glTranslated(), glRotated() e glScaled(). Questi comandi generano matrici di traslazione, rotazione e ridimensionamento, che vengono moltiplicate per la matrice della vista utilizzando la funzione glMultMatrix(). Come puoi vedere, OpenGL prende il sopravvento operazioni su matrici e li esegue secondo speciale algoritmi veloci con la massima efficienza. Per esempio:

Trasferimento

Se l'argomento è maggiore di 0, l'oggetto verrà spostato verso valori positivi lungo l'asse quando viene visualizzato. Se l'argomento è inferiore a 0, l'oggetto visualizzato verrà spostato verso valori negativi lungo l'asse.

Ridimensionamento

Questo viene fatto utilizzando una funzione che ha il seguente prototipo:

Se l'argomento è maggiore di 1, l'oggetto verrà ingrandito quando visualizzato. Se l'argomento è inferiore a 1, l'oggetto verrà ridotto quando visualizzato. Se l'argomento è negativo, l'oggetto verrà riflesso anche quando visualizzato. Valore nullo L'argomento è consentito, ma porterà al fatto che le dimensioni dell'oggetto saranno zero.

Giro

Questo viene fatto utilizzando una funzione che ha il seguente prototipo:

void glRotated(doppio angolo, doppio x, doppio y, doppio z); glRuotato(30.,0.,0.,1.); //Ruota attorno a z
glInizio(GL_LINE_LOOP);
//Impostazione dei vertici
glVertice2d(-2., 2.);
glVertice2d(2., 2.);
glVertice2d(2., -2.);
glVertice2d(-2., -2.);
glEnd();

Il primo argomento determina l'angolo di rotazione e i restanti tre sono le coordinate del vettore attorno al quale viene eseguita la rotazione.

Pila di matrici

Un meccanismo utile per costruire immagini complesse è uno stack di matrici. Utilizzando le funzioni glPushMatrix() e glPopMatrix(), è possibile memorizzare la matrice corrente nello stack e ripristinarla dopo qualsiasi modifica al sistema di coordinate.

Visualizza elenchi

Un meccanismo interessante e molto efficace per creare una scena sono gli elenchi. Questo è un meccanismo che consente di ricordare sequenze di comandi OpenGL ed eseguirle nuovamente. Ciò può migliorare significativamente l’efficienza dell’imaging grande quantità oggetti identici.

Ogni elenco di visualizzazione deve avere un identificatore. Può essere un numero intero arbitrario che puoi assegnare tu stesso. Per evitare conflitti tra identificatori di elenco, la libreria OpenGL consiglia di utilizzare la funzione

GLuint glGenLists(gamma GLsizei);

che trova un identificatore libero e lo restituisce. L'argomento della funzione specifica il numero di elenchi consecutivi per i quali devono essere ottenuti gli identificatori. Se non sono rimasti identificatori liberi, la funzione restituisce zero.

Per iniziare a generare un elenco, è necessario chiamare la funzione

void glNewList(elenco GLuint, modalità GLenum);

Il primo argomento specifica l'identificatore dell'elenco generato e il secondo determina se l'elenco verrà solo generato (GL_COMPILE) o visualizzato immediatamente (GL_COMPILE_AND_EXECUTE). Successivamente potrebbero esserci i comandi OpenGL che devono essere salvati nell'elenco. Non tutti i comandi potrebbero essere inclusi.

La formazione della lista termina con la funzione:

void glEndList(void);

Una volta generati, gli elenchi di visualizzazione vengono salvati in struttura interna Dati della finestra OpenGL e verranno eliminati quando la finestra verrà chiusa o distrutta.

Per eseguire l'elenco di visualizzazione, utilizzare il comando:

void glCallList(lista GLuint);

che accetta l'identificatore della lista come argomento.

La funzione glCallList() può essere chiamata in qualsiasi programma memte quando è necessario eseguire i comandi memorizzati nell'elenco.

Diamo un'occhiata ad un esempio:

vuoto Disegna(vuoto)
{
//Cancella il buffer del colore

glColor3d(1.0, 1.0, 0.0);

glInizio(GL_LINES);
glVertice2d(-50., .0);
glVertice2d(50., .0);

For(int i=-50; i<50; i++)
{
glVertice2d(i, .0);
se(i% 5)
{
glVertice2d(i, -1.);
}
altrimenti se(i% 10)
{
glVertice2d(i, -2.);
}
altro
{
glVertice2d(i, -3.);
}
}
glEnd();

glInizio(GL_LINES);
glVertice2d(.0, -50.);
glVertice2d(.0, 50.);
for(int j=-50; j<50; j++)
{
glVertice2d(.0, j);
se(j%5)
{
glVertice2d(-1., j);
}
altrimenti se(j%10)
{
glVertice2d(-2., j);
}
altro
{
glVertice2d(-3., j);
}
}
glEnd();
//Termina l'esecuzione del comando
glFlush();
}

vuoto Disegna(vuoto)
{
//Cancella il buffer del colore
glCancella(GL_COLOR_BUFFER_BIT);
//Imposta il colore di visualizzazione
glColor3d(1.0, 1.0, 0.0);

//Formazione dell'asse
asse int = glGenLists(1);
se (asse!= 0)
{
glNewList(asse, GL_COMPILE);
glInizio(GL_LINES);
glVertice2d(0., .0);
glVertice2d(100., .0);

For(int i=0.; i<97; i++)
{
glVertice2d(i, .0);
se(i% 5)
{
glVertice2d(i, 1.);
}
altrimenti se(i% 10)
{
glVertice2d(i, 2.);
}
altro
{
glVertice2d(i, 3.);
}
}
glEnd();
//La formazione delle frecce può essere aggiunta in seguito
glInizio(GL_LINE_STRIP);
glVertice2d(97., 1.);
glVertice2d(100.,.0);
glVertice2d(97., -1.);
glEnd();
glEndList();
}
//Disegna l'asse orizzontale
glPushMatrice();
glTradotto(-50.,0.,0.);
glRuotato(180.,1.,0.,0.);
glListaCall(asse);
glPopMatrice();

//Disegna l'asse verticale
glPushMatrice();
glTradotto(0.,-50.,0.);
glRuotato(90.,0.,0.,1.);
glListaCall(asse);
glPopMatrice();

//Termina l'esecuzione del comando
glFlush();
}

Il motore non muove la nave. La nave rimane sul posto e il motore muove l'universo rispetto ad essa.

Questa è una parte molto importante delle lezioni, assicurati di leggerla più volte e di comprenderla bene.

Coordinate omogenee

Finora abbiamo trattato i vertici tridimensionali come triplette (x, y, z). Introduciamo un altro parametro w e operiamo con vettori della forma (x, y, z, w).

Ricorda per sempre che:

  • Se w == 1, allora il vettore (x, y, z, 1) è una posizione nello spazio.
  • Se w == 0, allora il vettore (x, y, z, 0) è la direzione.

Cosa ci dà questo? Ok, per la rotazione questo non cambia nulla, poiché sia ​​nel caso di rotazione del punto che nel caso di rotazione del vettore di direzione si ottiene lo stesso risultato. Tuttavia, in caso di trasferimento vi è una differenza. La traduzione del vettore di direzione darà lo stesso vettore. Ne parleremo più avanti.

Le coordinate omogenee ci consentono di operare con i vettori in entrambi i casi utilizzando una formula matematica.

Matrici di trasformazione

Introduzione alle matrici

Il modo più semplice per immaginare una matrice è come un array di numeri, con un numero di righe e colonne rigorosamente definito. Ad esempio, una matrice 2x3 si presenta così:

Tuttavia, nella grafica 3D utilizzeremo solo matrici 4x4 che ci permetteranno di trasformare i nostri vertici (x, y, z, w). Il vertice trasformato è il risultato della moltiplicazione della matrice per il vertice stesso:

Matrice x Vertice (in quest'ordine!!) = Trasforma. vertice

Abbastanza semplice. Lo useremo abbastanza spesso, quindi ha senso che sia il computer a farlo:

In C++, utilizzando GLM:

glm::mat4 miaMatrice; glm::vec4 mioVettore; glm:: // Presta attenzione all'ordine! Lui è importante!

Nel GLSL:

mat4 miaMatrice ; vec4 mioVettore ; // Non dimenticare di riempire qui la matrice e il vettore con i valori necessari vec4Vettoretrasformato = miaMatrice * mioVettore ; // Sì, è molto simile a GLM :)

Prova a sperimentare con questi frammenti.

Matrice di trasferimento

La matrice di trasferimento si presenta così:

dove X, Y, Z sono i valori che vogliamo aggiungere al nostro vettore.

Quindi, se vogliamo spostare il vettore (10, 10, 10, 1) di 10 unità nella direzione X, otteniamo:

... otteniamo (20, 10, 10, 1) un vettore omogeneo! Ricorda che l'1 nel parametro w significa posizione, non direzione, e la nostra trasformazione non ha cambiato il fatto che stiamo lavorando con la posizione.

Vediamo ora cosa succede se il vettore (0, 0, -1, 0) rappresenta una direzione:

... e otteniamo il nostro vettore originale (0, 0, -1, 0). Come già detto in precedenza un vettore con parametro w = 0 non può essere trasferito.

Ora è il momento di metterlo in codice.

In C++, con GLM:

#includere // Dopo glm::mat4 miaMatrice = glm::translate(glm::mat4(), glm::vec3(10.0 f, 0.0 f, 0.0 f)); glm::vec4 mioVettore(10.0 f, 10.0 f, 10.0 f, 0.0 f); glm::vec4 trasformatoVettore = miaMatrice * mioVettore ;

Nel GLSL:

vec4Vettoretrasformato = miaMatrice * mioVettore ;

In effetti, non lo farai mai in uno shader, molto spesso lo farai glm::translate() in C++ per calcolare la matrice, passarla a GLSL e quindi eseguire la moltiplicazione nello shader

Matrice identità

Questa è una matrice speciale che non fa nulla, ma la stiamo toccando perché è importante ricordare che A per 1.0 è uguale ad A:

Nel C++:

glm::mat4 miaMatriceIdentità = glm::mat4(1.0 f);

Matrice di scala

Sembra altrettanto semplice:

Quindi, se vuoi applicare il ridimensionamento vettoriale (posizione o direzione, non importa) entro 2.0 in tutte le direzioni, allora hai bisogno di:

Si noti che w non cambia e si nota anche che la matrice identità è un caso speciale di matrice di scala con un fattore di scala pari a 1 su tutti gli assi. Inoltre, la matrice identità è un caso speciale della matrice di trasferimento, dove (X, Y, Z) = (0, 0, 0), rispettivamente.

Nel C++:

// aggiungi #include e #includere glm::mat4 miaScalingMatrix = glm::scale(2.0 f, 2.0 f, 2.0 f);

Matrice di rotazione

Più complicato di quelli discussi in precedenza. Tralasceremo qui i dettagli poiché non è necessario saperlo esattamente per l'uso quotidiano. Per informazioni più dettagliate, puoi seguire il collegamento Domande frequenti su matrici e quaternioni (una risorsa piuttosto popolare e la tua lingua potrebbe essere disponibile lì)

Nel C++:

// aggiungi #include e #includere glm::vec3 mioAsseRotazione(??, ??, ??); glm::rotate(angle_in_degrees, myRotationAxis);

Mettere insieme le trasformazioni

Quindi ora possiamo ruotare, traslare e ridimensionare i nostri vettori. Il passo successivo sarebbe quello di combinare le trasformazioni, che viene implementato utilizzando la seguente formula:

VettoreTrasformato = MatriceTraslazione *MatriceRotazione *MatriceScala *VettoreOriginale;

ATTENZIONE! Questa formula in realtà mostra che prima viene eseguito il ridimensionamento, poi viene eseguita la rotazione e solo infine viene eseguita la traslazione. Questo è esattamente il modo in cui funziona la moltiplicazione di matrici.

Assicurati di ricordare in quale ordine viene fatto tutto questo, perché l'ordine è davvero importante, dopotutto puoi controllarlo tu stesso:

  • Fai un passo avanti e gira a sinistra
  • Giratevi a sinistra e fate un passo avanti

La differenza è davvero importante da capire perché la incontrerai continuamente. Ad esempio, quando lavori con i personaggi del gioco o con alcuni oggetti, ridimensiona sempre, poi ruota e solo dopo trasla.

In effetti, l'ordine sopra è quello che generalmente ti serve per i personaggi del gioco e altri oggetti: ridimensionalo prima se necessario; poi ne imposti la direzione e poi lo sposti. Ad esempio, per un modello di nave (rotazioni rimosse per semplicità):

  • Senso vietato:
    • Muovi la nave su (10, 0, 0). Il suo centro è ora a 10 unità dall'origine.
    • Ridimensioni la tua nave di 2 volte. Ogni coordinata viene moltiplicata per 2 “rispetto all'originale”, che è lontano... Quindi, ti ritrovi su una grande nave, ma il suo centro è 2 * 10 = 20. Non quello che volevi.
  • Il modo giusto:
    • Ridimensioni la tua nave di 2 volte. Ottieni una grande nave, centrata nell'origine.
    • Stai spostando la tua nave. Ha sempre le stesse dimensioni e alla distanza corretta.

In C++, con GLM:

glm::mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix ; glm::vec4 mioTrasformatoVector = mioModelMatrix * mioOriginalVector ;

Nel GLSL:

trasformazione mat4 = mat2 * mat1 ; vec4 out_vec = trasforma * in_vec ;

Matrici del mondo, della vista e della proiezione

Per il resto di questo tutorial, assumeremo di sapere come eseguire il rendering del modello 3D preferito di Blender, Suzanne the Monkey.

Le matrici del mondo, della vista e della proiezione sono uno strumento utile per separare le trasformazioni.

Matrice mondiale

Questo modello, proprio come il nostro triangolo rosso, è definito da un insieme di vertici, le cui coordinate sono date rispetto al centro dell'oggetto, cioè il vertice con coordinate (0, 0, 0) sarà al centro dell'oggetto oggetto.

Successivamente vorremmo spostare il nostro modello mentre il giocatore lo controlla utilizzando la tastiera e il mouse. Tutto ciò che facciamo è applicare il ridimensionamento, quindi ruotare e traslare. Queste azioni vengono eseguite per ogni vertice, in ogni fotogramma (eseguite in GLSL, non in C++!) e quindi il nostro modello si muove sullo schermo.

Ora i nostri picchi nello spazio mondiale. Ciò è mostrato dalla freccia nera nella figura. Siamo passati dallo spazio degli oggetti (tutti i vertici sono definiti rispetto al centro dell'oggetto) allo spazio mondiale (tutti i vertici sono definiti rispetto al centro del mondo).

Questo è mostrato schematicamente in questo modo:

Visualizza matrice

Per citare ancora Futurama:

Il motore non muove la nave. La nave rimane nello stesso posto e il motore muove l'universo attorno ad essa.

Prova a immaginarlo in relazione a una macchina fotografica. Ad esempio, se vuoi fotografare una montagna, non sposti la fotocamera, sposti la montagna. Questo non è possibile nella vita reale, ma è incredibilmente facile nella computer grafica.

Quindi, inizialmente la tua fotocamera è al centro del sistema di coordinate mondiali. Per muovere il mondo è necessario entrare in un'altra matrice. Diciamo che vuoi spostare la telecamera di 3 unità a DESTRA (+X), che equivale a spostare l'intero mondo di 3 unità a SINISTRA (-X). Nel codice appare così:

// Aggiungi #include e #includere glm::mat4 ViewMatrix = glm::translate(glm::mat4(), glm::vec3(-3.0 f, 0.0 f, 0.0 f));

Ancora una volta, l'immagine qui sotto lo mostra per intero. Siamo passati dal sistema di coordinate globali (tutti i vertici sono impostati rispetto al centro del sistema mondiale) al sistema di coordinate della fotocamera (tutti i vertici sono impostati rispetto alla fotocamera):

E mentre il tuo cervello digerisce questo, daremo un'occhiata alla funzione che GLM ci fornisce, più specificamente glm::LookAt:

glm::mat4 CameraMatrix = glm::LookAt(posizionecamera, // Posizione della telecamera nello spazio mondiale cameraTarget // Indica dove stai guardando nello spazio mondiale upVector // Un vettore che indica la direzione verso l'alto. Tipicamente (0, 1, 0));

Ed ecco un diagramma che mostra cosa facciamo:

Tuttavia, questa non è la fine.

Matrice di proiezione

Quindi ora siamo nello spazio della telecamera. Ciò significa che un vertice che riceve le coordinate x == 0 ey == 0 verrà visualizzato al centro dello schermo. Tuttavia, quando si visualizza un oggetto, anche la distanza dalla telecamera (z) gioca un ruolo importante. Per due vertici con gli stessi xey, il vertice con un valore z maggiore apparirà più vicino dell'altro.

Questa si chiama proiezione prospettica:

E fortunatamente per noi, una matrice 4x4 può fare questa proiezione:

// Crea una matrice davvero difficile da leggere, ma è pur sempre una matrice 4x4 standard glm::mat4 proiezioneMatrice = glm::prospettiva (glm::radianti (FoV), // Campo visivo verticale in radianti. Tipicamente tra 90° (molto largo) e 30° (stretto) 4,0f/3,0f // Proporzioni. Dipende dalle dimensioni della tua finestra. Tieni presente che 4/3 == 800/600 == 1280/960 0,1 f // Vicino al piano di ritaglio. Deve essere maggiore di 0. 100.0f // Piano di ritaglio lontano.);

Siamo passati da Camera Space (tutti i vertici sono definiti rispetto alla telecamera) a Homogeneous Space (tutti i vertici sono in un piccolo cubo. Tutto ciò che si trova all'interno del cubo viene visualizzato sullo schermo).

Ora guardiamo le immagini seguenti in modo che possiate capire meglio cosa sta succedendo con la proiezione. Prima della proiezione, abbiamo oggetti blu nello spazio della telecamera, mentre la figura rossa mostra la vista della telecamera, cioè tutto ciò che vede la telecamera.

Utilizzando la matrice di proiezione si ottiene il seguente effetto:

In questa immagine, la vista della telecamera è un cubo e tutti gli oggetti sono deformati. Gli oggetti più vicini alla fotocamera appaiono grandi, mentre quelli più lontani appaiono piccoli. Proprio come nella realtà!

Ecco come apparirà:

L'immagine è quadrata, quindi vengono applicate le seguenti trasformazioni matematiche per allungare l'immagine e adattarla alle dimensioni effettive della finestra:

E questa immagine è ciò che verrà effettivamente prodotto.

Combinazione di trasformazioni: matrice ModelViewProjection

... Solo le trasformazioni di matrice standard che già ami!

// C++: calcolo della matrice glm::mat4 MVPmatrix = proiezione * vista * modello; // Ricordare! In ordine inverso!

// GLSL: applica la matrice vertice_trasformato = MVP * vertice_in;

Mettere tutto insieme

  • Il primo passo è creare la nostra matrice MVP. Questo deve essere fatto per ogni modello visualizzato.

// Matrice di proiezione: campo visivo di 45°, proporzioni 4:3, portata: 0,1 unità<->100 unità glm::mat4 Proiezione = glm::prospettiva(glm::radianti(45,0 f), 4,0 f / 3,0 f, 0,1 f, 100,0 f); // Oppure, per l'ortocamera glm::mat4 Vista = glm::lookAt(glm::vec3(4, 3, 3), // La telecamera è nelle coordinate mondiali (4,3,3) glm::vec3(0, 0, 0), // E diretto all'origine glm::vec3(0, 1, 0) // "Testa" è in alto); // Matrice del modello: matrice identità (il modello è all'origine) glm::mat4 Modello = glm::mat4(1.0 f); // Individualmente per ciascun modello // La matrice ModelViewProjection finale, che è il risultato della moltiplicazione delle nostre tre matrici glm::mat4 MVP = Proiezione * Vista * Modello ; // Ricorda che la moltiplicazione delle matrici viene eseguita in ordine inverso

  • Il secondo passo è passare questo a GLSL:

// Ottiene l'handle della variabile nello shader // Solo una volta durante l'inizializzazione. GLuint MatrixID = glGetUniformLocation(IDprogramma, "MVP"); // Passa le nostre trasformazioni allo shader corrente // Questo viene fatto nel ciclo principale, poiché ogni modello avrà una matrice MVP diversa (almeno la parte M) glUniformMatrix4fv(IDMatrice, 1, GL_FALSE, &MVP[0][0]);

  • Il terzo passo è utilizzare i dati ricevuti in GLSL per trasformare i nostri vertici.

// Dati di input del vertice, diversi per tutte le esecuzioni di questo shader. layout (posizione = 0) in vec3 vertexPosition_modelspace; // Valori che rimangono costanti su tutta la mesh. mat4 uniforme MVP; vuoto principale ()( // Posizione di output del nostro vertice: MVP * position gl_Position = MVP * vec4(vertexPosition_modelspace, 1); )

  • Pronto! Ora abbiamo lo stesso triangolo della Lezione 2, ancora situato nell'origine (0, 0, 0), ma ora lo vediamo in prospettiva dal punto (4, 3, 3).

Nella Lezione 6 imparerai come modificare questi valori in modo dinamico utilizzando la tastiera e il mouse per creare la telecamera che sei abituato a vedere nei giochi. Ma prima impareremo come dare ai nostri modelli colori (lezione 4) e texture (lezione 5).

Compiti

  • Prova a cambiare i valori glm::perspective
  • Invece di usare la proiezione prospettica, prova a usare la proiezione ortogonale (glm:ortho)
  • Modifica ModelMatrix per spostare, ruotare e ridimensionare il triangolo
  • Utilizzare l'attività precedente, ma con un ordine di operazioni diverso. Presta attenzione al risultato.

Il motore non muove la nave.
La nave rimane sul posto e
i motori muovono l'universo
Intorno a lui.

Futurama

Questa è una delle lezioni più importanti. Leggilo attentamente almeno otto volte.

Coordinate omogenee

Nelle lezioni precedenti abbiamo assunto che il vertice si trovi alle coordinate (x, y, z). Aggiungiamo un'altra coordinata: w. D'ora in poi, i nostri vertici saranno alle coordinate (x, y, z, w)

Capirai presto di cosa si tratta, ma per ora lo diamo per scontato:

  • Se w==1, allora il vettore (x,y,z,1) è la posizione nello spazio
  • Se w==0, allora il vettore (x,y,z,0) è la direzione.

Ricordatelo come un assioma senza dimostrazione!!!

E questo cosa ci dà? Ebbene, niente per la rotazione. Se ruoti un punto o una direzione, otterrai lo stesso risultato. Ma se ruoti il ​​movimento (quando sposti il ​​punto in una certa direzione), tutto cambia radicalmente. Cosa significa "direzione del movimento"? Niente di speciale.

Coordinate omogenee ci permettono di operare con un unico hardware per entrambi i casi.

Matrici di trasformazione

Introduzione alle matrici

Per dirla semplicemente, una matrice è semplicemente un array di numeri con un numero fisso di righe e colonne.

Ad esempio, una matrice 2 per 3 sarebbe simile a questa:

Nella grafica 3D utilizziamo quasi sempre matrici 4x4. Questo ci permette di trasformare i nostri vertici (x,y,z,w). È molto semplice: moltiplichiamo il vettore di posizione per la matrice di trasformazione.

Matrice*Vertice = vertice trasformato

Non tutto è così spaventoso come sembra. Punta il dito sinistro su a e il dito destro su x. Questa sarà l'ascia. Sposta il dito sinistro sul numero successivo, b, e il dito destro sul numero successivo, y. Ce l'abbiamo fatta. Ancora una volta – cz. E ancora una volta - dw. Ora sommiamo tutti i numeri risultanti – ax+by +cz +dw. Abbiamo ricevuto il nostro nuovo x. Ripeti lo stesso per ogni linea e otterrai un nuovo vettore (x,y,z,w).

Si tratta però di un'operazione piuttosto noiosa, quindi lasciamo che sia il computer a farlo per noi.

In C++ utilizzando la libreria GLM:

glm::mat4 miaMatrice;


glm::vec4 mioVettore;



glm:: vecc 4 trasformatoVector = myMatrix * myVector; // Non dimenticare l'ordine!!! Questo è estremamente importante!!!

Nel GLSL:

mat4 miaMatrice;


vec4 mioVettore;


// riempie la matrice e il vettore con i nostri valori... lo saltiamo


vecc 4 trasformatoVector = myMatrix * myVector; // esattamente come in GLM

(Per qualche motivo mi sembra che tu non abbia copiato questo pezzo di codice nel tuo progetto e non l'abbia provato... dai, provalo, è interessante!)

Matrice del movimento

La matrice degli spostamenti è probabilmente la matrice più semplice di tutte. Eccola qui:


Qui X, Y, Z sono i valori che vogliamo aggiungere alla nostra posizione del vertice.

Quindi, se dobbiamo spostare il vettore (10,10,10,1) di 10 punti, nella posizione X, allora:
(Provalo tu stesso, per favore!)

...E otteniamo (20,10,10,1) in un vettore omogeneo. Come spero ricordi, 1 significa che il vettore rappresenta una posizione, non una direzione.

Ora proviamo a trasformare la direzione (0,0,-1,0) nello stesso modo:

E alla fine abbiamo ottenuto lo stesso vettore (0,0,-1,0).
Come ho detto, non ha senso cambiare direzione.

Come lo codifichiamo?

In C++ utilizzando GLM:

#includere // Dopo


glm::mat4 miaMatrice = glm::translate(10.0f, 0.0f, 0.0f);


glm::vec4 mioVettore(10.0f, 10.0f, 10.0f, 0.0f);


glm:: vecc 4 trasformatoVector = myMatrix * myVector; // e quale risultato otterremo?


E in GLSL: In GLSL, raramente qualcuno lo fa. Molto spesso si usa la funzione glm::translate(). Innanzitutto, creano una matrice in C++, quindi la inviano a GLSL e lì viene eseguita solo una moltiplicazione:

vec4 vettore trasformato = miaMatrice * mioVettore;

Matrice identità

Questa è una matrice speciale. Lei non fa nulla. Ma lo menziono perché è importante sapere che moltiplicando A per 1,0 si ottiene A:

glm::mat4 miaMatriceIdentità = glm::mat4(1.0f);

Matrice di scala

Anche la matrice di ridimensionamento è abbastanza semplice:

Quindi se vuoi raddoppiare il vettore (posizione o direzione, non importa) in tutte le direzioni:

Ma la coordinata w non è cambiata. Se chiedi: "Cos'è il ridimensionamento della direzione?" Non utile spesso, ma a volte utile.

(notare che ridimensionare la matrice identità con (x,y,z) = (1,1,1))

C++:

// Utilizzo#includere E#includere


glm::mat4 miaScalingMatrix = glm::scale(2.0f, 2.0f ,2.0f);

Matrice di rotazione

Ma questa matrice è piuttosto complessa. Pertanto, non mi soffermerò sui dettagli della sua implementazione interna. Se proprio vuoi, è meglio leggere (FAQ su matrici e quaternioni)

INCON++:

// Utilizzo#includere E#includere


glm::vec3 mioAsseRotazione(??, ??, ??);


glm::rotate(angle_in_degrees, myRotationAxis);

Trasformazioni combinate

Ora sappiamo come ruotare, spostare e ridimensionare i nostri vettori. Sarebbe bello sapere come mettere tutto insieme. Questo viene fatto semplicemente moltiplicando le matrici l'una per l'altra.

VettoreTrasformato = MatriceTraslazione * MatriceRotazione * MatriceScala *VettoreOriginale;

E ordina ancora!!! Per prima cosa devi ridimensionare, quindi scorrere e solo dopo spostarti.

Se applichiamo le trasformazioni in un ordine diverso, non otterremo lo stesso risultato. Prova questo:

  • Fai un passo avanti (non far cadere il computer dal tavolo) e gira a sinistra
  • Girate a sinistra e fate un passo avanti.

Sì, dovresti sempre ricordare l'ordine delle azioni quando controlli, ad esempio, un personaggio del gioco. Innanzitutto, se necessario, eseguiamo il ridimensionamento, quindi impostiamo la direzione (rotazione) e infine ci muoviamo. Vediamo un piccolo esempio (ho tolto la rotazione per facilitare i calcoli):

Senso vietato:

  • Sposta la nave su (10,0,0). Il suo centro è ora a 10 X dal centro.
  • Stiamo aumentando le dimensioni della nostra nave di 2 volte. Ogni coordinata viene moltiplicata per 2 rispetto al centro che è lontano... E alla fine otteniamo una nave delle dimensioni richieste ma in una posizione di 2*10=20. Il che non è esattamente quello che volevamo.

Il modo giusto:

  • Aumentiamo le dimensioni della nave di 2 volte. Ora abbiamo una grande nave situata al centro.
  • Spostiamo la nave. Le dimensioni della nave non sono cambiate e si trova nel posto giusto.
La moltiplicazione matrice-matrice viene eseguita più o meno allo stesso modo della moltiplicazione matrice-vettore. Non entreremo nei dettagli e lasciamo che i curiosi lo leggano da fonti specializzate. Faremo semplicemente affidamento sulla libreria GLM.
BC++:
glm::mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix;
glm::vec4 mioTransformedVector = mioModelMatrix * mioOriginalVector;
Nel GLSL:
trasformazione mat4 = mat2 * mat1;
vec4 out_vec = trasforma * in_vec;

Matrici di modelli, viste e proiezioni

A titolo illustrativo, supponiamo di sapere già come disegnare il nostro modello 3D preferito del programma Blender – la testa della scimmia Suzanne – in OpenGL.

Le matrici Modello, Vista e Proiezione rappresentano un metodo molto conveniente per separare le trasformazioni. Se lo desideri davvero, non sei obbligato a usarli (non li abbiamo usati nelle lezioni 1 e 2). Ma ti consiglio vivamente di usarli. È solo che quasi tutte le librerie 3D, i giochi, ecc. li usano per separare le trasformazioni.

Modelli a matrice

Questo modello, come il nostro triangolo preferito, è definito da un insieme di vertici. Le coordinate X, Y, Z sono fornite rispetto al centro dell'oggetto. Quindi, se un vertice si trova alle coordinate (0,0,0), allora è al centro dell'intero oggetto

Ora abbiamo l'opportunità di spostare il nostro modello. Ad esempio, perché l'utente lo controlla utilizzando la tastiera e il mouse. Questo è molto semplice da fare: ridimensiona*ruota*muovi e il gioco è fatto. Applichi la tua matrice a tutti i vertici in ciascun fotogramma (in GLSL, non in C++) e tutto si muove. Tutto ciò che non si muove si trova al centro del “mondo”.

I vertici sono nello spazio mondiale. La freccia nera nella figura mostra come ci spostiamo dallo spazio modello allo spazio mondiale (tutti i vertici sono stati specificati rispetto al centro del modello, ma sono diventati relativi al centro del mondo)

Questa trasformazione può essere rappresentata dal seguente diagramma:


Visualizza Matrice

Rileggiamo la citazione di Futurama:

“Il motore non muove la nave. La nave rimane sul posto e i motori muovono l'universo attorno ad essa."


Lo stesso può essere applicato alla fotocamera. Se vuoi fotografare una montagna da una certa angolazione, puoi spostare la fotocamera...o la montagna. Questo è impossibile nella vita reale, ma molto semplice e conveniente nella computer grafica.

Per impostazione predefinita, la nostra fotocamera si trova al centro delle coordinate mondiali. Per muovere il nostro mondo dobbiamo creare una nuova matrice. Ad esempio, dobbiamo spostare la nostra fotocamera di 3 unità a destra (+X). Equivale a spostare l'intero mondo di 3 unità a sinistra (-X). E mentre i tuoi cervelli si sciolgono, proviamo:


// Usa #include e #includere


glm::mat4 ViewMatrix = glm::translate(-3.0f, 0.0f ,0.0f);

L'immagine qui sotto lo dimostra: ci spostiamo dallo spazio Mondo (tutti i vertici sono definiti rispetto al centro del mondo come abbiamo fatto nella sezione precedente) allo spazio Camera (tutti i vertici sono definiti rispetto alla telecamera).

E prima che la tua testa esploda completamente, dai un'occhiata a questa fantastica funzionalità del nostro buon vecchio GLM:

glm::mat4 CameraMatrix = glm::LookAt(


posizionedellafotocamera, // Posizione della telecamera nelle coordinate mondiali


cameraTarget, // il punto che vogliamo guardare nelle coordinate mondiali


upVector// più probabilmente glm:: vecc3(0,1,0) e (0,-1,0) mostreranno tutto sottosopra, il che a volte è anche interessante.


Ecco un'illustrazione di quanto sopra:


Ma con nostra gioia, non è tutto.

Matrice di proiezione

Ora abbiamo le coordinate nello spazio della telecamera. Ciò significa che dopo tutte queste trasformazioni, il vertice che si trova in x==0 e y==0 verrà renderizzato al centro dello schermo. Ma non possiamo usare solo le coordinate X,Y per capire dove disegnare il vertice: bisogna tenere conto anche della distanza dalla telecamera (Z)! Se abbiamo due vertici, uno di essi sarà più vicino al centro dello schermo rispetto all'altro, poiché ha una coordinata Z maggiore.

Questa si chiama proiezione prospettica:


E fortunatamente per noi, la matrice 4x4 può anche rappresentare trasformazioni promettenti:

glm::mat4 proiezioneMatrice = glm::prospettiva(


FoV, // Campo di visualizzazione orizzontale in gradi. O grandezzaavvicinandosi. Comecome se « lente» SUtelecamera. Tipicamente tra 90 (super ampio, come un fisheye) e 30 (come un piccolo cannocchiale)


4.0 F / 3.0 F, // Proporzioni. Dipende dalle dimensioni della tua finestra. Ad esempio, 4/3 == 800/600 == 1280/960, suona familiare, non è vero?


0.1 F, // Vicino al campo di ritaglio. Deve essere impostato il più grande possibile, altrimenti si verificheranno problemi di precisione.


100.0 F // Campo di ritaglio lontano. È necessario mantenerlo il più piccolo possibile.


);

Ripetiamo quello che abbiamo appena fatto:

Ci siamo spostati dallo spazio della telecamera (tutti i vertici sono dati in coordinate relative alla telecamera) in uno spazio omogeneo (tutti i vertici sono nelle coordinate di un piccolo cubo (-1,1). Tutto ciò che è nel cubo è acceso lo schermo.)

E lo schema finale:


Ecco un'altra immagine per rendere più chiaro cosa succede quando moltiplichiamo tutte queste assurdità della matrice di proiezione. Prima di moltiplicare per la matrice di proiezione, abbiamo oggetti blu definiti nello spazio della fotocamera e un oggetto rosso che rappresenta il campo visivo della fotocamera: lo spazio che cade nell'obiettivo della fotocamera:

Dopo aver moltiplicato per la matrice di proiezione, otteniamo quanto segue:

Nell'immagine precedente, il campo visivo si è trasformato in un cubo ideale (con le coordinate dei vertici da -1 a 1 lungo tutti gli assi) e tutti gli oggetti sono deformati in prospettiva. Tutti gli oggetti blu vicini alla telecamera sono diventati grandi, mentre quelli più lontani sono diventati piccoli. Proprio come nella vita!

Questa è la visione che abbiamo dalla “lente”:

Tuttavia, è quadrata e dobbiamo applicare un'altra trasformazione matematica per adattare l'immagine alle dimensioni della finestra.

L'assonometria è una proiezione parallela. La Tabella 3.3 elenca innanzitutto le matrici delle proiezioni ortografiche sui piani di coordinate ottenute dalle loro definizioni.

Tabella 3.3.Matrici di trasformazioni progettuali e proiezioni

Proiezione ortografica su XOY

Proiezione ortografica su YOZ

Proiezione ortografica su XOZ

Proiezione ortografica sul piano x=p

Matrice di trasformazione trimetrica al piano XOY

Matrice di trasformazione isometrica al piano XOY

Matrice di proiezione isometrica sul piano XOY

Matrice di proiezione obliqua su XOY

Matrice di proiezione libera su XOY

Matrice di proiezione del mobile su XOY

Matrice di trasformazione prospettica con un punto di fuga (il piano dell'immagine è perpendicolare all'asse x)

Matrice di trasformazione prospettica con un punto di fuga (il piano dell'immagine è perpendicolare all'asse y)

Matrice di trasformazione prospettica con un punto di fuga (il piano dell'immagine è perpendicolare all'asse applicato)

Matrice di trasformazione prospettica con due punti di fuga (il piano dell'immagine è parallelo all'asse y)

Matrice di trasformazione prospettica con tre punti di fuga (piano dell'immagine di posizione arbitraria)

Isometria, dimetria e trimetria si ottengono mediante una combinazione di rotazioni seguite da una proiezione dall'infinito. Se devi descrivere la proiezione sul piano XOY, devi prima eseguire una trasformazione di rotazione di un angolo rispetto all'asse delle ordinate, quindi all'angolo rispetto all'asse delle ascisse. La Tabella 3.3 mostra la matrice di trasformazione trimetrica. Per ottenere una matrice di trasformazione dimetrica in cui, ad esempio, i coefficienti di distorsione lungo gli assi delle ascisse e delle ordinate siano uguali, il rapporto tra gli angoli di rotazione deve obbedire alla relazione

Cioè, scegliendo l'angolo , puoi calcolare l'angolo e determinare la matrice di proiezione dimetrica. Per una trasformazione isometrica, la relazione tra questi angoli si trasforma in valori rigorosamente definiti, che sono:

La Tabella 3.3 mostra la matrice di trasformazione isometrica, nonché la matrice di proiezione isometrica sul piano XOY. La necessità di matrici del primo tipo risiede nel loro utilizzo negli algoritmi per la rimozione di elementi invisibili.

Nelle proiezioni oblique, le rette sporgenti formano con il piano di proiezione un angolo diverso da 90 gradi. La Tabella 3.3 mostra la matrice generale della proiezione obliqua sul piano XOY, nonché le matrici delle proiezioni libere e cabinet, in cui:

Le proiezioni prospettiche (Tabella 3.3) sono rappresentate anche da trasformazioni prospettiche e proiezioni prospettiche sul piano XOY. V X, V Y e V Z sono centri di proiezione - punti sugli assi corrispondenti. –V X, -V Y, -V Z saranno i punti in cui convergono fasci di rette parallele agli assi corrispondenti.

Il sistema di coordinate dell'osservatore è Sinistra sistema di coordinate (Fig. 3.3), in cui l'asse z e è diretto dal punto di vista in avanti, l'asse x e è diretto a destra e l'asse y e è diretto verso l'alto. Questa regola viene adottata per garantire che gli assi x e e y e coincidano con gli assi x s e y s sullo schermo. La determinazione dei valori delle coordinate dello schermo x s e y s per il punto P porta alla necessità di dividere per la coordinata z e. Per costruire un'immagine prospettica accurata, è necessario dividere per la coordinata di profondità di ciascun punto.

La Tabella 3.4 mostra i valori del descrittore di vertice S(X,Y,Z) del modello (Fig. 2.1) sottoposto a trasformazioni di rotazione e trasformazione isometrica.

Tabella 3.4. Descrittori dei vertici del modello

Modello originale

M(R(z,90))xM(R(y,90))

Oggi daremo uno sguardo più da vicino al dispositivo con fotocamera virtuale. Cominciamo con l'immagine.

Nella figura vediamo lo spazio delle coordinate della telecamera. La direzione ("sguardo") della telecamera coincide sempre con la direzione positiva dell'asse z e la telecamera stessa si trova nell'origine.

Lo spazio interno della piramide mostrata in figura è quella parte del mondo virtuale che l'utente vedrà.

Notare i tre piani. Il primo si trova a una distanza di 1 lungo l'asse z. Questo è il piano vicino. Il giocatore non vedrà mai cosa c'è davanti. In questo caso il valore di z è uguale a uno, ma in generale può essere qualsiasi cosa. Un difetto della visualizzazione grafica è associato al piano vicino. Questo difetto si manifesta soprattutto nei tiratori (a causa dell'ampia libertà della fotocamera). Quando ti avvicini troppo a un oggetto, puoi finire "dentro". Tra i giochi più recenti, questo difetto è stato particolarmente pronunciato in Left 4 dead: quando una folla di zombi cadeva sul giocatore, molto spesso era possibile guardare dentro altri personaggi.

Il piano situato a una distanza di 100 unità lungo l'asse z è chiamato piano lontano. Ancora una volta, il valore può essere arbitrario. L'utente non vedrà mai oggetti situati oltre questo piano.

I sei piani che limitano lo spazio visibile all'utente sono chiamati piani di ritaglio: sinistro, destro, superiore, inferiore, vicino e lontano.

Il piano situato tra il vicino e il lontano è la proiezione. Di seguito posizioneremo questo piano in z=1, cioè coinciderà con quello più vicino. Qui ho separato i piani di prossimità e di proiezione per dimostrare che non sono la stessa cosa. Il piano di proiezione è destinato alla trasformazione finale delle coordinate: la trasformazione dallo spazio tridimensionale della telecamera allo spazio bidimensionale.

È grazie al piano di proiezione che l'utente vedrà il mondo virtuale. In realtà, questo aereo è ciò che vedrà l'utente. Il piano di proiezione è direttamente correlato a concetti quali buffer di primo piano/sfondo, finestra del programma e schermo utente. Tutti questi concetti possono essere considerati come un'immagine rettangolare, rappresentata nella memoria del computer da una serie di numeri.

La conversione delle coordinate dal mondo tridimensionale al piano di proiezione è la più complessa tra quelle che abbiamo studiato finora.

Campo visivo

Nella figura sopra, il piano di proiezione (e quindi l'immagine che l'utente vedrà) ha una larghezza maggiore della sua altezza. La larghezza e l'altezza del piano di proiezione vengono specificate utilizzando gli angoli. Esistono nomi diversi per questi angoli: campi visivi o aree di visualizzazione. In inglese: campi visivi.

Le aree di visualizzazione sono specificate da due angoli. Chiamiamoli: fovx - area di visualizzazione orizzontale, fovy - area di visualizzazione verticale. Dettagli sulle aree di visualizzazione: di seguito.

Buffer Z/buffer w/buffer di profondità (buffer z/buffer w/buffer di profondità)

Diamo un'occhiata all'immagine, che mostra due triangoli: a una distanza di 25 e 50 unità dalla fotocamera. La figura (a) mostra la posizione dei triangoli nello spazio (vista dall'alto) e la figura (b) mostra l'immagine finale:

Come puoi immaginare, l'immagine deve essere disegnata partendo dagli elementi più distanti e terminando con quelli più vicini. La soluzione ovvia è calcolare la distanza dall'origine (dalla fotocamera) a ciascun oggetto e quindi confrontare. La computer grafica utilizza un meccanismo leggermente più avanzato. Questo meccanismo ha diversi nomi: z-buffer, w-buffer, buffer di profondità. La dimensione dello z-buffer in termini di numero di elementi è la stessa della dimensione del buffer di sfondo e principale. La componente z dell'oggetto più vicino alla telecamera viene inserita nello z-buffer. In questo esempio, dove il triangolo blu si sovrappone al triangolo verde, le coordinate z del blu verranno inserite nel buffer di profondità. Parleremo degli z-buffer in modo più dettagliato in una lezione separata.

Proiezione ortografica/parallela

L'operazione in cui la dimensione dello spazio diminuisce (lo spazio era tridimensionale, è diventato bidimensionale) si chiama proiezione. Prima di tutto, siamo interessati alla proiezione prospettica, ma prima conosceremo la proiezione parallela (proiezione parallela o ortografica).

Per calcolare una proiezione parallela è sufficiente scartare la coordinata extra. Se abbiamo un punto nello spazio [ 3 3 3 ], allora con una proiezione parallela sul piano z=1, verrà proiettato nel punto .

Proiezione prospettica su un piano di proiezione

In questo tipo di proiezione tutte le linee convergono in un punto. Questo è esattamente il modo in cui funziona la nostra visione. Ed è con l'aiuto della proiezione prospettica che viene modellato il "look" di tutti i giochi.


Confronta questo disegno con il disegno che mostra le coordinate omogenee della lezione precedente. Per passare dallo spazio tridimensionale allo spazio bidimensionale, è necessario dividere le prime due componenti dei vettori per la terza: [ x/z y/z z/z ] = [ x/z y/z 1 ].

Come ho scritto sopra, il piano di proiezione può essere posizionato ovunque tra vicino e lontano. Posizioneremo sempre il piano di proiezione su z=1, ma in questo tutorial esamineremo altre opzioni. Diamo un'occhiata all'immagine:


Indichiamo la distanza dal piano di proiezione dall'origine delle coordinate come d. Considereremo due casi: d=1 e d=5. Un punto importante: la terza componente di tutti i vettori dopo la proiezione deve essere uguale a d - tutti i punti si trovano sullo stesso piano z=d. Ciò può essere ottenuto moltiplicando tutte le componenti del vettore per d: [ xd/z yd/z zd/z ]. Con d=1 otteniamo: [ x/z y/z 1 ], questa è la formula che serviva per trasformare le coordinate omogenee.

Ora, se spostiamo il piano di proiezione nel punto z=5 (rispettivamente d=5), otteniamo: [ xd/z yd/z zd/z ] = [ 5x/z 5y/z 5 ]. L'ultima formula proietta tutti i vettori dello spazio su un piano, dove d=5.
Abbiamo un piccolo problema qui. La formula precedente funziona con vettori tridimensionali. Ma abbiamo concordato di utilizzare vettori quadridimensionali. Il quarto componente in questo caso può essere semplicemente scartato. Ma non lo faremo, poiché il suo utilizzo fornisce alcune funzionalità specifiche di cui parleremo più avanti.

È necessario trovare il divisore comune della terza e della quarta componente, quando diviso per il quale il valore d rimane nella terza componente e l'unità nella quarta. Questo divisore è d/z. Ora dal solito vettore [ x y z 1 ] dobbiamo ottenere un vettore pronto per la proiezione (divisione) [ x y z z/d ]. Questo viene fatto utilizzando la matrice di trasformazione (controlla il risultato moltiplicando qualsiasi vettore per questa matrice):


L'ultima trasformazione non è ancora una proiezione. Qui riduciamo semplicemente tutti i vettori alla forma di cui abbiamo bisogno. Lascia che ti ricordi che posizioneremo il piano di proiezione su d=1, il che significa che i vettori appariranno così: [ x y z z ].

Matrice di trasformazione prospettica

Esamineremo la matrice di trasformazione prospettica utilizzata in DirectX:

Ora sappiamo a cosa è destinato l'elemento _34. Sappiamo anche che gli elementi _11 e _22 scalano l'immagine orizzontalmente e verticalmente. Diamo un'occhiata a cosa si nasconde esattamente dietro i nomi xScale e yScale.

Queste variabili dipendono dalle aree di visualizzazione di cui abbiamo discusso sopra. Aumentando o diminuendo questi angoli, puoi ridimensionare (ridimensionare o ingrandire) l'immagine - modificare le dimensioni e le proporzioni del piano di proiezione. Il meccanismo dello zoom ricorda vagamente lo zoom delle fotocamere/fotocamere: il principio è molto simile. Diamo un'occhiata all'immagine:


Dividiamo l'angolo fov in due parti e consideriamo solo la metà. Quello che vediamo qui: aumentando l'angolo fov/2 (e, di conseguenza, l'angolo fov), aumentiamo il peccato dell'angolo e diminuiamo il cos. Ciò porta ad un aumento del piano di proiezione e, di conseguenza, ad una diminuzione degli oggetti proiettati. L'angolo ideale per noi sarebbe fov/2 = P/4. Ti ricordo che un angolo espresso in P/4 radianti equivale a 45 gradi. In questo caso, fov sarà pari a 90 gradi. Perché un angolo di 45 gradi è adatto a noi? In questo caso, non vi è alcun ridimensionamento e cos(P/4)/sin(P/4)=1.

Ora possiamo facilmente ridimensionare l'immagine verticalmente (orizzontalmente) utilizzando il seno e il coseno di metà dell'area di visualizzazione (la funzione cotangente in C++ è chiamata cot):

scala y = cos(fovY/2)/sin(fovY/2) = lettino(fovY/2)
DirectX utilizza solo il FOV verticale (fovY) e il ridimensionamento orizzontale dipende dal FOV verticale e dalle proporzioni.

Lascia che ti ricordi che la dimensione della finestra nei nostri programmi è 500x500. Proporzioni: da 1 a 1. Pertanto, le variabili saranno uguali: xScale=1, yScale=1.

Rapporto d'aspetto monitor/TV standard: 4:3. Questo rapporto corrisponde alle risoluzioni dello schermo: 640x480, 800x600, 1600x1200. Non toccheremo ancora la modalità a schermo intero, ma possiamo modificare la dimensione della finestra del programma. È possibile modificare la dimensione della finestra (nei parametri attuali), ad esempio, in 640X480. Ma per evitare che tutti gli oggetti vengano allungati (i quadrati sembreranno rettangoli), non dimenticare di modificare le variabili corrispondenti nella matrice di proiezione.

Quasi dimenticavo, forum per xScale in DirectX:

Scala x = Scala y/proporzioni
I rapporti d'aspetto sono impostati semplicemente: 1/1, 4/3, 16/9: questi sono quelli standard.

Resta da scoprire lo scopo degli elementi _33, _34 della matrice di trasformazione prospettica. zf è la coordinata z del piano lontano (da lontano - lontano) e zn è la coordinata z del piano vicino (da vicino - vicino). Nota che l'elemento _43 = _33 * -zn.

Il modo più semplice per capire esattamente cosa fanno queste formule è con degli esempi. Moltiplichiamo il vettore standard [ x y z w ] per la matrice presentata sopra. Ti consiglio di farlo prendendo un pezzo di carta e una matita (spero che ti ricordi come moltiplicare due matrici). Le componenti del vettore assumeranno la forma seguente.

1° = x*xScala
2° = y*yScala
3° = z*(zf/(zf-zn)) + w*(-(zn*zf)/(zf-zn)) = (zf/(zf-zn))*(z - w*zn)
4° = (w*z)/d
Eseguiamo una trasformazione di proiezione (dividiamo tutti gli elementi nel 4° componente e assumiamo che d=1 e w=1):

1° = (d*x*xScala)/(w*z) = (x*xScala)/z
2° = (d*y*yScala)/(w*z) = (y*xScala)/z
3° = (zf/(zf-zn))*(z - w*zn)*(w*d/z) = (zf/(zf-zn))*(1 - zn/z)
4° = 1
Di conseguenza, abbiamo ricevuto un vettore del modulo:

[ x/(z*xScala) y/(z*yScala) (zf/(zf-zn))*(1-zn/z) 1 ]
Ora, se specifichi valori specifici per zf e zn, troverai quanto segue (per valori positivi): se il vettore si trova prima del piano vicino, la componente z dopo la trasformazione sarà inferiore a zero, se il vettore si trova dietro il piano lontano, quindi la componente z sarà maggiore di unità.

Non c'è differenza dove si trovano esattamente i piani vicino e lontano: zn=1, zf=10 o zn=10 e zf=100 (o qualsiasi altro valore) - dopo la trasformazione, l'area visibile verrà posizionata nell'intervallo da zero a uno compreso.

Questo è esattamente lo scopo delle formule negli elementi _33, _34 della matrice di proiezione: proiettare la distanza dal piano vicino a quello lontano nel segmento. Verificalo calcolando i valori di diversi vettori per valori specifici di zn,zf (sì, su un pezzo di carta!!!).

I migliori articoli sull'argomento