Trame animate per modelli; Come scrivere uno shader?


9

Stavo guardando un po 'di Rocket League e ho notato che c'erano adesivi e ruote animati.

Immagine.

Vorrei implementare qualcosa di simile agli effetti nell'immagine sopra.

Come potrei scrivere un Unity Shader per fare l'effetto ruota?

Non so molto di Shader, ma posso modificare lo standard Unity Shader per fare l'effetto animazione?


3
Gli shader sono sicuramente la soluzione ideale qui, anche per qualcosa di semplice come una trama scorrevole. Unity ha una _Timevariabile integrata che puoi aggiungere alle tue coordinate di trama nello shader (vertice) per ottenere un effetto di scorrimento sporco a buon mercato. L'effetto esagono sarebbe anche abbastanza semplice. Se modifichi la tua domanda per evidenziare solo un effetto e chiedi "come dovrei implementarlo in uno shader Unity", probabilmente possiamo aiutarti. Assicurati di specificare se lo shader deve rispondere all'illuminazione / all'ombra, poiché ciò ha un impatto sul modo in cui verrebbe scritto.
DMGregory

Grazie per le informazioni. Ho cambiato la mia domanda, è un po 'meglio? Non so davvero dell'illuminazione e dell'ombra, quindi l'ho lasciato fuori per ora fino a quando non avrò una migliore comprensione di Shader. Immagino che li desidererei, ma potrei semplicemente copiare parti dallo shader standard giusto?
JacketPotatoeFan

3
Espanderò in una risposta un po 'più tardi stasera (anche se sentiti libero di battermi, se qualcuno vuole rispondere ora!) - ma in genere scrivi uno shader Unity che utilizza l'illuminazione come "Surface Shader" mentre uno che non ha bisogno di illuminazione è spesso più semplice da scrivere come "Unlit Shader". Mi sembra che quelle ruote siano spente (la leggera ombra sul bordo assomiglia a SSAO), quindi vorrei lasciare l'illuminazione fuori dall'immagine per cominciare. Puoi chiarire: vuoi esattamente il motivo geometrico mostrato qui, o la possibilità di scorrere trame arbitrarie verso l'esterno e colorarle in modo arcobaleno in quel modo?
DMGregory

Grazie mille, qualunque informazione tu possa darmi per iniziare sarebbe grandiosa. Ammiro davvero le persone che sanno scrivere shader, ho esaminato alcuni articoli per Unity Shaders e sì, molto confuso. Penso che semplicemente scorrere le trame (o le trame UV?) Sarebbe abbastanza buono, e con la capacità di tingere.
JacketPotatoeFan

Risposte:


13

Lo costruirò in pochi strati in modo da poter vedere come si riunisce.

Inizia creando un nuovo shader in Unity scegliendo Create --> Shader --> Unlitnel menu Risorse o nel menu contestuale del tasto destro del mouse nella finestra Progetto.

Nel blocco superiore aggiungeremo un _ScrollSpeedsparametro per controllare la velocità con cui la trama si sposta in ciascuna direzione:

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

Ciò espone una nuova proprietà float a 4 componenti nella finestra di ispezione dei materiali, con il nome descrittivo "Velocità di scorrimento" (simile all'aggiunta di una publico una Serializedvariabile a uno MonoBehaviourscript)

Successivamente useremo questa variabile nello shader Vertex per spostare le coordinate della trama ( o.uv), aggiungendo solo due righe allo shader predefinito:

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;
}

Sbattilo su un quad (con una bella trama giraffa libera di Kenney ) e otterrai:

Un quad con una giraffa ripetuta che scorre attraverso di essa.

Per far scorrere la trama verso l'esterno in un anello, potremmo semplicemente usare una mesh suddivisa come una ragnatela, con le coordinate uv v in aumento dal centro verso l'esterno. Ma questo darà alcuni artefatti a forma di lama da solo. Mostrerò invece come possiamo calcolare i nostri UV per frammento.

Questo è un po 'più costoso, sia per le operazioni di trigma e lunghezza (che sono più costose della matematica di base) sia perché non è così efficiente prevedere e memorizzare nella cache i dati delle trame nell'hardware quando si calcolano texcoords per frammento, rispetto al solo interpolarli tra i vertici. Ma per un piccolo effetto speciale come questo, non è eccessivo.

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;
}

