IT knowledge base
CTRL+F per cercare la tua parola chiave

Impara OpenGL. Lezione 4.6 - Mappe cubiche

OGL3

Carte cubiche

Fino ad ora, abbiamo dovuto utilizzare solo trame 2D, tuttavia OpenGL supporta molti più tipi di trame. E in questo tutorial esamineremo un tipo di mappa di texture, che in realtà è una combinazione di diverse trame separate: è una mappa del cubo .
Una mappa cubo è essenzialmente un singolo oggetto trama contenente 6 trame 2D separate, ognuna delle quali corrisponde a un lato del cubo con trama. Perché un cubo del genere sarebbe utile? Perché unire sei trame separate in una mappa invece di utilizzare oggetti trama separati? La linea di fondo è che le selezioni della mappa del cubo possono essere effettuate utilizzando un vettore di direzione.
Soddisfare
Parte 1. Inizio
  1. OpenGL
  2. Crea una finestra
  3. Ciao finestra
  4. Ciao triangolo
  5. Shader
  6. trame
  7. trasformazioni
  8. Sistemi di coordinate
  9. telecamera

Parte 2. Illuminazione di base
  1. Colori
  2. Nozioni di base sull'illuminazione
  3. Materiali (modifica)
  4. Mappe texture
  5. Fonti di luce
  6. Molteplici fonti di illuminazione

Parte 3. Caricamento di modelli 3D
  1. Libreria Assimp
  2. Classe poligono mesh
  3. Classe modello 3D

Parte 4. Funzionalità OpenGL avanzate
  1. Prova di profondità
  2. Prova dello stampino
  3. Miscelazione dei colori
  4. Ritaglio facce
  5. Buffer frame
  6. Carte cubiche
  7. Manipolazione avanzata dei dati
  8. GLSL avanzato
  9. Shader geometrico
  10. Istanza
  11. levigante

Parte 5. Illuminazione avanzata
  1. Illuminazione avanzata. Modello Blinn-Fong.
  2. Correzione gamma
  3. Mappe delle ombre
  4. Mappe ombre omnidirezionali
  5. Mappatura normale
  6. Mappatura della parallasse
  7. HDR
  8. fioritura
  9. Rendering differito
  10. SSAO

Parte 6. PBR
  1. Teoria
  2. Sorgenti luminose analitiche
  3. IBL. Irraggiamento diffuso.
  4. IBL. Irraggiamento speculare.

Immagina un cubo unitario (un cubo con lati di unità 1x1x1) dal centro del quale ha origine un vettore di direzione. Una trama recuperata da una mappa del cubo con questo vettore di direzione arancione all'interno sarebbe simile a questa:

La lunghezza del vettore di direzione non è importante. OpenGL deve solo conoscere la direzione per eseguire il recupero finale corretto dalla trama.

Se immaginiamo una forma di cubo su cui è allungata una mappa del cubo, risulta che il vettore di direzione che indica l'area di selezione è simile alle coordinate interpolate dei vertici del cubo. Con questo in mente, possiamo effettuare selezioni dalla mappa utilizzando i dati delle coordinate del cubo, è solo necessario che il cubo stesso rimanga posizionato simmetricamente rispetto all'origine. In questo caso, le coordinate della trama dei vertici del cubo possono essere prese uguali ai vettori di posizione dei vertici del cubo. Di conseguenza, le coordinate della trama si riferiscono correttamente alle trame delle singole facce del cubo.

Creazione di una mappa cubo

