Image d'arrière-plan de l'unité Flou



L'une des tâches qu'un développeur d'applications Unity peut rencontrer est de mettre l'image actuelle en arrière-plan afin de porter l'attention de l'utilisateur sur quelque chose de nouveau, tel qu'un menu ou un message qui apparaît. L'article raconte l'expérience de la résolution de ce problème par un développeur qui possède des connaissances de base dans Unity, sans utiliser de ressources externes ou une licence Unity supplémentaire. J'espère que ce matériel sera utile à ceux qui ont été confrontés à un problème similaire et n'ont pas trouvé de solutions efficaces à différentes étapes de celui-ci.

La mise en évidence des éléments d'interface importants (et la suppression de ceux inutiles) est l'un des principaux outils pour améliorer l'expérience utilisateur. Cela est particulièrement prononcé lorsque vous travaillez avec des applications mobiles. Ce changement d'attention peut se faire de différentes manières - en passant doucement les boutons au-delà des bords de l'écran, en assombrissant l'arrière-plan, le comportement dynamique, etc. Cela inclut le flou de l'image. Cet effet, d'une part, réduit le contraste des éléments d'arrière-plan et il devient plus difficile à saisir pour l'œil. D'un autre côté, le flou a un effet subconscient lorsque l'image devient floue et que nous la percevons différemment. Cette approche est souvent utilisée dans les jeux, et à partir d'exemples bien connus de jeux sur Unity, nous pouvons nommer les moments lors du choix du menu des quêtes de Hearthstone ou surécran de fin de duel .

Imaginez maintenant que nous voulons faire la même chose ou similaire dans notre application préférée. Nous ne sommes pas non plus sans budget et avons peu d'expérience avec Unity.

Première partie - Shader


La première pensée qui a surgi était que tout devait déjà être réalisé. Sûrement en utilisant un shader. Et cela devrait certainement être dans la boîte Unity. Une recherche rapide et une tentative d'installation ont montré qu'il existe un tel shader dans Unity, mais il s'avère qu'il fait partie des actifs standard et qu'il n'est pas disponible pour une licence gratuite.

Mais c'est flou! Un effet de base qui est mathématiquement simple et devrait également être facilement mis en œuvre et intégré dans le projet. Même si nous ne savons rien des shaders. Ensuite, allez sur Internet, et la recherche a rapidement montré qu'il existe des codes sources de shaders prêts à l'emploi pour l'effet, et même différents.

Pour la première implémentation, ce shader a été pris . Pour le tester, ajoutez simplement le shader en tant que fichier source au projet, associez-le au matériau, liez le matériau à Image.

Deuxième partie - Interface utilisateur


Si nous nous soucions de l'utilisateur de l'application, il est nécessaire de prêter l'attention requise à l'interface utilisateur. Si vous ajoutez simplement un effet de flou à l'arrière-plan et que vous l'oubliez, cela n'est pas tout à fait conforme aux principes internes de création d'un produit.

Par conséquent, pour d'autres actions, vous devez toucher ce qui s'est passé, puis penser et ajuster de manière à provoquer la perception nécessaire de l'utilisateur. Ce point est très sensible au contexte. Selon le contraste de l'image, la variété des couleurs et d'autres facteurs, diverses valeurs et outils supplémentaires peuvent être sélectionnés. Dans notre cas, à ce stade, un bon résultat a été obtenu avec un rayon de flou de 25 (paramètre du shader) et un transparent supplémentaire de 70% (en dehors du shader via une image séparée). Cependant, ce n'est que l'image d'arrière-plan finale. La transition elle-même, selon les sentiments au téléphone, était trop forte.

Le shader installé dans l'image est calculé sur chaque image, et donc, pour effectuer une commutation en douceur, il suffit de changer dynamiquement le paramètre de rayon de flou et la transparence. Vous pouvez organiser cela de différentes manières, mais en substance, le traitement est une mise à jour dans les gestionnaires de mise à jour en fonction de l'heure. La transition elle-même dans la version finale de l'application est de 0,2 seconde, mais il s'avère que c'est important pour la perception de l'utilisateur.

Troisième partie - Productivité


Ces derniers mois, j'ai entendu des plaintes de plusieurs utilisateurs différents (pas même des programmeurs) selon lesquels les programmes de jeux utilisent souvent toutes les ressources informatiques disponibles au ralenti et sans freins. Par exemple, cela peut s'exprimer par l'utilisation maximale de la carte vidéo dans le cas d'un programme minimisé sur plateau, ou indépendamment de tout et du chargement constant d'un thread de processeur. Les raisons en sont intuitives - les revenus du développeur ou de l'éditeur ne dépendent pas de l'optimisation (les gens ne font pas attention à de telles choses lors de l'achat), et les développeurs ordinaires sont probablement plus préoccupés par les KPI locaux et la nécessité d'un retard. Cependant, dans la pratique, je ne voudrais pas entrer dans cette catégorie.

Après avoir terminé l'étape précédente lors du lancement suivant avec un effet de flou d'arrière-plan, après avoir tenu le téléphone dans votre main pendant un certain temps, une sensation de réchauffement est apparue. Si vous passez plus de temps sur le menu, tout devient bien pire. Et même si l'utilisateur dans le menu ne passe probablement pas beaucoup de temps et probablement, je ne voudrais pas du tout laisser tomber la batterie.

