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

Impara OpenGL. Lezione 4.10 - Istanza

OGL3

Istanza

Immagina di aver concepito una scena contenente un numero enorme di modelli di oggetti e la maggior parte di questi modelli contiene gli stessi dati di vertice, solo le matrici di trasformazione applicate ad essi differiscono. Ad esempio, una scena con un campo erboso, dove ogni filo d'erba è rappresentato da un modellino formato letteralmente da una coppia di triangoli. Ovviamente, per ottenere l'effetto desiderato, dovrai eseguire il rendering di questo modello non una, ma mille, diecimila volte per fotogramma. Poiché ogni foglia contiene letteralmente un paio di triangoli, la sua resa sarà quasi istantanea. Ma migliaia di chiamate ripetute per rendere le funzioni hanno colpito cumulativamente le prestazioni in modo molto evidente.
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.

Se avessimo davvero pianificato di visualizzare molti oggetti nella scena nel modo descritto, nel codice sarebbe simile a questo:
for (unsigned int ix = 0; ix <model_count; ++ ix)
{
     // bind VAO, textures, set uniforms, etc ...
    DoSomePreparations ();
    glDrawArrays (GL_TRIANGLES, 0, vertex_count);
}
Quando eseguiamo il rendering di molte istanze dello stesso modello, raggiungeremo rapidamente un collo di bottiglia delle prestazioni: molte chiamate per eseguire il rendering di funzioni primitive. Rispetto al tempo necessario per il rendering diretto, l'invio di dati alla GPU che si desidera eseguire il rendering utilizzando funzioni come glDrawArrays o glDrawElemenets richiede molto tempo. Questo tempo viene impiegato per la preparazione richiesta da OpenGL prima dell'output diretto dei dati del vertice: trasferimento dei dati alla GPU sul buffer di lettura corrente, la posizione e il formato dei dati dell'attributo del vertice e così via. E tutto questo scambio viene effettuato su un bus relativamente lento che collega CPU e GPU. Si verifica una situazione paradossale: il rendering dei dati dei vertici è velocissimo, ma la trasmissione dei comandi per il rendering è piuttosto lenta.
Sarebbe fantastico poter inviare i dati richiesti alla scheda grafica una volta, quindi chiedere a OpenGL di eseguire il rendering di molti oggetti utilizzando questi dati con una sola chiamata. Benvenuto nel mondo delle istanze !
L'istanza è una tecnologia che consente di visualizzare più oggetti utilizzando una chiamata alla funzione di rendering, che ci salva dallo scambio CPU -> GPU non necessario durante il rendering. Tutto quello che devi fare per iniziare a usare le istanze è cambiare le chiamate glDrawArrays e glDrawElemenets a glDrawArraysInstanced e glDrawElementsInstanced, rispettivamente. Le versioni istanziate accettano un parametro aggiuntivo, oltre a quelli già familiari dalle versioni normali delle funzioni. Questo parametro è il numero di istanze dell'istanza, ad es. il numero di istanze del modello di cui eseguire il rendering. Pertanto, forniamo alla GPU una volta tutti i dati necessari per il rendering, quindi diciamo come eseguire il rendering del numero desiderato di istanze dell'oggetto in una sola chiamata a una funzione speciale. E la scheda video renderà l'intero set di oggetti senza accedere costantemente alla CPU.
Di per sé, tale possibilità non è molto utile: visualizzando migliaia di oggetti allo stesso modo, nella stessa posizione, ci ritroveremo comunque con l'immagine di un singolo oggetto - tutte le istanze si sovrapporranno l'una all'altra. Per risolvere questo problema nei vertex shader, è disponibile una variabile GLSL incorporata gl_InstanceID .
Quando si utilizzano funzioni che supportano l'istanza per il rendering, il valore di questa variabile aumenterà di uno per ogni istanza visualizzata, a partire da zero. Quindi, rendendo la 43a istanza di un oggetto, nel vertex shader otterremo gl_InstanceID uguale a 42. Avendo un indice univoco corrispondente all'istanza, potremmo, ad esempio, usarlo per selezionare da un ampio array di vettori di posizione in per rendere ogni istanza in un certo punto della scena...
Per avere un'idea migliore dell'essenza dell'istanza, proviamo a capire un semplice esempio che esegue il rendering di un centinaio di quad (rettangoli) in coordinate di dispositivo normalizzate (NDC) con una singola chiamata di estrazione. Lo spostamento viene determinato utilizzando un campione dell'uniforme, che è un array contenente cento vettori di spostamento. Il risultato è una bella griglia di rettangoli che riempiono l'intera area della finestra:
Ogni quad è composto da due triangoli, che ci danno sei vertici. Ciascun vertice contiene un vettore di posizione NDC a due componenti e un vettore di colore. Di seguito sono riportati i dati del vertice dell'esempio: la dimensione dei triangoli è scelta abbastanza piccola da riempire correttamente lo schermo in grandi quantità:
 float quadVertices [] = {
    // coordinates // colors
    -0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
     0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
    -0.05f, -0.05f, 0.0f, 0.0f, 1.0f,

    -0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
     0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
     0.05f, 0.05f, 0.0f, 1.0f, 1.0f
};
Il colore quad è impostato dal frammento shader, che reindirizza semplicemente il colore del vertice interpolato ottenuto dal vertex shader direttamente alla variabile di output:
#version 330 core
out vec4 FragColor;
  
