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

Волосы на геонодах ► 05. Дублирование волос (интерполированные дети)

В старой системе волос, которая создавалась с помощью системы частиц типа Hair (волосы) была возможность добавления к основным волосам "детей" - Children. Таким образом, созданные изначально волосы использовались в качестве направляющих, позволяя относительно удобно управлять ими, например, в режиме ручного редактирования Particle Edit, аналогичного режиму Sculpt в новой системе волос, а основная масса волос создавалась за счёт их дубликатов, распространяющихся вокруг родительских волос на контролируемом удалении. В режиме Interpolated дети распространялись вокруг родителей с привязкой к поверхности объекта, таким образом, чтобы их корни не отрывались от неё. Вот эту систему детей мы сегодня и воссоздадим для новой системы волос в Геометрических Нодах.
Буду очень благодарен всем, кто решит поддержать проект материально. В данный момент это можно сделать только здесь, на Boosty, сделав разовый платёж или оформив спонсорскую подписку приемлемого для вас уровня (от 200 р. в месяц).
Прошлый раз мы остановились на следующем этапе: у нас есть нод-группы, регулирующие длину волос, угол наклона и изгиб волос после предварительного добавления контрольных точек ↓
Для последующего удобства контроля отдельных параметров, детей мы будем добавлять до добавления дополнительных контрольных точек, то есть до ноды Resample Curve. Для начала нам надо продублировать уже существующие волосы. Это можно сделать с помощью нода Duplicate Elements. Нам понадобится режим Spline, чтобы дублировались сплайны волосков целиком ↓ 
Поскольку нод Duplicate Elements возвращает только копии элементов, чтобы исходные волосы у нас не пропадали и также продолжали использоваться, нужно объединить исходные волосы и их детей с помощью нода Join Geometry. В Геометрических Нодах во входы некоторых нодов, в том числе Join Geometry, может подключаться сразу несколько линков, что после Shader Editor может быть непривычным ↓
Сам по себе нод Duplicate Elements только дублирует элементы и возвращает их копии, больше он с ними ничего не делает, поэтому после добавления они полностью совпадают с исходной геометрией, и визуально изменения могут быть незаметными, сколько бы копий в параметре Amount мы ни добавляли. Однако, если мы для этой ветки используем нод Set Position и изменим Offset, то копии волос начнут сдвигаться, и их станет видно ↓
Но когда волосы сдвигаются в одном и том же направлении, это скорее напоминает эффект раздвоения изображения. Нам нужно, чтобы каждая копия каждого волоса сдвигалась на случайную величину. Для этого мы используем нод Random Value. Поскольку нам нужно сдвигать позиции волос, являющиеся векторными значениями, переведём тип данных этого нода в режим Vector
Ниже в этом ноде, с помощью минимального и максимального значения, определяется диапазон генерируемых случайных чисел. По умолчанию в режиме Vector минимальные допустимые значения равны (0, 0, 0), а максимальные (1, 1, 1). При использовании этого нода для сдвига элементов в исходном его виде, копии элементов будут сдвигаться на случайную величину от 0 до 1 метра по осям X, Y и Z. Причём обратите внимание, что сдвиг будет происходить по каждой оси только в положительном диапазоне (от 0 до 1 м), то есть все дети будут сдвигаться относительно родителей хоть и на разные значения, но единонаправленно - вправо, вперёд и вверх, а не во все стороны.
Чтобы это исправить, нам нужно, чтобы минимальные и максимальные значения диапазона случайных чисел были равноудалёнными от нуля в отрицательную и положительную область значений. Для этого мы можем использовать для определения минимума и максимума одно и то же число, только для минимума его нужно будет вычесть из нуля. Поскольку это число будет одинаковое для всех трёх осей, нам нет необходимости использовать векторы: мы можем напрямую подключать простые числовые значения к векторным входам. В этом случае одно и то же число будет использовано для всех трёх осей.
Добавим временный нод Value, который в дальнейшем будет заменён на входящее значение нод-группы. Подключим его к параметру Max в ноде Random Value напрямую, а для параметра Min предварительно вычтем его из 0 с помощью Math > Subtract (вычитание)↓
Теперь, увеличивая значение Value, мы будем увеличивать разлёт между элементами ↓
Поскольку нам, вероятно, нужно будет разносить детей на крайне небольшое расстояние от родителей, для удобства управления предварительно умножим Value на 0.01 с помощью Math в режиме Multiply (умножение) ↓
↑ Если присмотреться, то сейчас на рандомную величину сдвигается не каждый волос, но каждая точка волоса. Это происходит потому, что по умолчанию для всех операций в качестве домена - типа элемента, над которым производятся все действия - используются отдельные точки (Points).
Чтобы рандомное значение назначалось для каждого сплайна, а не для каждой его точки отдельно, нам нужно интерполировать домен с помощью нода Interpolate Domain, включив его в цепь перед входом Offset и переключив в нём тип домена на Spline, а тип данных на Vector, поскольку мы имеем дело с векторными значениями. Напомню, что в Geometry Nodes потоки данных идут между нодами не последовательно, а обоюдонаправленно, и работа каждого нода зависит не только от того, что подключено к его входам, но и от того, к чему он подключен сам. Это объясняет, почему Interpolate Domain ставится в цепи позже нода Random Value, а не до него ↓
Возможно, стоило сказать об этом раньше, но как-то к слову не пришлось. По умолчанию в настройках рендера в панели внизу справа, во вкладке с иконкой фотоаппарата, в разделе Curves (до версии Блендер 3.3.0 раздел назывался Hair) параметр Shape выставлен на Strand. В этом режиме волосы отображаются просто как кривые-направляющие. Чтобы полноценно регулировать толщину и форму волос необходимо переключить этот параметр на Strip
Если присмотреться, что происходит сейчас при отдалении детей от родителей, то мы увидим, что чуда всё же не происходит, и они просто произвольно отдаляются друг от друга в пространстве, а вовсе не привязываются корнями к поверхности ↓
Чтобы это исправить, нам понадобится нод Geometry Proximity (остальные ноды компактизируем и закроем) ↓
Geometry Proximity для каждой точки, на которую он воздействует (в нодах, подключенных к нему справа), рассчитывает ближайшую точку на поверхности объекта, которая подключена к его входу Target (в нодах, подключенных к нему слева). Таким образом, если мы подключим объект-эмиттер в качестве Target  к Geometry Proximity, и будем использовать Position из Geometry Proximity в качестве входа Position в Set Position для геометрии волос, то все точки волос будут расположены на ближайших к ним точках поверхности объекта-эмиттера. Проще говоря, он все точки волос прижмёт к поверхности эмиттера.
Нам понадобится геометрия эмиттера, которую мы можем получить с помощью нода Object Info. Переключим его на Relative и в качестве объекта выберем объект-эмиттер, у меня это Cube
Нам нужны позиции точек (вертексов) геометрии эмиттера, поэтому нам понадобится нод Transfer Attribute. Хотя сейчас, спустя неделю после того, как я подготовил эти скриншоты, я понимаю, что в нём нет необходимости, потому что при подключении Object Info к Geometry Proximity в последнем в качестве Source Position автоматически будут использованы точки подключенной к нему геометрии. С другой стороны, если в чём-то не уверен, то согласно одному из правил программирования, явно прописанное лучше, чем скрытое. Переключим тип данных в Transfer Attribute на Vector
Подключим в качестве атрибута нод Position. Полученное в Transfer Attribute поле подключим в качестве Source Position для Geometry Proximity, а геометрию эмиттера - в качестве Target для Geometry Proximity (повторюсь, можно просто подключить Object Info к Geometry Proximity, без Transfer Attribute и Position, эффект будет тот же самый) ↓
↑ Обратите внимание, что полученный результат мы подключаем ко второму ноду Set Position, в качестве Position, а не в качестве Offset, то есть то, что получилось определяет позицию точек, а не вектор их сдвига.
Сейчас к поверхности "прижимаются" все точки дубликатов волос. Для того, чтобы воздействие оказывалось только на их корни, мы можем использовать вход Selection (выбор элементов) в Set Position. Подключим к нему нод Endpoint Selection (выбор конечных точек), его можно найти по имени через поиск. Этот нод определяет, сколько контрольных точек будет выбрано с начала (Start Size) и с конца (End Size) каждого сплайна. Если установить значение Start Size на 1, а End Size на 0, это будет означать, что в каждом сплайне окажется выделенной одна точка с начала волоса и ноль точек с конца. Таким образом, второй Set Position будет оказывать воздействие только на начальные точки волос ↓ 
Можно было бы этим и ограничиться, но мне бы хотелось ещё добавить в эту же группу ещё один параметр, который в старой системе частиц типа Hair обозначался, как Clump. Этот параметр позволял сжимать концы прядей волос, состоящих из родительского волоса и его дочерних копий. 
Для этого мы можем, например, масштабировать получившийся вектор сдвига волос с помощью Vector Math в режиме Scale  ↓ 
🛈 Опять же, свежим взглядом я вижу, что мы могли бы вместо масштабирования вектора просто умножать исходное значение Value на инвертированный Factor в Spline Parameter. Но поскольку далее в цепи используется Interpolate Domain, переключающий домен на Spline, может быть не вполне очевидным, срабатывали бы назначаемые разным точкам значения по отдельности или назначались бы для сплайна целиком, и почему. Я, например, только подключив всё и проверив на практике, смог увериться, что такой вариант тоже срабатывает, но со стопроцентной уверенностью объяснить причину я пока не могу. Вероятно, потому что Interpolate Domain с типом данных Vector срабатывает только для векторных значений. Или потому, что Spline Parameter не может работать с доменом типа Spline, а только с его точками. Так или иначе, оба эти варианта - лишь мои предположения.
Для того, чтобы регулировать, для какой именно части каждого волоса будет срабатывать этот дополнительный фактор, подключим выход Factor нода Spline Parameter, который, как мы должны помнить из предыдущих уроков, для каждого волоса генерирует от его начала до конца значения от 0 до 1, линейно интерполирующиеся по всей длине волоса. В таком виде полученный вектор сдвига с помощью Vector Math > Scale у корней волос будет умножаться на 0, а у концов волос - на 1 ↓
↑ Это здорово, если нам надо растить, например, небольшие кустики травы, но в случае с волосами идея была в прямо противоположном - сохранять корни дочерних волос отодвинутыми от родительских волос по поверхности эмиттера, а концы сжимать друг с другом в пряди.
Всё, что нам нужно сделать для этого - инвертировать Factor сплайнов с помощью Math в режиме Subtract (вычитание), вычтя Factor из единицы ↓
Почему вычитание значений из 1 меняет их местами, подробно рассматривалось в предыдущем уроке, поэтому я на этом здесь не останавливаюсь.
Теперь нужно сделать так, чтобы можно было регулировать, насколько концы волос будут стягиваться друг к другу. Для этого давайте ещё раз проговорим, как работает вся система. У нас для каждого дочернего волоса с помощью Random Value генерируется рандомный вектор, на который дочерний волос смещается относительно родительского волоса с помощью Offset в Set Position. С помощью Vector Math > Scale мы умножаем этот вектор на значения Spline Parameter >  Factor, которые равны для начал волос единице, а для концов - нулю, после того, как мы их инвертировали, вычтя из единицы с помощью Math > Subtract. Таким образом, мы с помощью Vector Math > Scale умножаем полученный в Random Value случайный вектор смещения для начал волос на 1 - и он остаётся тем же, что и был сгенерирован, смещая волосы на исходный вектор, а для концов волос на 0 - и он  становится равным нулю, не влияя на смещение волос.
Чтобы нивелировать этот эффект, нам надо сделать с Factor нечто такое, чтобы значения на конце волоса возросли от 0 до 1, а на начале волоса остались равными 1. Мы можем этого добиться, например, постепенно добавляя значения от 0 до 1 ко всему диапазону Factor с помощью Math в режиме Add (сложение), с активированной галочкой Clamp (не путать с Clump), которая не даст результату подняться выше единицы ↓
Получается, что при значении Value, который будет отвечать за это действие, равным нулю, на начале волоса фактор будет равен единице, а на конце нулю, не давая концам волос расползтись от конца родительского волоса. А при увеличении этого Value до единицы, к значению Factor на конце волоса будет прибавляться единица, делая его равным 0+1=1, а к значению на начале волоса, равному 1, также будет прибавляться единица, но за счёт активированной в Math > Add  галочки Clamp результат не сможет выйти за пределы единицы и останется нетронутым.
Единственное, будет логичнее, если при значении Value, равным 0, волосы будут полностью не подвержены этому сжатию на концах, а на единице будут полностью сжаты. Сейчас всё происходит наоборот, поэтому перед использованием Value в качестве этого параметра, мы предварительно инвертируем его, вычтя из единицы с помощью Math > Subtract (вычитание) ↓
Ещё одна небольшая правочка - сделаем множитель значения общего разлёта дочерних волос не 0.01, а 0.1, чтобы регулировать разлёт в рамках не десятками, а где-то в районе единицы-двойки-тройки ↓
Компактизируем и реаранжируем ноды, объединим их в рамочки по смыслу, выделим все будущие входящие значения группы в Value. Нам понадобится регулировать:
• Количество дочерних копий
• Дальность разлёта дочерних копий
• Уровень сжатия концов дочерних копий с родительским волосом
Обратите внимание, что поскольку для количества копий используется целочисленное, а не дробное значение, вместо Value для этого используется нод Integer, в котором, как и в Value, можно задавать число, но только не дробное, а целое ↓
Объединим выделенные ноды в группу с помощью Ctrl + G, выстроим в N-панели во вкладке Group правильный порядок входов. Чтобы в качестве параметра Clump использовать слайдер фактора с голубой полоской, подключим к свободному выходу в Group Input вход Fac нода Mix (нод Mix нужно добавить отдельно)↓
Назовём этот выход Clump, переподключим его вместо Value, удалим нод Mix и старый вход ↓
Выйдем с помощью Tab из нод-группы, переименуем её в Children, и активируем щиток, чтобы Блендер не удалил группу при закрытии проекта, если она не будет использована ни в одном блоке данных ↓
Выровняем все нод-группы, проверим, что все параметры работают, как задумано ↓
Поздравляю! Четвёртый элемент управления готов!
Вы видите эти уроки, благодаря тем, кто забирает их к себе на стенки, рассказывает друзьям, знакомым, в сообществах, пабликах, чатах, коммьюнити. Без такой поддержки развитие проекта было бы невозможным, и я благодарен каждому.
Выражаю большую благодарность спонсорам. Вы даёте мне возможность продолжать, а другим - бесплатно учиться.
avatar
Добрый вечер. Что-то у меня совсем ничего не получается. Хочу после размещения интерполированных дочерних кривых сдвинуть их по направлению к мешу, пока их стартовая точка не коснется меша. Подскажите что не так?

Subscription levels

1-й уровень

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

2-й уровень

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

3-й уровень

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

4-й уровень

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

5-й уровень

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

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

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