Una mappa cubo, come qualsiasi altro oggetto texture, viene creata secondo le regole già familiari: creiamo direttamente un oggetto texture e lo leghiamo a un target texture adatto ( texture target ) prima di eseguire qualsiasi azione sulla texture. Nel nostro caso, il punto di ancoraggio sarà GL_TEXTURE_CUBE_MAP :
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
Poiché la mappa del cubo è una combinazione di sei immagini di texture separate, dovremo effettuare sei chiamate a glTexImage2D con un insieme di parametri simili a quelli utilizzati nei tutorial precedenti. Questa volta, però, ad ogni chiamata, il valore del parametro texture target assumerà uno dei valori speciali associati a ciascuna faccia della mappa del cubo. Fondamentalmente, diciamo a OpenGL per quale faccia creare l'immagine della texture. E così sei volte: un glTexImage2D per ogni faccia.
Poiché ci sono solo sei facce, in OpenGL ci sono sei speciali texture target che sono specificamente responsabili della creazione di texture per tutte le facce della mappa del cubo:
Non è raro che i membri enum di OpenGL abbiano valori numerici che cambiano in modo lineare. Nel caso degli elementi elencati, questo è vero anche. Questo ci consente di inizializzare facilmente le trame per tutte le facce utilizzando un semplice ciclo che parte dal target della texture GL_TEXTURE_CUBE_MAP_POSITIVE_X e semplicemente incrementando il target di 1 ad ogni passaggio:
int width, height, nrChannels;
unsigned char *data;  
for(GLuint i = 0; i < textures_faces.size(); i++)
{
    data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
    glTexImage2D(
        GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
        0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
    );
}
Qui stiamo usando un vettore chiamato textured_faces , che contiene i percorsi a tutti i file di texture necessari per definire la mappa del cubo nell'ordine corrispondente all'ordine degli ID faccia dalla tabella. Questo creerà trame per ciascuna delle facce della cubemap attualmente ancorata.
Un oggetto texture della mappa del cubo ha tutte le stesse proprietà di qualsiasi texture, quindi non fa male modificare il filtro delle texture e le modalità di ripetizione:
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); 
Non essere sorpreso dal parametro ancora sconosciuto GL_TEXTURE_WRAP_R - imposta solo la modalità di ripetizione per la terza coordinata della trama (una coordinata la z per le posizioni). Usiamo la modalità di ripetizione GL_CLAMP_TO_EDGE , poiché le coordinate della trama che sono esattamente tra due bordi possono portare alla mancanza di un campionamento corretto (a causa di alcune limitazioni hardware). Questa modalità di ripetizione selezionata consente di restituire il valore dal confine della trama, anche nei casi di selezioni tra i bordi della mappa.
Prima di eseguire il rendering degli oggetti utilizzando una mappa del cubo, dobbiamo specificare l'unità di trama da utilizzare e ancorare la mappa del cubo. Tutto è esattamente come nel caso delle trame 2D.
Nel codice del frammento shader, dovrai cambiare il tipo di campionatore in samplerCube , poiché passeremo un vettore 3D di coordinate della trama alla funzione trama invece di uno 2D. Esempio di codice shader utilizzando la mappa del cubo di seguito:
in vec3 textureDir; // direction vector, also representing the 3D texture coordinate
uniform samplerCube cubemap; // sampler for cube map

void main ()
{
    FragColor = texture (cubemap, textureDir);
}
Bene, tutto questo è divertente, ma finora sembra inutile. Non saltare alle conclusioni, alcuni grandi effetti sono molto facili da implementare usando le mappe dei cubi. Uno di questi è la creazione di uno skybox.

Skybox

Uno skybox è un enorme cubo che racchiude l'intera scena attuale, strutturato con sei immagini dell'ambiente che circonda la scena, e dà al giocatore l'illusione che la scena in cui si trova sia molto più grande di quanto non sia in realtà. Di solito gli skybox nei giochi vengono implementati utilizzando immagini di montagne, cieli nuvolosi o notturni - tutto questo, penso, ti è familiare. Ad esempio, ecco uno skybox con la trama del cielo stellato notturno dalla terza parte di TES:
Penso che tu abbia già intuito che le mappe dei cubi chiedono semplicemente di essere utilizzate qui: abbiamo un cubo con sei facce e la necessità di strutturarlo correttamente in base alla faccia. Nello screenshot precedente, solo alcune immagini del cielo notturno davano al giocatore l'effetto di trovarsi in un enorme universo, sebbene in realtà sia prigioniero di un minuscolo cubo.
Ci sono abbastanza risorse in rete dove puoi trovare set di texture preparate per skybox. Ad esempio, qui e qui sono sufficienti per molti esperimenti. Spesso, le texture skybox pubblicate sulla rete seguono la seguente presentazione:
Se pieghi l'immagine lungo i bordi (piegando i bordi "dietro lo schermo"), otterrai un cubo completamente strutturato, all'interno del quale viene creata una sensazione di ampio spazio. Alcune risorse seguono questo layout di trama, quindi dovrai tagliare sei trame tu stesso per le singole facce. Ma, nella maggior parte dei casi, sono disposte in una serie di sei immagini separate.
Qui in buona risoluzione puoi scaricare la texture per lo skybox, che useremo nel tutorial.

