Всем привет! Меня зовут Григорий Дядиченко, я СТО Foxsys, и я всё ещё люблю графику. В прошлый раз я рассказывал, что неплохим упражнением является сборка различных базовых материалов для тренировки в создании интересных эффектов. Давайте сегодня проведём такое упражнение вместе и разберём, каким образом можно получить в Unity такой материал как акрил, который будет работать в AR и на мобильных платформах. Кому интересно - добро пожаловать под кат!
Результат
Для начала небольшое видео с результатом, чтобы было понятно, что мы будем делать. Так же в данном репозитории лежит всё решение с исходниками. А теперь пожалуй начнём.
Какой акрил будем делать?
Когда-то давно майкрософт выпустил ролик с замечательной дизайн концепцией Microsoft fluent design. Там показаны достаточно интересные подходы к дизайну, которые круто смотрятся в дополненной и виртуальной реальности. Базовая идея основывается на объёме, свете, моушене и реалистичных материалах. По сути то, что Microsoft называет в своих интерфейсах акрилом мы и будем реализовывать. Подобное “мутное стекло” так же используется в айос и выглядит достаточно интересно. Один из примеров интересного применения в приложениях - это приложение New Yourk Times в разделе с дополненной реальности.
В данном случае мы акрил будем рассматривать не с точки зрения физических процессов, какой-то сложной математики, а возьмём подход прямо из документации MSDN с небольшой доработкой для 3д, который допустим очень интересно использовать для проектов в дополненной реальности на Microsoft Hololens, но так же можно использовать в любых других проектах.
Данный акрил состоит по сути из:
Blur/Размытие
Color Burn/Затемнение основы
Tint/Задание оттенка
Noise/Шум
Для добавления объёма мы так же добавим в этот процесс Эффект Френеля. Так как он отлично имитирует нужный нам визуал, для того, чтобы у нашей модели были видны грани. Иначе данный акрил применим только для 2д интерфейсов, так как по самой технике не имеет объёма.
Blur или размытие
На первом же шаге, если подойти к нему неправильно и не знать нюансов платформ у нас возникнут проблемы. Во-первых, на мобильных платформах очень дорого использовать GrabPass, а во-вторых, если размывать мы будем достаточно большой кадр по алгоритму того же Гаусса у нас просядет производительность (просто будет убит филлрейт). Поэтому мы поступим хитрее. В целом блюр можно написать самостоятельно, но я пользуюсь модифицированным https://github.com/PavelDoGreat/Super-Blur Это очень крутой репозиторий с размытием обладающий двумя нужными нам функциями. Размытием полного кадра (оно дешевле чем грабпасс) и возможностью уменьшить разрешение полного кадра до нужного нам. Так как он всё равно буде сильно размыт, то нет необходимости в высоком качестве кадра, и можно взять его даунсемпл. В целом эти параметры можно в будущем крутить под конкретный эффект, платформу и доступный лимит ресурсов, чтобы получить разные материалы.
Сразу стоит сказать, что сам по себе акрил конечно в этом случае получится нечестный. Так как он либо будет учитывать сам себя, либо не будет учитывать другие акриловые объекты находящиеся за ним. Так как по сути в дальнейшем мы наше размытие пробрасываем в 3д модель и используем, как текстуру на модели. Для этого мы рассчитываем uv координаты исходя из позиции вершины на экране.
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = ComputeScreenPos(o.vertex);
}
float4 frag(v2f i) : COLOR
{
float4 col = tex2D(_MainTex, i.uv.xy / i.uv.w);
return col;
}
Photoshop Blend Modes
По данной теме почти та же самая история. Их можно написать самостоятельно, но в целом существует уже готовый .cginc со всеми нужными блендмодами. https://github.com/penandlim/JL-s-Unity-Blend-Modes Правда для непрозрачных цветов. Для поддержки прозрачности нужно дорабатывать режимы смешивания самостоятельно.
По сути тут мы смешиваем наш кадр заднего фона с неким цветом и получаем необходимый нам эффект. _BurnColor можно регулировать чтобы добиваться разного визуала. Но по умолчанию его можно в целом не трогать. И мы получим эффект, как на картинке.
Эффект Френеля
Так как у нас нет рефракции и по сути 3д модель выступает сейчас, как некая маска для того, чтобы показывать нам, что же скрыто за ней в размытой текстуре экрана - нам нужно как-то добавить объём. Можно в целом реализовать преломления, но они тут необязательны, так как с таким сильным размытием визуальная разница будет незаметна. По сути акрил - это отражающий материал, поэтому мы можем задать нужный нам объём Эффектом Френеля.
Возьмём самую простую реализацию эффекта Френеля (их очень много с разными свойствами, возможностью к редактированью и скоростью работы). Взяв угол за основу скалярное произведение между векторами от вершины объекта до камеры и нормалью в данной вершине.
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = ComputeScreenPos(o.vertex);
//Freshel
float3 i = normalize(ObjSpaceViewDir(v.vertex));
o.fresnel = _FresnelBias + _FresnelScale * pow(1 + dot(i, v.normal), -_FresnelPower);
}
half4 frag(v2f i) : COLOR
{
float4 col = tex2D(_MainTex, i.uv.xy / i.uv.w);
//Color Burn
col.xyz = LinearBurn(col.xyz, _BurnColor.xyz);
//Freshnel
col = lerp(col, 1 - _FresnelColor, 1 - i.fresnel);
return col;
}
А в фрагментной части будем смешивать цвета в зависимости от этого параметра. И получим необходимый нам объём + возможность регулировать цвет отражений от нашего акрила. Для простоты и удобства проще ставить его такой же, как и цвет самого акрила. Но в целом для достижения другого художественного эффекта его так же можно регулировать.
Шум
С шумом всё довольно просто. У нашей модели есть развёртка. Так как в uv один мы пишем параметры пересчитанные для вершинной части, то для передачи в фрагментную часть оригинальной развёртки можно использовать uv2 (или поменять их местами, тут кому как больше нравится) А дальше мы смешиваем шум по принципу того, что чёрный - это у нас альфа канал равный нулю, а в белый - альфа равная единице. И получаем нужное нам лёгкое “напыление на материале”. Собственно всё.
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = ComputeScreenPos(o.vertex);
o.uv2 = v.texcoord;
float3 i = normalize(ObjSpaceViewDir(v.vertex));
//Freshel
o.fresnel = _FresnelBias + _FresnelScale * pow(1 + dot(i, v.normal), -_FresnelPower);
return o;
}
half4 frag(v2f i) : COLOR
{
float4 col = tex2D(_MainTex, i.uv.xy / i.uv.w);
//Color Burn
col.xyz = LinearBurn(col.xyz, _BurnColor.xyz);
//Noise
float4 noise = tex2D(_NoiseTex, i.uv2);
col = lerp(col, noise, noise.r);
//Freshnel
col = lerp(col, 1 - _FresnelColor, 1 - i.fresnel);
return col;
}
Tint или задание оттенка
Так как в Unity чаще всего Tint подразумевает умножение цветов, то таким образом его и сделаем. Просто умножим нужный нам цвет, на цвет полученный в шаге Color Burn.
И всё, шейдер готов и у нас есть такой прикольный акриловый материал в Unity. По своей сути при том, что кажется что он прозрачный - он является непрозрачным материалом достаточно шустро работающим даже на слабых мобильных устройствах. Но за скорость надо платить и этот эффект не является честным акрилом и всё же обладает рядом недостатков. Но в большом числе случаев такой реализации будет достаточно, чтобы добиться нужного визуального эффекта в сцене.
Важное уточнение: эффекты разбирались не в порядке их наложения по слоям, так что обратите внимание на порядок вызова методов в шейдере, так как от этого сильно зависит визуальный эффект.
Вот и тренировке конец
В статье я пошагово разобрал, как можно сделать эффект акрила. Одну из вариаций. Подобные рецепты и трюки можно придумать или найти для большого количества материалов. Вопрос в ваших ограничениях, и как вы можете поставить их к себе на пользу. Допустим с одним акриловым объектом по центру сцены в AR эта штука вообще смотрится почти идеально.
Все исходники вы можете найти в репозитории, и если у меня будет в обозримом будущем время я постараюсь разобрать ещё несколько материалов и добавить их в репозиторий. Спасибо за внимание!
А в конце хочу предложить несколько простых упражнений (одно из них решено в репозитории, так что рекомендую не подглядывать, если захотите их про решать):
Сделать возможность собрать на основе этого шейдера окно с выложенной мозаикой
Сделать возможность красить 3д модели в разные цвета в один dc без использования текстурных атласов.
Сделать шум не в виде текстуры, а рассчитываемым получив нужный мутный визуальный эффект.