in vec3 fColor;

void main()
{
    FragColor = vec4(fColor, 1.0);
} 
Niente di nuovo per noi. Ma nel vertex shader, le cose sono diverse:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out vec3 fColor;

uniform vec2 offsets[100];

void main()
{
    vec2 offset = offsets[gl_InstanceID];
    gl_Position = vec4(aPos + offset, 0.0, 1.0);
    fColor = aColor;
}  
Qui abbiamo dichiarato un array uniforme di offset contenente cento vettori di spostamento. Nel codice dello shader, otteniamo il valore di offset recuperando dall'array il valore della variabile gl_InstanceID . Di conseguenza, utilizzando questo shader, possiamo renderizzare centinaia di quad situati in diverse posizioni sullo schermo.
Tuttavia, è necessario ulteriore lavoro: l'array offset stesso non si riempirà. Compiliamolo nella nostra applicazione, prima di entrare nel ciclo di rendering principale:
glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for(int y = -10; y < 10; y += 2)
{
    for(int x = -10; x < 10; x += 2)
    {
        glm::vec2 translation;
        translation.x = (float)x / 10.0f + offset;
        translation.y = (float)y / 10.0f + offset;
        translations[index++] = translation;
    }
}  
Qui vengono creati un centinaio di vettori di traduzione, definendo una griglia uniforme 10x10.
Non dimentichiamo di passare i dati generati nell'array uniforme dello shader:
shader.use();
for(unsigned int i = 0; i < 100; i++)
{
    stringstream ss;
    string index;
    ss << i; 
    index = ss.str(); 
    shader.setVec2(("offsets[" + index + "]").c_str(), translations[i]);
}   
In questo pezzo di codice, convertiamo la variabile di ciclo i in una variabile di tipo stringa per poter impostare dinamicamente una stringa sul nome dell'uniforme e ottenere la posizione dell'uniforme con questo nome. Per ogni elemento dall'array uniforme offset, passiamo il corrispondente vettore offset generato.

Se è disponibile C++ 11 o successivo, utilizzare meglio std :: to_string(). ca.

Ora che il lavoro preparatorio è terminato, puoi finalmente iniziare il rendering. Come promemoria , è necessario utilizzare glDrawArraysInstanced o glDrawElementsInstanced per invocare un rendering istanziato. Poiché nell'esempio non stiamo utilizzando un index buffer, viene utilizzato il codice seguente:
glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);   
I parametri passati alla funzione render sono identici a quelli passati a glDrawArrays , ad eccezione dell'ultimo parametro, che imposta il numero desiderato di istanze da rendere. Poiché vogliamo visualizzare 100 quad in una griglia 10x10, passiamo il numero 100. L'esecuzione del codice dovrebbe portare all'output dell'immagine già familiare con un centinaio di rettangoli colorati.

