Cet article contient trop d'eau.

«Nous commençons à développer un nouveau jeu et nous avons besoin d'eau fraîche. Pouvez-vous faire ça? "


- m'a demandé. «Oui, pas question! Bien sûr que je peux, »ai-je répondu, mais ma voix tremblait traîtreusement. «Et aussi sur Unity?» - et il m'est apparu clairement qu'il y avait beaucoup de travail à faire.

Donc, un peu d'eau. Jusqu'à ce moment, je n'avais pas vu Unity, exactement comme C #, alors j'ai décidé de faire un prototype sur les outils que je connaissais: C ++ et DX9. Ce que je savais et pouvais pratiquer dans la pratique à ce moment-là, c'était les textures de défilement des normales pour former la surface, et la cartographie de déplacement primitive basée sur elles. Il a fallu immédiatement tout changer absolument. Forme animée réaliste de la surface de l'eau. Ombrage compliqué (fortement). Génération de mousse. Système LOD attaché à la caméra. J'ai commencé à chercher des informations sur Internet pour savoir comment faire tout cela.

Le premier point, bien sûr, était une compréhension de la simulation des eaux océaniques de Jerry Tessendorf .

Les téléavertisseurs académiques avec un tas de formules abstruses ne m'ont jamais été donnés beaucoup, donc après quelques lectures, j'ai peu compris. Les principes généraux étaient clairs: chaque image est générée par une carte des hauteurs utilisant la transformée de Fourier rapide, qui, en fonction du temps, change doucement de forme pour former une surface d'eau réaliste. Mais comment et quoi compter je ne savais pas. J'ai lentement exploré la sagesse du calcul de la FFT sur les shaders en D3D9, et le code source avec un article quelque part dans la nature d'Internet, que j'ai essayé de trouver pendant une heure, mais en vain (malheureusement), m'a vraiment aidé. Le premier résultat a été obtenu (effrayant comme une guerre nucléaire):


Les succès initiaux ont plu, et le transfert d'eau vers Unity a commencé avec son achèvement.

Plusieurs exigences ont été avancées pour l'eau dans le jeu sur les batailles navales:

  • Look réaliste. Beau comme raccourcis rapprochés et éloignés, mousse dynamique, dispersion, etc.
  • Prise en charge de diverses conditions météorologiques: calme, tempête et conditions intermédiaires. Changement d'heure.
  • Physique de la flottabilité des navires sur une surface simulée, objets flottants.
  • Comme le jeu est multijoueur, l'eau doit être la même pour tous les participants à la bataille.
  • Dessin de surface: zones dessinées du vol des noyaux de volée, mousse de l'entrée des noyaux dans l'eau.

Géométrie


Il a été décidé de construire une structure en forme d'arbre, avec un centre autour de la caméra, qui est reconstruite discrètement lorsque l'observateur se déplace. Pourquoi discret? Si vous déplacez le maillage en douceur avec la caméra ou utilisez la reprojection de l'espace d'écran comme dans l'article Rendu d'eau en temps réel - présentant le concept de grille projetée , puis dans les plans à long terme, en raison de la résolution insuffisante du maillage géométrique, les polygones «sauteront» vers le haut et vers le bas. C'est très frappant. L'image ondule. Pour surmonter cela, il faut soit augmenter considérablement la résolution du polygone de maille d'eau, soit «aplatir» la géométrie sur de longues distances, soit construire et déplacer les polygones de sorte que ces déplacements ne soient pas visibles. Notre eau est progressive (hehe) et j'ai choisi la troisième voie. Comme dans toute technique similaire (particulièrement familière à tous ceux qui ont créé un terrain dans les jeux), vous devez vous débarrasser des jonctions en T aux frontières des transitions de niveaux de détail. Pour résoudre ce problème, au départ 3 types de quads avec des paramètres de pavage donnés sont calculés:



Le premier type est destiné aux quads qui ne sont pas des transitions pour réduire les détails. Aucun des deux côtés n'a un nombre de sommets réduit de 2 fois. Le deuxième type est pour les quadrats limites, mais pas angulaires. Le troisième type est le quadrilatère angulaire. Le maillage final de l'eau est construit en tournant et en mettant à l'échelle ces trois types de mailles.

Voici à quoi ressemble un rendu avec une couleur différente des niveaux d'eau LOD.


Les premières images montrent la connexion de deux niveaux de détails différents.

La vidéo en tant que cadre est remplie de quads d'eau:


