Black Hole (Houdini Tutorial)
Хочу поделиться с вами процессом создания своей первой самостоятельной работы в Houdini. Поскольку мне близка тематика космоса, я решил сосредоточиться на самом загадочном явлении во Вселенной.
Далее я подробнее опишу процесс создания черной дыры в Houdini с последующим рендером в Karma.
К этому туториалу я буду прикреплять скриншоты экрана и подробнее останавливаться на написании VEX-кода, поскольку для меня это стало первым знакомством с программированием в рабочем процессе. Сразу отмечу, что основную часть кода я написала при помощи ChatGPT. Огромное спасибо современным технологиям за возможность так быстро осваивать новую сферу, не отрываясь от рабочего процесса.
А теперь основная часть....
1) Создаем само тело черной дыры: у меня это sphere (Primitive)
2) Теперь я хочу изменять радиус сферы либо ее массу для этого напишем VEX код применяя формулу Шварцшильда для вычисления радиуса горизонта события
Rs = 2GM/c², где:
- Rs — радиус Шварцшильда;
- G — гравитационная постоянная;
- M — масса объекта;
- c — скорость света в вакууме.
создадим AttributeWrangle назовем ее "attribwrangle_radius_g"
Вводимые параметры:
- Mode — режим работы: • 0 — вводим массу, считаем радиус; • 1 — вводим радиус, считаем массу.
- Kef — коэффициент реального радиуса к отображаемому. Например, реальный радиус равен 30 км, Kef = 0.1, тогда отображаемый радиус в параметрах Houdini будет равен 3 единицам.
- Mass Sun — масса в солнечных массах.
- Radius_km — радиус Шварцшильда.
Выводимые параметры:
3) Применим наш радиус к нашей сфере
Для этого создаем ноду Transform (называем ее "transform_blak_hole") и в параметр Uniform Scale передаем созданный нами атрибут R_kef
4) Создаем ноду Transform для общего управления и Null для вывода ее через SOP Import в сцену.
Самым сложным элементом в этой работе для меня стало создание эффекта гравитационного линзирования. В физическом смысле чёрная дыра искривляет пространство-время вокруг себя, и именно из-за этого возникает такой эффект. В Houdini реализовать его «честным» способом довольно сложно, поэтому я решил имитировать линзирование с помощью показателя преломления, назначенного на сферу. Такой подход, конечно, является упрощением, но он позволяет визуально передать суть гравитационного линзирования и добиться характерного искажения фона вокруг чёрной дыры.
отдельное спасибо YouTube и туториалам по Blender за помощь в частичном решении этой задачи.
1) Удаляем все атрибуты кроме R_kef при помощи ноды Attribute Delete.
2) Создаем цикл foreach begin.
применим такие параметры:
2.1 Внутри цикла создаем ноду Attribute Create в ней на Primitive создаем Integer параметр ID (который к каждому создаваемому примитиву будет присваивать свой ID).
Для этого в Value записываем:
2.2 Создаём Attribute Wrangle называем ее "attribwrangle_n_r": на первый вход подаём созданный ранее Attribute Create, а на второй — ноду из цикла с Metadata (пример можно посмотреть выше, нода называется "attribwrangle_n_r").
VEXpression
Данный Attribute Wrangle создаёт параметр R_size, который мы затем прибавляем к Uniform Scale в следующей, созданной нами ноде Transform.
2.3 В ноде Transform в Uniform Scale пишем:
2.4 Создаем Attribute Wrangle в ней считаем новый радиус и записываем его на примитивы как "R"
VEXpression
данную ноду подключаем на foreach_end
По итогу мы получаем 64 сферы распределенные ближе к центру.
3) Создаем ноду Attribute Wrangle на первый вход подключаем выход foreach_end на второй выход с ноды "transform_blak_hole"
VEXpression
В этой ноде мы получаем атрибут "nr" — он понадобится нам в дальнейшем. Это параметр показателя преломления, который мы будем назначать в материалах на эти сферы.
4) Снова создаем Attribute Wrangle где находим максимум и минимум атрибута "nr" (понадобиться далее)
VEXpression
5) Нормализуем показатель "nr", для этого:
в Attribute Wrangle пишем
VEXpression
Получаем две настройки "min и max"выставляем их как у меня:
6) Удаляем ненужные атрибуты нодой Attribute Delete
7) Нодой Attribute Promote переносим атрибут "nr" с Primitive на Point и конвертируем наши примитивы нодой Convert в полигональную модель.
8) Создаём очередной цикл foreach_begin с теми же параметрами, что и в предыдущем.
8.1 В цикле создаем три ноды Attribute Wrangle ("attribwrangle_ID_1", "attribwrangle_ID_63", "attribwrangle_ID_0") подключенные как показано выше:
attribwrangle_ID_1
VEXpression
нода выбирает один конкретный примитив по его ID, который вычисляется на основе detail-атрибута "iteration" из второго входа. Остальные примитивы удаляются.
attribwrangle_ID_63
VEXpression
нода удаляет все примитивы, у которых ID равен iter или iter + 1, где iter = 63 - iteration (где iteration берётся как detail с второго входа).
attribwrangle_ID_0
VEXpression
нода находит значение iter = 63 - iteration (где iteration берётся как detail с второго входа) и удаляет все примитивы, у которых атрибут ID не равен этому числу.
8.2 Создаем ноду Attribute Wrangle называем ее "attribwrangle_Vector" на нее подаем ноду "attribwrangle_ID_1"
attribwrangle_Vector
VEXpression
8.3 Создаем ноду Ray на первый вход подаем "attribwrangle_Vector" на второй "attribwrangle_ID_0"
8.4 Нодой Attribute Delete удаляем созданный нами вектор "v"
8.5 Блюрим наше полученное новое значение "nr" нодой Attribute Blur
8.6 Объединяем всё с помощью ноды Merge:
ноды attribwrangle_ID_63, attribwrangle_ID_0, а также результат, полученный из ноды Attribute Blur и подаем на выход цикла.
9) Далее я создал ноду для кэширования (можно и без нее обойтись).
10) Создаем ноду Transform и в ее параметры translate копируем параметры из ноды Transform_Main.
11) Параметры translate из ноды Transform_Main я также скопировал на созданную мной точку с помощью ноды Add.
12) Создаем ноду Attribute Wrangle на нее подаем нашу точку
VEXpression
в созданный нами параметр Camera Path подаем нашу копию камеры
13) Создаём ноду Copy to Points: на первый вход подаём наш Transform, на второй — Attribute Wrangle. Это позволяет направить сферу в ту сторону, куда смотрит камера.
14) Создаем Null для вывода ее через SOP Import в сцену.
15) В сцене создаем и назначаем на наши сферы материал "karmamaterial_lens"
Все эти параметры я настроил эмпирическим путём, методом проб и ошибок.
В данном методе есть изъян — под некоторым углом и при сильном приближении становится заметен переход от одной сферы к другой.
Буду очень признателен, если кто-то поделится способом избавиться от этого).
Перейдём к созданию аккреционного диска, который делает нашу модель чёрной дыры более узнаваемой и кинематографичной.
При создании аккреционного диска я во многом опирался на материалы из этого видео.
1) Создаем Grid
2) Накладываем на него UV Texture
3) Создаем Attribute Wrangler
Rune Over: Points
VEXpression
Эта нода даёт два параметра:
- Inner Rad — внутренний радиус (в моём случае в 3 раза больше горизонта событий).
- Outer Rad — внешний радиус (в 10 раз больше минимального радиуса).
3) Далее создаем Attribute VOP называем его "flat_disc" в нем:
Мне хотелось более тяжёлого и реалистичного варианта визуализации аккреционного диска, поэтому я решил использовать частицы вместо обычной геометрии/шейдинга.
1) Создадим точку в центре нодой Add.
2) Создаем Attribute Wrangler
Rune Over: Detail
VEXpression
при помощи наших настроек мы можем менять:
Minr - минимальный радиус окружности;
Maxr - максимальный радиус окружности;
numRings - число окружностей;
ptsPerRing - количество точек на каждой окружности;
biasToMin - смещение к центру окружности.
4) Следующий Attribute Wrangler объединит все точки в окружности
VEXpression
5) Нодой Attribute Delete удаляем все атрибуты кроме тех которые нам далее понадобятся.
6) Создаем цикл Foreach
в цикле....
6.1 Создаем ноду Resample
6.2 После создаем ноду Fuse (она нужна чтобы замкнуть наши окружности).
Snap Distance: 0.001
6.3 Создаём Attribute Wrangle:
на первый вход подаём то, что приходит с ноды "Fuse", а на второй — то, что приходит с ноды "attribwrangle_radius_g".
VEXpression
Показатели:
Amount — степень наклона вектора к окружности.
Speed — сила вектора (его длина).
Direction — направление вектора (-1 — завитки по часовой стрелке, 1 — против часовой стрелки).
Для наглядности показываю на меньшем количестве окружностей, а также показываю, как настроить отображение вектора v.
Переходим к следующему этапу...
7) Создаем Tube
8) Создаем PolyExtrude
9) Создаем ноду VDB from Polygons и Convert VDB
10) Далее создаём ноду Volume VOP: на первый вход подаём то, что приходит с ноды Convert, а на второй — наш вектор "v", созданный ранее в цикле (см. выше).
в Volume VOP:
у нас получаться следующие настройки :
11) Далее создаем ноду Null называем ее "OUT_V" (она нам пригодиться далее).
для визуализации можете построить следующую нодовую структуру
Идем дальше...
12) Переходим обратно к ноде "flat_disc" и создаем Attribute Delete и Subdivide (Depth = 2) (не обязательно)
13) Создаем Attribute VOP в нем:
14) Создаем Attribute Wrangle
VEXpression
15) Создаем Null и называем его "OUT_EMTTER"
16) Теперь создадим наши точки...
создаем DOP Network в нем:
идем по порядку...
16.1 source_first_input
16.2 popadvectbyvolumes
16.3 popforce
16.4 popdrag
16.5 Результат должен быть приблизительно таким:
17) Нодой Attribute Delete удаляем ненужные атрибуты кроме "v", "density", "P"
18) Далее нам важно, чтобы случайные частицы не попадали в область горизонта событий, поскольку с физической точки зрения там их быть не может. Для этого я их просто удалю.
Создадим Group
на вход параметра Bounding Object подаём объект transform_black_hole, масштабированный нодой Transform в 3 раза (то есть до значения, равного минимальному радиусу горизонта событий).
19) Также при желании можно ограничить случайно вылетающие частицы нашим созданным объёмом. Для этого снова создаём Group, на вход параметра Bounding Object подаём объект из ноды polyextrude и инвертируем эту группу с помощью ноды Group Invert.
20) Созданные нами группы подаём на ноду Delete, а затем удаляем все группы с помощью ноды Group Delete, так как далее они нам уже не понадобятся.
21) Далее создаю файл кэша с помощью ноды File Cache. Длительность симуляции выбирайте под свои задачи — мне, к примеру, хватило 310 кадров, и финальный рендер я делал как раз на этом кадре.
Самым интересным этапом для меня стала настройка цвета излучения (температуры) и яркости аккреционного диска. Я хотел добиться максимально физически достоверного результата, поэтому использовал несколько способов вычисления температуры каждой точки: по скорости v, плотности газа density, а также в зависимости от расстояния до центра чёрной дыры. Дополнительно применил эффект Доплера для воссоздания красного смещения относительно наблюдателя.
1) Начнем...
Создаем ноду Attribute Wrangler называю ее "attribwrangle_pose_radius"
VEXpression
2) Создаём очередной Attribute Wrangler и называем его "attribwrangle_temperature".
на первый вход подаём ноду "attribwrangle_pose_radius",
на второй — выход нашего цикла, в котором мы создавали на окружностях вектор "v",
на третий — ноду "transform_black_hole".
VEXpression
Max Density - подбираешь под сцену
Hot Radius - характерный радиус нагрева
Max Shear - "трение" как мера сдвига скорости относительно центра (подбираешь под сцену)
T Min - минимальная температура
T Max - максимальная температура
Max Dv - добавочное трение к другому способу вычисления температуры (подбираешь под сцену)
Pot Max - потенциал (подбираешь под сцену)
Temperatur Regim - способы вычисления температуры (0 - первый способ (по скорости и плотности), 1 - второй способ (в зависимости от массы и расстояния до черной дыры), любое другое значение (кроме 0 и 1) — оба способа одновременно.
Если T Min и T Max оставить равными нулю (как в моём примере), то значения температур нода подбирает сама — на основе массы звезды, согласно формуле тонкого аккреционного диска Шакуры–Сюняева.
3) Вычисляем максимальное и минимальное значения температуры, полученной на предыдущей ноде, для этого создадим Attribute Wrangler и назовем его "attribwrangle_temp_max_min"
Run Over: Detail
VEXpression
4) Наконец мы сможем наглядно увидеть что у нас получилось, для этого создаем ноду Color
В рампе мы уже настраиваем наш цвет. Сразу отмечу, что это художественный вариант: в реальности возле чёрных дыр газ разогревается до таких высоких температур, что максимум его излучения уходит в ультрафиолет и рентген, а видимый свет играет гораздо меньшую роль.
В Range пишем
должно получиться примерно вот так ...
Если не хочется сильно заморачиваться, можно просто создать Attribute Wrangler и назвать его "attribwrangle_Temp_to_Color" — он автоматически сгенерирует для нас цвет от темно-красного к голубоватому.
VEXpression
И покажу ещё один вариант, более корректный в физическом смысле.
Создаем Attribute Wrangler и называем его "attribwrangle_temp_real"
VEXpression
получим вот такой результат....
Бонусом рендер)....
4) Далее реализуем эффект Доплера
Первый вариант — через направление камеры и вектор скорости v. Второй вариант — через расстояние до центра (причём у меня второй вариант уже учитывает первый).
4.1 Создаем Attribute Wrangler, называем "attribwrangle_dopler_ef_V"
VEXpression
в Cam Path подаем нашу копию камеры:
Speed of Light — параметр подбирается экспериментально для достижения наилучшего визуального эффекта (в моей сцене значение равно 40).
4.2 Создаем Attribute Wrangler, называем"attribwrangle_dopler_r"
VEXpression
Center - я не трогаю (оставляю все параметры на 0)
Grav Factor - также как и в прошлой ноде Speed of Light подбирается экспериментально (у меня равно 1.3).
5) Теперь создадим ноду, которая будет формировать нашу яркость (emission).
создаем Attribute Wrangler, называем"attribwrangle_emis"
VEXpression
6) Так же создадим эффект доплера для нашей эмиссии.
создаем Attribute Wrangler, называем"attribwrangle_dopler"
VEXpression
в Cam Path подаем нашу копию камеры:
7) Очередной нодой Attribute Delete удаляем лишние атрибуты оставляем только "Cd", "emission", "v".
8) Для перемещения диска по сцене я создаю ноду Transform и копирую в её параметры translate и rotate значения из ноды Transform_Main. Благодаря этому движение диска синхронизировано с основным объектом.
9) Создаем Null называем его "geo_disk" и импортируем его в сцену нодой SOP Import.
про настройку материала расскажу позже...
Мы уже на финишной прямой: осталась заключительная деталь — фотонное кольцо, яркое кольцевое свечение вокруг чёрной дыры, возникающее из-за многократного огибания светом области сильного гравитационного поля.
10) Создаем circle с такими параметрами:
Uniform Scale:
Divisions:
11) Нодой fuse замыкаем circle
12) Создаём на точках вектор скорости "v": для этого добавляем ноду Attribute Wrangle на первый вход подаем fuse, на второй — выход нашего цикла, в котором мы создавали на окружностях вектор "v", и называем её "attribwrangle_foton_vec".
VEXpression
13) Создаем ноду Copy and Transform и ноду Scatter
14) Создаём две ноды Attribute Noise, чтобы добавить немного шума нашим точкам, и выравниваем их в центр сцены с помощью ноды Match Size.
15) Крайний элемент создаем ноду Transform называем ее "transform_Rotate_Anim" и анимируем ее круговое движение.
16) Создаём ноду Null "photon_circle" и объединяем её с аккреционным диском нодой Merge до ноды "attribwrangle_pose_radius".
17) Настроим материал аккреционного диска + фотонное кольцо.
Рендер
Сцену настраивайте на своё усмотрение (можете повторить мои настройки), но есть пара нюансов. В ноде karmarendersettings обязательно установите Refraction Limit равным 64 (количество создаваемых нами сфер в гравитационной линзе) или 128, 256, 512 и т.п. — главное, чтобы значение было кратно 64.
также, если вы хотите сохранить HDRI-карту при рендере в Dome Light, во вкладке Karma включите опцию Render Light Geometry.
Хочу искренне поблагодарить всех, кто досмотрел этот туториал до конца или хотя бы пролистал его целиком. Мне очень приятно осознавать, что моя работа могла кому‑то пригодиться или хотя бы просто заинтересовать. При подготовке я старался опираться на реальные физические свойства и довольно долго изучал этот вопрос, чтобы сделать материал как можно более правдоподобным, а изображение — одновременно физически убедительным и художественно красивым. Для меня важно, чтобы за красивой картинкой стояла какая‑то логика и понимание процессов, а не просто набор эффектов. Поэтому, если хотя бы один человек смог почерпнуть из этого туториала что‑то новое, вдохновиться на свои эксперименты или посмотреть на знакомые вещи под другим углом, значит всё это время и усилия были вложены не зря.
Отдельно буду рад любым подсказкам и идеям. Особенно по поводу решений проблем с гравитационной линзой, о которых я говорил выше — ваш опыт, наблюдения и любые наработки в этой области для меня очень ценны. Если у вас есть мысли, как улучшить подход или сделать результат ещё ближе к реальности, буду благодарен за обратную связь.
Спасибо вам за внимание и интерес)