La mise en œuvre de l'effet aquarelle dans les jeux

image

introduction


Quand, en janvier 2019, nous avons commencé à discuter de notre nouveau jeu de teintes. , nous avons immédiatement décidé que l'effet aquarelle serait l'élément le plus important. Inspirés par cette publicité Bulgari , nous avons réalisé que la mise en œuvre de l'aquarelle devait être cohérente avec la haute qualité des ressources restantes que nous avions prévu de créer. Nous avons trouvé un article intéressant par des chercheurs d'Adobe (1) . La technique d'aquarelle qui y est décrite était merveilleuse et, en raison de sa nature vectorielle (plutôt que pixelisée), elle pouvait fonctionner même sur des appareils mobiles faibles. Notre implémentation est basée sur cette étude, nous en avons modifié et / ou simplifié certaines parties car nos exigences de performance étaient différentes. teinte .- c'est un jeu, par conséquent, en plus du dessin lui-même, nous devions rendre l'environnement 3D entier et exécuter la logique du jeu dans une seule image. Nous avons également cherché à nous assurer que la simulation était effectuée en temps réel et que le joueur voyait immédiatement ce qui était dessiné.


Simulation d'aquarelle en teinte.

Dans cet article, nous partagerons les détails individuels de la mise en œuvre de cette technique dans le moteur de jeu Unity et expliquerons comment nous l'avons adaptée pour fonctionner de manière transparente sur les appareils mobiles bas de gamme. Nous parlerons davantage des principales étapes de cet algorithme, mais sans démontrer le code. Cette implémentation a été créée dans Unity 2018.4.2 et mise à jour ultérieurement vers la version 2018.4.7.

Quelle est la teinte.?


Teinte . - C'est un jeu de puzzle qui permet au joueur de compléter les niveaux, en mélangeant les couleurs des aquarelles pour qu'elles correspondent aux couleurs de l'origami. Le jeu est sorti à l'automne 2019 sur Apple Arcade pour iOS, macOS et tvOS.


Teinte de capture d'écran.

Exigences


La technique décrite dans mon article peut être divisée en trois étapes principales effectuées dans chaque cadre:

  1. Générez de nouveaux spots en fonction de l'entrée du joueur et ajoutez-les à la liste des spots
  2. Simulation de peinture pour tous les spots de la liste
  3. Rendu ponctuel

Ci-dessous, nous parlerons en détail de la façon dont nous avons mis en œuvre chacune des étapes.

Notre objectif était d'atteindre 60 FPS, c'est-à-dire que ces étapes et toute la logique décrite ci-dessous sont exécutées 60 fois par seconde.

Obtenir des commentaires


Dans chaque image, nous transformons l'entrée du joueur (selon la plate-forme, il peut s'agir d'une touche, de la position de la souris ou du curseur virtuel) en une structure splatData qui contient la position, le vecteur de mouvement, la couleur et la pression (2). Tout d'abord, nous vérifions la longueur de balayage du joueur à l'écran et la comparons avec une valeur de seuil donnée. Avec des balayages courts, nous générons un spot par image à la position d'entrée. Dans le cas contraire, nous remplissons la distance entre les points de début et de fin du balayage du joueur avec de nouveaux points créés avec une densité prédéterminée (cela garantit une densité de peinture constante quelle que soit la vitesse de balayage). La couleur indique la peinture actuellement utilisée et la pente du mouvement indique la direction du balayage. De nouveaux spots créés sont ajoutés à une collection appelée splatList, qui contient également tous les spots créés précédemment. Il est utilisé pour simuler et rendre la peinture dans les étapes suivantes. Chaque tache individuelle dénote une «goutte» de peinture qui doit être rendue - le bloc de construction principal de la peinture à l'aquarelle. Le dessin aquarelle fini sera le résultat du rendu de dizaines / centaines de points d'intersection. De plus, la valeur de la durée de vie (en images) est affectée au spot nouvellement créé, ce qui détermine la durée de simulation du spot.


Un exemple d'interpolation de longs spots de balayage. Les cercles creux indiquent des taches créées à intervalles réguliers.

Toile


Comme la vraie peinture, nous avons besoin d'une toile. Pour l'implémenter, nous avons créé une zone limitée dans l'espace 3D qui ressemble à une feuille de papier. Les coordonnées d'entrée du lecteur et toutes les autres opérations, telles que le rendu d'un maillage, sont enregistrées dans l'espace de canevas. De même, la taille en pixels de tout tampon utilisé pour simuler le dessin dépend de la taille du canevas. Le terme «canvas» tel qu'il est utilisé dans cet article n'est en aucun cas associé à la classe Canvas de Unity UI.


Le rectangle vert montre la zone de toile dans le jeu

Place