Permettez-moi de vous rappeler que c'était il y a longtemps (et pas vrai). Désormais, de manière plus optimale et flexible, vous pouvez le faire directement sur le GPU (GPU Pro 5. Quadtrees sur le GPU). Et il dessinera en un seul appel, et la tessellation peut augmenter le détail.

Plus tard, le projet est passé au D3D11, mais les mains n'ont pas atteint la mise à niveau de cette partie du rendu océanique.

Génération de forme d'onde


Pour cela, nous avons besoin d'une transformation de Fourier rapide. Pour la résolution (nécessaire) sélectionnée de la texture de la vague (appelons cela pour l'instant, je vais vous expliquer quelles données y sont stockées), nous préparons les données initiales en utilisant les paramètres définis par les artistes (force, direction du vent, dépendance des vagues sur la direction du vent et autres). Tout cela doit être introduit dans les soi-disant formules. Spectre de Phillips Nous modifions les données initiales obtenues pour chaque trame en tenant compte du temps et effectuons la FFT sur celles-ci. En sortie, nous obtenons un pavage de texture dans toutes les directions qui contient le décalage des sommets du maillage plat. Pourquoi pas juste une carte des hauteurs? Si vous ne stockez que le décalage en hauteur, le résultat sera une masse "bouillonnante" irréaliste, qui ne ressemble qu'à distance à la mer:


Si nous considérons les déplacements pour les trois coordonnées, de belles ondes réalistes «nettes» seront générées:


Une texture animée ne suffit pas. Le carrelage est visible, pas assez de détails dans un avenir proche. Nous prenons l'algorithme décrit et faisons non pas une, mais 3 textures générées par fft. Le premier est les grosses vagues. Il définit la forme d'onde de base et est utilisé pour la physique. Le second est des ondes moyennes. Et enfin, le plus petit. 3 générateurs FFT (la 4ème option est le mix final):


Les paramètres des couches sont définis indépendamment les uns des autres, et les textures résultantes sont mélangées dans le shader d'eau dans la forme d'onde finale. Parallèlement aux décalages, des cartes normales de chaque couche sont également générées.

L '"uniformité" de l'eau pour tous les participants à la bataille est assurée par la synchronisation des paramètres océaniques au début de la bataille. Ces informations sont transmises par le serveur à chaque client.

Modèle de flottabilité physique


