Textures animées pour les modèles; Comment écrire un shader?

9

Je regardais de la Rocket League et j'ai remarqué qu'il y avait des décalcomanies et des roues animées.

Image.

Je voudrais implémenter quelque chose de similaire aux effets de l'image ci-dessus.

Comment pourrais-je écrire un Unity Shader pour faire l'effet roue?

Je ne connais pas grand chose aux Shaders, mais puis-je éditer le Unity Shader standard pour faire l'effet d'animation?

VestePotatoeFan
la source
3
Les shaders sont certainement la solution incontournable ici, même pour quelque chose d'aussi simple qu'une texture de défilement. Unity a une _Timevariable intégrée que vous pouvez ajouter à vos coordonnées de texture dans le (vertex) shader pour obtenir un effet de défilement très bon marché. L'effet hexagonal serait également assez simple. Si vous modifiez votre question pour mettre en évidence un seul effet et demandez "comment pourrais-je implémenter cela dans un shader Unity", nous pouvons probablement vous aider. Assurez-vous de spécifier si le shader doit répondre à l'éclairage / à l'ombre, car cela a un impact sur la façon dont il serait écrit.
DMGregory
Merci pour l'info. J'ai changé ma question, est-ce un peu mieux? Je ne connais pas vraiment l'éclairage et l'ombrage, j'ai donc laissé cela de côté jusqu'à ce que je comprenne mieux les Shaders. Je suppose que je les voudrais, mais je pourrais simplement copier des parties du shader standard, non?
JacketPotatoeFan
3
Je développerai cela dans une réponse un peu plus tard ce soir (mais n'hésitez pas à me battre, si quelqu'un veut répondre maintenant!) - mais vous écriviez généralement un shader Unity qui utilise l'éclairage comme "Surface Shader" tandis que celui qui n'a pas besoin d'éclairage est souvent plus simple à écrire en tant que "Shader non éclairé". Il me semble que ces roues ne sont pas éclairées (le léger ombrage sur le bord ressemble à SSAO), donc je laisserais l'éclairage hors de l'image pour les débutants. Pouvez-vous clarifier: voulez-vous exactement le motif géométrique montré ici, ou la possibilité de faire défiler des textures arbitraires vers l'extérieur et les teinter en arc-en-ciel comme ça?
DMGregory
Merci beaucoup, toutes les informations que vous pouvez donner pour me lancer seraient formidables. J'admire vraiment les gens qui savent écrire des shaders, j'ai parcouru quelques articles pour Unity Shaders, et oui, très déroutant. Je pense que simplement faire défiler des textures (ou des uv de texture?) Serait assez bon, et avec la possibilité de teinter.
JacketPotatoeFan

Réponses:

13

Je vais construire cela en quelques couches afin que vous puissiez voir comment cela se combine.

Commencez par créer un nouveau shader dans Unity en choisissant Create --> Shader --> Unlitdans le menu Actifs ou dans le menu contextuel du clic droit de la fenêtre Projet.

Dans le bloc supérieur, nous ajouterons un _ScrollSpeedsparamètre pour contrôler la vitesse à laquelle la texture se déplace dans chaque direction:

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _ScrollSpeeds ("Scroll Speeds", vector) = (-5, -20, 0, 0)
}

Cela expose une nouvelle propriété flottante à 4 composants dans l'inspecteur des matériaux, avec le nom convivial "Vitesse de défilement" (similaire à l'ajout d'une publicou d' une Serializedvariable à un MonoBehaviourscript)

Ensuite, nous utiliserons cette variable dans le shader Vertex pour décaler les coordonnées de texture ( o.uv), en ajoutant seulement deux lignes au shader par défaut:

sampler2D _MainTex;
float4 _MainTex_ST;
// Declare our new parameter here so it's visible to the CG shader
float4 _ScrollSpeeds;

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);

    // Shift the uvs over time.
    o.uv += _ScrollSpeeds * _Time.x;

    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}

Giflez cela sur un quad (avec une belle texture de girafe gratuite par Kenney ) et vous obtenez:

Un quad avec une girafe à répétition défilant dessus.