Visuellement, le spot est représenté par un maillage rond dont le bord est composé de 25 sommets. Vous pouvez le percevoir comme une «goutte» qu'un pinceau humide laisse sur un morceau de papier si vous le touchez pendant un très court instant. Nous ajoutons un petit décalage aléatoire à la position de chaque sommet, ce qui assure l'inégalité des bords des taches de peinture.


Exemples de mailles maillées.

Pour chaque sommet, nous stockons également le vecteur de vitesse vers l'extérieur, qui est ensuite utilisé dans la phase de simulation. Nous générons plusieurs de ces maillages avec de petites variations entre les formes et stockons leurs données dans un objet skriptuemy ( un objet scriptable ). Chaque fois qu'un joueur dessine une place en temps réel, nous lui attribuons un maillage choisi aléatoirement dans cet ensemble. Il convient de mentionner qu'à différentes résolutions d'écran, la toile a une taille différente en pixels. Pour que sur tous les appareils le coefficient de la taille des spots soit le même, lorsque le jeu démarre, on change l'échelle en fonction de la taille de la toile.


Un exemple de vecteurs ponctuels stockés avec de nouvelles données ponctuelles.

Lorsqu'un maillage ponctuel est généré, nous enregistrons également sa «zone de mouillage», qui définit un ensemble de pixels qui se trouvent à l'intérieur des bordures ponctuelles d'origine. La zone de mouillage est utilisée pour simuler l' advection . Lors de l'exécution de l'application au moment de la création de chaque nouveau spot, nous marquons la toile en dessous comme mouillée. Lors de la simulation du mouvement de la peinture, nous lui permettons de "s'étendre" sur les zones de la toile qui sont déjà mouillées. Nous stockons la teneur en humidité de la toile dans le tampon global de la carte humide , qui est mis à jour à mesure que chaque nouveau spot est ajouté. En plus de participer au mélange de deux couleurs, l'advection joue un rôle important dans l'apparence finale du trait de peinture lui-même.


Remplissage de la wetmap , les pixels à l'intérieur de la forme du spot (cercle vert) marquent le tampon de la wetmap (grille) comme mouillé (vert). Le tampon wetmap lui-même a une résolution beaucoup plus élevée.

De plus, chaque tache contient également une valeur d' opacité , qui est fonction de sa surface; il représente l'effet du stockage du pigment (une quantité constante de pigment sur place). Lorsque la taille d'un point augmente pendant la simulation, son opacité diminue et vice versa.


Un exemple de peinture sans advection (à gauche) et avec elle (à droite).


Exemples d'advection de peinture.

Cycle de simulation


Une fois que l'entrée du joueur dans l'image actuelle est reçue et convertie en nouveaux spots, l'étape suivante consiste à simuler les spots pour simuler la propagation des aquarelles. Au début de cette simulation, nous avons une liste de spots qui doivent être mis à jour et une wetmap mise à jour .

Dans chaque image, nous parcourons la liste des spots et modifions les positions de tous les sommets des spots en utilisant l'équation suivante:


où: m est le nouveau vecteur de mouvement, a est le paramètre de correction constant (0,33), b est le vecteur de pente de mouvement = direction normalisée du balayage du joueur multiplié par 0,3, cr est la valeur scalaire de la rugosité de la toile = Random.Range (1,1 + r), r est le paramètre de rugosité global, pour la peinture standard, nous le fixons à 0,4, v est le vecteur de vitesse créé à l'avance avec le maillage ponctuel, vm est le facteur de vitesse, la valeur scalaire que nous utilisons localement dans certaines situations pour accélérer l'advection, x (t + 1) - nouvelle position potentielle du sommet, x (t) - position actuelle du sommet, brEst le vecteur de rugosité de la branche = (Random.Range (-r, r), Random.Range (-r, r)), w (x) est la valeur de mouillage dans le tampon de la carte humide.

Le résultat de ces équations est appelé marche aléatoire biaisée , il imite le comportement des particules dans de la vraie peinture aquarelle. Nous essayons de déplacer chaque sommet du point vers l'extérieur à partir de son centre ( v ), en ajoutant du hasard. Ensuite, la direction du mouvement change légèrement avec la direction de la course ( b ) et est à nouveau randomisée par une autre composante de rugosité ( br ). Ensuite, cette nouvelle position de sommet est comparée à un wetmap . Si le canevas dans la nouvelle position était déjà mouillé (valeur dans le tampon de la wetmapsupérieur à 0), alors nous donnons au sommet une nouvelle position x (t + 1) , sinon nous ne changeons pas sa position. Par conséquent, la peinture ne se répandra que dans les zones de la toile qui étaient déjà humides. À la dernière étape, nous recalculons la zone spot, qui est utilisée dans le cycle de rendu pour modifier son opacité.