Array istanziati

L'esempio precedente è abbastanza praticabile e fa fronte al suo compito. Ma c'è un problema: se i nostri appetiti crescono e vogliamo produrre molto più di 100 copie, molto presto raggiungeremo il tetto della quantità consentita di dati uniformi inviati allo shader. Una trasmissione dati alternativa tramite Uniform sono gli array instansirovannye ( array istanziati), che sono definiti come attributi del vertice, il cui campione si verifica solo quando si modifica l'istanza dell'oggetto pre-renderizzato dell'indice corrente. Di conseguenza, ciò consente di trasferire quantità di dati molto più grandi in un modo più conveniente.
Per i normali attributi dei vertici, GLSL recupera i nuovi valori dei dati dei vertici ogni volta che viene eseguito il codice del vertex shader. Tuttavia, impostando l'attributo del vertice come un array istanziato, stiamo costringendo GLSL a recuperare un nuovo valore di attributo per ogni istanza successiva dell'oggetto, non il vertice successivo dell'oggetto. Di conseguenza, è possibile utilizzare i normali attributi di vertice per i dati rappresentati dal vertice e gli array istanziati per i dati univoci per un'istanza di un oggetto.
Per capire meglio come funziona, modifichiamo il codice di esempio per utilizzare un array istanziato invece di un array uniforme. Dovremo aggiornare il codice dello shader con un nuovo attributo di vertice:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;

out vec3 fColor;

void main()
{
    gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
    fColor = aColor;
}  
Qui non usiamo più la variabile gl_InstanceID e possiamo accedere direttamente all'attributo offset , senza dover recuperare dall'array.
Poiché l'implementazione di un array istanziato è essenzialmente basata su attributi del vertice come position o color , è necessario memorizzare i dati nell'oggetto buffer dei vertici e impostare il puntatore dell'attributo del vertice. Innanzitutto, salviamo i dati dell'array delle traduzioni in un nuovo oggetto buffer:
unsigned int instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); 
Imposteremo anche il puntatore dell'attributo vertice e attiveremo l'attributo:
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);	
glVertexAttribDivisor(2, 1);   
Il codice è familiare tranne che per l'ultima riga con la chiamata glVertexAttribDivisor . Questa funzione dice a OpenGL quando recuperare un nuovo elemento da un attributo di vertice. Il primo parametro - l'indice dell'attributo di interesse, e il secondo - un delimitatore di attributo (divisore di attributo). Per impostazione predefinita, è impostato su 0, che corrisponde all'aggiornamento dell'attributo per ogni nuovo vertice elaborato dal vertex shader. Impostando questo parametro su 1, diciamo a OpenGL di aggiornare l'attributo su ogni successiva istanza renderizzata. Impostando il delimitatore su 2, aggiorneremo ogni due istanze e così via. Fondamentalmente, impostando il delimitatore su 1, indichiamo che l'attributo con il delimitatore dato è un array istanziato.
Se ora disegniamo la scena usando glDrawArraysInstanced , otteniamo la seguente immagine:
Esattamente come l'ultima volta, ma implementato utilizzando un array istanziato, che consente di passare molti più dati al vertex shader per fornire un rendering istanziato.
Tanto per divertimento, proviamo a ridurre gradualmente ogni quad, partendo dall'angolo in alto a destra verso l'angolo in basso a sinistra. Usa di nuovo la variabile gl_InstanceID , perché no?
void main()
{
    vec2 pos = aPos * (gl_InstanceID / 100.0);
    gl_Position = vec4(pos + aOffset, 0.0, 1.0);
    fColor = aColor;
}  
Di conseguenza, otterremo un'immagine in cui le prime istanze sono rese minuscole, ma quando il numero dell'istanza si avvicina a 100, la dimensione di ciascun rettangolo tende a quella originale. Questa condivisione di array istanziati e gl_InstanceID è perfettamente legale.
Se sei in dubbio di aver imparato come funziona il rendering istanziato, o vuoi semplicemente studiare la struttura dell'intero codice di esempio, i sorgenti sono disponibili qui .
Tutto questo va bene, ovviamente, ma questi esempi danno una piccola idea dei reali vantaggi dell'istanziazione. Naturalmente, i dettagli tecnici sono mostrati qui, ma l'essenza stessa dell'istanziazione viene rivelata solo quando si esegue il rendering di un numero folle di oggetti simili, qualcosa a cui non siamo ancora arrivati. Ecco perché nella prossima sezione dovremo andare nello spazio esterno per vedere in prima persona il vero potere dell'istanza.