Caricamento dello skybox

Poiché lo skybox è rappresentato come una mappa cubo, il processo di caricamento non è molto diverso da quanto già menzionato nel tutorial. Per il caricamento, viene utilizzata la seguente funzione, che richiede un vettore con sei percorsi ai file di texture:
unsigned int loadCubemap(vector<std::string> faces)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

    int width, height, nrChannels;
    for (unsigned int i = 0; i < faces.size(); i++)
    {
        unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
                         0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
            );
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
            stbi_image_free(data);
        }
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    return textureID;
}
Il codice della funzione non dovrebbe sorprendere. Questo è lo stesso codice della mappa del cubo mostrato nella sezione precedente, ma messo insieme in un'unica funzione.
vector<std::string> faces;
{
    "right.jpg",
    "left.jpg",
    "top.jpg",
    "bottom.jpg",
    "front.jpg",
    "back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);
Prima di chiamare la funzione stessa, è necessario riempire il vettore con i percorsi dei file di trama e nell'ordine corrispondente all'ordine delle descrizioni degli elementi dell'enumerazione OpenGL, che descrive l'ordine delle facce del cubo carta geografica.

Visualizzazione dello skybox

Poiché non possiamo fare a meno di un oggetto cubo, abbiamo bisogno di nuovi VAO, VBO e un array di vertici, che possono essere scaricati qui .
Le selezioni da una mappa del cubo utilizzata per coprire un cubo 3D possono essere effettuate utilizzando le coordinate sul cubo stesso. Quando il cubo è simmetrico rispetto all'origine, ciascuna delle coordinate dei suoi vertici definisce anche un vettore di direzione dall'origine. E questo vettore di direzione viene utilizzato solo per campionare dalla trama nel punto corrispondente al vertice del cubo. Di conseguenza, non dobbiamo nemmeno passare le coordinate della trama allo shader!
Per il rendering hai bisogno di un nuovo set di shader, abbastanza semplici. Il vertex shader è banale, viene utilizzato un solo attributo del vertice, senza le coordinate della trama:
#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    TexCoords = aPos;
    gl_Position = projection * view * vec4(aPos, 1.0);
}  
La cosa interessante qui è che stiamo riassegnando le coordinate del cubo di input alla variabile contenente le coordinate della trama per lo shader dei frammenti. Lo shader dei frammenti li usa per campionare con samplerCube :
#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main()
{    
    FragColor = texture(skybox, TexCoords);
}
E questo shader è piuttosto semplice. Prendiamo le coordinate passate dei vertici del cubo come coordinate della trama ed effettuiamo una selezione dalla mappa del cubo.
Anche il rendering non contiene nulla di complicato: leghiamo la mappa del cubo come texture corrente e lo skybox sampler riceve immediatamente i dati necessari. Visualizzeremo prima lo skybox di tutti gli oggetti nella scena e disattiveremo anche il test di profondità. Ciò garantisce che lo skybox sia sempre di fronte ad altri oggetti.
glDepthMask (GL_FALSE);
skyboxShader.use ();
// ... setting the view and projection matrices
glBindVertexArray (skyboxVAO);
glBindTexture (GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays (GL_TRIANGLES, 0, 36);
glDepthMask (GL_TRUE);
// ... output the rest of the scene
Se provi a eseguire il programma in questo momento, dovrai affrontare il seguente problema. In teoria, vorremmo che l'oggetto skybox rimanesse posizionato simmetricamente rispetto alla posizione del giocatore, non importa quanto lontano si sposti - solo lì puoi creare l'illusione che l'ambiente raffigurato sullo skybox sia davvero enorme. Ma la matrice di visualizzazione che stiamo utilizzando applica una serie completa di trasformazioni: rotazione, ridimensionamento e traslazione. Quindi, se il giocatore si muove, si muove anche lo skybox! Per evitare che le azioni del giocatore influiscano sullo skybox, dovremmo rimuovere le informazioni sul movimento dalla matrice di visualizzazione.
Potresti ricordare il tutorial introduttivo sull'illuminazione e il fatto che siamo stati in grado di eliminare le informazioni di spostamento nelle matrici 4x4 semplicemente estraendo la sottomatrice 3x3. Puoi ripetere questo trucco semplicemente convertendo la dimensione della matrice di visualizzazione prima in 3x3, poi di nuovo in 4x4:
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));  
I dati di offset verranno rimossi, ma il resto dei dati di trasformazione verrà preservato, il che consentirà al giocatore di guardarsi intorno normalmente.
Di conseguenza, otteniamo un'immagine che trasmette la sensazione di un ampio spazio grazie allo skybox. Volando accanto al contenitore, sentirai questo senso di scala, che aggiunge molto realismo all'intera scena:
Prova altre selezioni di texture e guarda l'effetto che hanno sulla sensazione della scena.

