Texturas animadas para modelos; Como escrever um shader?

9

Estava assistindo alguns Rocket League e percebeu que havia adesivos e rodas animados.

Imagem.

Eu gostaria de implementar algo semelhante aos efeitos na imagem acima.

Como eu escreveria um Unity Shader para fazer o efeito de roda?

Não sei muito sobre Shaders, mas posso editar o Unity Shader padrão para fazer o efeito de animação?

CasacoBatataFan
fonte
3
Os shaders são definitivamente a solução ideal aqui, mesmo para algo tão simples quanto uma textura de rolagem. O Unity possui uma _Timevariável interna que você pode adicionar às coordenadas de textura no shader (vértice) para obter um efeito de rolagem muito barato. O efeito hexagonal também seria bastante direto. Se você editar sua pergunta para destacar apenas um efeito e perguntar "como eu implementaria isso em um sombreador do Unity", provavelmente poderemos ajudá-lo. Certifique-se de especificar se o sombreador precisa responder à iluminação / sombreamento, pois isso afeta a forma como seria gravado.
DMGregory
Obrigado pela informação. Mudei minha pergunta, isso é um pouco melhor? Eu realmente não sei sobre iluminação e sombreamento, então deixei isso de fora por enquanto até entender melhor o Shaders. Eu acho que gostaria deles, mas eu poderia copiar partes do shader padrão, certo?
precisa saber é o seguinte
3
Vou expandir isso em uma resposta um pouco mais tarde hoje à noite (embora sinta-se à vontade para me responder, se alguém quiser responder agora!) - mas você normalmente escreveria um sombreador Unity que usa a iluminação como um "sombreador de superfície" enquanto um que não precisa de iluminação é geralmente mais simples de escrever como um "Unlit Shader". Parece-me que essas rodas estão apagadas (a leve sombra na borda parece SSAO), então eu deixaria a iluminação fora da imagem para iniciantes. Você pode esclarecer: deseja exatamente o padrão geométrico mostrado aqui ou a capacidade de rolar texturas arbitrárias para fora e pintá-las dessa maneira?
DMGregory
Muito obrigado, qualquer informação que você possa dar para me iniciar seria ótimo. Eu realmente admiro as pessoas que sabem escrever shaders, procurei alguns artigos em Unity Shaders e, sim, muito confusos. Eu acho que simplesmente rolar através de texturas (ou texturas uvs?) Seria bom o suficiente e com a capacidade de tingir.
precisa saber é o seguinte

Respostas:

13

Criarei isso em algumas camadas para que você possa ver como isso acontece.

Comece criando um novo sombreador no Unity, escolhendo Create --> Shader --> Unlitno menu Ativos ou no menu de contexto do botão direito do mouse na janela Projeto.

No bloco superior, adicionaremos um _ScrollSpeedsparâmetro para controlar a rapidez com que a textura se move em cada direção:

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

Isso expõe uma nova propriedade de flutuação de 4 componentes no inspetor de materiais, com o nome amigável "Velocidades de rolagem" (semelhante à adição de uma variável publicou Serializeda um MonoBehaviourscript)

Em seguida, usaremos essa variável no shader Vertex para alterar as coordenadas da textura ( o.uv), adicionando apenas duas linhas ao shader padrão:

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

Coloque isso em um quadrilátero (com uma adorável textura de girafa grátis da Kenney ) e você terá:

Um quadrilátero com uma girafa repetindo a rolagem.

Para fazer a textura rolar para fora em um anel, podemos usar uma malha subdividida como uma teia de aranha, com a coordenada uv v aumentando do centro para fora. Mas isso dará alguns artefatos em forma de serra por conta própria. Em vez disso, mostrarei como podemos calcular nossos UVs por fragmento.

Isso é um pouco mais caro, tanto pelas operações trig & length (que são mais caras que a matemática básica) quanto pelo fato de não ser tão eficiente prever e armazenar em cache os dados de textura no hardware ao calcular os cabos de texto por fragmento, em comparação com apenas interpolá-los entre vértices. Mas, para um pequeno efeito especial como esse, não é excessivo.

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

