Shaders de carte interactive Unity

image

Ce didacticiel concerne les cartes interactives et leur création dans Unity à l'aide de shaders.

Cet effet peut servir de base à des techniques plus complexes, telles que les projections holographiques ou même une table de sable du film "Black Panther".

Une inspiration pour ce tutoriel est le tweet publié par Baran Kahyaoglu , montrant un exemple de ce qu'il crée pour Mapbox .



La scène (à l'exception de la carte) a été prise à partir de la démo du vaisseau spatial graphique à effet visuel Unity (voir ci-dessous), qui peut être téléchargée ici .


Partie 1. Décalage de sommet


Anatomie de l'effet


La première chose que vous pouvez immédiatement remarquer est que les cartes géographiques sont plates : si elles sont utilisées comme textures, elles n'ont pas la tridimensionnalité qu'un véritable modèle 3D de la zone de carte correspondante aurait.

Vous pouvez appliquer cette solution: créez un modèle 3D de la zone nécessaire dans le jeu, puis appliquez-y une texture de la carte. Cela aidera à résoudre le problème, mais cela prend beaucoup de temps et ne permettra pas de réaliser l'effet de «défilement» de la vidéo Baran Kahyaoglu.

De toute évidence, une approche plus technique est préférable. Heureusement, les shaders peuvent être utilisés pour modifier la géométrie d'un modèle 3D. Avec leur aide, vous pouvez transformer n'importe quel avion en vallées et montagnes de la région dont nous avons besoin.

Dans ce tutoriel, nous utilisons une carte de Chillot , Chilli, célèbre pour ses collines caractéristiques. L'image ci-dessous montre la texture de la région tracée sur un maillage rond.


Bien que nous voyions des collines et des montagnes, elles sont encore complètement plates. Cela détruit l'illusion du réalisme.

Extrusion de normales


La première étape pour utiliser des shaders pour changer la géométrie est une technique appelée extrusion normale . Elle a besoin d' un modificateur de sommet : une fonction qui peut manipuler les sommets individuels d'un modèle 3D.

La façon dont le modificateur de sommet est utilisé dépend du type de shader utilisé. Dans ce didacticiel, nous allons modifier le Shader standard de surface - l'un des types de shaders que vous pouvez créer dans Unity.

Il existe de nombreuses façons de manipuler les sommets d'un modèle 3D. L'une des toutes premières méthodes décrites dans la plupart des didacticiels de vertex shader est l' extrusion de normales . Elle consiste à repousser chaque sommet ( extrusion ), ce qui donne au modèle 3D un aspect plus gonflé. «À l'extérieur» signifie que chaque sommet se déplace dans la direction de la normale.


Pour les surfaces lisses, cela fonctionne très bien, mais dans les modèles avec de mauvaises connexions de vertex, cette méthode peut créer d'étranges artefacts. Cet effet est bien expliqué dans l'un de mes premiers tutoriels: Une introduction douce aux shaders , où j'ai montré comment extruder et intruder un modèle 3D.


L'ajout de normales extrudées à un shader de surface est très facile. Chaque shader de surface a une #pragma , qui est utilisée pour transmettre des informations et des commandes supplémentaires. L'une de ces commandes est vert , ce qui signifie que la fonction vert sera utilisée pour traiter chaque sommet du modèle 3D.

Le shader édité est le suivant:

 #pragma surface surf Standard fullforwardshadows addshadow vertex:vert ... float _Amount; ... void vert(inout appdata_base v) { v.vertex.xyz += v.normal * _Amount; } 

Puisque nous addshadow la position des sommets, nous devons également utiliser addshadow si nous voulons que le modèle projette correctement des ombres sur lui-même.

Qu'est-ce que appdata_base?
Comme vous pouvez le voir, nous avons ajouté une fonction de modificateur de sommets ( vert ), qui prend en paramètre une structure appelée appdata_base . Cette structure stocke des informations sur chaque sommet individuel du modèle 3D. Il contient non seulement la position du sommet ( v.vertex ), mais également d'autres champs, par exemple , la direction normale ( v.normal ) et les informations de texture associées au sommet ( v.texcoord ).

Dans certains cas, cela ne suffit pas et nous pouvons avoir besoin d'autres propriétés, telles que la couleur du sommet ( v.color ) et la direction tangente ( v.tangent ). Les modificateurs de sommet peuvent être spécifiés à l'aide d'une variété d'autres structures d' appdata_tan , y compris appdata_tan et appdata_full , qui fournissent plus d'informations au prix de faibles performances. Vous pouvez en savoir plus sur appdata (et ses variantes) dans le wiki Unity3D .

Comment les valeurs sont-elles renvoyées par vert?
La fonction supérieure n'a pas de valeur de retour. Si vous connaissez le langage C #, vous devez savoir que les structures sont transmises par valeur, c'est-à-dire que lorsque v.vertex change v.vertex cela n'affecte que la copie de v , dont la portée est limitée par le corps de la fonction.

Cependant, v également déclaré inout , ce qui signifie qu'il est utilisé à la fois pour l'entrée et la sortie. Toute modification apportée change la variable elle-même, que nous transmettons à vert . Les mots clés inout et out très souvent utilisés en infographie, et ils peuvent à peu près être corrélés avec ref et out en C #.

Extrusion de normales avec des textures


Le code que nous avons utilisé ci-dessus fonctionne correctement, mais il est loin de l'effet que nous voulons atteindre. La raison en est que nous ne voulons pas extruder tous les sommets de la même quantité. Nous voulons que la surface du modèle 3D corresponde aux vallées et montagnes de la région géographique correspondante. Tout d'abord, nous devons en quelque sorte stocker et récupérer des informations sur la quantité de chaque point sur la carte qui est soulevée. Nous voulons que l'extrusion soit influencée par la texture dans laquelle les hauteurs du paysage sont encodées. Ces textures sont souvent appelées cartes de hauteur , mais souvent elles sont également appelées cartes de profondeur , selon le contexte. Après avoir reçu des informations sur les hauteurs, nous pourrons modifier l'extrusion de l'avion en fonction de la carte des hauteurs. Comme le montre le schéma, cela nous permettra de contrôler la montée et la descente des zones.


Il est assez simple de trouver une image satellite de la zone géographique qui vous intéresse et une carte d'élévation associée. Voici la carte satellite de Mars (ci-dessus) et la carte d'altitude (ci-dessous) qui ont été utilisées dans ce tutoriel:



J'ai parlé en détail du concept de la carte de profondeur dans une autre série de tutoriels intitulée "Photos 3D de Facebook de l'intérieur: shaders de parallaxe" [ traduction en Habré].

Dans ce didacticiel, nous supposerons que la carte des hauteurs est stockée sous forme d'image en niveaux de gris, où le noir et le blanc correspondent à des hauteurs inférieures et supérieures. Nous avons également besoin de ces valeurs pour une mise à l'échelle linéaire , c'est-à-dire que la différence de couleur, par exemple, à 0.1 correspond à une différence de hauteur entre 0 et 0.1 ou entre 0.9 et 1.0 . Pour les cartes de profondeur, ce n'est pas toujours vrai, car beaucoup d'entre elles stockent des informations de profondeur à une échelle logarithmique .

Pour échantillonner une texture, deux éléments d'information sont nécessaires: la texture elle-même et les coordonnées UV du point que nous voulons échantillonner. Ce dernier est accessible via le champ texcoord , stocké dans la structure appdata_base . Il s'agit de la coordonnée UV associée au sommet actuel en cours de traitement. L'échantillonnage de texture dans une fonction de surface se fait à l'aide de tex2D , cependant lorsque nous sommes dans une , tex2Dlod est requis.

Dans l'extrait de code ci-dessous, une texture appelée _HeightMap utilisée pour modifier la valeur d'extrusion effectuée pour chaque sommet:

 sampler2D _HeightMap; ... void vert(inout appdata_base v) { fixed height = tex2Dlod(_HeightMap, float4(v.texcoord.xy, 0, 0)).r; vertex.xyz += v.normal * height * _Amount; } 

Pourquoi tex2D ne peut-il pas être utilisé comme fonction de sommet?
Si vous regardez le code généré par Unity pour le Surface Shader standard, vous remarquerez qu'il contient déjà un exemple de la façon d'échantillonner des textures. En particulier, il échantillonne la texture principale (appelée _MainTex ) dans une fonction de surface (appelée surf ) à l'aide de la fonction tex2D intégrée.

Et en fait, tex2D utilisé pour échantillonner les pixels d'une texture, indépendamment de ce qui y est stocké, de sa couleur ou de sa hauteur. Cependant, vous pouvez remarquer que tex2D ne peut pas être utilisé dans une fonction de sommet.

La raison en est que tex2D ne lit pas seulement les pixels de la texture. Elle décide également de la version de la texture à utiliser, en fonction de la distance à la caméra. Cette technique est appelée mipmapping : elle vous permet d'avoir des versions plus petites d'une seule texture qui peuvent être utilisées automatiquement à différentes distances.

Dans la fonction de surface, le shader sait déjà quelle texture MIP utiliser. Ces informations peuvent ne pas encore être disponibles dans la fonction vertex, et par conséquent tex2D ne peut pas être utilisé en toute confiance. Contrairement à cela, la fonction tex2Dlod peut recevoir deux paramètres supplémentaires, qui dans ce didacticiel peuvent avoir une valeur nulle.

Le résultat est clairement visible dans les images ci-dessous.



Dans ce cas, une légère simplification peut être apportée. Le code que nous avons examiné précédemment peut fonctionner avec n'importe quelle géométrie. Cependant, nous pouvons supposer que la surface est absolument plate. En fait, nous voulons vraiment appliquer cet effet au plan.

Par conséquent, vous pouvez supprimer v.normal et le remplacer par float3(0, 1, 0) :

 void vert(inout appdata_base v) { float3 normal = float3(0, 1, 0); fixed height = tex2Dlod(_HeightMap, float4(v.texcoord.xy, 0, 0)).r; vertex.xyz += normal * height * _Amount; } 

Nous pourrions le faire car toutes les coordonnées dans appdata_base sont stockées dans l'espace modèle , c'est-à-dire qu'elles sont définies par rapport au centre et à l'orientation du modèle 3D. La transition, la rotation et la mise à l'échelle avec transformation dans Unity modifient la position, la rotation et l'échelle de l'objet, mais n'affectent pas le modèle 3D d'origine.

Partie 2. Effet de défilement


Tout ce que nous avons fait ci-dessus fonctionne plutôt bien. Avant de continuer, nous allons extraire le code nécessaire pour calculer la nouvelle hauteur de sommet dans une fonction getVertex distincte:

 float4 getVertex(float4 vertex, float2 texcoord) { float3 normal = float3(0, 1, 0); fixed height = tex2Dlod(_HeightMap, float4(texcoord, 0, 0)).r; vertex.xyz += normal * height * _Amount; return vertex; } 

Ensuite, toute la fonction vert aura la forme:

 void vert(inout appdata_base v) { vertex = getVertex(v.vertex, v.texcoord.xy); } 

Nous l'avons fait car ci-dessous, nous devons calculer la hauteur de plusieurs points. En raison du fait que cette fonctionnalité sera dans sa propre fonction distincte, le code deviendra beaucoup plus simple.

Calcul des coordonnées UV


Cependant, cela nous amène à un autre problème. La fonction getVertex dépend non seulement de la position du sommet actuel (v.vertex), mais aussi de ses coordonnées UV ( v.texcoord ).

Lorsque nous voulons calculer le décalage de hauteur de sommet que la fonction vert traite actuellement, les deux éléments de données sont disponibles dans la structure appdata_base . Cependant, que se passe-t-il si nous devons échantillonner la position d'un point voisin? Dans ce cas, nous pouvons connaître la position xyz dans l'espace du modèle , mais nous n'avons pas accès à ses coordonnées UV.

Cela signifie que le système existant est capable de calculer le décalage de hauteur uniquement pour le sommet actuel. Une telle restriction ne nous permettra pas d'avancer, nous devons donc trouver une solution.

Le moyen le plus simple est de trouver un moyen de calculer les coordonnées UV d'un objet 3D, en connaissant la position de son sommet. C'est une tâche très difficile, et il existe plusieurs techniques pour la résoudre (l'une des plus populaires est la projection triplanaire ). Mais dans ce cas particulier, nous n'avons pas besoin de faire correspondre UV et géométrie. Si nous supposons que le shader sera toujours appliqué au maillage plat, la tâche devient triviale.

Nous pouvons calculer les coordonnées UV (image inférieure) à partir des positions des sommets (image supérieure) du fait que les deux sont superposés linéairement sur un maillage plat.



Cela signifie que pour résoudre notre problème, nous devons transformer les composantes XZ de la position du sommet en coordonnées UV correspondantes.


Cette procédure est appelée interpolation linéaire . Il est discuté en détail sur mon site Web (par exemple: The Secrets Of Color Interpolation ).

Dans la plupart des cas, les valeurs UV sont comprises entre 0 avant 1 ; les coordonnées de chaque sommet, en revanche, sont potentiellement illimitées. Du point de vue des mathématiques, pour la conversion de XZ en UV, nous n'avons besoin que de leurs valeurs limites:

  • X m i n , X m a x
  • Z m i n , Z m a x
  • U m i n , U m a x
  • V m i n , V m a x

qui sont indiqués ci-dessous:


Ces valeurs varient en fonction du maillage utilisé. Sur le plan Unity, les coordonnées UV sont comprises entre 0 avant 1 et les coordonnées des sommets sont comprises entre - 5 avant + 5 .

Les équations pour convertir XZ en UV sont:

(1)
image


Comment sont-ils affichés?
Si vous n'êtes pas familier avec le concept d'interpolation linéaire, ces équations peuvent sembler assez intimidantes.

Cependant, ils sont affichés tout simplement. Regardons juste un exemple. U . Nous avons deux intervalles: l'un a des valeurs de Xmin avant Xmax un autre de Umin avant Umax . Données entrantes pour les coordonnées X est la coordonnée du sommet en cours de traitement, et la sortie sera la coordonnée U utilisé pour échantillonner la texture.

Nous devons maintenir la proportionnalité entre X et son intervalle, et U et son intervalle. Par exemple, si X compte alors 25% de son intervalle U comptera également pour 25% de son intervalle.

Tout cela est illustré dans le diagramme suivant:


On peut en déduire que la proportion composée du segment rouge par rapport au rose doit être la même que la proportion entre le segment bleu et le bleu:

(2)

Maintenant, nous pouvons transformer l'équation ci-dessus pour obtenir U :


et cette équation a exactement la même forme que celle montrée ci-dessus (1).

Ces équations peuvent être implémentées dans le code comme suit:

 float2 _VertexMin; float2 _VertexMax; float2 _UVMin; float2 _UVMax; float2 vertexToUV(float4 vertex) { return (vertex.xz - _VertexMin) / (_VertexMax - _VertexMin) * (_UVMax - _UVMin) + _UVMin; } 

Maintenant, nous pouvons appeler la fonction getVertex sans avoir à lui passer v.texcoord :

 float4 getVertex(float4 vertex) { float3 normal = float3(0, 1, 0); float2 texcoord = vertexToUV(vertex); fixed height = tex2Dlod(_HeightMap, float4(texcoord, 0, 0)).r; vertex.xyz += normal * height * _Amount; return vertex; } 

Alors la fonction entière vert prend la forme:

 void vert(inout appdata_base v) { v.vertex = getVertex(v.vertex); } 

Effet de défilement


Grâce au code que nous avons écrit, la carte entière est affichée sur le maillage. Si nous voulons améliorer l'affichage, nous devons apporter des modifications.

Formalisons un peu plus le code. Premièrement, nous devrons peut-être zoomer sur une partie distincte de la carte, plutôt que de la regarder dans son ensemble.


Cette zone peut être définie par deux valeurs: sa taille ( _CropSize ) et son emplacement sur la carte ( _CropOffset ), mesurés dans l' espace des sommets (de _VertexMin à _VertexMax ).

 // Cropping float2 _CropSize; float2 _CropOffset; 

Après avoir reçu ces deux valeurs, nous pouvons à nouveau utiliser l'interpolation linéaire pour que getVertex appelé non pas pour la position actuelle du haut du modèle 3D, mais pour le point mis à l'échelle et transféré.


Code pertinent:

 void vert(inout appdata_base v) { float2 croppedMin = _CropOffset; float2 croppedMax = croppedMin + _CropSize; // v.vertex.xz: [_VertexMin, _VertexMax] // cropped.xz : [croppedMin, croppedMax] float4 cropped = v.vertex; cropped.xz = (v.vertex.xz - _VertexMin) / (_VertexMax - _VertexMin) * (croppedMax - croppedMin) + croppedMin; v.vertex.y = getVertex(cropped); } 

Si nous voulons faire défiler, il suffira de mettre à jour _CropOffset travers le script. Pour cette raison, la zone de troncature se déplacera, faisant défiler le paysage.

 public class MoveMap : MonoBehaviour { public Material Material; public Vector2 Speed; public Vector2 Offset; private int CropOffsetID; void Start () { CropOffsetID = Shader.PropertyToID("_CropOffset"); } void Update () { Material.SetVector(CropOffsetID, Speed * Time.time + Offset); } } 

Pour que cela fonctionne, il est très important de définir le mode Wrap de toutes les textures sur Répéter . Si cela n'est pas fait, nous ne pourrons pas boucler la texture.

Pour l'effet zoom / zoom, il suffit de changer _CropSize .

Partie 3. Ombrage du terrain


Ombrage plat


Tout le code que nous avons écrit fonctionne, mais a un sérieux problème. L'ombrage du modèle est quelque peu étrange. La surface est correctement courbée, mais réagit à la lumière comme si elle était plate.

Cela se voit très clairement dans les images ci-dessous. L'image du haut montre un shader existant; le bas montre comment cela fonctionne réellement.



Résoudre ce problème peut être un grand défi. Mais d'abord, nous devons déterminer quelle est l'erreur.

L'opération d'extrusion normale a changé la géométrie générale du plan que nous avons utilisé initialement. Cependant, Unity n'a modifié que la position des sommets, mais pas leurs directions normales. La direction du sommet normal , comme son nom l'indique, est un vecteur de longueur unitaire ( direction ) indiquant la perpendiculaire à la surface. Les normales sont nécessaires car elles jouent un rôle important dans l'ombrage d'un modèle 3D. Ils sont utilisés par tous les shaders de surface pour calculer la manière dont la lumière doit être réfléchie par chaque triangle du modèle 3D. Cela est généralement nécessaire pour améliorer la tridimensionnalité du modèle, par exemple, il fait rebondir la lumière sur une surface plane tout comme elle rebondirait sur une surface incurvée. Cette astuce est souvent utilisée pour rendre les surfaces low-poly plus lisses qu'elles ne le sont réellement (voir ci-dessous).


Cependant, dans notre cas, c'est le contraire qui se produit. La géométrie est courbe et lisse, mais comme toutes les normales sont dirigées vers le haut, la lumière est réfléchie par le modèle comme si elle était plate (voir ci-dessous):


Vous pouvez en savoir plus sur le rôle des normales dans l'ombrage des objets dans l'article sur le mappage normal (Bump Mapping) , où les cylindres identiques sont très différents, malgré le même modèle 3D, en raison de différentes méthodes de calcul des normales de sommet (voir ci-dessous).



Malheureusement, ni Unity ni le langage de création de shaders n'ont de solution intégrée pour recalculer automatiquement les normales. Cela signifie que vous devez les modifier manuellement en fonction de la géométrie locale du modèle 3D.

Calcul normal


La seule façon de résoudre le problème de l'ombrage est de calculer manuellement les normales en fonction de la géométrie de la surface. Une tâche similaire a été discutée dans un article de Vertex Déplacement - Melting Shader Part 1 , où elle a été utilisée pour simuler la fusion de modèles 3D dans Cone Wars .

Bien que le code fini devra fonctionner en coordonnées 3D, limitons la tâche à seulement deux dimensions pour l'instant. Imaginez que vous devez calculer la direction de la normale correspondant au point sur la courbe 2D (la grande flèche bleue dans le diagramme ci-dessous).


D'un point de vue géométrique, la direction de la normale (grosse flèche bleue) est un vecteur perpendiculaire à la tangente passant par le point qui nous intéresse (une fine ligne bleue). La tangente peut être représentée comme une ligne située sur la courbure du modèle. Un vecteur tangent est un vecteur unitaire qui repose sur une tangente.

Cela signifie que pour calculer la normale, vous devez suivre deux étapes: premièrement, trouvez la ligne tangente au point souhaité; puis calculez le vecteur perpendiculaire à celui-ci (qui sera la direction nécessaire de la normale ).

Calcul de la tangente


Pour obtenir la normale, nous devons d'abord calculer la tangente . Il peut être approximé en échantillonnant un point à proximité et en l'utilisant pour construire une ligne près du sommet. Plus la ligne est petite, plus la valeur est précise.

Trois étapes sont nécessaires:

  • Étape 1. Déplacez une petite quantité sur une surface plane
  • Étape 2. Calculez la hauteur du nouveau point.
  • Étape 3. Utilisez la hauteur du point actuel pour calculer la tangente

Tout cela peut être vu dans l'image ci-dessous:


Pour que cela fonctionne, nous devons calculer les hauteurs de deux points, pas d'un. Heureusement, nous savons déjà comment procéder. Dans la partie précédente du didacticiel, nous avons créé une fonction qui échantillonne la hauteur d'un paysage en fonction d'un point de maillage. Nous l'avons appelé getVertex .

Nous pouvons prendre la nouvelle valeur de sommet au point courant, puis à deux autres. L'un sera pour la tangente, l'autre pour la tangente en deux points. Avec leur aide, nous obtenons la normale. Si le maillage d'origine utilisé pour créer l'effet est plat (et dans notre cas, il l'est), nous n'avons pas besoin d'accéder à v.normal et nous pouvons simplement utiliser float3(0, 0, 1) pour tangent et tangent à deux points, respectivement float3(0, 0, 1) et float3(1, 0, 0) . Si nous voulions faire de même, mais, par exemple, pour une sphère, il serait beaucoup plus difficile de trouver deux points appropriés pour calculer la tangente et la tangente à deux points.

Illustrations vectorielles


Après avoir obtenu les vecteurs tangents et tangents appropriés à deux points, nous pouvons calculer la normale en utilisant une opération appelée produit vectoriel . Il existe de nombreuses définitions et explications d'une œuvre vectorielle et de ce qu'elle fait.

Un produit vectoriel reçoit deux vecteurs et renvoie un nouveau. Si deux vecteurs initiaux étaient unitaires (leur longueur est égale à l'unité) et qu'ils sont situés à un angle de 90, alors le vecteur résultant sera situé à 90 degrés par rapport aux deux.

Au début, cela peut être déroutant, mais graphiquement, il peut être représenté comme suit: le produit vectoriel de deux axes en crée un troisième. C’est X f o i s Y = Z  mais aussi X f o i s Z = Y  , et ainsi de suite.

Si nous faisons un pas suffisamment petit (dans le code, c'est offset ), alors les vecteurs de la tangente et de la tangente à deux points seront à un angle de 90 degrés.Avec le vecteur normal, ils forment trois axes perpendiculaires orientés le long de la surface du modèle.

Sachant cela, nous pouvons écrire tout le code nécessaire pour calculer et mettre à jour le vecteur normal.

 void vert(inout appdata_base v) { float3 bitangent = float3(1, 0, 0); float3 tangent = float3(0, 0, 1); float offset = 0.01; float4 vertexBitangent = getVertex(v.vertex + float4(bitangent * offset, 0) ); float4 vertex = getVertex(v.vertex); float4 vertexTangent = getVertex(v.vertex + float4(tangent * offset, 0) ); float3 newBitangent = (vertexBitangent - vertex).xyz; float3 newTangent = (vertexTangent - vertex).xyz; v.normal = cross(newTangent, newBitangent); v.vertex.y = vertex.y; } 

Tout mettre ensemble


Maintenant que tout fonctionne, nous pouvons retourner l'effet de défilement.

 void vert(inout appdata_base v) { // v.vertex.xz: [_VertexMin, _VertexMax] // cropped.xz : [croppedMin, croppedMax] float2 croppedMin = _CropOffset; float2 croppedMax = croppedMin + _CropSize; float4 cropped = v.vertex; cropped.xz = (v.vertex.xz - _VertexMin) / (_VertexMax - _VertexMin) * (croppedMax - croppedMin) + croppedMin; float3 bitangent = float3(1, 0, 0); float3 normal = float3(0, 1, 0); float3 tangent = float3(0, 0, 1); float offset = 0.01; float4 vertexBitangent = getVertex(cropped + float4(bitangent * offset, 0) ); float4 vertex = getVertex(cropped); float4 vertexTangent = getVertex(cropped + float4(tangent * offset, 0) ); float3 newBitangent = (vertexBitangent - vertex).xyz; float3 newTangent = (vertexTangent - vertex).xyz; v.normal = cross(newTangent, newBitangent); v.vertex.y = vertex.y; v.texcoord = float4(vertexToUV(cropped), 0,0); } 

Et sur cela, notre effet est enfin terminé.


Où aller ensuite


Ce tutoriel peut devenir la base d'effets plus complexes, par exemple des projections holographiques ou même une copie de la table de sable du film "Black Panther".


Forfait Unity


Le package complet de ce tutoriel peut être téléchargé sur Patreon , il contient tous les actifs nécessaires pour jouer l'effet décrit.

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


All Articles