Pour faire défiler la texture vers l'extérieur dans un anneau, nous pourrions simplement utiliser un maillage subdivisé comme une toile d'araignée, avec la coordonnée uv v augmentant du centre vers l'extérieur. Mais cela donnera à lui seul des artefacts en forme de lame de scie. Au lieu de cela, je vais montrer comment nous pouvons calculer nos UV par fragment.

C'est un peu plus cher, à la fois en raison des opérations de trig et de longueur (qui sont plus chères que les mathématiques de base) et parce qu'il n'est pas aussi efficace de prévoir et de mettre en cache les données de texture dans le matériel lors du calcul des texcoords par fragment, par rapport à simplement les interpoler entre les sommets. Mais pour un petit effet spécial comme celui-ci, ce n'est pas excessif.

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);

    // Shift the UVs so (0, 0) is in the middle of the quad.
    o.uv = v.uv - 0.5f;

    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    // Convert our texture coordinates to polar form:
    float2 polar = float2(
           atan2(i.uv.y, i.uv.x)/(2.0f * 3.141592653589f), // angle
           length(i.uv)                                    // radius
        );

    // Apply texture scale
    polar *= _MainTex_ST.xy;

    // Scroll the texture over time.
    polar += _ScrollSpeeds.xy * _Time.x;

    // Sample using the polar coordinates, instead of the original uvs.
    // Here I multiply by MainTex
    fixed4 col = tex2D(_MainTex, polar);

    // apply fog
    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

Cela nous donne quelque chose comme ça (ici, j'ai augmenté les paramètres de mosaïque dans le matériau afin qu'il soit plus clair ce qui se passe - envelopper une seule répétition de la tuile autour du cercle semble déformé et bizarre)

Carrelage de girafes rayonnant vers l'extérieur depuis le centre d'un quad

Enfin, pour teinter la texture par un dégradé de défilement, il suffit d'ajouter le dégradé comme deuxième texture et de les multiplier ensemble.

Nous ajoutons d'abord le nouveau paramètre de texture en haut:

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _TintTex("Tint Texture", 2D) = "white" {}
    _ScrollSpeeds ("Scroll Speeds", vector) = (-5.0, -20.0, 0, 0)
}

Et déclarez-le dans notre bloc CGPROGRAM pour que le shader CG puisse le voir:

sampler2D _MainTex;
float4 _MainTex_ST;

// Declare our second texture sampler and its Scale/Translate values
sampler2D _TintTex;
float4 _TintTex_ST;

float4 _ScrollSpeeds;

Mettez ensuite à jour notre fragment shader pour utiliser les deux textures:

