Optimisation des modèles 3D pour la scène du jeu

Cet article complète une série de publications du studio Krasnodar Plarium sur divers aspects du travail avec des modèles 3D dans Unity. Articles précédents: "Fonctionnalités du travail avec Mesh dans Unity" , "Unity: édition procédurale de Mesh" , "Importation de modèles 3D dans Unity et pièges" , "Indentation de pixels dans le scan de texture" .

Il y a près de 2 ans, nous avons écrit un article dans lequel nous parlions de l'option d'optimisation de la géométrie 3D dans une scène avec des restrictions sur l'angle de la caméra et la rotation des objets correspondants. Depuis, peu de choses ont coulé, mais la possibilité d'améliorer la solution, d'envisager différentes approches et d'espionner les autres hante l'esprit des développeurs. Dans cet article, nous décrirons une version améliorée de l'algorithme basé sur la peinture de polygones, ainsi que parler d'essayer de transférer une partie de ce travail vers un package 3D.



Recadrer dans la scène


Nous avons déjà considéré le principe de base de cet algorithme dans l'article ci-dessus: nous éteignons tous les effets et les objets transparents, peignons les polygones non traités avec une couleur et ceux traités avec des couleurs différentes, restituons et extrayons le résultat. Dans l'ancienne version, ils peignaient pour que tout le noir soit redondant et qu'un seul triangle soit marqué en rouge.

Dans les commentaires de cet article, l'un des lecteurs a souligné la possibilité d'optimiser l'algorithme en établissant une correspondance biunivoque entre l'ensemble des polygones et un ensemble de nombres uniques. Ensuite, il sera possible de traiter plus d'un triangle de la même manière. Considérez cette option.

Dans ce cas, ainsi que la dernière fois, une pré-formation est censée être liée à la désactivation de tous les objets sifflants sur la scène et des objets garantis de ne pas affecter la visibilité du modèle cible. Les vues de caméra sont traitées de manière presque indépendante; elles ne sont connectées que par un tampon d'index commun de polygones visibles. De plus, un prétraitement de la géométrie est effectué pour chaque angle, pendant lequel les polygones sont retournés qui sont retournés vers la caméra (face arrière). Cela est dû au fait qu'à un certain stade de l'algorithme, un maillage temporaire est créé avec un nombre de sommets beaucoup plus important que celui d'origine. Ce nombre peut facilement dépasser le seuil de 65 535, ce qui nécessitera des gestes supplémentaires dans les calculs et entraînera une baisse des performances. Dans tous les cas, ces polygones seront supprimés, car leur couleur ne tombera pas dans le cadre. Cependant, étant donné que chaque triangle donne potentiellement lieu à trois sommets de déchets, l'élimination des polygones inutiles à l'avance facilite l'étape principale de l'algorithme et réduit les coûts de mémoire.

Soit un modèle 3D dont la géométrie est représentée par un maillage. Pour peindre un polygone spécifique dans une couleur unique, vous devez peindre tous ses sommets dans cette couleur. Étant donné que dans le cas général, un sommet peut appartenir à différents polygones, cela ne fonctionnera pas pour résoudre le problème de front. Peu importe la façon dont nous colorions un sommet, lors du rendu, sa couleur se glissera sur tous les triangles qui le possèdent, conformément à l'algorithme d'interpolation sur le côté de la carte vidéo.


Un exemple d'interpolation des couleurs lors de l'affichage de polygones avec des sommets communs

Par conséquent, il est nécessaire de diviser le maillage en polygones indépendants distincts, tout en préservant la topologie et la géométrie de l'objet. Dictum factum. Nous transformons les tableaux de triangles et de sommets de manière à créer pour chaque triangle 3 sommets uniques, dont la position est déterminée par les sommets correspondants du maillage d'origine. Il est à noter que dans le cas général, un tel maillage aura un nombre de sommets significativement plus important que l'original. Et si ce nombre dépasse 65 535, lors de la création du maillage, vous devez spécifier le format d'indexation approprié.

Convertissez le maillage d'origine en un maillage avec des sommets uniques à chaque polygone
private static Mesh GetNotSmoothMesh(Mesh origin) { var oVertices = origin.vertices; var oTriangles = origin.triangles; var vertices = new Vector3[oTriangles.Length]; var triangles = new int[oTriangles.Length]; for (int i = 0; i < triangles.Length; i++) { vertices[i] = oVertices[oTriangles[i]]; triangles[i] = i; } return new Mesh() { indexFormat = vertices.Length > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16, vertices = vertices, triangles = triangles }; } 