Isso nos dá algo assim (aqui eu aumentei os parâmetros de ladrilhos no material, para ficar mais claro o que está acontecendo - envolver apenas uma única repetição do ladrilho ao redor do círculo parece distorcido e estranho)

Girafas lado a lado irradiando para fora do centro de um quadrilátero

Por fim, para tingir a textura com um gradiente de rolagem, podemos apenas adicionar o gradiente como uma segunda textura e multiplicá-los juntos.

Primeiro, adicionamos o novo parâmetro de textura na parte superior:

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

E declare-o em nosso bloco CGPROGRAM para que o shader CG possa vê-lo:

sampler2D _MainTex;
float4 _MainTex_ST;

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

float4 _ScrollSpeeds;

Atualize nosso shader de fragmento para usar as duas 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;
}

E agora a nossa girafa fica muito trippy:

longe, cara ....

Com uma seleção um pouco mais artística de texturas e taxas de rolagem, isso pode criar um efeito bastante semelhante ao mostrado na pergunta.


Você pode notar dois pequenos artefatos com a versão que mostrei acima:

  • Os rostos próximos ao centro do círculo ficam esticados / magros / pontudos e, em seguida, à medida que avançam para o exterior, são esmagados / largos.

    Essa distorção ocorre porque temos um número fixo de faces em torno do perímetro, mas a circunferência que elas estão ampliando fica maior à medida que o raio aumenta, enquanto a altura permanece a mesma.

    Podemos corrigir isso remapeando o componente vertical da amostra de textura para seguir uma curva logarítmica, para que as repetições da textura fiquem mais afastadas à medida que o raio aumenta e se aproximando mais em direção ao centro. (De fato, isso nos dá uma regressão infinita de girafas cada vez menores ...)

  • Há uma fileira de um ou dois pixels embaçados ao longo do meio esquerdo do quad.

    Isso acontece porque a GPU examina duas coordenadas de amostra de textura adjacentes para descobrir qual filtro usar. Quando as amostras estão próximas, ela mostra que a textura está sendo exibida grande / fechada e mostra o nível mip mais detalhado. Quando as amostras estão distantes, supõe-se que devemos mostrar a textura com um zoom minúsculo ou distante, e amostras de um mipmap menor / mais desfocado para garantir que não obtemos artefatos de aliasing brilhantes.

    O problema está aqui, estamos no ponto de contorno das coordenadas polares, de -180 a 180 graus. Então, na verdade, estamos amostrando pontos muito semelhantes em nosso espaço de textura repetitivo, mesmo que suas coordenadas numéricas façam com que pareçam distantes. Portanto, podemos fornecer nossos próprios vetores de gradiente de amostra para corrigir isso.

Aqui está uma versão com estas correções:

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
fonte
2
Algumas pequenas melhorias que se pode fazer: 1) o uso de uma curva logarítmica para a direção v ajuda a textura a manter sua forma enquanto ela rola para fora (em vez de se estreitar até um ponto no meio, você obtém uma cadeia infinita de cópias menores até o mip o mancha) 2) calculando seus próprios gradientes de textura e usando tex2Dgradcorrige o artefato de filtragem em que o ângulo se move de -pi a + pi (essa é a linha fina de pixels borrados no lado esquerdo). Aqui está o resultado
retocado
Incrível, obrigado pela quebra também. Sinto que Shaders é algo que talvez nunca seja capaz de escrever (especialmente coisas como o que você fez para o "polar", as coisas de matemática não significam nada para mim, pois eu só tenho habilidades matemáticas muito básicas).
precisa saber é o seguinte
11
Coisas assim costumavam apenas olhar para cima. ;) Eu apenas faço o suficiente para sair do teclado. Tente brincar com diferentes funções matemáticas e observe os resultados - ter uma ferramenta como esta para me ajudar a visualizar o que a matemática estava fazendo geometricamente é como eu comecei minha prática em primeiro lugar. (Não com shaders especificamente, mas com um visualizador antigo do WinAmp chamado WhiteCap ...) Mesmo quando a matemática do shader dá terrivelmente errado, muitas vezes é estranhamente interessante de se olhar. : D
DMGregory