Ottimizzazione del rendering dello skybox

Come notato, lo skybox viene renderizzato davanti a tutti gli altri oggetti nella scena. Funziona con il botto, ma non troppo economicamente in termini di risorse. Giudicate voi stessi: durante il rendering in anticipo, dovremo eseguire uno shader di frammenti per tutti i pixel dello schermo, anche se solo alcuni di essi rimarranno visibili nella scena finale e la maggior parte di essi potrebbe essere scartata utilizzando un test di profondità anticipato, risparmiandoci risorse preziose.
Quindi, per motivi di efficienza, vale la pena visualizzare lo skybox per ultimo. In questo caso, il buffer di profondità verrà riempito con i valori di profondità di tutti gli oggetti nella scena e lo skybox verrà visualizzato solo dove il test di profondità iniziale è andato a buon fine, risparmiandoci le chiamate allo shader dei frammenti. Ma c'è un problema: molto probabilmente non entrerà proprio nulla nell'inquadratura, dal momento che lo skybox è solo un cubo 1x1x1 e fallirà la maggior parte dei test di profondità. È anche impossibile disabilitare il test di profondità prima del rendering, perché lo skybox verrà semplicemente visualizzato nella parte superiore della scena. Devi fare in modo che il buffer di profondità creda che la profondità dell'intero skybox sia 1 affinché il test di profondità fallisca se c'è un altro oggetto davanti allo skybox.
Nella lezione sui sistemi di coordinate, abbiamo detto che la divisione in prospettiva viene eseguita dopo l'esecuzione del vertex shader e divide i componenti xyz di gl_Positions per il valore del componente w . Inoltre, dalla lezione sul test di profondità, sappiamo che la componente z ottenuta dopo la divisione è uguale alla profondità del vertice dato. Usando queste informazioni, possiamo impostare il componente z di gl_Positions in modo che sia uguale al componente w , che dopo la divisione in prospettiva ci darà un valore di profondità di 1 ovunque:
void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;
} 
Di conseguenza, nelle coordinate del dispositivo normalizzate, il vertice avrà sempre un componente z = 1, che è il valore massimo nel buffer di profondità. E lo skybox verrà visualizzato solo nelle aree dello schermo dove non ci sono altri oggetti (solo qui il test di profondità sarà superato, poiché in altri punti lo skybox si sovrappone a qualcosa).
Tuttavia, dobbiamo cambiare il tipo della funzione di profondità in GL_LEQUAL invece dello standard GL_LESS . Per impostazione predefinita, il buffer di profondità cancellato viene riempito con un valore di 1, di conseguenza è necessario fornire le condizioni affinché lo skybox superi con successo il test per valori di profondità non strettamente inferiori a quelli memorizzati nel buffer, ma inferiori o uguale.
Il codice che utilizza questa ottimizzazione può essere trovato qui .

Mappatura ambientale

