Graphic

Performances Voxel – InformatiKKa

Charles est un ingénieur en logiciel et un professeur d’université qui s’intéresse à la technologie, à la médecine, à l’économie et à la nutrition.

Affichage dans le shader de géométrie, les maillages triangulaires et Unity Terrain.

Affichage dans le shader de géométrie, les maillages triangulaires et Unity Terrain.

Problème de voxels

Ce projet a commencé lorsque j’ai voulu créer des créatures, des objets et des paysages à partir de voxels. J’ai aimé l’idée des voxels car ils peuvent être utilisés pour construire et décomposer des objets de manière intuitive. Les problèmes étaient la vitesse, l’utilisation de la mémoire et le manque de standardisation.

Je savais que je devrais écrire mon propre paquet de voxels, alors j’ai fait une brève enquête sur certains actifs gratuits et payants pour Unity. La plupart d’entre eux esquivaient toute responsabilité en matière de performances et étaient adaptés à l’utilisation de quelques dizaines de milliers de voxels dans une scène de jeu comme vous pourriez utiliser des effets de particules. Je voulais utiliser les voxels comme mécanisme de base du jeu.

Même avec des classes gonflées utilisées pour des voxels individuels, les PC modernes ont tellement de RAM que ce n’est presque pas un problème. Le plus gros problème était la vitesse.

Une analyse

Le profilage a montré que la conversion d’un morceau de voxel en un maillage de triangles occupait plus de 90 % du temps d’exécution. La création rapide de grandes cartes de voxels était une simple question de réutilisation des appels à la fonction de bruit de Perlin, et une fois converti en maillages, un volume de voxels fonctionne comme n’importe quel autre objet.

La conversion de blocs peut facilement être effectuée en parallèle, mais avec huit threads, les performances étaient encore moins que satisfaisantes. L’utilisation d’un shader de géométrie pour afficher les voxels plus directement n’a été d’aucune aide et a commencé à prendre du retard avec des volumes de seulement quelques millions de voxels. L’envoi de voxels de surface uniquement au shader a renvoyé le problème aux threads du processeur. Les performances du processeur sont adéquates pour les mises à jour, mais pas pour les volumes massifs initiaux requis pour la génération de cartes aléatoires.

A lire aussi :  Comment créer des animations 2D dans Unity

La solution

L’utilisation d’une série de noyaux Compute Shader pour produire des cartes et les compresser en un tableau de voxels visibles s’est avérée être une solution satisfaisante. Un shader de géométrie peut accepter un tampon GPU qui contient le tableau pour éviter tout besoin de copier vers et depuis la RAM du système principal avant l’affichage initial.

Dans l’actif Unity que j’ai écrit pour implémenter et tester ce processus, j’ai opté pour des morceaux de voxel de cube 256 de chaque côté qui utilisent un octet par voxel. L’envoi d’un morceau vers ou depuis le GPU prendrait moins d’une seconde sur un bus PCIe plus récent, mais serait toujours beaucoup trop lent. Avec la génération de carte et l’affichage initial entièrement sur la carte graphique, le processeur est libéré pour d’autres configurations de scène et les données de voxel brutes peuvent être transférées selon les besoins (le cas échéant).

Performance

La vitesse des shaders de calcul et de géométrie dépendra du matériel, mais un GPU intégré prend environ un tiers de seconde par morceau. La conversion en maillage prend un peu plus de temps et utilise plus de mémoire. La conversion vers Unity TerrainData est très rapide, mais ne stocke qu’une carte de hauteur.

Seize millions de voxels (256 x 256 x 256) dans un bloc occupent 16 Mo. Les voxels de surface sont généralement de 512 Ko (128K x 4 octets) pour chaque morceau prêt à être affiché.

Éponge de Menger de niveau cinq affichant 3,2 millions de voxels.

Éponge de Menger de niveau cinq affichant 3,2 millions de voxels.

Tests de résistance

Il s’agit d’une fractale qui est parfois utilisée pour tester les bibliothèques de voxels car elle n’a pas de voxels cachés. Autrement dit, chaque voxel a au moins une face visible sous un certain angle. C’est 14,3 millions (243 x 243 x 243) en volume total, avec 3,2 millions de voxels solides.

Faites défiler pour continuer

En utilisant un GPU intégré, le FPS varie de 5 à 9 lorsque la caméra se déplace autour et à travers l’objet.

A lire aussi :  Comment créer une animation GIF avec GIMP : c'est facile !

Démonstrations

