Анімовані текстури для моделей; Як написати шейдер?


9

Переглядав якусь ракетну лігу і помітив, що там були анімовані наклейки та колеса.

Зображення.

Я хотів би реалізувати щось подібне до ефектів на зображенні вище.

Як би я пішов писати Unity Shader, щоб зробити ефект колеса?

Я мало знаю про шейдери, але чи можу я відредагувати стандартний Unity Shader, щоб зробити анімаційний ефект?


3
Шейдери - це безперечно рішення, навіть для чогось такого простого, як текстура прокрутки. У Unity є вбудована _Timeзмінна, яку ви можете додати до текстурних координат у (вершинній) шейдері, щоб дешево отримати бруд ефекту прокрутки. Ефект шестикутника теж був би досить простим. Якщо ви відредагуєте своє запитання, щоб виділити лише один ефект і запитати "як би я реалізував це в шейдері Unity", ми можемо вам допомогти. Обов’язково вкажіть, чи повинен шейдер реагувати на освітлення / затінення, оскільки це впливає на те, як це було б написано.
DMGregory

Дякую за інформацію. Змінили моє запитання, це трохи краще? Я насправді не знаю про освітлення та затінення, тому покинув це поки, поки не зрозумію Шейдери. Я думаю, я хотів би їх, але я міг би просто скопіювати деталі зі стандартного шейдера, правда?
JacketPotatoeFan

3
Я розкрию його у відповідь трохи пізніше сьогодні ввечері (хоча не соромтесь бити мене на це, якщо хтось хоче відповісти зараз!) - але ти зазвичай пишеш шейдер Unity, який використовує освітлення як "поверхневий шейдер" тоді як той, якому не потрібне освітлення, найчастіше простіше написати як "Unlit Shader". Мені здається, що ці колеса не освітлені (незначне затінення на краю схоже на SSAO), тож я б покинув освітлення поза картиною для початку. Чи можете ви уточнити: чи ви хочете саме вказаний тут геометричний малюнок, або можливість прокручувати довільні текстури назовні & райдужно-відтіняти їх так?
DMGregory

Велике спасибі, будь-яка інформація, яку ви можете дати, щоб розпочати мене, було б чудово. Я дуже захоплююся людьми, які можуть писати шейдери, я переглянув кілька статей для Unity Shaders, і так, дуже заплутано. Я думаю, що просто прокручування текстур (або текстури Uvs?) Було б досить добре і з можливістю підфарбовування.
JacketPotatoeFan

Відповіді:


13

Я буду це робити в декілька шарів, щоб ви могли бачити, як воно поєднується.

Почніть зі створення нового шейдера в Unity, вибравши Create --> Shader --> Unlitабо меню Активи або контекстне меню правою кнопкою миші у вікні проекту.

У верхньому блоці ми додамо _ScrollSpeedsпараметр для контролю швидкості руху текстури в кожному напрямку:

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

Це відкриває нове 4-компонентне властивість float в інспекторі матеріалів з дружньою назвою "Scroll Speeds" (подібно до додавання publicабо Serializedзмінної до MonoBehaviourсценарію)

Далі ми використаємо цю змінну в шейдері Vertex для переміщення координат текстури ( o.uv), додавши до шейдера за замовчуванням лише два рядки:

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

Пощеплюйте це на квадраті (з прекрасною текстурою жирафа Кенні ), і ви отримаєте:

Квадроцикл з повторюваною жирафою прокручується по ньому.

Щоб текстура прокручувалася назовні в кільце, ми могли просто використовувати сітку, поділену як павутина, при цьому координата uv v збільшується від центру назовні. Але це дасть окремі артефакти у формі пиляння самостійно. Натомість я покажу, як ми можемо обчислити наші УФ-речовини на фрагмент.

Це трохи дорожче, як через операції триггерів та тривалості (які дорожчі, ніж основна математика), так і тому, що не так ефективно прогнозувати та кешувати текстурні дані в апараті при обчисленні текстових рядків на фрагмент, порівняно з простою інтерполяцією їх між вершинами. Але для такого особливого ефекту, як це, він не надмірний.

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