Quindi, una mappa cubo contiene informazioni sull'ambiente della scena, proiettate in un oggetto texture. Un tale oggetto può essere utile non solo per creare skybox. Utilizzando una mappa cubo con una texture ambientale, è possibile simulare le proprietà riflettenti o rifrangenti degli oggetti. Queste tecniche che utilizzano le mappe dei cubi sono chiamate tecniche di mappatura dell'ambiente e la più nota di queste è l'imitazione della riflessione e della rifrazione.

Riflessione

La riflessione è la proprietà di un oggetto di visualizzare lo spazio circostante, ad es. a seconda dell'angolo di visione, l'oggetto acquisisce colori più o meno identici a quelli dell'ambiente. Uno specchio è un esempio ideale: riflette l'ambiente a seconda della direzione dello sguardo dell'osservatore.
I fondamenti della fisica dei riflessi non sono troppo difficili. L'immagine seguente mostra come trovare un vettore di riflessione e utilizzarlo per campionare da una mappa del cubo:
Vettore di riflessione$ \ color {verde} {\ bar {R}} $ si basa sulla direzione dello sguardo$ \ color {grigio} {\ bar {I}} $rispetto al vettore normale$ \ colore {rosso} {\ bar {N}} $... Il calcolo diretto può essere eseguito utilizzando la funzione incorporata GLSL riflettere () . Il vettore risultante$ \ color {verde} {\ bar {R}} $ può quindi essere utilizzato come vettore di direzione per campionare il valore del colore riflesso dalla mappa del cubo. Di conseguenza, l'oggetto apparirà come se riflettesse lo skybox circostante.
Poiché lo skybox è già predisposto nella nostra scena, la creazione di riflessi non è molto difficile. Modifichiamo leggermente lo shader dei frammenti utilizzato dal contenitore per conferirgli proprietà riflettenti:
#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 Position;

uniform vec3 cameraPos;
uniform samplerCube skybox;

void main()
{             
    vec3 I = normalize(Position - cameraPos);
    vec3 R = reflect(I, normalize(Normal));
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
Innanzitutto, calcoliamo il vettore della direzione della telecamera (vista) I e lo usiamo per calcolare il vettore di riflessione R , che viene utilizzato per campionare dalla mappa del cubo. Stiamo usando di nuovo Normal e Position interpolati, quindi dovremo perfezionare anche il vertex shader:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 Normal;
out vec3 Position;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    Normal = mat3(transpose(inverse(model))) * aNormal;
    Position = vec3(model * vec4(aPos, 1.0));
    gl_Position = projection * view * model * vec4(aPos, 1.0);
} 
Perché le normali vengono utilizzate di nuovo, quindi la loro trasformazione richiede il ritorno utilizzando la matrice normale. Il vettore Posizione contiene le coordinate globali del vertice. Verrà utilizzato nello shader dei frammenti per calcolare il vettore dello sguardo.
A causa della presenza di normali, sarà necessario aggiornare il set di dati del vertice utilizzato, nonché aggiornare il codice associato all'impostazione dei puntatori dell'attributo del vertice. Inoltre, non dimenticare di impostare l' uniforme cameraPos su ogni iterazione di rendering.
Prima di disegnare il contenitore stesso, dovresti associare l'oggetto mappa del cubo:
glBindVertexArray(cubeVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);  		
glDrawArrays(GL_TRIANGLES, 0, 36);	
Come risultato dell'esecuzione del programma, otteniamo un cubo che conduce come uno specchio ideale che riflette l'intero ambiente.
Il codice sorgente è qui .
Quando a un intero oggetto è assegnato questo effetto speciale, sembra essere realizzato con un materiale altamente riflettente come l'acciaio lucido o il cromo. Se carichi un modello di nanotuta dal tutorial di caricamento dei modelli , l'intera tuta sembrerà di metallo solido:
Sembra fantastico, ma in realtà raramente un oggetto è completamente specchiato. Ma è possibile introdurre mappe di riflessione, che aggiungeranno un po' più di dettaglio ai modelli. Come le mappe diffuse e le mappe speculari speculari, questo tipo di mappe è una trama regolare, il cui campionamento determina il grado di riflessione speculare per un frammento. Con il loro aiuto, puoi specificare le aree del modello che hanno un riflesso speculare e la sua intensità. Negli esercizi di questa lezione, ti verrà presentato il compito di aggiungere una mappa di riflessione al caricatore di modelli che hai sviluppato in precedenza.