Questo ci dà qualcosa del genere (qui ho aumentato i parametri di piastrellatura nel materiale, quindi è più chiaro cosa sta succedendo: avvolgere solo una singola ripetizione della piastrella attorno al cerchio sembra distorto e strano)

Giraffe di piastrellatura che si irradiano verso l'esterno dal centro di un quad

Infine, per tingere la trama con un gradiente di scorrimento, possiamo semplicemente aggiungere il gradiente come seconda trama e moltiplicarli insieme.

Per prima cosa aggiungiamo il nuovo parametro di trama in alto:

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

E dichiaralo nel nostro blocco CGPROGRAM in modo che lo shader CG possa vederlo:

sampler2D _MainTex;
float4 _MainTex_ST;

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

float4 _ScrollSpeeds;

Quindi aggiorna il nostro shader di frammenti per utilizzare entrambe le trame:

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;
}

E ora la nostra giraffa diventa davvero trippy:

lontano, amico ....

Con una selezione leggermente più artistica di trame e velocità di scorrimento, questo può creare un effetto abbastanza simile a quello mostrato nella domanda.


Potresti notare due piccoli artefatti con la versione che ho mostrato sopra:

  • Le facce vicino al centro del cerchio diventano allungate / magre / appuntite, quindi mentre si muovono verso l'esterno vengono schiacciate / larghe.

    Questa distorsione si verifica perché abbiamo un numero fisso di facce attorno al perimetro, ma la circonferenza che stanno estendendosi si allarga all'aumentare del raggio, mentre la loro altezza rimane invariata.

    Possiamo risolvere questo problema rimappando la componente verticale del campione di trama per seguire una curva logaritmica, quindi le ripetizioni della trama sono più separate man mano che il raggio aumenta e si avvicinano verso il centro. (In realtà, questo ci dà un regresso infinito di giraffe sempre più piccole ...)

  • C'è una fila di uno o due pixel sfocati lungo la metà sinistra del quadrato.

    Ciò accade perché la GPU esamina due coordinate di esempio di trama adiacenti per capire quale filtro utilizzare. Quando i campioni sono ravvicinati, indica che la trama viene visualizzata grande / chiusa e mostra il livello di mip più dettagliato. Quando i campioni sono distanti, si suppone che dobbiamo mostrare la trama con un piccolo zoom o molto lontano, e campiona da una mipmap più piccola / più sfocata per garantire che non ci siano artefatti scintillanti di aliasing.

    Il problema è qui, siamo al punto di avvolgimento in coordinate polari, da -180 a 180 gradi. Quindi in realtà stiamo campionando da punti molto simili nel nostro spazio texture ripetuto, anche se le loro coordinate numeriche li fanno sembrare distanti. Quindi possiamo fornire i nostri vettori di gradiente di campionamento per correggere questo.

Ecco una versione con queste correzioni:

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;
}

2
Un paio di piccoli miglioramenti che si possono apportare: 1) l'uso di una curva logaritmica per la direzione v aiuta la texture a mantenere la sua forma mentre scorre verso l'esterno (invece di restringersi fino a un punto nel mezzo, si ottiene solo una catena infinita di copie più piccole fino a il mip lo spalma) 2) calcolando i tuoi gradienti di trama e usando tex2Dgradcorregge l'artefatto di filtraggio in cui l'angolo si avvolge da -pi a + pi (che è la sottile linea di pixel sfocati sul lato sinistro). Ecco il risultato
ritoccato

Fantastico, grazie anche per la rottura. Sento che gli shader sono qualcosa che potrei non essere mai in grado di scrivere (specialmente cose come quelle che hai fatto per il "polare", le cose matematiche non significano nulla per me, dato che ho solo abilità matematiche di base).
JacketPotatoeFan

1
Roba come quella gente di solito guardava in alto. ;) Lo faccio abbastanza da rotolare giù dalla tastiera. Prova a giocare con diverse funzioni matematiche e guarda i risultati - avere uno strumento come questo per aiutarmi a visualizzare ciò che la matematica stava facendo geometricamente è come ho ottenuto la mia pratica in primo luogo. (Non con gli shader in particolare, ma un vecchio visualizzatore WinAmp chiamato WhiteCap ...) Anche quando la matematica degli shader va terribilmente male, è spesso stranamente interessante da guardare. : D
DMGregory
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.