Récemment, j'ai commencé à travailler sur un jeu qui se déroule dans un système solaire généré de manière procédurale. Après un peu de courbe d'apprentissage (n'ayant jamais travaillé avec Scala, OpenGL 2 ES ou Libgdx auparavant), j'ai une démonstration technologique de base où vous tournez autour d'une seule planète texturée procédurale:
Le problème que je rencontre est la performance de la génération de texture. Un bref aperçu de ce que je fais: une planète est un cube qui a été déformé en sphère. De chaque côté, une texture anxn (par exemple 256 x 256) est appliquée, qui sont regroupées dans une texture 8n xn qui est envoyée au shader de fragment. Les deux derniers espaces ne sont pas utilisés, ils ne sont là que pour s'assurer que la largeur est une puissance de 2. La texture est actuellement générée sur le CPU, en utilisant la version mise à jour 2012 de l'algorithme de bruit simplex lié à dans le papier 'Simplex le bruit démystifié ». La scène que j'utilise pour tester l'algorithme contient deux sphères: la planète et l'arrière-plan. Les deux utilisent une texture en niveaux de gris composée de six octaves de bruit simplex 3D.Par exemple, si nous choisissons 128x128 comme taille de texture, il y a 128 x 128 x 6 x 2 x 6 = environ 1,2 million d'appels à la fonction de bruit.
Le plus proche de la planète concerne ce qui est montré dans la capture d'écran et puisque la résolution cible du jeu est 1280x720, cela signifie que je préférerais utiliser des textures 512x512. Combinez cela avec le fait que les textures réelles seront bien sûr plus compliquées que le bruit de base (il y aura une texture de jour et de nuit, mélangée dans le shader de fragments basé sur la lumière du soleil et un masque spéculaire. J'ai besoin de bruit pour les continents, de variation de couleur du terrain , nuages, lumières de la ville, etc.) et nous examinons quelque chose comme 512 x 512 x 6 x 3 x 15 = 70 millions d'appels de bruit pour la planète seule. Dans le jeu final, il y aura des activités lors des voyages entre les planètes, donc une attente de 5 ou 10 secondes, peut-être 20, serait acceptable car je peux calculer la texture en arrière-plan pendant le voyage, bien que plus vite, mieux c'est.
Pour en revenir à notre scène de test, les performances sur mon PC ne sont pas trop terribles, mais encore trop lentes étant donné que le résultat final sera environ 60 fois pire:
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
C'est après que j'ai déplacé tout le code essentiel aux performances vers Java, car essayer de le faire dans Scala était bien pire. L'exécuter sur mon téléphone (un Samsung Galaxy S3), cependant, produit un résultat plus problématique:
128x128 : 2s
256x256 : 7s
512x512 : 29s
Déjà beaucoup trop long, et cela ne tient même pas compte du fait que ce sera des minutes au lieu des secondes dans la version finale. Il est clair que quelque chose doit être fait. Personnellement, je vois quelques pistes potentielles, mais je ne suis pas encore particulièrement intéressé par l'une d'entre elles:
- Ne précalculez pas les textures, mais laissez le fragment shader tout calculer. Probablement pas faisable, car à un moment donné, j'ai eu l'arrière-plan en quad plein écran avec un pixel shader et j'ai obtenu environ 1 fps sur mon téléphone.
- Utilisez le GPU pour rendre la texture une fois, stockez-la et utilisez ensuite la texture stockée. Upside: peut être plus rapide que de le faire sur le CPU car le GPU est censé être plus rapide lors des calculs en virgule flottante. Inconvénient: les effets qui ne peuvent pas (facilement) être exprimés en fonction du bruit simplex (par exemple les tourbillons de planètes gazeuses, les cratères lunaires, etc.) sont beaucoup plus difficiles à coder en GLSL qu'en Scala / Java.
- Calculez une grande quantité de textures de bruit et envoyez-les avec l'application. J'aimerais éviter cela si possible.
- Réduisez la résolution. M'achète un gain de performances 4x, ce qui n'est pas vraiment suffisant et je perds beaucoup de qualité.
- Trouvez un algorithme de bruit plus rapide. Si quelqu'un en a un, je suis tout ouïe, mais le simplexe est déjà censé être plus rapide que le perlin.
- Adoptez un style pixel art, permettant des textures de résolution inférieure et moins d'octaves de bruit. Alors que j'avais initialement envisagé le jeu dans ce style, j'en suis venu à préférer l'approche réaliste.
- Je fais quelque chose de mal et les performances devraient déjà être d'un ou deux ordres de grandeur meilleures. Si tel est le cas, faites-le moi savoir.
Si quelqu'un a des suggestions, des conseils, des solutions de contournement ou d'autres commentaires concernant ce problème, j'aimerais les entendre.
En réponse à Layoric, voici le code que j'utilise:
//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;
}
Réponses:
Vous pouvez combiner les approches (2) et (3) comme ceci:
la source
La génération de texture procédurale est ab * * d'un mofo en termes de temps de calcul. C'est ce que c'est.
La meilleure implémentation de Simplex Noise que j'ai trouvée est celle de Stefan Gustavson .
Au-delà de l'amélioration du temps de calcul réel (il est en fait assez difficile de passer outre le fait que vous demandez simplement beaucoup de choses à votre ordinateur lorsque vous calculez des textures procédurales 1024x1024), l'un des meilleurs moyens de réduire la perception temps d'attente est d'avoir votre application effectue le plus de travail possible en arrière-plan.
Commencez donc à générer des textures au lancement du jeu sur le fil d'arrière-plan , pendant que l'utilisateur manipule toujours les options et le menu ou regarde la bande-annonce de démarrage du niveau.
L'autre chose à considérer est de simplement mettre en cache plusieurs centaines de textures générées sur le disque et de sélectionner au hasard l'une d'entre elles au moment du chargement. Plus de disque, mais moins de temps de chargement.
la source