Rifrazione

Un'altra forma di visualizzazione dell'ambiente è la rifrazione, che è in qualche modo simile alla riflessione. La rifrazione è un cambiamento nella direzione del movimento di un raggio di luce causato dalla sua transizione attraverso l'interfaccia tra due mezzi. È la rifrazione che dà l'effetto di cambiare la direzione della luce in un mezzo liquido, che è facilmente percepibile se metà della tua mano è immersa nell'acqua.
La rifrazione è descritta dalla legge di Snell , combinata con una mappa del cubo, che assomiglia a questa:
Ancora una volta abbiamo un vettore di osservazione$ \ color {grigio} {\ bar {I}} $, il vettore normale$ \ colore {rosso} {\ bar {N}} $, e il vettore rifrattivo finale$ \ color {verde} {\ bar {R}} $ ... Come puoi vedere, la direzione del vettore dello sguardo è distorta a causa della rifrazione. Ed è il vettore modificato$ \ color {verde} {\ bar {R}} $ verrà utilizzato per campionare da una mappa del cubo.
Il vettore di rifrazione può essere facilmente calcolato utilizzando un'altra funzione GLSL incorporata, rifrange () , che accetta un vettore normale, un vettore sguardo e un rapporto di indici di rifrazione di materiali adiacenti.
L'indice di rifrazione determina il grado di deviazione della direzione del raggio di luce che attraversa il materiale. Ogni sostanza ha il suo coefficiente, ecco alcuni esempi:
I dati della tabella vengono utilizzati per calcolare la relazione tra gli indici di rifrazione dei materiali attraverso i quali passa la luce. Nel nostro caso il raggio passa dall'aria al vetro (supponiamo che il nostro contenitore sia di vetro), il che significa che il rapporto è 1/1,52 = 0,658.
Abbiamo già scattato la mappa del cubo, i dati dei vertici, comprese le normali, sono stati caricati e la posizione della telecamera è stata trasferita all'uniforme corrispondente. Non resta che cambiare lo shader dei frammenti:
void main()
{             
    float ratio = 1.00 / 1.52;
    vec3 I = normalize(Position - cameraPos);
    vec3 R = refract(I, normalize(Normal), ratio);
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}  
Modificando i valori degli indici di rifrazione, puoi creare effetti visivi completamente diversi. Quando avviiamo l'applicazione, purtroppo non ci aspettiamo un'immagine particolarmente impressionante, poiché il cubo di vetro non enfatizza troppo l'effetto di rifrazione e sembra solo una lente d'ingrandimento cubica. È un'altra cosa se usi un modello di una nanotuta: diventa immediatamente chiaro che l'oggetto è fatto di una sostanza simile al vetro:
Si può immaginare che con l'aiuto dell'applicazione magistrale di illuminazione, riflessione, rifrazione e trasformazione dei vertici, si possa ottenere una simulazione molto impressionante della superficie dell'acqua.
Vale la pena notare, tuttavia, che per risultati fisicamente affidabili, è necessario simulare la seconda rifrazione quando il raggio esce dalla parte posteriore dell'oggetto. Ma per scene semplici, anche una rifrazione semplificata, tenendo conto solo di un lato dell'oggetto, è abbastanza.

Modifica delle mappe ambientali

In questa lezione abbiamo imparato come utilizzare una combinazione di più immagini per creare uno skybox, che, sebbene appaia fantastico, non tiene conto della possibile presenza di oggetti in movimento nella scena. Questo non è evidente ora, poiché c'è un solo oggetto nella scena. Ma se ci fosse un oggetto riflettente nella scena circondato da altri oggetti, vedremmo solo il riflesso dello skybox, come se non ci fossero altri oggetti in vista.
Utilizzando i frame buffer fuori schermo è possibile creare una nuova mappa cubo della scena dal punto in cui si trova l'oggetto riflettente, visualizzando la scena in sei direzioni. Una tale mappa cubo generata dinamicamente potrebbe essere utilizzata per creare superfici riflettenti o rifrangenti realistiche in cui sono visibili altri oggetti. Questa tecnica è chiamata mappatura dinamica dell'ambiente perché prepariamo nuove mappe cubo che includono l'intero ambiente al volo durante il processo di rendering.
L'approccio è ottimo, ma con uno svantaggio significativo: la necessità di eseguire il rendering dell'intera scena sei volte per ogni oggetto utilizzando una mappa ambientale cubica. Questo è un colpo molto significativo per le prestazioni di qualsiasi applicazione. Le applicazioni moderne cercano di sfruttare al meglio gli skybox statici e le mappe cubiche pre-renderizzate ove possibile per approssimare la visualizzazione dinamica dell'ambiente.
Sebbene il rendering dinamico sia un'ottima tecnica, devi utilizzare molte modifiche e hack pieni di risorse per applicarlo alla tua applicazione senza un significativo impatto sulle prestazioni.

