Шейдер для волос туториального персонажа из Vikings: War of Clans

Не так давно мы опубликовали материал о тестировании Unity Cloth на мобильных устройствах. В другой статье на render.ru рассказали о том, как арт-отделу Plarium Krasnodar удалось сделать модель персонажа с высокой детализацией и при этом не перегрузить проект. Теперь давайте поговорим о работе над шейдером и анимацией для волос этой модели.

ТЗ

Перед командой Graphic Development стояли следующие задачи:

  • определить допустимый уровень детализации геометрии;

  • устранить угловатость модели и текстуры;

  • создать реалистичный блик;

  • придать прическе подвижности.

Сначала мы решили определить требования к модели.

Детализация геометрии

Особых требований к детализации геометрии выявлено не было (как обычно — чем меньше вершин, тем лучше). Для замера производительности мы брали тестовый незаскиненный исходник и с помощью 3D-пакета с использованием модификаторов Smooth увеличивали детализацию.

Детализация

PlayerLoop (ms) mediana

1396 вертексов

33.15

4423 вертексов

33.2

15537 вертексов

33.1

58003 вертексов

33.2

Показания взяты с устройства Android Samsung Galaxy Tab S2

Устранение угловатости

С волосами персонажа была очевидная проблема: шейдер вместе с текстурой создавал жесткие концы, а анизотропный блик подчеркивал выступающие друг за другом полигоны волос. 

Из этого стали понятны требования к текстуре и модели:

  • полигоны прядей не должны пересекаться там, где виден блик, — так он будет красиво ходить по волосам, не подчеркивая угловатость;

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

67876bc51fe93445ac186ca2f53b9d81.jpg

Затем мы принялись за текстуру. Чтобы кончики волос смотрелись лучше, требовалась коррекция альфа-канала. 

Здесь выбор стоял между двумя вариантами:  

  • первый — использовать жесткую обрезку cutout-шейдером (т. е. фильтровать альфа-канал по пороговому значению, получая полностью прозрачный или непрозрачный пиксель, без промежуточных областей);

  • второй — использовать мягкий transparent-шейдер, который работает плавнее, но более требователен к железу. 

Cutout shader/Transparent shaderCutout shader/Transparent shader

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

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

В итоге был написан шейдер, содержащий в себе три прохода. Первый записывает правильный силуэт прически в альфу текстуры, вместе с объемом волос и их правильной обрезкой на кончиках. Второй рисует cutout-геометрию, а третий (transparent) — добавляет мягкие кончики. 

Реалистичный блик

Чтобы волосы выглядели живыми, они должны блестеть. Для достижения такого эффекта мы рассматривали два варианта: анизотропный динамический блик, реагирующий на освещение, и статичный фейковый, который мог бы облегчить обработку шейдера.

Для создания статичного блика мы использовали технологию текстурирования Matcap — имитацию материала и окружения с помощью всего одного изображения.

Источник изображения Matcap ShaderИсточник изображения Matcap Shader

Эта технология позволяет натягивать предзапеченное в текстуре освещение на 3D-модель для имитации освещения (теней, бликов, оттенков и т. д.). То есть мы могли наложить поверх текстуры волос нарисованный блик так, как нам удобно. Форму блика можно настраивать, но он никак не будет реагировать на смену освещения. 

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

Анизотропный бликАнизотропный бликMatcap-бликMatcap-блик

Разница в шейдерном коде при обработке анизотропного и фейкового бликов:

Anisotrophic

Matcap

fixed4 frag (v2f i) : SV_Target
{
float3 specular;
float3 binormal = normalize (cross (i.normal, i.tangent.xyz));
float3 tangent = normalize (mul (unity_ObjectToWorld, i.tangent));

float HdotN = dot (halfDir, i.normal);
float VdotN = dot (i.viewDir, i.normal);
float anX = dot (halfDir, binormal) / _AnisotropicX;
float anY = dot (halfDir, tangent) / 4;

float diffuseSpecular = lerp (1.0, specTex.g, _SpecularPower);
specular = diffuseSpecular *_LightColor * _Specular * _SpecularColor * exp (-2.0 * (anX * anX + anY + anY) / (1.0 + HdotN));
}

v2f vert (appdata v)
{

float3 worldNorm = UnityObjectToWorldNormal (v.normal);
worldNorm = mul ((float3×3)UNITY_MATRIX_V, worldNorm);
o.matcapUV = worldNorm.xy * 0.5 + 0.5;

}

fixed4 frag (v2f i) : SV_Target
{

fixed specularMask = tex2D (_MatcapTex, i.uv).g;

}

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

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

Normal map mask / Alpha specular maskNormal map mask / Alpha specular mask

Применение альфа-маски в коде:

fixed specDetail = tex2D(_MatcapTex, i.uv).b;specDetail = pow(specDetail, _MatcapPow);matcapSpec = matcapSpec * specDetail;
93d877e49a183423eba214bd483417d9.jpg

Анимация и цветокоррекция

За основу мы взяли обычную костную анимацию для прядей. А для создания дополнительной динамики решили использовать шевеление в вертексном шейдере.

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

679d63ffb38fbf75219c904c6bbe6e1e.gif

Исходники:

Детализация геометрии

2500

Количество текстур

3

Разрешение текстуры 1

512×512

Разрешение текстуры 2

256×256

Разрешение текстуры 3

128×128

Шейдер:

Количество проходов

3

Количество текстурных читок

1 проход: 3 читки, 2 проход: 3 читки,
3 проход: 1 читка

Количество математических операций в вертексном шейдере

1 проход: 54 math, 2 проход: 51 math,
3 проход: 13 math

Количество математических операций во фрагментном шейдере

1 проход: 11 math, 2 проход: 17 math,
3 проход: 0 math

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

© Habrahabr.ru