Campo di asteroidi

Immagina una scena in cui un enorme pianeta è circondato da un'enorme cintura di asteroidi. Una tale cintura potrebbe contenere migliaia, se non decine di migliaia di formazioni rocciose. L'output di una scena del genere diventerà molto rapidamente quasi impossibile su qualsiasi scheda video decente. Ma è in questo scenario che si suggerisce l'uso dell'istanziazione, dal momento che tutti gli asteroidi della cintura possono essere rappresentati come un unico modello. Ogni asteroide sarà leggermente diverso dai suoi vicini grazie alla sua matrice di trasformazione unica.
Per mostrare l'effetto positivo dell'istanza, proveremo prima a eseguire il rendering di questa scena senza utilizzarla. La scena conterrà un grande pianeta, di cui è possibile scaricare un modello qui , oltre a un ampio set di asteroidi appositamente posizionati attorno al pianeta. Il modello dell'asteroide può essere scaricato qui .
Nel codice dell'applicazione, forniamo il caricamento dei modelli di dati utilizzando il caricatore, per comprendere le lezioni dedicate alla modellazione .
Per ottenere la configurazione della scena richiesta, creeremo una matrice di trasformazione unica per ciascun asteroide, che verrà utilizzata come matrice modello durante il rendering di ciascuno di essi. La matrice si forma in più fasi. Innanzitutto, viene applicata una trasformazione di trasporto per posizionare l'asteroide da qualche parte all'interno dell'anello. Applichiamo anche un piccolo offset casuale per aggiungere realismo alla distribuzione degli asteroidi. Successivamente, viene aggiunto il ridimensionamento casuale e la rotazione attorno al vettore di rotazione. Di conseguenza, otteniamo una matrice di trasformazione che posiziona ogni asteroide da qualche parte nelle vicinanze del pianeta, fornendo allo stesso tempo il suo aspetto unico. E la cintura di asteroidi è piena di un mucchio di blocchi di pietra che sono diversi l'uno dall'altro.
unsigned int amount = 1000;
glm :: mat4 * modelMatrices;
modelMatrices = new glm :: mat4 [amount];
srand (glfwGetTime ()); // set the seed for the case generator. numbers
float radius = 50.0;
float offset = 2.5f;
for (unsigned int i = 0; i <amount; i ++)
{
    glm :: mat4 model (1.0f);
    // 1.wrap: place along the circle with 'radius'
    // and add an offset within [-offset, offset]
    float angle = (float) i / (float) amount * 360.0f;
    float displacement = (rand ()% (int) (2 * offset * 100)) / 100.0f - offset;
    float x = sin (angle) * radius + displacement;
    displacement = (rand ()% (int) (2 * offset * 100)) / 100.0f - offset;
    // keep the field height noticeably smaller than the dimensions in the XZ plane
    float y = displacement * 0.4f;
    displacement = (rand ()% (int) (2 * offset * 100)) / 100.0f - offset;
    float z = cos (angle) * radius + displacement;
    model = glm :: translate (model, glm :: vec3 (x, y, z));

    // 2.scaling: random scaling within (0.05, 0.25f)
    float scale = (rand ()% 20) / 100.0f + 0.05;
    model = glm :: scale (model, glm :: vec3 (scale));

    // 3.rotate: rotate a random angle along
    float rotAngle = (rand ()% 360);
    model = glm :: rotate (model, rotAngle, glm :: vec3 (0.4f, 0.6f, 0.8f));

    // 4.add to the array of matrices
    modelMatrices [i] = model;
}
Questo pezzo di codice può sembrare intimidatorio, ma qui stiamo semplicemente posizionando ogni asteroide nel piano XZ lungo un cerchio definito dal raggio e aggiungendo anche un piccolo offset casuale all'interno (- offset , offset ) relativo a questo cerchio. Modifichiamo la coordinata Y in misura minore per dare all'anello degli asteroidi la forma, di fatto, di un anello. Inoltre, vengono applicati ridimensionamento e rotazione e il risultato viene archiviato nell'array modelMatrices di importo. In questo esempio vengono create 1000 matrici modello, una per asteroide.
Dopo aver caricato i modelli di pianeti e asteroidi, oltre a compilare gli shader, puoi avviare il codice di rendering:
// render the planet
shader.use ();
glm :: mat4 model (1.0f);
model = glm :: translate (model, glm :: vec3 (0.0f, -3.0f, 0.0f));
model = glm :: scale (model, glm :: vec3 (4.0f, 4.0f, 4.0f));
shader.setMat4 ("model", model);
planet.Draw (shader);
  
