Vor kurzem habe ich begonnen, an einem Spiel zu arbeiten, das in einem prozedural erzeugten Sonnensystem stattfindet. Nach einer kurzen Einarbeitungsphase (die noch nie mit Scala, OpenGL 2 ES oder Libgdx funktioniert hat) habe ich eine grundlegende Tech-Demo, in der Sie einen einzelnen prozedural strukturierten Planeten umrunden:
Das Problem, auf das ich stoße, ist die Leistung der Texturgenerierung. Ein kurzer Überblick darüber, was ich tue: Ein Planet ist ein Würfel, der zu einer Kugel deformiert wurde. Auf jede Seite wird eine angstbehaftete Textur (z. B. 256 x 256) angewendet, die zu einer 8n xn-Textur gebündelt wird, die an den Fragment-Shader gesendet wird. Die letzten beiden Leerzeichen werden nicht verwendet. Sie dienen nur dazu, sicherzustellen, dass die Breite eine Potenz von 2 ist. Die Textur wird derzeit auf der CPU mit der aktualisierten Version 2012 des Simplex-Rauschalgorithmus generiert, der im Artikel 'Simplex' verlinkt ist Lärm entmystifiziert '. Die Szene, mit der ich den Algorithmus teste, enthält zwei Kugeln: den Planeten und den Hintergrund. Beide verwenden eine Graustufentextur, die aus sechs Oktaven 3D-Simplex-Rauschen besteht. Wenn wir also 128 x 128 als Texturgröße auswählen, werden 128 x 128 x 6 x 2 x 6 = ungefähr 1,2 Millionen Aufrufe an die Rauschfunktion gesendet.
Da die Zielauflösung des Spiels 1280 x 720 beträgt, bevorzuge ich die Verwendung von 512 x 512-Texturen. Kombinieren Sie das mit der Tatsache, dass die tatsächlichen Texturen natürlich komplizierter sind als das Grundrauschen. (Es wird eine Tag- und Nacht-Textur geben, die in den auf Sonnenlicht basierenden Fragment-Shader eingemischt wird, und eine Spiegelmaske. Ich brauche Rauschen für Kontinente, Geländefarbabweichungen , Wolken, Lichter der Stadt usw.) und wir betrachten so etwas wie 512 x 512 x 6 x 3 x 15 = 70 Millionen Geräusche, die den Planeten allein fordern. Im letzten Spiel wird es Aktivitäten beim Reisen zwischen Planeten geben, daher wäre ein Warten von 5 oder 10 Sekunden, möglicherweise 20 Sekunden, akzeptabel, da ich die Textur im Hintergrund auf Reisen berechnen kann, obwohl dies offensichtlich umso schneller ist, je besser.
Zurück zu unserer Testszene, die Leistung auf meinem PC ist nicht allzu schlecht, obwohl sie immer noch zu langsam ist, wenn man bedenkt, dass das Endergebnis etwa 60-mal schlechter ausfallen wird:
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
Dies geschah, nachdem ich den gesamten leistungskritischen Code nach Java verschoben hatte, da der Versuch, dies in Scala zu tun, viel schlimmer war. Das Ausführen dieses Befehls auf meinem Telefon (einem Samsung Galaxy S3) führt jedoch zu einem problematischeren Ergebnis:
128x128 : 2s
256x256 : 7s
512x512 : 29s
Schon viel zu lange, und das berücksichtigt nicht einmal die Tatsache, dass es in der endgültigen Version Minuten statt Sekunden sind. Offensichtlich muss etwas getan werden. Persönlich sehe ich ein paar mögliche Wege, obwohl ich noch keinen davon besonders mag:
- Berechnen Sie die Texturen nicht vorab, sondern lassen Sie den Fragment-Shader alles berechnen. Wahrscheinlich nicht machbar, weil ich irgendwann den Hintergrund als Vollbild-Quad mit einem Pixel-Shader hatte und ungefähr 1 fps auf meinem Handy hatte.
- Verwenden Sie die GPU, um die Textur einmal zu rendern, zu speichern und die gespeicherte Textur von da an zu verwenden. Vorteil: Möglicherweise schneller als auf der CPU, da die GPU bei Gleitkommaberechnungen schneller sein soll. Nachteil: Effekte, die (leicht) als Funktionen von Simplex-Rauschen ausgedrückt werden können (z. B. Gasplanetenwirbel, Mondkrater usw.), sind in GLSL viel schwieriger zu codieren als in Scala / Java.
- Berechnen Sie eine große Anzahl von Rauschstrukturen und versenden Sie diese mit der Anwendung. Ich würde das gerne vermeiden, wenn es überhaupt möglich ist.
- Verringern Sie die Auflösung. Kauft mir einen 4-fachen Leistungszuwachs, der nicht wirklich ausreicht, und ich verliere viel an Qualität.
- Finden Sie einen schnelleren Rauschalgorithmus. Wenn jemand eins hat, bin ich ganz Ohr, aber Simplex soll schon schneller sein als Perlin.
- Verwenden Sie einen Pixel-Art-Stil, der Texturen mit geringerer Auflösung und weniger Rauschoktaven ermöglicht. Während ich mir das Spiel ursprünglich in diesem Stil vorgestellt hatte, bevorzuge ich den realistischen Ansatz.
- Ich mache etwas falsch und die Leistung sollte schon ein oder zwei Größenordnungen besser sein. Wenn dies der Fall ist, lassen Sie es mich bitte wissen.
Wenn jemand Vorschläge, Tipps, Problemumgehungen oder andere Kommentare zu diesem Problem hat, würde ich sie gerne hören.
Als Antwort auf Layoric, hier ist der Code, den ich benutze:
//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;
}