La vidéo de démonstration du terrain montre une grille de 16 morceaux avec toutes les données générées au moment de l’exécution et vers la fin, vous pouvez voir l’intégralité de la carte 1024×1024. Même une zone de voxels de 256×256 pourrait être une scène de jeu, mais avec cet atout, des scènes beaucoup plus grandes peuvent être générées et maintenues actives avec un impact minimal sur le processeur et la RAM principale.

Implémentation : shader de géométrie

Un shader de géométrie a des frais généraux. L’une des raisons est qu’il peut produire une quantité variable de sortie qui devra être déplacée dans un bloc de mémoire contigu. Ma solution consiste à produire la même quantité de sortie pour chaque appel de fonction de géométrie et à minimiser autant que possible les branchements et les copies.

Chaque face de cube nécessite quatre sommets, et la caméra peut voir au plus trois faces en fonction de sa position par rapport au voxel. Cela fait trois branches qui dessinent des visages très similaires, à l’exception d’un décalage le long d’un des axes. La variable de décalage est affectée pour minimiser l’effet de la branche et utilisée pour ajuster la position de la face lorsque les sommets sont créés.

Voir le code source complet sur le lien ci-dessous.

Nuanceur de géométrie

// For each voxel that is visible from some angle, paint the
// three sides that the given camera might see.
[maxvertexcount(12)]
void geom( point inputGS p[1], inout TriangleStream triStream )
{
float4 pos = p[0].pos * float4( _Size, _Size, _Size, 1 );
float4 shift;
float4 voxelPosition = pos + _chunkPosition;
float halfS = _Size * 0.5;  // x, y, z is the center of the voxel,
                            // paint sides offset by half of Size
input pIn1, pIn2, pIn3, pIn4;

  pIn1._color = p[0]._color;
  pIn1.uv = float2( 0.0f, 0.0f );

  pIn2._color = p[0]._color;
  pIn2.uv = float2( 0.0f, 1.0f );

  pIn3._color = p[0]._color;
  pIn3.uv = float2( 1.0f, 0.0f );

  pIn4._color = p[0]._color;
  pIn4.uv = float2( 1.0f, 1.0f );


  shift = (_cameraPosition.x < voxelPosition.x)
        ? float4( 1, 1, 1, 1 ) : float4( -1, 1, -1, 1 );

  pIn1.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, -halfS, halfS, 0 ) ));
  triStream.Append( pIn1 );

  pIn2.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, halfS, halfS, 0 ) ));
  triStream.Append( pIn2 );

  pIn3.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, -halfS, -halfS, 0 )));
  triStream.Append( pIn3 );

  pIn4.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, halfS, -halfS, 0 ) ));
  triStream.Append( pIn4 );

  triStream.RestartStrip();

...

Directions futures

J'ai créé cet atout pour mon propre usage, en le conditionnant dans ce que j'espère être une forme simple, performante et réutilisable. J'ai des idées pour les versions ultérieures, mais j'aimerais aussi entendre les vôtres si vous êtes prêt à les partager, ou faites-moi savoir comment vous utilisez cet atout pour conquérir le monde des voxels.

A lire aussi :  Top 3 des applications de montage vidéo gratuites pour les utilisateurs d'Android

Une idée avec laquelle j'ai joué récemment est de changer l'étape d'optimisation de pré-affichage pour renvoyer plusieurs tampons GPU au lieu d'un seul. Cela permettrait à des shaders plus spécialisés de gérer différentes informations de voxel, telles que la prise en charge des nuages, des liquides ou un changement graphique pour différentes faces de voxel.

Laissez-moi savoir ce que vous pensez!

Le didacticiel vidéo présenté ci-dessous est de Charles Humphrey qui est un érudit et un gentleman pour avoir montré comment passer un tampon GPU d'un Compute Shader à un Geometry Shader, une technique disponible récemment dans Unity, donc cela reste un peu un art noir. Le didacticiel montre également comment obtenir des effets météorologiques dans Unity en utilisant le mode panneau d'affichage afin que les flocons de neige soient toujours face à la caméra. C'est un tas d'astuces de shader soignées que je pense qu'il aimerait approfondir et mettre dans l'un de ses atouts sur le magasin Unity, mais il est tiré dans dix directions différentes à la fois.

Cet article est exact et fidèle au meilleur de la connaissance de l'auteur. Le contenu est uniquement à des fins d'information ou de divertissement et ne remplace pas un conseil personnel ou un conseil professionnel en matière commerciale, financière, juridique ou technique.

Bouton retour en haut de la page