// render meteorites
for (unsigned int i = 0; i <amount; i ++)
{
    shader.setMat4 ("model", modelMatrices [i]);
    rock.Draw (shader);
}
Per prima cosa, disegniamo un modello del pianeta, che deve essere spostato e ridimensionato leggermente per adattarsi alla scena. Quindi rendiamo gli asteroidi in una quantità uguale alla quantità dell'array di trasformazione preparato. Prima di produrre ogni asteroide, dobbiamo trasferire i dati corrispondenti a un'uniforme contenente una matrice modello.
Il risultato è un'immagine che ricorda un'istantanea dallo spazio, con un pianeta dall'aspetto piuttosto credibile circondato da una cintura di asteroidi:
Questa scena fa 1001 chiamate alla funzione di disegno per fotogramma, 1000 delle quali sono per il modello di asteroide. Le fonti sono qui .
Se iniziamo ad aumentare il numero di asteroidi visualizzati, noteremo rapidamente che la scena smette di ridisegnare uniformemente e il numero di fotogrammi al secondo diminuisce drasticamente. Una volta arrivati ​​al punto di provare a eseguire il rendering di 2000 asteroidi, il rendering diventa così insensibile che il semplice movimento nella scena è quasi impossibile.
Ora, proviamo a fare lo stesso, ma usando le istanze. Per prima cosa, modifichiamo un po' il vertex shader:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in mat4 instanceMatrix;

out vec2 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    gl_Position = projection * view * instanceMatrix * vec4(aPos, 1.0); 
    TexCoords = aTexCoords;
} 
Non usiamo più un'uniforme contenente una matrice modello. Invece, dichiariamo un nuovo attributo vertice che memorizza le matrici, in cui posizioneremo l'array istanziato delle matrici di trasformazione. Va notato che quando si imposta il tipo di attributo del vertice con una dimensione maggiore della dimensione vec4, bisogna tener conto di una particolarità. È un dato di fatto Mat4 quattro vec4 collegato, allora questo attributo sarà accantonato quanto quattro posizione di indice (posizione) dell'attributo vertice. Qui abbiamo assegnato un indice di allocazione di 3 all'attributo, il che significa che le colonne della matrice ricevono gli indici di allocazione 3, 4, 5 e 6.
Nel codice client, dovremo specificare i puntatori agli attributi dei vertici per ciascuno di questi indici di posizione impliciti. E non dimenticare di inizializzare ciascuno di essi come un array istanziato:
// create VBO
unsigned int buffer;
glGenBuffers (1,& buffer);
glBindBuffer (GL_ARRAY_BUFFER, buffer);
glBufferData (GL_ARRAY_BUFFER, amount * sizeof (glm :: mat4),& modelMatrices [0], GL_STATIC_DRAW);
  