Це дає нам щось подібне (тут я збільшив параметри плитки в матеріалі, щоб було зрозуміліше, що відбувається - загортання лише одного повторення плитки навколо кола виглядає спотвореним і дивним)

Черепичні жирафи, що випромінюються назовні від центру квадроцикла

Нарешті, щоб відтінити текстуру за допомогою градієнта прокрутки, ми можемо просто додати градієнт як другу текстуру і помножити їх між собою.

Спочатку додаємо новий параметр текстури вгорі:

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

І оголосити це в нашому блоці CGPROGRAM, щоб шейдер CG міг його бачити:

sampler2D _MainTex;
float4 _MainTex_ST;

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

float4 _ScrollSpeeds;

Потім оновіть наш фрагмент шейдер, щоб використовувати обидві текстури:

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

А тепер наша жирафа стає по-справжньому хитрою:

далеко, чоловіче….

При трохи більш художньому підборі текстур та швидкості прокрутки це може створити ефект, подібний до того, який показано в питанні.


Ви можете помітити два невеликі артефакти з версією, яку я показав вище:

  • Обличчя біля центру кола отримують розтягнутий / худий / точковий, потім, рухаючись до зовнішньої сторони, вони стискаються / ширяються.

    Це викривлення відбувається тому, що у нас є фіксована кількість граней по всьому периметру, але окружність, яку вони розтягують, стає ширшою, оскільки радіус збільшується, а їх висота залишається однаковою.

    Ми можемо це виправити, переставивши вертикальний компонент зразка текстури, щоб слідувати логарифмічній кривій, тому повтори текстури віддаляються в міру збільшення радіуса і ближче до центру. (Насправді це дає нам нескінченний регрес все менших і менших жирафів ...)

  • Уздовж середини зліва від квадрата є ряд з одного або двох розмитих пікселів.

    Це відбувається тому, що GPU розглядає дві суміжні координати зразка текстури, щоб визначити, яку фільтрацію використовувати. Коли зразки близькі один до одного, він показує, що текстура відображається великою / близькою та показує найточніший рівень мип. Коли зразки знаходяться далеко один від одного, він здогадується, що ми мусимо показувати текстуру в невеликому масштабі або далеко, і це зразки з меншої / розмитої карти карти, щоб гарантувати, що ми не отримаємо іскробезпечних артефактів.

    Проблема тут полягає в тому, що ми знаходимося в точці обгортання в полярних координатах, від -180 до 180 градусів. Таким чином, ми фактично відбираємо вибірку з дуже схожих точок у нашому повторюваному просторі текстур, навіть якщо їх числові координати роблять їх схожими. Таким чином, ми можемо надати власні вектори градієнтів вибірки для виправлення цього.

Ось версія з цими виправленнями:

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
Кілька невеликих удосконалень, які можна зробити: 1) використання логарифмічної кривої для напрямку v допомагає текстурі зберігати форму, коли вона прокручується назовні (замість того, щоб звузитися до точки в середині, ви просто отримаєте нескінченний ланцюжок менших копій, поки міп змалює це) 2) обчислення власних градієнтів текстури та за допомогою tex2Dgradфіксує артефакт фільтрації, де кут обертається від -pi до + pi (це тонка лінія розмитих пікселів з лівого боку). Ось
підсунутий

Дивовижно, дякую також за розбиття. Я відчуваю, що шейдери - це те, що я ніколи не зможу написати (особливо такі речі, як те, що ти зробив для «полярних», математичні речі для мене просто нічого не означають, оскільки я маю лише дуже базові математичні навички).
JacketPotatoeFan

1
Такі речі, як зазвичай, люди просто піднімають очі. ;) Я просто роблю це достатньо, щоб він скочувався з клавіатури. Спробуйте пограти з різними математичними функціями, і подивіться на результати - мати такий інструмент, який допоможе мені візуалізувати, що математика робила геометрично, - це, як я отримав свою практику в першу чергу. (Зокрема, не для шейдерів, а старого візуалізатора WinAmp під назвою WhiteCap ...) Навіть коли математика шейдерів стає жахливо неправильною, на неї часто дивно дивитися. : D
DMGregory
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.