EN
Andrey Sokolov
Andrey Sokolov
731 subscribers
goals
21 of 500 paid subscribers
Когда я наберу 500 платных подписчиков, смогу больше времени уделять записи видео и разработке аддонов для Blender.
358.51 of $ 1 128 money raised
Донаты || Donates
37.35 of $ 564 money raised
Cделать аддон True Time Remapping бесплатным для всех желающих навсегда. Make True Time Remapping add-on free for everyone forever.
112.78 of $ 113 money raised
На дисковый накопитель 4Tb для хранения бэкапов курса «Blender Избушка».

Математика в 3Д ► 17. Lens Flares. Процедурный N-Gon

Надеюсь, все понимают, что элементы, которые мы создаём для Lens Flares - это просто примеры того, что можно делать в Shader Editor с помощью математики, и что они достаточно универсальны, чтобы использовать их во многих других случаях? Помимо этого они интересны и в плане развития мышления для решения конкретных встающих перед нами задач. Сегодня речь пойдёт о таком случае. Мы создадим на первый взгляд простую фигуру - равносторонний полигон с процедурно управляемым количеством углов, а потом добавим к нему сглаживание и размытие краёв. Такие полигоны мы сможем использовать, например, в качестве эффекта боке.
Напомню, что в прошлый раз мы остановились на написании небольшого скрипта, который добавляет в пространство имён драйверов новую функцию, вычисляющую вектор сдвига системы координат на место проекции объекта источника света на плэйн (догадываюсь, насколько заумно это должно звучать для тех, кто случайно оказался здесь, не пройдя предыдущие уроки):
ТЕОРИЯ
Перед тем, как начать лихорадочно путём подбора подключать друг к другу ноды, пытаясь таким образом решить задачу по созданию фигуры N-Гона (это так не работает), давайте остановимся и подумаем, а как нам получить его углы. Возможно, самым прельстительным образом может показаться использование для этого сферы, то есть Vector Math в режиме Length (измерение длины вектора). С помощью сферы точно можно сгладить углы до состояния полной окружности, а чтобы получить сами углы, кажется, достаточно просто сплюснуть сферу - ну или будем говорить круг, поскольку всё равно имеем дело с проекцией на плоскость - с нужных сторон:
Теоретически мы сможем это сделать, если в нужных местах вычтем из значения длины вектора соответствующие значения. Например, можно использовать арктангенс осей X и Y, чтобы получить "часовой" градиент, подобрать сдвиг вращения вокруг центра с помощью добавления значения (Add), подключить Absolute, чтобы перевести отрицательный диапазон в положительную область значений, с помощью умножения (Multiply) подобрать ширину и, вычтя с помощью Subtract результат из единицы, инвертировать диапазон. Вроде выглядит, как рабочий вариант. Останется придумать, как его размножить на всю окружность, и можно вычитать эти значения из сферы, с помощью Multiply контролируя, насколько сильно будет сплющиваться сфера, и, если понадобится, с помощью Power корректируя огибающую:
Так я рассуждал, когда решал эту задачу в первый раз. Всё выглядело красиво, но не учитывался важный нюанс. Градиент, который вычитался из окружности, продолжал расширяться от центра к краям и дальше, поэтому значения вычитались не равномерно, а с постоянным сдвигом:
Если вы можете рассчитать формулу для корректировки огибающей градиента в этом случае, чтобы части оранжевой окружности при вычитании результата разворачивались в ровные линии, напишите, пожалуйста, в комментариях. У меня с математикой неважно, я эту задачу не осилил и использовал другой подход. Что если взять не сферу, а линейный градиент по одной из осей и просто разворачивать его в нужных направлениях:
Тогда мы сможем использовать хотя бы тот же Less Than для жёсткого порога, чтобы создавать чёткие грани:
И нам останется просто в разных секторах доворачивать текстурные координаты на нужный угол:
Чтобы регулировать угол доворота, нам нужно разделить окружность на сектора, в каждом из которых будет своё целочисленное значение - 0,1,2,3,4,5 и т.д. - по количеству секторов, и умножая эти значения на соответствующий коэффициент, в зависимости от количества углов, мы сможем регулировать, на сколько градусов в каждом секторе нам надо довернуть систему координат. 
Это сильно напоминает "часовой" градиент, поэтому мы будем использовать Acrtan2 осей X и Y. Напомню, для перпендикулярных осей он возвращает значения градиента от -Пи до Пи. 
Нам нужно вывести их в положительный диапазон, чтобы доворачивать все сектора в одну сторону, но на разные значения. Это можно сделать, предварительно добавив значения с помощью Add. И с его же помощью мы сможем регулировать начальный угол разворота, с которого пойдёт отсчёт секторов.
Чтобы получить сектора - целочисленные значения с жёсткими границами - мы можем использовать Math в режиме Truncate. Этот режим мы ещё не рассматривали, это сокращение. Он работает аналогично округлению (Round), только Round округляет дробное число к ближайшему целому числу, меньшему или большему, а Truncate просто отсекает дробную часть, таким образом всегда округляя значение к ближайшему меньшему значению (в положительном диапазоне). 
Чтобы делить окружность на ровные сектора нам удобнее всего сначала нормализовать градиент, приведя его значения к диапазону от 0 до 1. Изначальные значения диапазона от -Пи до Пи, соответственно, мы просто делим на 2Пи с помощью Divide
И теперь, умножая получившийся диапазон на целое число с помощью Multiply, мы будем получать количество секторов в окружности, соответствующее этому целому числу.
В результате должно получиться примерно следующее:
Точный корректирующий коэффициент для Add, который сдвигает сектора на исходный нужный угол, мы подберём уже в процессе. А сейчас давайте приступим к сборке этой системы.
ПРАКТИКА
↑ Добавляем Texture Coordinate и разделяем текстурные координаты Object на отдельные оси с помощью Separate XYZ
↑ Добавляем нод Mapping для коррекции текстурных координат, нам из него понадобится только вращение - Rotation
↑ Подключим ко входу Rotation нод Combine XYZ, чтобы регулировать значение вращения по отдельным осям. Проверим, по какой оси градиент будет вращаться по часовой или против часовой стрелки. В нашем случае это должна быть ось Z. Обратите внимание, что при использовании нода Combine XYZ в качестве Rotation мы будем задавать значения не в градусах, а в радианах.
🛈 Нам это сейчас не понадобится, но просто для справки: чтобы получить из градусов радианы, значения градусов надо разделить на 180 и умножить на Пи. 
↑ Компактизируем ноды, отвечающие за градиент: скроем лишние входы с  помощью Ctrl+H и закроем ноды с помощью H. Разделим текстурные координаты Object с помощью Separate XYZ.
🛈 Мы не можем использовать ранее созданный Separate XYZ, потому что он искажается с помощью Mapping, а нам нужны координатные оси в чистом виде.
↑ С помощью Math > Arctan2 (арктангенс с двумя параметрами) осей X и Y получаем часовой градиент в диапазоне от -Пи до Пи
↑ С помощью Math > Add (сложение) выводим диапазон в положительные значения, прибавив к нему число Пи (он становится от 0 до 2Пи). Это можно сделать, просто вбив вместо числового значения слово pi. Если вы вобьёте # перед словом pi, то на значение добавится драйвер, который всегда будет генерировать точное число Пи. При повторном изменении выражения в драйвере вбивать уже не нужно.
↑ Добавим Math > Truncate (сокращение), чтобы сократить дробные значения до целочисленных и получить целочисленные сектора с жёсткими границами. Их мы после дополнительной коррекции будем использовать в качестве значений вращения системы координат, которую мы сделали вначале. 
↑ Сначала приведём всё к диапазону от 0 до 1. Сейчас диапазон "часового" градиента от 0 до 2Пи. Чтобы его нормализовать, нужно разделить его на 2Пи. Это можно так же сделать в Math > Divide (деление) с помощью драйвера, вбиваем вместо значения #2*pi. Всё становится чёрным, потому что Truncate сокращает все значения от 0 до 1 до ближайшего меньшего целого значения, а именно до нуля.
↑ Теперь мы можем добавить Math > Multiply, и когда мы будем умножать с его помощью предыдущий результат на целочисленные значения, у нас будут получаться ровные сектора.
↑ Добавим временный Value в качестве предварительной замены входа группы. Чтобы, когда мы будем использовать этот параметр в качестве входа группы для определения количества углов N-гона, случайно не устанавливались дробные значения, делающие сектора неровными, добавим после Value перед умножением ещё один нод Math > Truncate. Теперь количество секторов будет меняться только при превышении входящим значением очередного целого числа.
↑ Компактизируем эти ноды, и, наоборот, временно развернём настроенные в самом начале. Подключим получившуюся систему секторов к оси Z, которая должна контролировать вращение... И что-то пошло не так, всё становится чёрным. Но это и правильно, потому что мы, начиная с левого нижнего квадрата окружности, поворачиваем каждый сектор на 1 радиан, то есть примерно на 57 градусов. А в левом нижнем секторе окружности по оси Х у нас располагается отрицательные диапазон. Нам надо решить две задачи:
• сделать так, чтобы сдвиг вращения происходил в каждом секторе не на 57 градусов, а на значение, равное 360 градусов (полная окружность) поделенное на количество углов (и переведённое в радианы для использования в Rotation)
• предварительно развернуть систему координат таким образом, чтобы в левом нижнем квадрате, откуда начинается отсчет секторов, находились положительные, а не отрицательные значения градиента, и чтобы градиент распространялся параллельно центру первого сектора. 
Решать задачи будем последовательно.
Сначала скорректируем значения сдвига вращения. Для этого нам полученные целые числа в секторах нужно умножить на корректирующий фактор, равный полной окружности в радианах (если в градусах это 360, то в радианах - 2Пи), поделённой на количество секторов.
↑ Для этого сначала с помощью Math > Divide (деление) делим 2Пи на входящий параметр с количеством углов (снова можно использовать #2*pi, чтобы добавить драйвер или просто 2*pi, чтобы добавить посчитанное значение без драйвера). Забегая вперёд - конечно, имеет смысл сделать это сразу с сокращённым значением, то есть подключать к Divide параметр, уже пропущенный через первый Truncate. А затем умножаем на результат деления значения секторов с помощью Math > Multiply (умножение).
↑ Теперь скорректируем предварительное вращение системы координат. Добавим перед нодом Mapping ещё один нод Mapping, и посмотрим, насколько нужно развернуть систему по оси Z. Сейчас градиент располагается слева направо, а нам нужно, чтобы он располагался вначале сверху вниз. Плюс потом мы его ещё будем доворачивать на нужный угол, чтобы он расположился параллельно центру первого сектора, но это уже будет зависеть от количества секторов, поэтому вначале мы просто довернём его на 90 градусов по часовой стрелке. 90 градусов это 0.5 Пи или Пи/2, если считать в радианах.
↑ Вновь используем Combine XYZ, чтобы регулировать вращение только по оси Z. Помним, что нам нужно будет ещё сделать градиент параллельным центру сектора, поэтому используем Math > Add (сложение) и в качестве нижнего значения используем pi/2 - опять же, можно с драйвером, можно без драйвера.
↑ Переподключим, если вы этого ещё не сделали, все линки, которые идут из параметра, определяющего количество углов, через Math > Truncate, который мы добавляли раньше, чтобы они сокращались до целых значений. И после того как мы сдвинули градиент на 90 градусов, добавим с помощью Math > Add к этому значению 360 градусов или 2пи, поделённое на количество секторов с помощью Math > Divide.
↑ Добавляем после всего Math  в режиме Less Than - и тадам! Нгон готов!
↑ Меняем значение параметра - меняется количество углов. Ну чисто магия. Подготовим ноды к добавлению в группу. Развернём все важные, в которых назначены драйвера или отдельные параметры, чтобы легко понимать, что где происходит. Объединим ноды в рамочки по смыслу, с помощью Ctrl+J, и назовём рамочки в N-панели, вкладка Node, графа Label. Это делается, чтобы когда мы зайдём в проект спустя некоторое время, у нас случайно не вытекли глаза.
↑ Добавим дополнительный вспомогательный Value для регулировки порога срабатывания, который будет отвечать за размер N-гона. Объединим все входящие параметры, которые используются одновременно для нескольких нодов, через точки Reroute (удерживая Shift провести по объединяемым линкам правой кнопкой мыши)
↑ Выделяем все нужные ноды и точки Reroute, нажимаем Ctrl+G для создания группы, и в N-панели во вкладке Group переназываем входы, устанавливаем их порядок, устанавливаем минимальные, максимальные и дефолтные значения. В частности, для количества углов имеет смысл сделать минимальное значение, равное 3, потому что меньше 3-х углов в N-гоне быть не может. Конечно, если вы поставите 2 угла, вселенная не сколлапсирует и компьютер не взорвётся, но и никакого полезного результата вы тоже не добьётесь.
↑ Выходим из группы, сразу активируем значок щитка, чтобы при сохранении проекта группа не удалилась, если нигде не будет использована, и называем группу N-Gon. Поздравляю, в своём простейшем виде группа готова. Но вы же меня знаете, зачем нам какой-то простейший вид?
СКРУГЛЕНИЕ УГЛОВ
↑ Для начала добавим скругление. Максимально углы можно сгладить до состояния окружности, логично? А для окружности (читаем - плоской проекции сферы) мы можем использовать Vector Math в режиме Length.
↑ Далее (всё гениальное просто) чтобы сглаживать углы, мы будем просто смешивать значения, формирующие N-гон со значениями, которые даёт сфера. И делать мы это будем при помощи ранее созданной группы Mix Values, которая внутри выглядит вот так ↓
↑ Подключаем всё, что создаёт N-гон, в качестве первого значения, сферу в качестве нижнего значения, и с помощью фактора смешивания определяем, насколько сглаживаются углы.
↑ На нуле они не сглаживаются совсем, на единице сглаживаются полностью
↑ Добавим это значение в качестве нового входа в группу N-Gon, зададим ему читаемое название, объединим всё связанное с скруглением углов в рамочку, назовём её Round или как-то ещё, чтобы вам было понятно, что она делает.
↑ Небольшое лайфхак, о котором не все знают. Когда линки из нода Group Input, содержащего все входы группы начинают напоминать паутину старухи Шелоб и мешают понимать, что куда и для чего подключено, мы можем этот нод продублировать столько раз, сколько нам будет удобно. Входящие значения во всех копиях нода Group Input будут использованы одни и те же. Это позволит визуально существенно упростить нодовое дерево.
↑ Особенно с учётом того, что после подключения к нужным параметрам можно нажать Ctrl+H, чтобы спрятать лишние выходы и оставить только необходимые.
↑ Итак, сейчас группа выглядит вот так, у нас появился дополнительный слайдер для регулировки скругления.
РАЗМЫТИЕ КРАЁВ
Остаётся добавить ещё один нюанс - размытие краёв. В реальности настолько жёстких краёв у бликов в камере практически не бывает, всегда есть хотя бы небольшой фрагмент перехода между бликом и его отсутствием.
↑ Это можно было бы реализовать, как мы уже делали с размытием краёв свечения или лучей, но для разнообразия мы сделаем это по-другому, с помощью Map Range - конвертора диапазона значений. Мы будем использовать его вместо Less Than.
🛈 Я довольно редко использую Map Range, поскольку это не одна, как может показаться, а шесть математических операций. РезультирующееЗначение = НовыйМинимум + (ВходящиеЗначение - СтарыйМинимум) / (СтарыйМаксимум - СтарыйМинимум) * (НовыйМаксимум - НовыйМинимум). Это при линейной конвертации. Для режима Smooth Step используется эрмитова интерполяция, и там всё ещё тяжелее. Map Range бывает очень удобным во многих случаях, просто полезно помнить, что у него "под капотом", чтобы не использовать его там, где в нём не т необходимости. Но здесь сделаем исключение, поскольку в последующих уроках мы все элементы будем сильно оптимизировать.
↑ Продублируем ещё раз Group Input, расспрячем все спрятанные выходы с помощью Ctrl+H. В ноде Map Range поставим галочку Clamp, в качестве его входящих значений мы будем использовать:
• для параметра Value то же, что мы использовали в Less Than - то есть всю нашу полную сборку;
• имея в виду, что Less Than инвертировал значения, менял местами чёрный и белый, в Map Range также СтарыйМинимум FromMin должен быть больше СтарогоМаксимума FromMax, поэтому для FromMin мы будем использовать значение Size...
• а для значения From Max - значение Size уменьшенное на значение Smooth - новый добавленный вход. Минимальное значение Smooth имеет смысл выставить на 0.001, и такое же значение, вероятно, сделать дефолтным, чтобы по умолчанию при добавлении новых копий группы в материалы сглаживание было минимальным.
🛈 Имейте в виду, что при добавлении в группу нового входа он автоматически появится во всех копиях нода Group Input, независимо от скрытых соккетов, и чтобы его спрятать, нужно будет выделить все копии Group Input, в которых он не нужен и два раза нажать Ctrl+H - то есть расспрятать все входы, и снова спрятать незадействованные, уже включая новосозданный. 
↑ Теперь мы можем снаружи регулировать размытие краёв сферы
↑ Внутри группы заменим на эту систему содержимое рамочки Threshold
↑ А чтобы регулировать огибающую размытия добавим ещё один вход с минимальным значением 0.001 и дефолтным значением 1, назовём его Smooth Shape и подключим в качестве экспоненты в Math > Power (возведение в степень), через который пропустим весь результат.
↑ С виду простая задача оказалась не такой уж простой, но мы справились. Группа получилась "тяжёлой", и если мы захотим использовать её многократно (а мы, разумеется, захотим), нам придётся её сильно оптимизировать. Но об этом речь пойдёт уже в следующих уроках.
Вы видите эти уроки, благодаря тем, кто забирает их к себе на стенки, рассказывает друзьям, знакомым, в сообществах, пабликах, чатах, коммьюнити. Без их поддержки развитие проекта было бы невозможным, и я благодарен каждому.
Со своей стороны выражаю большую благодарность спонсорам. Вы даёте мне возможность продолжать.
avatar
Ура. До прочтения урока я сделал свой многоугольник, у него нельзя было регулировать количество вершин и не было сглаживания, но я наконец то сделал что то сам. Спасибо вам за уроки.

Subscription levels

1-й уровень

$ 2,26 per month
1-й уровень

2-й уровень

$ 5,7 per month
2-й уровень

3-й уровень

$ 11,3 per month
3-й уровень

4-й уровень

$ 22,6 per month
4-й уровень

5-й уровень

$ 57 per month
5-й уровень

Максимальная поддержка

$ 113 per month
Максимальная поддержка
Go up