Не так давно мы опубликовали материал о тестировании 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
Устранение угловатости
С волосами персонажа была очевидная проблема: шейдер вместе с текстурой создавал жесткие концы, а анизотропный блик подчеркивал выступающие друг за другом полигоны волос.
Из этого стали понятны требования к текстуре и модели:
— полигоны прядей не должны пересекаться там, где виден блик, — так он будет красиво ходить по волосам, не подчеркивая угловатость;
— верхние слои прядей волос должны быть более детализированными, чем нижние, чтобы создавать общую форму и эффект подвижности.
Затем мы принялись за текстуру. Чтобы кончики волос смотрелись лучше, требовалась коррекция альфа-канала.
Здесь выбор стоял между двумя вариантами:
-
первый — использовать жесткую обрезку cutout-шейдером (т. е. фильтровать альфа-канал по пороговому значению, получая полностью прозрачный или непрозрачный пиксель, без промежуточных областей);
-
второй — использовать мягкий transparent-шейдер, который работает плавнее, но более требователен к железу.
Мягкий шейдер делал всего лишь одну дополнительную операцию — читку буфера кадра (для смешивания текущего пикселя волос с уже имеющейся на экране картинкой), за счет чего и достигался плавный уход в прозрачность. К тому же занимаемая экранная площадь прически нашего персонажа и количество наложений были невелики — так что особой разницы в ресурсозатратности между шейдерами в нашем случае не оказалось. Однако с мягким шейдером волосы смотрелись лучше.
Но тут была одна загвоздка — камера отрисовывала изображение в текстуру. Поэтому для того, чтобы оно корректно обрезалось по альфе и волосы отображались на текстуре, требовался дополнительный проход, который бы эту альфу создавал.
В итоге был написан шейдер, содержащий в себе три прохода. Первый записывает правильный силуэт прически в альфу текстуры, вместе с объемом волос и их правильной обрезкой на кончиках. Второй рисует cutout-геометрию, а третий (transparent) — добавляет мягкие кончики.
Реалистичный блик
Чтобы волосы выглядели живыми, они должны блестеть. Для достижения такого эффекта мы рассматривали два варианта: анизотропный динамический блик, реагирующий на освещение, и статичный фейковый, который мог бы облегчить обработку шейдера.
Для создания статичного блика мы использовали технологию текстурирования Matcap — имитацию материала и окружения с помощью всего одного изображения.
Эта технология позволяет натягивать предзапеченное в текстуре освещение на 3D-модель для имитации освещения (теней, бликов, оттенков и т. д.). То есть мы могли наложить поверх текстуры волос нарисованный блик так, как нам удобно. Форму блика можно настраивать, но он никак не будет реагировать на смену освещения.
Визуальная разница между анизотропным и фейковым бликами:
Разница в шейдерном коде при обработке анизотропного и фейкового бликов:
Anisotrophic |
Matcap |
fixed4 frag (v2f i) : SV_Target float HdotN = dot(halfDir, i.normal); float diffuseSpecular = lerp(1.0, specTex.g, _SpecularPower); |
v2f vert (appdata v) fixed4 frag (v2f i) : SV_Target |
Визуальная разница между анизотропным и фейковым бликами в рамках сцены, где появлялась девушка, была слабо различима, да и угол, под которым ее видел игрок, менялся не сильно. Но первый вариант генерировал больше математических операций, поэтому мы выбрали второй.
Чтобы блик смотрелся органично и не создавал плоского эффекта, решили применить маску. Сначала мы пробовали вариант с картой нормали, но позже заменили ее менее тяжелой альфа-маской, которая давала похожий эффект.
Применение альфа-маски в коде:
fixed specDetail = tex2D(_MatcapTex, i.uv).b;
specDetail = pow(specDetail, _MatcapPow);
matcapSpec = matcapSpec * specDetail;
Анимация и цветокоррекция
За основу мы взяли обычную костную анимацию для прядей. А для создания дополнительной динамики решили использовать шевеление в вертексном шейдере.
Для цветовой коррекции применили запеченное освещение в виде коэффициентов, которое называется SHA, или сферические гармоники. Такой метод рендеринга может создавать высокореалистичное затенение с относительно небольшими ресурсозатратами. Его основой служат предварительно рассчитанные коэффициенты, и он отлично подходит для рассеянного освещения от окружения (например, засветов от неба). Этот же принцип использовался не только для волос, но и для самой модели персонажа.
Исходники:
Детализация геометрии |
2500 |
Количество текстур |
3 |
Разрешение текстуры 1 |
512×512 |
Разрешение текстуры 2 |
256×256 |
Разрешение текстуры 3 |
128×128 |
Шейдер:
Количество проходов |
3 |
Количество текстурных читок |
1 проход: 3 читки, 2 проход: 3 читки, |
Количество математических операций в вертексном шейдере |
1 проход: 54 math, 2 проход: 51 math, |
Количество математических операций во фрагментном шейдере |
1 проход: 11 math, 2 проход: 17 math, |
В получившемся шейдере нет ничего революционного — техники, которые применялись для его создания, используются во многих других проектах. Но именно благодаря совокупности этих техник мы добились нужного результата.