Puisqu'il fallait faire non seulement une belle photo, mais aussi le comportement réaliste des navires. Et compte tenu également du fait qu'une mer orageuse (grosses vagues) devait être présente dans le jeu, une autre tâche qui devait être résolue était d'assurer la flottabilité des objets à la surface de la mer générée. J'ai d'abord essayé de relire le GPU à la texture de la vague. Mais, comme il est rapidement devenu clair que toute la physique du combat naval doit être effectuée sur le serveur, la mer, ou plutôt sa première couche, qui définit la forme d'onde, doit également être lue sur le serveur (et, très probablement, il n'y a pas de rapide et / ou compatible GPU), il a été décidé d'écrire une copie fonctionnelle complète du générateur GPU FFT sur le CPU sous la forme d'un plug-in C ++ natif pour Unity. Je n'ai pas implémenté l'algorithme FFT moi-même et l'ai utilisé dans la bibliothèque Intel Performance Primitives (IPP). Mais toute la liaison et le post-traitement des résultats ont été effectués par moi, suivis d'une optimisation sur SSE et d'une parallélisation par threads. Cela comprenait la préparation du tableau de données pour la FFT chaque trame, et la conversion finale des valeurs calculées en une carte de décalage d'onde.

Il y avait une autre caractéristique intéressante de l'algorithme, qui était basée sur les exigences de la physique de l'eau. Ce qu'il fallait, c'était une fonction permettant d'obtenir rapidement la hauteur des vagues à un point donné du monde. C'est logique, car c'est la base pour construire la flottabilité de n'importe quel objet. Mais, comme à la sortie du processeur FFT nous obtenons offsetmap, pas heightmap, la sélection habituelle de la texture ne nous a pas donné la hauteur des vagues si nécessaire. Pour plus de simplicité, considérez l'option 2D:



Pour former une vague, les texels (éléments de texture représentés par des lignes verticales) contiennent un vecteur (flèches) qui définit le décalage du sommet du maillage plat (points bleus) dans le sens de sa position finale (la pointe de la flèche). Supposons que nous prenions ces données et essayions d'en extraire la hauteur de l'eau au point qui nous intéresse. Par exemple, nous devons connaître la hauteur en hB. Si nous prenons le vecteur texel tB, nous obtenons alors un décalage vers un point proche de hC, qui peut être très différent de ce dont nous avons besoin. Il existe deux options pour résoudre ce problème: à chaque demande de hauteur, vérifiez l'ensemble des texels adjacents jusqu'à ce que nous trouvions celui qui a un décalage par rapport à la position qui nous intéresse. Dans notre exemple, nous trouvons que le texel tA contient le décalage le plus proche. Mais cette approche ne peut pas être qualifiée de rapide. Le balayage du rayon du texel n'est pas clair sur la taille (et si la mer orageuse ou calme, les déplacements peuvent varier considérablement) peut prendre beaucoup de temps.

La deuxième option - après avoir calculé la carte de décalage, convertissez-la en carte de hauteur en utilisant l'approche de diffusion. Cela signifie que pour chaque vecteur de décalage, nous écrivons la hauteur de l'onde qu'il définit au point où il est décalé. Ce sera un tableau de données distinct, qui sera utilisé pour obtenir la hauteur au point d'intérêt. En utilisant notre illustration, la cellule tB contiendra la hauteur hB obtenue à partir du vecteur tA → hB. Il y a encore une fonctionnalité. La cellule tA ne contiendra pas de valeur valide, car aucun vecteur ne s'y déplace. Pour combler ces "trous", un passage est fait pour les remplir de valeurs voisines.

Voici à quoi cela ressemble si vous effectuez la visualisation des déplacements à l'aide de vecteurs (rouge - grand décalage, vert - petit):


Le reste est simple. Le plan de la ligne de flottaison conditionnelle est réglé pour le navire. Sur celui-ci, une grille rectangulaire de points d'échantillonnage est déterminée, qui définit les lieux d'application des forces poussant hors de l'eau pour le navire. Ensuite, pour chaque point, nous vérifions s'il est sous l'eau ou non en utilisant la carte de la hauteur de l'eau décrite ci-dessus. Si le point est sous l'eau, appliquez alors la force verticale jusqu'à la coque physique du corps à ce point, mise à l'échelle par la distance du point à la surface de l'eau. Si au-dessus de l'eau, nous ne faisons rien, la gravité fera tout pour nous. En fait, les formules y sont un peu plus compliquées (toutes pour le réglage fin du comportement du navire), mais le principe de base est le suivant. Dans la vidéo de visualisation de la flottabilité ci-dessous, les cubes bleus sont les emplacements des échantillons, et les lignes qui les séparent sont la magnitude de la force poussant hors de l'eau.


Dans l'implémentation du serveur, il y a un autre point d'optimisation intéressant. Il n'est pas nécessaire de simuler différentes eaux pour différentes instances de combat si elles passent dans les mêmes conditions météorologiques (les mêmes paramètres du simulateur FFT). La décision logique a donc été de créer un pool de simulateurs, auxquels les unités de combat répondent aux demandes d'obtention d'eau simulée avec les paramètres donnés. Si les paramètres sont les mêmes dans plusieurs cas, la même eau y retournera. Ceci est implémenté à l'aide de l'API Memor Mapped File. Lorsque le simulateur FFT est créé, il donne accès à ses données en exportant des descripteurs des blocs nécessaires. L'instance de serveur, au lieu de lancer un vrai simulateur, lance un "mannequin" qui donne simplement les données ouvertes par ces descripteurs. Il y avait quelques bugs amusants liés à cette fonctionnalité. En raison d'erreurs de comptage des références, le simulateur a été détruit, mais le fichier mappé en mémoire est actif pendant qu'au moins une poignée est ouverte. Les données ont cessé de se mettre à jour (il n'y a pas de simulateur) et l'eau s'est «arrêtée».

Côté client, nous avons besoin d'informations sur la forme d'onde pour calculer la pénétration des noyaux dans les systèmes d'onde et de particules et de mousse. Les dommages sont calculés sur le serveur et là, il est également nécessaire de déterminer correctement si le cœur est entré dans l'eau (la vague peut fermer le navire, en particulier en cas de tempête). Ici, il est déjà nécessaire de faire le traçage de la carte de hauteur par analogie comme cela se fait dans le mappage de parallaxe ou les effets SSAO.

Ombrage


En principe, comme ailleurs. Les réflexions, les réfractions, la diffusion souterraine sont malicieusement malaxées, en tenant compte de la profondeur du fond, nous prenons en compte l'effet de Fresnel, nous considérons le spéculaire. Nous considérons la diffusion des crêtes en fonction de la position du soleil. La mousse est générée comme suit: créez un «point de mousse» sur les crêtes des vagues (utilisez la hauteur comme métrique), puis appliquez les points nouvellement créés aux points des images précédentes tout en réduisant leur intensité. Ainsi, nous obtenons un maculage de taches de mousse sous la forme d'une queue à partir d'une crête ondulée.


Nous utilisons la texture «taches» obtenue comme masque auquel nous mélangons les textures des bulles, des taches, etc. Nous obtenons un motif dynamique de mousse assez réaliste à la surface des vagues. Ce masque est créé pour chaque couche FFT (je vous le rappelle, nous en avons 3), et dans le mix final ils se mélangent tous.

La vidéo ci-dessus montre un masque en mousse. Les première et deuxième couches. Je modifie les paramètres du générateur et le résultat est visible sur la texture.

Et une vidéo d'une mer orageuse légèrement maladroite. Ici, vous pouvez voir clairement la forme d'onde, les capacités du générateur et la mousse:


Dessin d'eau


Image d'utilisation:



Utilisé pour:

  • Marqueurs, visualisation de la zone d'expansion des noyaux.
  • Dessin de mousse au point où les noyaux ont touché l'eau.
  • Sillage mousseux du navire
  • Extraire l'eau sous le navire pour éliminer l'effet des vagues qui inondent le pont et la cale inondée.

Le cas de base évident est la texturation projective. Il a été mis en œuvre. Mais il y a des exigences supplémentaires. Espèces voisines - savon en raison d'une résolution insuffisante (vous pouvez augmenter, mais pas à l'infini), et je veux que ces dessins projectifs sur l'eau soient bien visibles. Où le même problème est-il résolu? C'est vrai, dans les ombres (carte d'ombre). Comment est-elle résolue là-bas? À droite, les Shadow Maps en cascade (division parallèle). Nous mettrons également cette technologie en service et l'appliquerons à notre tâche. Nous divisons le tronc de la caméra en N (3-4 généralement) sous-croûtes. Pour chacun, nous construisons un rectangle descriptif dans le plan horizontal. Pour chacun de ces rectangles, nous construisons une matrice de projection orthographique et dessinons tous les objets d'intérêt pour chacune de ces caméras ortho. Chacune de ces caméras dessine dans une texture distincte, puis, dans le shader océanique, nous les combinons en une seule image projective solide.


J'ai donc mis un énorme avion avec une texture de drapeau sur la mer:



Voici ce que contiennent les scissions:



En plus des images habituelles, il est nécessaire de dessiner un masque supplémentaire en mousse (pour les traces de navires et les endroits de frappe de noyaux) exactement de la même manière, ainsi qu'un masque pour évacuer l'eau sous les navires. C'est beaucoup de caméras et de nombreuses allées. Au début, cela a fonctionné de manière si freinée, mais ensuite, après être passé au D3D11, en utilisant la «propagation» de la géométrie dans le shader géométrique et en dessinant chaque copie dans une cible de rendu distincte via SV_RenderTergetArrayIndex, il a été possible d'accélérer considérablement cet effet.

Améliorations et mises à niveau


D3D11 est les mains très libres dans de nombreux moments. Après être passé à celui-ci et à Unity 5, j'ai créé un générateur FFT sur les shaders de calcul. Visuellement, rien n'a changé, mais c'est devenu un peu plus rapide. La traduction de l'erreur de calcul de la texture des réflexions d'un rendu de caméra à part entière à la technologie Screen Space Planar Reflections a donné une bonne amélioration des performances. J'ai écrit sur l'optimisation des objets de surface de l'eau ci-dessus, mais mes mains n'ont pas atteint le transfert du maillage au GPU Quadtree.

Beaucoup pourrait être fait de manière plus optimale et plus simple. Par exemple, ne clôturez pas les jardins avec un simulateur de processeur, mais exécutez simplement l'option GPU sur un serveur avec un périphérique WARP (logiciel) d3d. Les tableaux de données ne sont pas très volumineux.

Eh bien, en général, en quelque sorte. Au moment où le développement a commencé, tout était moderne et cool. Maintenant, il est déjà hors de propos à certains endroits. Il existe plus de matériaux disponibles, même il existe un analogue similaire à github: Crest . La plupart des jeux qui ont des mers utilisent une approche similaire.

Source: https://habr.com/ru/post/fr451604/


All Articles