Texturas animadas para modelos; ¿Cómo escribir un sombreador?

9

Estaba viendo algunos Rocket League y noté que había calcomanías y ruedas animadas.

Imagen.

Me gustaría implementar algo similar a los efectos en la imagen de arriba.

¿Cómo haría para escribir un Unity Shader para hacer el efecto de rueda?

No sé mucho sobre Shaders, pero ¿puedo editar el Unity Shader estándar para hacer el efecto de animación?

ChaquetaPatataFan
fuente
3
Los sombreadores son definitivamente la solución ideal aquí, incluso para algo tan simple como una textura de desplazamiento. Unity tiene una _Timevariable incorporada que puede agregar a sus coordenadas de textura en el sombreador (vértice) para obtener un efecto de desplazamiento muy barato. El efecto del hexágono también sería bastante sencillo. Si edita su pregunta para resaltar solo un efecto y pregunta "cómo implementaría esto en un sombreador de Unity", probablemente podamos ayudarlo. Asegúrese de especificar si el sombreador necesita responder a la iluminación / sombreado, ya que eso tiene un impacto en cómo se escribiría.
DMGregory
Gracias por la info. Han cambiado mi pregunta, ¿está un poco mejor? Realmente no sé sobre la iluminación y el sombreado, así que lo dejé fuera por ahora hasta que comprenda mejor a Shaders. Supongo que los querría, pero podría copiar partes del sombreador estándar, ¿verdad?
JacketPotatoeFan
3
Lo ampliaré en una respuesta un poco más tarde esta noche (¡aunque siéntase libre de ganarme, si alguien quiere responder ahora!), Pero normalmente escribiría un sombreador de Unity que utiliza la iluminación como un "Sombreador de superficie" mientras que uno que no necesita iluminación es a menudo más fácil de escribir como un "Sombreador apagado". Me parece que esas ruedas no están iluminadas (el ligero sombreado en el borde parece SSAO), por lo que dejaría la iluminación fuera de la imagen para empezar. ¿Puede aclarar: desea exactamente el patrón geométrico que se muestra aquí, o la capacidad de desplazar texturas arbitrarias hacia afuera y teñirlas así?
DMGregory
Muchas gracias, cualquier información que pueda dar para comenzar sería genial. Realmente admiro a las personas que pueden escribir sombreadores, he revisado algunos artículos para Unity Shaders, y sí, muy confuso. Creo que simplemente desplazarse a través de texturas (¿o texturas uvs?) Sería lo suficientemente bueno y con la capacidad de teñir.
JacketPotatoeFan

Respuestas:

13

Construiré esto en unas pocas capas para que pueda ver cómo se combina.

Comience creando un nuevo sombreador en Unity eligiendo Create --> Shader --> Unliten el menú Activos o en el menú contextual del botón derecho en la ventana Proyecto.

En el bloque superior agregaremos un _ScrollSpeedsparámetro para controlar qué tan rápido se mueve la textura en cada dirección:

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

Esto expone una nueva propiedad flotante de 4 componentes en el inspector de materiales, con el nombre descriptivo "Velocidades de desplazamiento" (similar a agregar una publico Serializedvariable a un MonoBehaviourscript)

A continuación, utilizaremos esta variable en el sombreador Vertex para cambiar las coordenadas de textura ( o.uv), agregando solo dos líneas al sombreador predeterminado:

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

Déle una palmada en un quad (con una hermosa textura de jirafa libre de Kenney ) y obtendrá:

Un quad con una jirafa que se repite desplazándose sobre él.

Para que la textura se desplace hacia afuera en un anillo, podríamos usar una malla subdividida como una telaraña, con la coordenada uv v aumentando desde el centro hacia afuera. Pero eso dará algunos artefactos en forma de hoja de sierra por sí solo. En cambio, mostraré cómo podemos calcular nuestros UV por fragmento.

Esto es un poco más costoso, tanto por las operaciones trigonométricas y de longitud (que son más costosas que las matemáticas básicas) como porque no es tan eficiente predecir y almacenar en caché los datos de textura en el hardware al calcular texcoords por fragmento, en comparación con solo interpolarlos entre vértices Pero para un pequeño efecto especial como este, no es excesivo.

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