for (unsigned int i = 0; i <rock.meshes.size (); i ++)
{
    unsigned int VAO = rock.meshes [i] .VAO;
    glBindVertexArray (VAO);
    // set up attributes
    GLsizei vec4Size = sizeof (glm :: vec4);
    glEnableVertexAttribArray (3);
    glVertexAttribPointer (3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void *) 0);
    glEnableVertexAttribArray (4);
    glVertexAttribPointer (4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void *) (vec4Size));
    glEnableVertexAttribArray (5);
    glVertexAttribPointer (5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void *) (2 * vec4Size));
    glEnableVertexAttribArray (6);
    glVertexAttribPointer (6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void *) (3 * vec4Size));

    glVertexAttribDivisor (3, 1);
    glVertexAttribDivisor (4, 1);
    glVertexAttribDivisor (5, 1);
    glVertexAttribDivisor (6, 1);

    glBindVertexArray (0);
}
Si noti che qui abbiamo barato un po' dichiarando VAO come variabile pubblica, non privata della classe Mesh - questo ci ha permesso di semplificare l'accesso all'oggetto array di vertici. Potrebbe non essere la soluzione più elegante e pulita, ma per le esigenze di un semplice esempio andrà bene. A parte questo piccolo trucco, il resto del codice dovrebbe essere chiaro. Qui stiamo semplicemente dicendo come OpenGL dovrebbe interpretare il contenuto del buffer per ogni elemento di attributo vertice rappresentato dalla matrice. Indichiamo anche che ciascuno di questi attributi è un array istanziato.
Successivamente, facciamo nuovamente riferimento ai modelli preparati VAO e chiamiamo il rendering:
// draw meteorites
instanceShader.use();
for(unsigned int i = 0; i < rock.meshes.size(); i++)
{
    glBindVertexArray(rock.meshes[i].VAO);
    glDrawElementsInstanced(
        GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount
    );
} 
Qui il rendering viene eseguito con lo stesso numero di asteroidi dell'esempio precedente, ma ora viene utilizzata l'istanza. Visivamente, il risultato sarà simile. La differenza principale apparirà con un aumento del numero di asteroidi. Senza istanziazione, potremmo ottenere un rendering fluido dalla scheda video nell'intervallo da 1000 a 1500 asteroidi. Con l'istanza, possiamo facilmente alzare l'asticella fino a raggiungere l'incredibile cifra di 100.000 asteroidi. Considerando che ognuno di essi contiene 576 vertici, otteniamo circa 57 milioni di vertici elaborati senza alcun degrado delle prestazioni!
Questa immagine è stata ottenuta visualizzando 100.000 asteroidi con le variabili raggio = 150.0f e offset = 25.0f . Il codice sorgente è qui .

Ognuno ha diverse configurazioni di macchine da lavoro, quindi il limite di 100.000 potrebbe essere in qualche modo ottimistico. Prova a regolare il numero specifico nel tuo caso in modo che il frame rate rimanga accettabile.

Come puoi vedere, in determinate attività, l'istanza può fornire miglioramenti significativi delle prestazioni. Questo è il motivo per cui questa tecnica viene utilizzata per eseguire il rendering di erba, piante, sistemi di particelle e altre scene simili a quella del tutorial - in effetti, qualsiasi scena in cui lo stesso oggetto viene renderizzato molte volte.
PS : abbiamo una telegramma-conferenza per coordinare i trasferimenti. Se hai un serio desiderio di aiutare con la traduzione, allora sei il benvenuto!