fixed4 frag(v2f i) : SV_Target
{
    float2 polar = float2(
           atan2(i.uv.y, i.uv.x) / (2.0f * 3.141592653589f), // angle
           length(i.uv)                                    // radius
        );

    // Copy the polar coordinates before we scale & shift them,
    // so we can scale & shift the tint texture independently.
    float2 tintUVs = polar * _TintTex_ST.xy;
    tintUVs += _ScrollSpeeds.zw * _Time.x;

    polar *= _MainTex_ST.xy;
    polar += _ScrollSpeeds.xy * _Time.x;

    fixed4 col = tex2D(_MainTex, polar);
    // Tint the colour by our second texture.
    col *= tex2D(_TintTex, tintUVs);

    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

Et maintenant, notre girafe devient vraiment trippante:

loin, mec ....

Avec une sélection légèrement plus artistique de textures et de taux de défilement, cela peut créer un effet assez similaire à celui montré dans la question.


Vous remarquerez peut-être deux petits artefacts avec la version que j'ai montrée ci-dessus:

  • Les visages près du centre du cercle deviennent étirés / maigres / pointus, puis lorsqu'ils se déplacent vers l'extérieur, ils sont écrasés / larges.

    Cette distorsion se produit parce que nous avons un nombre fixe de faces autour du périmètre, mais la circonférence qu'elles couvrent s'élargit à mesure que le rayon augmente, tandis que leur hauteur reste la même.

    Nous pouvons résoudre ce problème en remappant la composante verticale de l'échantillon de texture pour suivre une courbe logarithmique, de sorte que les répétitions de la texture sont plus éloignées lorsque le rayon augmente et plus proches les unes des autres vers le centre. (En fait, cela nous donne une régression infinie de girafes de plus en plus petites ...)

  • Il y a une rangée d'un ou deux pixels flous le long du milieu à gauche du quad.

    Cela se produit car le GPU examine deux coordonnées d'échantillon de texture adjacentes pour déterminer le filtrage à utiliser. Lorsque les échantillons sont proches les uns des autres, il indique que la texture est affichée grande / proche et montre le niveau de mip le plus détaillé. Lorsque les échantillons sont éloignés, il suppose que nous devons montrer la texture à un zoom minuscule ou loin, et il échantillonne à partir d'un mipmap plus petit / plus flou pour nous assurer que nous n'obtenons pas d'artefacts de crénelage scintillants.

    Le problème est ici, nous sommes au point de bouclage en coordonnées polaires, de -180 à 180 degrés. Nous échantillonnons donc à partir de points très similaires dans notre espace de texture répétitif, même si leurs coordonnées numériques leur donnent l'impression d'être éloignées. Nous pouvons donc fournir nos propres vecteurs de gradient d'échantillonnage pour corriger cela.

Voici une version avec ces corrections:

fixed4 frag(v2f i) : SV_Target
{
    float2 polar = float2(
           atan2(i.uv.y, i.uv.x) / (2.0f * 3.141592653589f), // angle
           log(dot(i.uv, i.uv)) * 0.5f                       // log-radius
        );

    // Check how much our texture sampling point changes between
    // neighbouring pixels to the sides (ddx) and above/below (ddy)
    float4 gradient = float4(ddx(polar), ddy(polar));

    // If our angle wraps around between adjacent samples,
    // discard one full rotation from its value and keep the fraction.
    gradient.xz = frac(gradient.xz + 1.5f) - 0.5f;

    // Copy the polar coordinates before we scale & shift them,
    // so we can scale & shift the tint texture independently.
    float2 tintUVs = polar * _TintTex_ST.xy;
    tintUVs += _ScrollSpeeds.zw * _Time.x;

    polar *= _MainTex_ST.xy;
    polar += _ScrollSpeeds.xy * _Time.x;

    // Sample with our custom gradients.
    fixed4 col = tex2Dgrad(_MainTex, polar, 
                           _MainTex_ST.xy * gradient.xy,
                           _MainTex_ST.xy * gradient.zw
                         );

    // Since our tint texture has its own scale,
    // its gradients also need to be scaled to match.
    col *= tex2Dgrad(_TintTex, tintUVs,
                          _TintTex_ST.xy * gradient.xy,
                          _TintTex_ST.xy * gradient.zw
                         );

    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}
DMGregory
la source
2
Quelques petites améliorations que l'on peut apporter: 1) l'utilisation d'une courbe logarithmique pour la direction v aide la texture à conserver sa forme lorsqu'elle défile vers l'extérieur (au lieu de se réduire à un point au milieu, vous obtenez simplement une chaîne infinie de copies plus petites jusqu'à ce que le mip l'étale) 2) calculer vos propres dégradés de texture et utiliser tex2Dgradcorrige l'artefact de filtrage où l'angle s'enroule de -pi à + pi (c'est la fine ligne de pixels flous sur le côté gauche). Voici le résultat des
retouches
Incroyable, merci aussi pour la panne. Je pense que les Shaders sont quelque chose que je ne pourrai peut-être jamais écrire (en particulier des trucs comme ce que vous avez fait pour le "polaire", les trucs en mathématiques ne signifient rien pour moi, car je n'ai que des compétences mathématiques très basiques).
JacketPotatoeFan
1
Des trucs comme ça, les gens regardaient généralement. ;) Je le fais juste assez pour qu'il roule du clavier. Essayez de jouer avec différentes fonctions mathématiques et regardez les résultats - avoir un outil comme celui-ci pour m'aider à visualiser ce que les mathématiques faisaient géométriquement est la façon dont j'ai commencé ma pratique. (Pas avec les shaders en particulier, mais avec un ancien visualiseur WinAmp appelé WhiteCap ...) Même lorsque les maths des shaders vont horriblement mal, il est souvent étrangement cool de regarder. : D
DMGregory