Eso nos da algo como esto (aquí aumenté los parámetros de mosaico en el material para que sea más claro lo que está sucediendo: envolver solo una sola repetición del mosaico alrededor del círculo parece distorsionado y extraño)

Azulejos de jirafas que irradian hacia afuera desde el centro de un quad

Por último, para teñir la textura con un gradiente de desplazamiento, podemos agregar el gradiente como una segunda textura y multiplicarlos.

Primero agregamos el nuevo parámetro de textura en la parte superior:

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

Y declararlo en nuestro bloque CGPROGRAM para que el sombreador CG pueda verlo:

sampler2D _MainTex;
float4 _MainTex_ST;

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

float4 _ScrollSpeeds;

Luego actualiza nuestro sombreador de fragmentos para usar ambas texturas:

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

Y ahora nuestra jirafa se pone realmente loca:

lejos, hombre ...

Con una selección ligeramente más artística de texturas y velocidades de desplazamiento, esto puede crear un efecto bastante similar al que se muestra en la pregunta.


Puede notar dos pequeños artefactos con la versión que he mostrado arriba:

  • Las caras cerca del centro del círculo se estiran / flacas / puntiagudas, luego, cuando se mueven hacia el exterior, se aplastan / ensanchan.

    Esta distorsión ocurre porque tenemos un número fijo de caras alrededor del perímetro, pero la circunferencia que abarcan se ensancha a medida que aumenta el radio, mientras que su altura se mantiene igual.

    Podemos arreglar esto reasignando el componente vertical de la muestra de textura para seguir una curva logarítmica, de modo que las repeticiones de la textura estén más separadas a medida que aumenta el radio, y más juntas hacia el centro. (De hecho, esto nos da una regresión infinita de jirafas cada vez más pequeñas ...)

  • Hay una fila de uno o dos píxeles borrosos a lo largo del centro-izquierda del quad.

    Esto sucede porque la GPU mira dos coordenadas de muestra de textura adyacentes para descubrir qué filtro usar. Cuando las muestras están juntas, calcula que la textura se muestra grande / cerrada y muestra el nivel de detalle más detallado. Cuando las muestras están muy separadas, adivina que debemos mostrar la textura con un pequeño zoom o muy lejos, y toma muestras de un mapa MIP más pequeño / borroso para garantizar que no se produzcan artefactos de alias brillantes.

    El problema está aquí, estamos en el punto de ajuste en coordenadas polares, de -180 a 180 grados. Por lo tanto, en realidad estamos tomando muestras de puntos muy similares en nuestro espacio de textura repetitivo, incluso si sus coordenadas numéricas hacen que parezcan muy distantes. Por lo tanto, podemos proporcionar nuestros propios vectores de gradiente de muestreo para corregir esto.

Aquí hay una versión con estas correcciones:

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
fuente
2
Se pueden hacer un par de pequeñas mejoras: 1) el uso de una curva logarítmica para la dirección v ayuda a que la textura conserve su forma a medida que se desplaza hacia afuera (en lugar de reducirse a un punto en el medio, solo obtienes una cadena infinita de copias más pequeñas hasta el mip lo mancha) 2) calculando sus propios gradientes de textura y utilizando tex2Dgradarregla el artefacto de filtrado donde el ángulo se envuelve de -pi a + pi (esa es la delgada línea de píxeles borrosos en el lado izquierdo). Aquí está el resultado retocado con más azul
DMGregory
Increíble, gracias por el desglose también. Siento que los Shaders son algo que nunca podré escribir (especialmente cosas como lo que has hecho para el "polar", las cosas de matemáticas simplemente no significan nada para mí, ya que solo tengo habilidades matemáticas básicas).
JacketPotatoeFan
1
Cosas como esa la gente generalmente solo miraba hacia arriba. ;) Simplemente lo hago lo suficiente como para que salga del teclado. Sin embargo, intente jugar con diferentes funciones matemáticas y observe los resultados: tener una herramienta como esta para ayudarme a visualizar lo que las matemáticas estaban haciendo geométricamente es cómo obtuve mi práctica en primer lugar. (No con los sombreadores específicamente, sino con un viejo visualizador de WinAmp llamado WhiteCap ...) Incluso cuando las matemáticas del sombreador salen terriblemente mal, a menudo es extrañamente genial de ver. : D
DMGregory