Vous devez maintenant désigner les polygones de ce maillage afin qu'après l'opération de rendu, il soit possible de déterminer celui qui est apparu à l'écran. Comme déjà mentionné, nous générons des couleurs uniques pour les polygones et peignons chacun des trois sommets dans la couleur correspondante. Le résultat est un nouveau maillage, que nous avons appelé maillage de couleur octet .


Maille de couleur octet

Coloration de maillage dans laquelle chaque sommet appartient à un seul polygone
 private static void ColorizePolygons(Mesh mesh) { var pColors = ColorsOfPolygons(mesh); var colors = new Color[mesh.vertexCount]; for (int i = 0; i < colors.Length; i++) { colors[i] = pColors[i / 3]; } mesh.colors = colors; } private static Color[] GetColorsOfPolygons(Mesh mesh) { var colors = new Color[mesh.triangles.Length / 3]; for (int i = 0; i < colors.Length; i++) { var color = Int2Color(i);//         ,     Color2Int //      ,       int     Color32 colors[i] = color; } return colors; } 


N'oubliez pas la coloration. Il est temps de rendre. Nous effectuons un rendu 3D pour tous les angles de caméra et, lors du traitement de chacun d'eux, reconstituons le tampon d'indices de polygone uniques dont les couleurs ont été détectées dans le cadre. Pour le temps des calculs pour la caméra, vous devez désactiver l'anticrénelage pour éviter l'apparition de nouvelles couleurs en raison de l'interpolation des pixels voisins.

Lecture et stockage des couleurs sous différents angles de caméra
 // CameraTransform —         //      SetCameraTransform private static HashSet<Color> GetVisibleColors(Camera camera, CameraTransform[] cameraTransforms) { var renderTexture = new RenderTexture(1920, 1080, 24);//for example var rtRect = new Rect(0, 0, renderTexture.width, renderTexture.height); var frame = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGB24, false);//    -  RGB24   ,    RGBA32 var visibleColorsSet = new HashSet<Color>(); foreach (var cameraTransform in cameraTransforms) { SetCameraTransform(camera, cameraTransform); CreateScreenShot(camera, renderTexture, frame, rtRect); visibleColorsSet.UnionWith(GetTextureColors(frame)); } return visibleColorsSet; } public static void SetCameraTransform(Camera camera, CameraTransform camTransform) { camera.transform.position = camTransform.Position; camera.transform.rotation = camTransform.Rotation; camera.fieldOfView = camTransform.FieldOfView; camera.orthographic = camTransform.IsOrthographic; camera.nearClipPlane = camTransform.NearClippingPlane; camera.farClipPlane = camTransform.FarClippingPlane; } private static HashSet<Color> GetTextureColors(Texture2D texture) { return new HashSet<Color>(texture.GetPixels()); } private static void CreateScreenShot(Camera cam, RenderTexture renderTexture, Texture2D screenShot, Rect renderTextureRect) { cam.targetTexture = renderTexture; cam.Render(); RenderTexture.active = cam.targetTexture; screenShot.ReadPixels(renderTextureRect, 0, 0); RenderTexture.active = null; cam.targetTexture = null; } } 


Il convient de mentionner qu'en raison de la discrétisation, certains triangles peuvent ne pas s'afficher en raison de la taille particulièrement petite de leur projection sur l'écran, et non pas parce que quelque chose les chevauche ou qu'ils sont tournés du mauvais côté. Nous avons implémenté une version conservatrice de l'algorithme. Dans ce cas, l' AABB de la projection du triangle sur l'écran est calculé, et si au moins un de ses côtés est inférieur au côté texel de l'image, alors un tel polygone est marqué comme visible. Cette approche protège contre les artefacts lors de l'exécution de l'algorithme avec une résolution inférieure à la résolution d'écran du périphérique cible. Si vous ignorez les petits polygones, le résultat sera également acceptable à condition que la résolution de la texture de rendu utilisée soit supérieure à la résolution des écrans des appareils prévus.

Nous avons implémenté cet algorithme de recadrage dans Unity et l'avons utilisé pour optimiser des objets statiques dont les modèles se trouvent plus d'une fois dans la scène dans différentes positions. C'est principalement le décor: pierres, arbres, statues, vases, etc. qui fait référence au préfabriqué souvent utilisé. Nous aimerions optimiser ces objets plus tôt, au stade de la création dans un package 3D, mais qui sait dans quelle pose fantasmagorique le level designer veut mettre son candélabre préféré.

Le fait de rogner l'ensemble d'objets du même type avec un tel outil réduit la taille de la scène, car lors du batching statique, les données du maillage préfabriqué commun sont en tout cas copiées au stade de la construction autant de fois que les objets dessinés actifs avec ce maillage sont représentés dans la scène. Notre méthode libère également de l'espace dans les atlas de textures, comme les lightmap . Nous utilisons l'espace économisé pour augmenter le détail des parties des modèles qui ont survécu au nettoyage.

Recadrage 3D


Néanmoins, il vaut mieux que l’artiste puisse couper tout ce qui est inutile dans son éditeur, réduisant ainsi le nombre d’étapes de préparation du contenu. Cela se justifie lorsque le modèle est utilisé dans une scène avec une seule rotation prédéterminée par rapport à la caméra. Auparavant, les objets qui seraient exactement tournés vers l'utilisateur d'un côté étaient souvent simplifiés manuellement avant leur intégration dans le projet. Il est important de noter que la mise en œuvre d'une telle simplification par programme dans Unity est beaucoup plus difficile en raison de la complexité du développement de l'emballage UV , de sorte que l'automatisation au stade d'un package 3D facilite parfois la vie d'un artiste.

Un des outils pour travailler avec des modèles 3D dans notre entreprise est Blender . Nous y sommes montés. Il semble qu'un tel logiciel "adulte", comme Blender , devrait avoir des fonctionnalités similaires. Cependant, il s'est avéré qu'il ne devrait pas. J'ai dû voir mon propre vélo.

La première idée a été d'utiliser l'outil de sélection familier - essentiellement répéter une partie du travail manuel de l'artiste pour un angle de caméra: sélectionner des polygones visibles, inverser la sélection, supprimer. Le plan était le suivant: déplacer la caméra, déterminer la projection AABB du modèle dans chaque position, puis demander le résultat de la sélection des polygones de la zone correspondant à l' AABB , obtenir l'union de l'ensemble des polygones de la vue actuelle avec les précédents et supprimer les polygones non sélectionnés à la fin.

Cependant, lors de la mise en œuvre du script, un inconvénient important a été constaté au niveau de la tâche. Les outils de sélection dans Blender (sélection de rectangle, sélection de cercle) perdent en précision avec l'augmentation du nombre d'éléments sélectionnés par unité de surface de l'écran (certains polygones ne sont pas sélectionnés), ce qui rend leur utilisation dans nos outils d'automatisation impossible. Fait intéressant: dans le même 3ds Max , un tel problème n'est pas observé.


Mise en évidence de loin dans Blender


Résultat de la sélection

La tentative suivante visait à résoudre le problème de front: nous avons envoyé des rayons de la caméra à travers chaque pixel de la fenêtre et regardé quels polygones étaient les premiers à se croiser avec au moins un rayon. Nous n'espérions pas de résultats précis avec cette approche, mais cela valait la peine d'essayer. Le résultat est évident: très faible productivité lors du traitement sur le CPU ou les mêmes trous avec un petit nombre de rayons.

Néanmoins, nous avons pris pied pour la mise en œuvre d'une approche plus avancée. L'idée était de sélectionner un certain nombre de points aléatoires sur chaque polygone puis d'envoyer des rayons de la caméra dans leur direction. Cette approche a bien fonctionné, mais nous avons eu quelques cas limites: des polygones ont également été coupés, dans lesquels l'angle entre le faisceau et leur normale était approximativement égal à π / 2. Ainsi, lorsque la caméra effectue un zoom en raison de distorsions de perspective, des zones de découpe peuvent s'ouvrir.

Cette méthode était, de l'avis des artistes, trop agressive, nous avons donc décidé d'arrêter de recadrer uniquement les faces arrière .

Conclusion


Ce n'est un secret pour personne qu'une attitude prudente envers les ressources de l'appareil lors de la création de jeux est le facteur le plus important affectant la qualité du produit final. Cela est particulièrement vrai pour les plates-formes mobiles, de mauvaise humeur à l'utilisation active de la RAM. La réduction du nombre de polygones vous permet de remplir plus efficacement l'espace des atlas de texture et réduit légèrement la charge de calcul.

N'oubliez pas non plus le coût des heures-homme et le coût des erreurs lors de l'utilisation des outils décrits ci-dessus, etc. L'approche proposée suppose un pipeline qui fonctionne bien pour le travail du département artistique, en particulier les employés impliqués dans l'intégration des modèles dans le projet.

Ainsi, ayant les conditions et les outils discutés dans cet article, nous adhérons aux règles suivantes. S'il est supposé que le modèle créé sera toujours tourné d'un côté vers l'utilisateur, et aussi si sous ces angles le chevauchement de certaines parties du modèle par d'autres est assez petit, alors l'artiste utilise notre outil de rognage de face arrière dans l'éditeur 3D, vérifie l'exactitude et procède à l'empaquetage du développement UV . Si le modèle est souvent utilisé dans différentes positions ou a une géométrie plus complexe, alors après l'importation dans le projet, nous exécutons l'algorithme décrit dans la première partie de l'article, en traitant tous les objets statiques de la scène avec.

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


All Articles