Exemple à l'échelle microscopique de simulation d'advection entre deux taches de peinture actives.

Cycle de rendu - Tampon humide


Après avoir raconté les spots, vous pouvez commencer à les rendre. À la sortie après la phase d'émulation, le maillage des taches se révèle souvent déformé (par exemple, des intersections se produisent), donc, pour leur rendu correct sans coûts supplémentaires pour la triangulation répétée, nous utilisons une solution avec un tampon de pochoir à deux passes. L'interface de dessin Unity Graphics est utilisée pour le rendu des spots , et le cycle de rendu est effectué à l'intérieur de la méthode Unity OnPostRender . Les maillages ponctuels sont rendus pour rendre la texture ( wetBuffer ) à l'aide d'une caméra distincte. Au début du cycle, wetBuffer est effacé et défini comme cible de rendu à l'aide de Graphics.SetRenderTarget (wetBuffer) . Suivant pour chaque spot actif de splatList nous exécutons la séquence montrée dans le diagramme suivant:


Diagramme du cycle de rendu.

Nous commençons par nettoyer le tampon de pochoir avant chaque point afin que l'état du tampon de pochoir du point précédent n'affecte pas le nouveau point. Ensuite, nous sélectionnons le matériau utilisé pour dessiner le spot. Ce matériau est responsable de la couleur du spot, et nous le sélectionnons en fonction de l'indice de couleur stocké dans splatData lorsque le joueur a dessiné le spot. Ensuite, nous modifions l'opacité des couleurs (canal alpha) en fonction de la zone du maillage ponctuel calculée à l'étape précédente. Le rendu lui-même est effectué à l'aide d'un shader de tampon de pochoir à deux passes. Dans la première passe (Material.SetPass (0)), nous passons le maillage ponctuel d'origine pour enregistrer les coordonnées dans lesquelles le maillage est rempli. Avec ce pass ColorMaskassigné une valeur de 0, donc le maillage lui-même n'est pas rendu. Dans la deuxième passe (Material.SetPass (1)), nous utilisons le quadrilatère décrit autour du maillage ponctuel. Nous vérifions la valeur dans le tampon de pochoir pour chaque pixel du quadrilatère; si la valeur est un, le pixel est rendu, sinon il est ignoré. À la suite de cette opération, nous rendons la même forme que le maillage ponctuel, mais il ne contiendra certainement pas d'artefacts indésirables, par exemple, des auto-intersections.


La procédure pour effectuer la technique du tampon double stencil (de gauche à droite). Notez que ce tampon de pochoir a une résolution beaucoup plus élevée que celle illustrée, de sorte qu'il peut conserver sa forme d'origine avec une grande précision.


Un exemple de trois points d'intersection rendus de manière traditionnelle, ce qui a conduit à l'apparition d'artefacts (à gauche), et en utilisant la technique de tampon de pochoir en deux passes avec l'élimination de tous les artefacts (à droite).

Après avoir rendu tous les spots dans wetBuffer, il s'affiche dans la scène du jeu. Notre toile utilise un shader de fortune combinant un wetBuffer , une carte papier diffuse et une carte normale papier.


Canvas shader: uniquement wetBuffer (à gauche), texture du papier ajoutée (au centre), carte normale ajoutée (à droite).

Le jeu prend en charge un mode pour les personnes daltoniennes, dans lequel des motifs séparés sont superposés sur la peinture. Pour ce faire, nous avons changé le matériau des taches en ajoutant la texture du motif avec du carrelage. Les motifs suivent les règles de mélange des couleurs du jeu, par exemple, le bleu (barres) + le jaune (cercles) donnent le vert (cercles dans les barres) à l'intersection. Pour mélanger les motifs de manière transparente, ils doivent être rendus dans le même espace UV. Nous ajustons les coordonnées UV du quadrilatère utilisé dans la deuxième passe du tampon de pochoir, en divisant les positions x et y (qui sont spécifiées dans l'espace du canevas) par la largeur et la hauteur du canevas. Par conséquent, nous obtenons les valeurs correctes de u, v dans l'espace de 0 à 1.


Un exemple de motifs de daltonisme.

Optimisation - tampon de taches séchées


Comme mentionné ci-dessus, l'une de nos tâches consistait à prendre en charge les appareils mobiles à faible consommation. Le rendu spot s'est avéré être le goulot d'étranglement de notre jeu. Chaque point nécessite trois appels de tirage (appelez deux passes + effacez le tampon du pochoir), et comme la ligne de peinture contient des dizaines ou des centaines de points, le nombre d'appels de tirage augmente rapidement et entraîne une baisse de la fréquence d'images. Pour y faire face, nous avons appliqué deux techniques d'optimisation: d'une part, le dessin simultané de tous les spots «séchés» dans dryBuffer , et d'autre part, l'accélération locale du séchage des spots après avoir atteint un certain nombre de spots actifs.