Esercizi

Prova ad aggiungere una mappa di riflessione al caricatore di modelli creato nel relativo tutorial . I dati del modello di nanotuta, inclusa la stessa texture della mappa di riflessione, possono essere scaricati qui . Faccio notare alcuni punti:
  • Ad Assimp chiaramente non piacciono le mappe speculari nella maggior parte dei formati di modello, quindi imbroglieremo un po' - memorizzeremo la mappa speculare come una mappa di retroilluminazione. Il caricamento della mappa, di conseguenza, dovrà essere eseguito con il trasferimento del tipo di texture aiTextureType_AMBIENT nella procedura di caricamento del materiale.
  • La mappa speculare è stata creata frettolosamente dalla mappa lucida speculare. Pertanto, in alcuni punti non si sovrappone molto bene al modello.
  • Poiché il caricatore del modello utilizza già tre unità di trama nello shader, dovrai utilizzare la quarta unità per associare la mappa del cubo, poiché viene recuperata dallo stesso shader.
Se tutto è fatto correttamente, il risultato sarà il seguente:
Dal traduttore :
Vale la pena notare che quando si lavora con gli sweep per gli skybox, è possibile riscontrare una notevole confusione con l'orientamento, specialmente quando si nominano le trame in base ai nomi delle facce ("sinistra", "destra", ...) e non in base alle direzioni degli assi (+ X - positivoX, posX; -X - negativoX, negativoX, ...).
Questa è una conseguenza del fatto che la specifica dell'espansione della mappa del cubo è piuttosto antica e ha le sue radici nella specifica Pixar RenderMan, dove la mappa del cubo utilizza un sistema di coordinate sinistrorso e il cubo stesso collassa come se l'osservatore fosse al centro . Lo stesso OpenGL, come ricordiamo, utilizza un sistema di coordinate destrorso.
Dovrai ricordarlo o se la scansione della trama di interesse non coincide con quella qui descritta, o se c'è la necessità di allineare i "lati" della mappa del cubo rispetto alle coordinate del mondo come se fosse piegata con l'osservatore dentro.
Sulla “croce” di sweep mostrata nella sezione Skybox, la faccia anteriore è considerata come tale dalla posizione di un osservatore che è fuori dal cubo, guardandolo direttamente. Nel codice, viene caricato sul target corrispondente all'asse Z positivo (in coordinate globali). Nell'applicazione, risulta che dall'interno del cubo vediamo un'immagine riflessa lungo l'asse X rispetto a quanto mostrato sulla trama.
L'autore stesso fornisce un link a questo sito, dove la scansione utilizzata non corrisponde a quella mostrata nella lezione. Per allinearli, dovrai scambiare i pannelli destro e sinistro, ruotare il pannello superiore in senso orario di 90 ° e quello inferiore in senso antiorario.

I modelli piatti di questo sito sono firmati secondo le direzioni degli assi e non i nomi delle facce. Tutto quello che devi fare è rinominare i file secondo la tabella qui.
È più facile capire l'essenza in azione - nell'applicazione assemblata. Firma lo skybox tagliando le immagini con i lati previsti (per curiosità, puoi anche aggiungere gli assi delle coordinate della trama) e vedere nell'applicazione come va a finire.
PS : abbiamo una telegramma-conferenza per coordinare i trasferimenti. Se vuoi inserirti nel ciclo, allora sei il benvenuto!