Di recente ho iniziato a lavorare su un gioco che si svolge in un sistema solare generato proceduralmente. Dopo un po 'di una curva di apprendimento (non avevo mai lavorato con Scala, OpenGL 2 ES o Libgdx prima), ho una demo tecnica di base che gira intorno a un singolo pianeta strutturato in modo procedurale:
Il problema in cui mi imbatto è la prestazione della generazione di texture. Una rapida panoramica di quello che sto facendo: un pianeta è un cubo che è stato deformato in una sfera. Su ciascun lato, viene applicata una trama anxn (ad es. 256 x 256), che sono raggruppate in una trama 8n xn che viene inviata allo shader di frammento. Gli ultimi due spazi non vengono utilizzati, sono solo lì per assicurarsi che la larghezza sia una potenza di 2. La trama è attualmente generata sulla CPU, utilizzando la versione aggiornata 2012 dell'algoritmo di rumore simplex collegato al documento 'Simplex rumore demistificato ". La scena che sto usando per testare l'algoritmo contiene due sfere: il pianeta e lo sfondo. Entrambi usano una trama in scala di grigi composta da sei ottave di rumore simplex 3D, quindi per esempio se scegliamo 128x128 come dimensione della trama ci sono 128 x 128 x 6 x 2 x 6 = circa 1,2 milioni di chiamate alla funzione rumore.
Il più vicino che raggiungerai al pianeta riguarda ciò che viene mostrato nello screenshot e poiché la risoluzione target del gioco è 1280x720, ciò significa che preferirei usare trame 512x512. Combina che con il fatto che le trame reali saranno ovviamente più complicate del rumore di base (ci sarà una trama diurna e notturna, miscelata nello shader di frammenti basato sulla luce solare e una maschera speculare. Ho bisogno di rumore per i continenti, variazione di colore del terreno , nuvole, luci della città, ecc.) e stiamo osservando qualcosa come 512 x 512 x 6 x 3 x 15 = 70 milioni di chiamate al rumore per il solo pianeta. Nel gioco finale, ci saranno attività quando si viaggia tra i pianeti, quindi un'attesa di 5 o 10 secondi, possibilmente 20, sarebbe accettabile poiché posso calcolare la trama in background mentre viaggio, anche se ovviamente più veloce è, meglio è.
Tornando alla nostra scena di test, le prestazioni sul mio PC non sono troppo terribili, anche se ancora troppo lente considerando che il risultato finale sarà circa 60 volte peggiore:
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
Questo dopo che ho spostato tutto il codice critico per le prestazioni su Java, dal momento che provare a farlo in Scala era molto peggio. L'esecuzione sul mio telefono (un Samsung Galaxy S3), tuttavia, produce un risultato più problematico:
128x128 : 2s
256x256 : 7s
512x512 : 29s
Già troppo a lungo, e questo non è nemmeno un fattore di fatto nel fatto che ci saranno minuti anziché secondi nella versione finale. Chiaramente qualcosa deve essere fatto. Personalmente, vedo alcune potenziali strade, anche se non sono ancora particolarmente appassionato di nessuno di essi:
- Non ricalcolare le trame, ma lascia che lo shader di frammenti calcoli tutto. Probabilmente non è possibile, perché a un certo punto avevo lo sfondo come quad a schermo intero con un pixel shader e ho ottenuto circa 1 fps sul mio telefono.
- Utilizzare la GPU per eseguire il rendering della trama una volta, archiviarla e utilizzare la trama memorizzata da quel momento in poi. Upside: potrebbe essere più veloce di farlo sulla CPU poiché la GPU dovrebbe essere più veloce nei calcoli in virgola mobile. Unico inconveniente: gli effetti che non possono (facilmente) essere espressi come funzioni del rumore simplex (ad es. Vortici di pianeti gassosi, crateri lunari, ecc.) Sono molto più difficili da codificare in GLSL che in Scala / Java.
- Calcola una grande quantità di trame di rumore e spediscile con l'applicazione. Vorrei evitarlo, se possibile.
- Abbassa la risoluzione. Mi offre un guadagno in termini di prestazioni 4x, il che non è abbastanza, inoltre perdo molta qualità.
- Trova un algoritmo di rumore più veloce. Se qualcuno ne ha uno, sono tutto orecchi, ma il simplex dovrebbe già essere più veloce del perlin.
- Adotta uno stile pixel art, che consente trame a bassa risoluzione e meno ottave di rumore. Mentre inizialmente immaginavo il gioco in questo stile, sono arrivato a preferire un approccio realistico.
- Sto facendo qualcosa di sbagliato e le prestazioni dovrebbero già essere di uno o due ordini di grandezza migliori. In questo caso, per favore fatemi sapere.
Se qualcuno ha suggerimenti, suggerimenti, soluzioni alternative o altri commenti su questo problema, mi piacerebbe ascoltarli.
In risposta a Layoric, ecco il codice che sto usando:
//The function that generates the simplex noise texture
public static Texture simplex(int size) {
byte[] data = new byte[size * size * columns * 4];
int offset = 0;
for (int y = 0; y < size; y++) {
for (int s = 0; s < columns; s++) {
for (int x = 0; x < size; x++) {
//Scale x and y to [-1,1] range
double tx = ((double)x / (size - 1)) * 2 - 1;
double ty = 1 - ((double)y / (size - 1)) * 2;
//Determine point on cube in worldspace
double cx = 0, cy = 0, cz = 0;
if (s == 0) { cx = 1; cy = tx; cz = ty; }
else if (s == 1) { cx = -tx; cy = 1; cz = ty; }
else if (s == 2) { cx = - 1; cy = -tx; cz = ty; }
else if (s == 3) { cx = tx; cy = - 1; cz = ty; }
else if (s == 4) { cx = -ty; cy = tx; cz = 1; }
else if (s == 5) { cx = ty; cy = tx; cz = - 1; }
//Determine point on sphere in worldspace
double sx = cx * Math.sqrt(1 - cy*cy/2 - cz*cz/2 + cy*cy*cz*cz/3);
double sy = cy * Math.sqrt(1 - cz*cz/2 - cx*cx/2 + cz*cz*cx*cx/3);
double sz = cz * Math.sqrt(1 - cx*cx/2 - cy*cy/2 + cx*cx*cy*cy/3);
//Generate 6 octaves of noise
float gray = (float)(SimplexNoise.fbm(6, sx, sy, sz, 8) / 2 + 0.5);
//Set components of the current pixel
data[offset ] = (byte)(gray * 255);
data[offset + 1] = (byte)(gray * 255);
data[offset + 2] = (byte)(gray * 255);
data[offset + 3] = (byte)(255);
//Move to the next pixel
offset += 4;
}
}
}
Pixmap pixmap = new Pixmap(columns * size, size, Pixmap.Format.RGBA8888);
pixmap.getPixels().put(data).position(0);
Texture texture = new Texture(pixmap, true);
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
return texture;
}
//SimplexNoise.fbm
//The noise function is the same one found in http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
//the only modification being that I replaced the 32 in the last line with 16 in order to end up with
//noise in the range [-0.5, 0.5] instead of [-1,1]
public static double fbm(int octaves, double x, double y, double z, double frequency) {
double value = 0;
double f = frequency;
double amp = 1;
for (int i = 0; i < octaves; i++) {
value += noise(x*f, y*f, z*f) * amp;
f *= 2;
amp /= 2;
}
return value;
}