L'analyse a montré que le shader choisi ci-dessus a une complexité asymptotique O (r 2 ) en fonction du rayon sélectionné. En d'autres termes, le nombre de calculs par point devient 625 fois plus important si vous augmentez le rayon de flou de 1 à 25. Dans ce cas, les calculs se produisent sur chaque image.

La première étape de la solution a été l'idée que tous les shaders ne sont pas également utiles et que l'effet de flou peut être mis en œuvre de différentes manières. Pour ce faire, vous pouvez prendre un flou distinct, dont l'essence est de flouter d'abord uniquement les lignes horizontalement, puis uniquement les lignes verticalement. En conséquence, nous obtenons O (r) , et ceteris paribus la complexité chute d'un ordre de grandeur. Un autre moyen serait de prendre une mipmap plus petite, qui prend déjà en charge une partie du flou. Ce shader a

été pris comme base. Cependant, son effet de flou s'est avéré insuffisant et avec un contraste élevé du graphique, l'image est devenue «écaillée». Pour obtenir de meilleurs effets, la distribution a été modifiée (éléments GRABPIXEL). Si dans le shader, de 0,18 à 0,05, rayon 4, dans notre version, de 0,14 à 0,03, rayon 6 (ess, mais la somme de tous devrait être 1).

Ainsi, la complexité du traitement a été réduite de 1 à 2 ordres de grandeur. Mais ce n'est pas nécessaire d'arrêter.

Quatrième partie - Contexte statique


Néanmoins, si nous laissons le shader sur l'image existante, alors il est constamment en travail, faisant les mêmes calculs sur chaque image. Si quelque chose se passe en arrière-plan, nous devons le faire. Mais c'est complètement facultatif si l'arrière-plan est statique. Ainsi, après un flou dynamique, vous pouvez vous souvenir du résultat, le mettre en arrière-plan et désactiver le shader.

Ensuite, essayons avec des extraits de code. La préparation de la texture de destination pour l'ensemble de l'écran peut ressembler à ceci:

_width = Screen.width / 2;
_height = Screen.height / 2;
_texture = new Texture2D(_width, _height);

var fillColor = Color.white;
var fillColorArray = _texture.GetPixels();
for (var i = 0; i < fillColorArray.Length; ++i)
{
	fillColorArray[i] = fillColor;
}
_texture.SetPixels(fillColorArray);
_texture.Apply();

_from.GetComponent<Image>().sprite = Sprite.Create(_texture, new Rect(0, 0, _texture.width, _texture.height), new Vector2(0.5f, 0.5f));

Autrement dit, une texture est préparée ici qui est 2 fois plus petite horizontalement et verticalement à partir de l'écran. Cela peut être fait presque toujours, car nous savons que les tailles d'écran des appareils sont un multiple de 2, et si cela est fait, cela réduira la taille de la texture et facilitera le flou.

RenderTexture temp = RenderTexture.GetTemporary(_width, _height);
Graphics.Blit(_from_image.mainTexture, temp, _material);
RenderTexture.active = temp;

_texture.ReadPixels(new Rect(0, 0, temp.width, temp.height), 0, 0, false);
_texture.Apply();

_to_image.sprite = Sprite.Create(_texture, new Rect(0, 0, _texture.width, _texture.height), new Vector2(0.5f, 0.5f));
RenderTexture.ReleaseTemporary(temp);

Pour capturer une image avec mainTexture en utilisant un shader (sur _material) Graphics.Blit est utilisé. Ensuite, nous mémorisons le résultat dans la texture préparée et le plaçons dans l'image cible.

Tout irait bien; en pratique, il était confronté à un tel effet que, selon l'appareil, l'image d'arrière-plan se retourne. La raison après une étude du problème et du débogage devient claire - la différence dans les systèmes de coordonnéesDirect3D et OpenGL, que Unity ne peut pas cacher. Dans le même temps, notre shader détecte les UV et les traite correctement, et l'inversion se produit déjà dans l'environnement Unity (ReadPixels). Il existe de nombreux conseils sur le net, tels que «si vous vous êtes retourné, retournez-le vous-même» ou «changez le signe UV». L'utilisation de la macro UNITY_UV_STARTS_AT_TOP n'a pas permis d'obtenir une bonne généralisation pour tous les appareils testés. De plus, par exemple, nous avons rencontré un tel cas que si vous préparez l'assemblage pour l'émulation dans Xcode sur l'iPhone, et que Unity n'utilisait pas Metal, mais uniquement OpenGLES, vous devez intercepter des cas tels que l'émulation d'un appareil fonctionnant sur un autre logiciel.

Après avoir essayé un certain nombre d'options, je me suis installé sur deux tablettes. Le premier force le rendu de la caméra(définition de forceIntoRenderTexture au moment de la capture d'image). Le second est la détermination du type de système graphique à la volée via SystemInfo.graphicsDeviceType, qui permet de définir un type OpenGL ou Direct3D (le premier groupe est OpenGLES2, OpenGLES3, OpenGLCore et Vulkan). De plus, dans notre implémentation pour Direct3D-like, nous devons retourner, ce qui se fait de manière procédurale (par exemple, comme ceci ).

Conclusion


J'espère que cet article sera utile à quelqu'un pour surmonter un rake inconnu, améliorer la vie des utilisateurs et comprendre Unity. Si vous regardez l'effet en utilisation de combat, alors dans Unity il ressemble à ceci . Dans l'application, les sensations sont quelque peu différentes, mais cette animation peut donner une idée suffisante.

All Articles