EN
Andrey Sokolov
Andrey Sokolov
730 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Д ► 12. Lens Flares. Свечение.

Сегодня предлагаю отдохнуть от процедурных текстур. Мы в любом случае будем с ними так или иначе работать в дальнейшем, поэтому полученные знания не пропадут втуне. А сегодня будет затронута другая интересная тема - создание процедурных бликов линзы - Lens Flares - при помощи чистой математики. Для начала мы на протяжении нескольких уроков будем создавать различные элементы, из которых будет состоять блик.
🛈 Для справки. Я начинал учиться компьютерной графике в After Effects по урокам великого Эндрю Крамера, и когда позже начал заниматься Blender, постоянно искал способы перетащить туда всё, чему научился в AE. У тех, кто в теме, это должно снять все вопросы. 
Идея сделать управляемые процедурные блики линзы в Блендер пришла мне несколько лет назад, понадобилось около недели борьбы с Пифагором, чтобы заставить её работать (если бы я хорошо знал математику, ушло бы минут двадцать), и я с тех пор не видел, чтобы кто-то смог это повторить или разобраться, как это сделано.
К слову, обещанный в конце этого видео урок я тогда так и не смог осилить, поэтому просто выложил файл с проектом. Ну что ж, вот его время и пришло.
О том, как сделать проекцию координат объекта на плэйн, поговорим позже. Вероятно, кто-то не захочет ждать и попробует решить эту задачу сам. А сегодня создадим один из главных элементов - сферу с регулируемым размером, мягкостью краёв и яркостью.
Буду очень благодарен всем, кто решит стать платными подписчиками или разово задонатить на означенные цели. Это сильно мотивирует на продолжение.
Настройка проекта
↑ Удалим куб и добавим плэйн
↑ Сбросим камере позицию и вращение (см. горячие клавиши на скриншоте), чтобы она оказалась в центре сцены, и её вращение совпадало с вращением плэйна.
↑ Выделим сначала плэйн, потом камеру и привяжем плэйн к камере в качестве чайлда
↑ Развернём камеру на 90° по оси Х
↑ Переместим плэйн на 5 метров по оси Y, чтобы он оказался перед камерой
↑ Перейдём в режим вида из камеры
↑ Отрегулируем размер плэйна таким образом, чтобы он заполнял всё окно камеры 
↑ Применим плэйну размер. Это важно, потому что мы будем использовать в качестве системы координат режим Object, а это значит, что система будет подвержена трансформациям объекта. Подробнее про системы координат.
↑ Переаранжируем окна: закроем всё лишнее, нам понадобятся пока только 3Д вьюпорт и Shader Editor. Добавим на плэйн новый материал, назовём его Lens Flares.
↑ Удалим Principled BSDF (выделить и нажать X). Добавим Shift+A > Input > Texture Coordinate.
Создание сферы
↑ Сферу, как могли догадаться те, кто проходил всё с первого урока, мы будем делать с помощью визуализации длины векторов текстурных координат, где чёрный - это значения 0 и ниже, белый - это 1, а всё, что белее белого - это значения выше 1. Длина вектора рассчитывается при помощи Shift+A > Convertor > Vector Math в режиме Length. Далее я буду пропускать комбинацию клавиш Shift+A, и писать только категорию нода, его название и режим. Например, в данном случае - Convertor > Vector Math > Length. Категорию нода я буду писать только один раз, во время первого добавления, чтобы вы знали, где его можно найти. Любой нод можно найти через поиск, если начать вбивать его название в графе Search после, того, как вы нажали Shift+A.
↑ Нам нужно, чтобы сфера была светлая в центре и темнела к краям, поэтому значения нужно инвертировать. Для этого мы вычитаем их из единицы с помощью Convertor > Math > Subtract (вычитание), у которого верхнее значение (из которого мы вычитаем длину векторов) равно 1. Увеличивая верхнее значение, мы будем увеличивать размер сферы, а уменьшая - уменьшать его. 
↑ Чтобы регулировать мягкость краёв сферы, мы будем использовать Convertor > Math > Power (возведение в степень).
↑ Однако, напрямую мы его использовать сможем только в ограниченном количестве случаев. У нас возникнет несколько проблем, которые мы последовательно устраним.
Во-первых, при возведении в чётные целочисленные степени (например в 2, то есть в квадрат), отрицательные значения, умножаясь сами на себя, переносятся в положительный диапазон. Откуда вообще берутся отрицательные значения при измерении длины вектора? Это происходит, когда мы вычитаем её из 1. В метре от центра длина векторов начинает превышать единицу. В двух метрах она уже равна 2, и будучи вычтенной из 1, становится равной -1. Поэтому возведя значения в квадрат, мы получим ситуацию, когда сфера мягко затухает к краям, а потом также мягко снова разгорается - и продолжает разгораться чем дальше, тем сильнее.
↑ Мы могли бы решить эту проблему, поставив галочку Clamp во время вычитания, чтобы все значения меньше нуля оставались нулём, а больше единицы - единицей...
↑ ...однако, это приведёт к ещё одной проблеме: при увеличении значения, из которого мы вычитаем длину векторов, и которое должно служить для увеличения размеров сферы, вместо того, чтобы плавно затухать от центра к краям, центр сферы закрашивается ровным белым цветом. Там должны быть значения больше 1, но Clamp их обрезает.
Мало того, если мы даже придумаем, как избавиться от отрицательных значений без Clamp (это, как мы увидим позже, делается очень просто, буквально в один нод), нам крайне желательно будет удерживать значения градиента в пределах от 0 до 1, чтобы сфера увеличивала только размер, но не яркость. К тому же, при значениях от 0 до 1 предсказуемо ведёт себя и возведение в степень: ноль остаётся нулём, единица единицей, а меняются только дробные значения между ними.
Сразу оговорюсь, что самым простым, ресурсо-экономичным и эффективным решением было бы оставить в Math > Subtract верхнее значение 1, оставить Clamp включённым, и больше этот нод не трогать, а для регулировки размера сферы использовать Vector Math > Scale, подключенный перед Vector Math > Length, таким образом превентивно изменяя размер входящих текстурных координат. Для использования в проектах так и стоит сделать.
Однако, как мы могли убедиться на практике, изменение входящих текстурных координат не всегда может быть палочкой-выручалочкой, и полезно уметь решить задачу, воздействуя уже на результирующих значения, а не на входящие координаты. Заодно мы в процессе соберём одну крайне полезную вспомогательную нодовую группу.
Итак, проблему с перебросом отрицательных чисел в положительный диапазон мы пока оставим в стороне и сосредоточимся на том, чтобы при добавлении значения, которое должно контролировать размер сферы, сама сфера не меняла яркость, точнее, чтобы яркость продолжала равномерно растягиваться от центра к краям, оставаясь в диапазоне от 0 до 1. 
↑ Это можно сделать, если разделить результат после вычитания на то же число, из которого мы вычитаем длину векторов.
↑ Добавим нод Input > Value и будем одним значением контролировать и то, из какого значения мы вычитаем длину векторов, и то, на какое значение мы делим результат. 
↑ При значениях от 1 и выше теперь всё работает отлично, но зато у нас возникают проблемы при значениях от 0 до 1. Раньше всё было наоборот - в диапазоне от 0 до 1 всё работало нормально, а проблемы начинались уже после 1. Поэтому теперь было бы неплохо сделать так, чтобы пока значение, определяющее размер сферы, находится в пределах от 0 до 1, мы использовали, как и раньше, просто вычитание из этого значения (с Clamp, чтобы не проваливаться в минус), а когда значение переваливает за 1, мы бы использовали дополнительно деление на то же число.
↑ Пользуясь штатными средствами, мы можем смешать оба нужных нам результата с помощью нода MixRGB, а в качестве фактора использовать Math > Greater Than, который будет для всех значений, которые превысят Threshold возвращать на выходе 1 - и тогда будет срабатывать то, что подключено в нижний слот MixRGB, а для остальных - 0, и тогда будет срабатывать то, что подключено в верхний слот MixRGB.
Технически это решает нашу проблему. Теперь всё работает правильно: сфера при увеличении размера на значения выше 1 остаётся той же яркости, а в диапазоне от 1 до 0 постепенно затухает.
Всё супер. Если не знать, как работает нод MixRGB, и сколько операций производится там "на заднем плане". Кому интересно, я сейчас объясню, а кому не интересно: там 17 операций вместо нужных нам 4-х - и можете переходить сразу к следующей картинке. Остальные - го ботанить! Я уверен, что сейчас даже те, кто думают, что уже всё поняли, сильно удивятся. 
Во-первых, нам нужно смешивать значения, а MixRGB смешивает цвета. А цвет - это три значения, а не одно - по одному значению для красного, зелёного и синего каналов. Смешивание одного канала в режиме Mix занимает 4 математические операции над числовыми значениями. Для 3-х каналов - это 12 числовых значений.
Но это ещё не всё. Самая жесть начинается там, где вы вообще даже не можете предположить - при конвертации цветовых значений, которые мы получаем на выходе из MixRGB в числовое значение перед входом в нод Math > Power. Вы никогда не задумывались как цвет, состоящий из значений красного, зелёного и синего каналов преобразуется в одно число? Какие есть предположения? Используется значение яркости одного из каналов? Тогда какого? Самого яркого? В общем, нет, не так. Может тогда, чтобы получить среднее значения яркости, используется сумма значений трёх каналов, поделенная на количество каналов, то есть на три? Вы удивитесь, но тоже нет.
Получение, того, что называется Luminance - то есть суммарного значения яркости цвета - производится по формуле 0.2126 * R + 0.7152 * G + 0.0722 * B, где R, G и B - это значения красного, зелёного и синего каналов. Вот так, если не знать, то и не догадаешься, правда? Если не верите, можете подключить после цветной картинки нод RGB to BW или Math > Multiply *1, чтобы цвет перевёлся в число, но не поменялся. И сравнить со сборкой с использованием Separate RGB для разделения каналов и суммой значений отдельных каналов, предварительно умноженных с помощью Math > Multiply на указанные выше значения. Результат будет тот же, я проверял.
Это не что-то специфичное для Blender, это общая формула вычисления яркости цвета для цветового пространства sRGB. Она выведена на основе человеческой физиологии, поскольку у нас разное количество рецепторов, с разной чувствительностью воспринимающих световые волны разной длины. 
Таким образом, вместо нужных нам для смешивания двух значений 4-х операций, мы получаем сначала 12 операций - по количеству каналов, а потом ещё 5, чтобы сконвертировать это обратно в одно значение. 17 операций вместо 4-х.
↑ Мы создадим специальную нод-группу для смешивания числовых значений, в которой будет минимально необходимое количество математических операций. Чтобы было проще и нагляднее, добавим 3 нода Input > Value, которые просто определяют входящие значения.
↑ Чтобы не путаться, в N-панели (открывается в Shader Editor по нажатию N на клавиатуре), во вкладке Node, в графе Label переназовём их - Fac, Value 1, Value 2
↑ Логично предположить, что поскольку в качестве фактора смешивания используются значения от 0 до 1, то микс производится путём умножения с помощью Math > Multiply (умножение) входящих значений на значение фактора. Ну, по крайней мере, что операция умножения там как-то участвует. Умножая числа на 0, мы всегда получаем 0, а умножая числа на 1, мы всегда получаем исходное число. Всё просто. Математика, какой там, первый класс, второй?
Идея микса в том, чтобы когда значение фактора 0, использовалось только первое значение, а второе игнорировалось. Чтобы при факторе 0 игнорировалось второе значение, нам ничего делать не надо: мы умножаем второе значение на фактор 0 и получаем 0, всё работает. А вот первое значение при факторе 0 должно использоваться максимально, поэтому его при факторе 0 нужно умножать на 1.
↑ Для этого вычтем значение фактора из 1 с помощью Math > Subtract (вычитание). Теперь когда фактор равен 0, первое значение умножается на (1 - 0), то есть на 1, а второе на 0. Таким образом, первое значение остаётся собой, а второе становится нулём.
↑ И нам остаётся только сложить два результата умножения с помощью Math > Add. При добавлении фактора, значение первого числа будет постепенно уменьшаться, потому что из 1 вместо 0 начнут вычитаться всё большие и большие значения, пока при факторе 1 из 1 не вычтется 1 и от первого значения не останется 0. А со вторым значением будет происходить обратное - оно начнёт умножаться на всё более возрастающий фактор, пока при факторе 1 не станет само собой.
Если хотите убедиться, что при сложении будет меняться только пропорция, возьмите несколько примеров и решите их. Например, оба значения равны 2, какая будет сумма значений при факторах 0, 0.25, 0.5, 0.75 и 1? Если вы всё правильно посчитаете, то ответ всегда будет 2. Блендер тоже всё посчитает правильно.
↑ Подготовим ноды к упаковке в нод-группу. Объединим через Reroute (маленькая точка) линки, которые выходят из фактора. Это делается с помощью Shift, удерживая правую кнопку мыши и пересекая ей линки, которые нужно объединить. Это делается для того, чтобы при объединении нодов в группу не образовывалось лишних входов. Добавим ещё один нод, всё равно какой, лишь бы к нему был подключен будущий выход из нод-группы и у них совпадали типы  входа/выхода - в данном случае я просто продублировал нод Math > Add с помощью Shift+D и подключил к нему на вход выход из будущей группы. Когда всё подготовлено, выделяем нужные ноды, включая Reroute, обводя их левой кнопкой мыши, либо по одному с Shift.
Ctrl+G - объединить выделенные ноды в группу
↑ В N-панели во вкладке Group выстраиваем входы в правильном порядке, переназываем входы и выходы группы и задаём им значения по умолчанию, которые будут использованы при повторном добавлении этой группы в материалы, а также минимальные и максимальные значения. 
↑ Чтобы сделать нашу группу ещё больше похожей на нод MixRGB, я хочу, чтобы значение фактора при добавлении подсвечивалось синей полоской, но в настройках нод-группы изменить тип существующего входа на фактор с синей полоской нельзя. Для того, чтобы это сделать я добавлю в группу нод MixRGB...
↑ ...подключу Fac (фактор) к нижнему свободному входу, чтобы в группе появился новый вход. Он автоматически создастся того же типа, который был к нему подключен...
↑ ...передвину его в N-панели с помощью стрелочек наверх, переподключу  к нему соответствующие линки...
↑ ...удалю ненужный больше нод MixRGB с помощью X и ненужный больше старый вход Fac. с помощью минусика в N-панели.
↑ Группа готова. Выйти из неё, как и зайти обратно, можно с помощью Tab. Добавить эту же группу в этот или другой материал теперь можно через Shift+A > Groups > ... там будет её название. Сейчас она называется NodeGroup, но нам стоит её переназвать, чтобы потом её легко можно было находить. 
↑ Удаляем ненужные больше ноды Value и Math > Add, там где в нод-группе белые буквы на чёрном фоне, переименовываем её во что-то осмысленное, например - Mix Values, и активируем значок щитка, чтобы если даже группа не будет задействована ни в одном материале, она сохранилась вместе с проектом и не удалилась при его закрытии. 
↑ Теперь мы можем поставить эту группу вместо MixRGB, и всё будет работать визуально так же, только в 4 с лишним раза "легче".
↑ Теперь подготовим основную нод-группу, которая будет содержать в себе нашу сферу. Переаранжируем ноды, закроем неиспользуемые входы, которые не будут меняться с помощью Ctrl+H, добавим вспомогательные ноды Value, которые временно будут имитировать входы будущей группы. В данном случае у нас будет 3 входа - векторный вход для текстурных координат (вдруг мы захотим их менять до входа в группу), значение, регулирующее размер сферы и значение, регулирующее сглаживание краёв (с помощью возведения в степень через Math > Power)
↑ Удерживая Shift, проводим правой кнопкой мыши по всем входящим линкам, чтобы объединить те из них, которые используются для одновременной регулировки нескольких параметров
↑ Выделяем все нужные ноды и все точки-рероуты
↑ Нажимаем Ctrl+G, чтобы объединить их в группу
↑ Переаранжировываем и компактизируем ноды внутри группы, скрыв всё лишнее, не подлежащее изменению с помощью H. Я оставил один Subtract открытым, потому что когда я зайду в группу через месяц, то мне станет интересно, зачем он здесь дублируется, когда я бы мог использовать тот Subtract, который потом идёт в Divide. То, что он открыт укажет мне, что это сделано осознанно, а если мне станет интересно, для чего, я открою второй Subtract, нажав H, и увижу, что там отключен Clamp. Обычно мне этого бывает достаточно, чтобы поверить, что другой я из прошлого нигде не ошибся. В N-панели выстраиваем порядок входов, переназываем их и устанавливаем минимальные, максимальные и дефолтные значения. Например, размер сферы не может быть меньше 0, поэтому его имеет смысл ограничить нулём.
↑ А значение экспоненты, которое мы используем для возведения в степень на всякий случай ограничить даже значением чуть выше 0, потому что возведение в нулевую степень возвращает единицу, и можно получить неожиданную белую вспышку, если значение дойдёт до 0.
↑ С помощью Tab выходим из группы
↑ Отключаем лишнее и проверяем, как она работает
↑ Тут же вспоминаем, что проблема с отрицательным диапазоном и целыми четными степенями никуда не делась 
↑ Но после всего, через что мы прошли, это уже мелочи: просто в группе для вторых значений (которые отвечают за сферу размером больше 1) мы с помощью Math > Maximum, который возвращает наибольшее из двух значений, ограничиваем нижний порог значений нулём. То есть вместо отрицательных значений будет использоваться 0, потому что он больше, чем любое отрицательное значение.
↑ Приводим ноды в порядок. Вы из будущего скажете себе за это огромное спасибо.
↑ Выходим обратно с помощью Tab, проверяем. Отлично! Теперь всё работает! Ну, почти всё. Поскольку сферу предполагается использовать для свечения, мне не хватает параметра, который бы регулировал её яркость. 
↑ Добавим перед выходом из группы нод Math > Multiply, и будем использовать в качестве уровня яркости его нижнее значение. Пока оно равно 1, яркость остаётся той же, что и была.
↑ Подключим его в свободный вход группы, переназовём его и выставим минимальные, максимальные и дефолтные значения. Я оставлю здесь возможность отрицательного диапазона, поскольку использование свечения в отрицательном диапазоне может оказывать интересные эффекты на нормальное свечение.
↑ Вот теперь точно всё готово 
↑ Цвет можно добавлять с помощью MixRGB в режиме Color. В отличие от ColorRamp, он работает и с диапазоном значений выше 1.
↑ Итак, мы создали простой, но удобный, а главное, универсальный инструмент. Дублируя группу с разными настройками и суммируя результаты с помощью Math > Add, меняя для некоторых копий входящие текстурные координаты, можно очень точно контролировать результат.
↑ Чуть не забыл! Не забудьте перед тем, как начнёте творить красоту, заменить внутри группы всё, что мы там натворили, на более простой и менее ресурсо-затратный вариант, о котором я говорил в начале.
Если вам нравятся эти уроки, поблагодарите, пожалуйста, хотя бы мысленно, тех, кто забирает их к себе на стенки, рассказывает о них друзьям, знакомым, в сообществах, пабликах, чатах, коммьюнити. Этих людей пока очень мало, но именно благодаря их действиям вы сейчас можете видеть эти уроки.
Со своей стороны выражаю большую благодарность спонсорам. Ваша поддержка даёт мне силы продолжать.
avatar
Начал ловить себя на том, что стал примерно определять как достичь того или иного результата , смотря на твои скриншоты, не читая описания. Думаю, что это хорошо)
Show more replies
avatar
Andrey Sokolov, я вот сейчас закончил урок, и я так понял, что эффект блика будет делаться в следующем уроке? А то скриншот в последнем абзаце заставляет меня задуматься, что я что то не так сделал.
avatar
Andrey Sokolov, а. я понял. Надо было повторить ту вереницу нодов с настройками)
avatar
Андрей, спасибо за подробный разбор! Я попробовала выразить результат по вашему уроку но через математическое выражение, собрав ноды по формуле перехода между градиентом, заданным длиной вектора, и желаемым результатом, который управляется параметрами (я их взяла 4, дополнительно задала размер ядра свечения). Мне кажется, вышло похоже - https://cloud.mail.ru/public/Qhy1/WY2ASf96p
avatar
Maria Shevchenko, отличное объяснение и дополнение с ядром! Определённо более математический подход, очень круто. Отдельное спасибо за упоминания и ссылки на мои уроки!
Небольшой (совсем небольшой, на грани несущественности) нюанс, касающийся внутренней работы Блендер, а именно ColorRamp, и почему я его стараюсь не использовать без необходимости. В таком виде, с двумя ползунками в режиме линейной интерполяции, он, помимо того, что ограничивает диапазон, ещё работает и как Map Range, то есть слегка сдвигает все значения внутри. В самом сдвиге нет ничего критичного, он визуально не заметен, но его алгоритм состоит из шести математических операций, а так как предполагается многократное использование этой системы в шейдере, каждая новая копия с использованием ColorRamp будет создавать небольшую дополнительную нагрузку на ресурсы. Поэтому для корректировки диапазона я обычно стараюсь его не использовать. В этом случае его можно заменить на связку из простых Minimum(.999) и Maximum(0).
avatar
Andrey Sokolov, Спасибо, очень ценно!

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