История одного эффекта
Часть 1. Реверс-инжиниринг, интриги, расследования
Несколько недель назад я увидел
классную подборку логотипов от Martin Naumann и подумал
а что если сделать такое на WebGL и оживить? Выглядит не сложно, будто выдавленный вперёд логотип, отражающий окружение. Задача сводится к построить текстуру с расстоянием до контура (называется это
SDF), по SDF определить высоту пикселя, посчитать направление отражения и взять какой-то хитрый градиент собственно для окружения. Ну что же, поехали! За пару вечеров справлюсь (ага, конечно :)
Для начала я сделал кружок прямо в шейдере, поигрался с градиентами и почти сразу получил неплохой результат (
видео 2). Градиент вычислялся из двух кольцевых градиентов с линейной интерполяцией между ними в зависимости от высоты нормали, по сути цилиндр. Вдохновившись, я решил повторить эффект на букве D. У неё есть и углы и скруглённая часть, то что нужно для теста. Чтобы сильно не заморачиваться в качестве SDF я использовал outer glow в ФШ, градиент теперь строился в зависимости от положения курсора для интерактивности (
видео 3). Ага, тоже прикольно, но как-то уже не то. Ну что же, начало положено, завтра продолжу – решил я и ушёл спать.
На следующий день я подумал что было бы неплохо взять всё же сразу какую-то надпись и работать с ней, например, название своей студии :) Дело в том, что до этого для определения горизонтальной составляющей нормали я брал вектор из центра экрана, но это работало бы только с кругом или с близкими к нему формами. А я же хочу впоследствии масштабировать эффект на угодно. Решение простое — для каждого пикселя текстуры с символом находим ближайший пиксель его контура и в rg каналах храним направление от него, в b — расстояние.
Довольно быстро накатал генератор таких картинок (
4), отдельно внешнее поле, отдельно внутреннее. Заблюрил rg каналы в фш, чтобы не было видно совсем уж ступенек, быстренько адаптировал шейдер и вуаля, картинка
5 готова :D
Отсутствие активации - это мой ответ Биллу Гейтсу за чипирование 5g ковида.
Гейтс Гейтсом, а результат по меньшей мере выглядит странно и нужно с этим что-то делать. Сейчас, в ретроспективе, очевидно, что с добавлением фич я мелкими шажками уходил в сторону от решения, но в процессе реализации одной фичи мне было сложно понять где я косячу с второй. Пришлось взять паузу, чтобы помедитировать на исходные логотипы.
После вечера раздумий, я переделал градиент ближе к начальному и решил брать какую-то нелинейную функцию от расстояния, чтобы получить собственно ту самую красоту на краях символов. Для того чтобы иметь возможность поиграть с этими функциями я и не генерировал карту нормалей изначально. Считать нормали в шейдере стало резко сложнее, но благо нормаль это перпендикуляр к касательной функции, а касательная функции это её производная. Производные за нас отлично считает
WolframAlpha (особенно когда пробуешь взять что-то типа производной
expSustainedImpulse). Получилось как-то так:
float slope = 1. - pow(1. - dist, 8.);
float slopeDerivative = 8. * pow(1. - dist, 7.);
if (slopeDerivative > 0.) {
normal = normalize(vec3(direction, 1. / slopeDerivative));
}
И вот мы уже на картинке
6, а до красоты ещё ооочень далеко. Проделывая кучу экспериментов с формой спусков у букв, цилиндрическими и куполообразными градиентами, с использованием источников света вместо градиентов (получив кстати в процессе
тот самый баг), я отдалялся от исходной картинки всё дальше и дальше. Лучшим что у меня получилось была картинка
7 (наверное потому что на ней почти ничего нет 😄), но если включить окружение получалась картинка
8. И дело было даже не столько в корявости поля направлений или градиентов, сколько в некорректности моих решений на уровне идеи.
В общем, спустя неделю экспериментов я сдался. И написал автору эффекта :) Он не хотел делиться своими секретами, но его наводок оказалось достаточно чтобы на следующий же день получить классный результат. О том, как это у меня получилось я напишу позже, а вы пока попробуйте догадаться как это в итоге устроено 😉
#my #tutorial