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

9

Смотрел какую-то Ракетную лигу и заметил там анимированные наклейки и колеса.

Образ,

Я хотел бы реализовать нечто похожее на эффекты на изображении выше.

Как мне написать шейдер Unity для создания эффекта колеса?

Я не очень разбираюсь в шейдерах, но могу ли я отредактировать стандартный шейдер Unity для создания эффекта анимации?

JacketPotatoeFan
источник
3
Шейдеры - определенно подходящее решение, даже для чего-то такого простого, как прокрутка текстуры. Unity имеет встроенную _Timeпеременную, которую вы можете добавить к своим координатам текстуры в (вершинном) шейдере, чтобы получить эффект прокрутки очень дешево. Эффект шестиугольника тоже будет довольно простым. Если вы отредактируете свой вопрос, чтобы выделить только один эффект и спросите «как бы я реализовал это в шейдере Unity», мы, вероятно, поможем вам. Обязательно укажите, должен ли шейдер реагировать на освещение / затенение, поскольку это влияет на то, как оно будет написано.
DMGregory
Спасибо за информацию. Изменились мои вопросы, это немного лучше? Я не очень разбираюсь в освещении и затенении, поэтому пока оставляю это в покое, пока не получу лучшее понимание шейдеров. Я думаю, что я хотел бы их, но я мог бы просто скопировать части из стандартного шейдера, верно?
JacketPotatoeFan
3
Я расскажу об этом чуть позже сегодня вечером (хотя не стесняйтесь побить меня, если кто-то захочет ответить сейчас!), Но вы обычно пишете шейдер Unity, который использует освещение как «Поверхностный шейдер» в то время как тот, который не нуждается в освещении, часто проще написать как «Unlit Shader». Мне кажется, что эти колеса не горят (небольшое затенение по краям похоже на SSAO), так что я бы оставил освещение за кадром для начала. Можете ли вы уточнить: хотите ли вы именно геометрический рисунок, показанный здесь, или возможность прокручивать произвольные текстуры наружу и радужно оттенять их таким образом?
DMGregory
Большое спасибо, любая информация, которую вы можете дать, чтобы начать меня, была бы отличной. Я действительно восхищаюсь людьми, которые могут писать шейдеры, я просмотрел несколько статей для шейдеров Unity, и да, очень запутанно. Я думаю, что простая прокрутка текстур (или текстур uvs?) Была бы достаточно хорошей, и с возможностью подкрашивания.
JacketPotatoeFan

Ответы:

13

Я построю это в несколько слоев, чтобы вы могли видеть, как это происходит вместе.

Начните с создания нового шейдера в Unity, выбрав Create --> Shader --> Unlitлибо в меню «Ресурсы», либо в контекстном меню правой кнопкой мыши в окне «Проект».

В верхнем блоке мы добавим _ScrollSpeedsпараметр для управления скоростью перемещения текстуры в каждом направлении:

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

Это открывает новое 4-компонентное свойство с плавающей точкой в ​​инспекторе материалов с понятным названием «Скорости прокрутки» (аналогично добавлению переменной 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, увеличивающейся от центра наружу. Но это даст некоторые артефакты в форме пилы. Вместо этого я покажу, как мы можем рассчитать наши UV для каждого фрагмента.

Это немного дороже, как из-за операций trig и length (которые стоят дороже, чем базовая математика), так и потому, что не так эффективно прогнозировать и кэшировать данные текстуры в аппаратном обеспечении при расчете текстовых координат по фрагментам, по сравнению с простой их интерполяцией. между вершинами. Но для такого небольшого особого эффекта это не чрезмерно.

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

И теперь наш жираф становится действительно триповым:

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

С немного более художественным выбором текстур и скоростей прокрутки это может создать эффект, очень похожий на тот, который показан в вопросе.


Вы можете заметить два небольших артефакта с версией, которую я показал выше:

  • Лица около центра круга становятся растянутыми / тощими / заостренными, затем, когда они движутся наружу, они становятся раздавленными / широкими.

    Это искажение происходит из-за того, что у нас есть фиксированное количество граней по периметру, но окружность, которую они охватывают, увеличивается с увеличением радиуса, а их высота остается неизменной.

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

  • Есть ряд из одного или двух размытых пикселей по среднему левому краю четырехугольника.

    Это происходит потому, что графический процессор просматривает две соседние координаты образца текстуры, чтобы выяснить, какую фильтрацию использовать. Когда сэмплы расположены близко друг к другу, он показывает, что текстура отображается крупно / близко, и показывает наиболее детальный уровень мипов. Когда сэмплы находятся далеко друг от друга, он догадывается, что мы должны показывать текстуру с небольшим увеличением или далеко, и он сэмплирует из более мелкого / размытого мип-карты, чтобы убедиться, что у нас не появятся искрящиеся артефакты наложения.

    Проблема в том, что мы находимся в точке разворота в полярных координатах, от -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 (это тонкая линия размытых пикселей на левой стороне). Вот подкрашенный результат с большим количеством синего
DMGregory
Потрясающе, спасибо за отказ. Я чувствую, что шейдеры - это то, что я никогда не смогу написать (особенно такие вещи, как то, что вы сделали для «полярников», математические вещи просто ничего не значат для меня, так как у меня есть только базовые математические навыки).
JacketPotatoeFan
1
Вещи, как это люди обычно смотрят вверх. ;) Просто мне этого достаточно, чтобы он скатывался с клавиатуры. Попробуйте поиграть с различными математическими функциями и посмотрите на результаты. Имея такой инструмент, который поможет мне визуализировать, что математически делает математика, - это то, как я получил свою практику в первую очередь. (Не специально с шейдерами, а со старым визуализатором WinAmp под названием WhiteCap ...) Даже когда математика шейдеров идет ужасно неправильно, на нее часто странно смотрится. : D
DMGregory