dryBufferEst une texture de rendu supplémentaire ajoutée au cycle de rendu. Comme mentionné précédemment, chaque spot a une durée de vie (en images), qui diminue avec chaque image. Une fois que la durée de vie atteint 0, la tache est considérée comme «séchée». Les zones sèches ne sont plus simulées, leur forme ne change pas et il n'est donc pas nécessaire de les rendre à nouveau dans chaque image.


DryBuffer en action; les taches grises montrent les taches copiées sur dryBuffer.

Chaque tache dont la durée de vie atteint 0 est supprimée de la splatList et «copiée» dans dryBuffer . Pendant le processus de copie, le cycle de rendu est réutilisé et cette fois, dryBuffer est défini comme texture de rendu cible .

Un mélange correct entre wetBuffer et dryBuffer ne peut pas être obtenu en chevauchant simplement les tampons dans le shader de toile, car la texture de rendu du tampon wetBuffercontient des taches déjà rendues avec une valeur alpha (ce qui équivaut à un alpha prémultiplié). Nous avons contourné ce problème en ajoutant une étape au début du cycle de rendu avant de parcourir itérativement les spots. À ce stade, nous rendons un quadrilatère de la taille d'une pyramide de découpage de caméra qui affiche dryBuffer . Grâce à cela, toute tache qui est rendue dans wetBuffer sera déjà mélangée avec des taches sèches, préalablement peintes.


Un mélange de taches humides et séchées.

Le tampon dryBuffer accumule tous les points «secs» et n'est pas effacé entre les images. Par conséquent, toute la mémoire associée aux taches expirées peut être effacée après avoir été «copiée» dans le tampon.


Grâce à l'optimisation avec dryBuffer , nous n'avons plus de limites sur la quantité de peinture qu'un joueur peut appliquer sur la toile.

L'utilisation de la technique dryBuffer séparément permet au joueur de dessiner avec une quantité presque infinie de peinture, mais ne garantit pas des performances constantes. Comme mentionné ci-dessus, le trait de peinture a une épaisseur constante , qui est obtenue en dessinant en utilisant l'interpolation de nombreux points entre les points de début et de fin du balayage. Dans le cas de nombreux balayages rapides et longs, le joueur peut générer un grand nombre de spots actifs. Ces spots seront simulés et rendus sur le nombre d'images spécifié par leur durée de vie, ce qui conduit finalement à des fréquences d'images plus faibles.

Pour garantir une fréquence d'images stable, nous avons modifié l'algorithme afin que le nombre de spots actifs soit limité par une valeur constante de maxActiveSplats . Tous les points dépassant cette valeur se "dessèchent" instantanément. Ceci est réalisé en réduisant la durée de vie des taches actives les plus anciennes à 0, c'est pourquoi elles sont copiées plus tôt dans le tampon de taches séchées. Étant donné que lorsque nous raccourcissons la durée de vie, nous obtenons une place dans l'état incomplet de la simulation (qui aura l'air assez intéressant), en même temps, nous augmentons la vitesse d'étalement de la peinture. En raison de l'augmentation de la vitesse, le spot atteint presque la même taille qu'à vitesse normale avec une durée de vie standard.


Démonstration de 40 points actifs maximum (en haut) et 80 (en bas). Les taches séchées copiées dans dryBuffer sont affichées en gris. La valeur indique la «quantité» de peinture qui peut être simulée en même temps.

La valeur de maxActiveSplats est le paramètre de performance le plus important, il nous permet de contrôler avec précision le nombre d'appels de dessin que nous pouvons allouer au rendu aquarelle. Nous le définissons au démarrage, en fonction de la puissance de la plate-forme et de l'appareil. Vous pouvez également modifier cette valeur pendant l'exécution de l'application si une diminution de la fréquence d'images est détectée.

Conclusion


La mise en œuvre de cet algorithme est devenue une tâche intéressante et difficile. Nous espérons que les lecteurs ont apprécié l'article. Vous pouvez poser des questions dans les commentaires à l' original . Si vous voulez apprécier notre aquarelle en action, essayez de jouer à la teinte. sur l'Apple Arcade .


Capture d'écran d'un jeu fonctionnant sur Apple TV

(1) S. DiVerdi, A. Krishnaswamy, R. MÄch et D. Ito, «Painting with Polygons: A Procedural Watercolour Engine», dans IEEE Transactions on Visualization and Computer Graphics, vol. 19, non. 5, pp. 723–735, mai 2013. doi: 10.1109 / TVCG.2012.295

(2) La pression n'est prise en compte que lors du dessin de l'Apple Pencil sur un iPad.

All Articles