Моделирование процедурного дискобола в HOUDINI 11 альтернативная версия

Здравствуйте, уважаемые посетители сайта RENDER.RU!
Несколько дней назад я увидел на сайте урок «Моделирование процедурного дискобола в HOUDINI 11», и вопрос, поставленный в этом уроке, мне показался интересным. Мне хочется представить альтернативное решение той же задачи, а именно получение параметрической модели дискобола. Безусловно, подобных решений можно придумать очень много, в том числе и просто вывести формулу для позиции и поворота для каждой N-ой «плитки» шара, при этом проект будет состоять из двух нод «Box» и «Copy». Но, кроме того, что это достаточно непросто, это был бы уже больше урок по геометрии, а не Houdini. Мне же хочется показать именно интуитивно понятный способ, который будет прост для любого, кто занимается 3D. Кроме этого я позволю себе ссылаться на упомянутый выше урок, и опускать некоторые моменты, уже описанные в нем.
И так, давайте по порядку. Сначала более точно сформулируем задачу. Мы хотим сделать «дискобол» нужного радиуса R, который состоит из одинаковых плиток шириной W и высотой H так, что при заданных R, W и H, наши «плитки» лежали бы максимально плотно. Кроме этого я хочу еще ввести параметр S – это относительный размер «плитки». Он нужен на тот случай, если я захочу получить зазор между «плитками». Умножая размер «плитки» на этот коэффициент уже после того, как наш шар будет создан, я смогу получить зазор любого размера.
Открываем Houdini, создаем ноду Geometry, и называем ее Discoball. Сразу же заходим в параметры ноды и создаем наши параметры (как это делается, описано в предыдущем уроке):


Радиус нашего шара -  radius float - значение по умолчанию 1
Ширина «плитки» - width float - значение по умолчанию 0.1
Высота «плитки» - height float - значение по умолчанию 0.1
Относительный размер «плитки» - size float - значение по умолчанию 1

Далее заходим в ноду Discoball, удаляем ноду File и добавляем ноду Box1. Это и будет наша «плитка». В поле SizeY ноды Box1 вводим ch("../height") . Это же самое мы можем получить, сделав Copy Parameter у ноды Discoball, а затем Paste Copied Relative References в поле Size ноды Box1. В поле SizeZ вводим ch("../width"), в поле SizeX   0.01. Толщина «плитки» нас не очень интересует, хотя ее тоже можно было бы связать с параметром.

Как же нам Box1 скопировать так, чтобы не только расставить «плитки » на нужные места, но и не потерять углы поворота «плиток», ведь именно это стало большой проблемой в предыдущем уроке? Очень просто! Надо вращать Box1 относительно сдвинутого на величину R центра. В первое поле Center(для Х координаты) вводим ch("../radius"). Box сдвинулся относительно центра.
Добавляем ноду Copy1 и подсоединяем к ее первому входу ноду Box1. Сколько копий нам нужно сделать, и насколько их поворачивать? Я хочу сделать сначала четверть окружности, поэтому ввожу в поле Number of Copies ноды Copy1 следующее выражение:
floor($PI*0.5*ch("../radius")/ch("../height"))
То есть четверть длины окружности делим на высоту плитки. Функция floor дает нам целое от деления.
Это, конечно, не идеальная формула, потому что в реальности окружность мы заменяем многоугольником со стороной H, но она вполне подходит. Теперь в поле RotateZ ноды Copy1 вводим 90/ch(ncy) , то есть количество градусов поворота каждой следующей копии относительно предыдущей. Получаем четверть окружности из наших «плиток».

Осталось повернуть наши «плитки» по другой оси, и мы получим полусферу. Но каждую из них надо поворачивать на разный угол и разное количество раз, ведь радиус окружности для них разный! Как это сделать? Сначала рассчитаем этот радиус. Eсли провести линию от центра шара к центру «плитки», то несложно увидеть, что радиус, по которому ее нужно вращать, равен косинусу угла ее поворота умножить на радиус шара R.

 

Дальше можно действовать по-разному, но я хочу воспользоваться пусть не самым простым, но интересным способом. Давайте передадим каждой «плитке» в качестве атрибута этот самый радиус, по которому она должна вращаться. Как и в предыдущем уроке используем вкладку Stamp ноды Copy. Заходим в нее и вводим имя первой переменной radius, а значение cos($CY*90/ch(ncy))*ch("../radius"). То есть, умножаем угол относительного поворота одной «плитки» на номер копии $CY , берем от этого косинус и умножаем на радиус. Не забываем включить галочку “Stamp Inputs”. И включим еще галочку Create Output Groups на первой вкладке, она нам пригодится.

Между нодами Box1 и Copy1 вставляем ноду Attribute Create. Называем атрибут radius, и даем ему высчитанное в Copy1 значение.

Теперь мы можем выделить ноду Copy1, и в DetailsView увидим, что у каждого примитива есть атрибут radius с нужным значением. Осталось повернуть ноду Copy1. Добавляем ноду ForEach, на первый вход подаем Copy1.  ForEach  это нода, повторяющая набор действий, которые в ней помещены. Этот набор может повторяться для каждой группы, значения атрибута, примитива/точки или просто определенное число раз. Выберем для каждого значения атрибута. В качестве атрибута возьмем наш радиус.

Теперь войдем внутрь ноды ForEach. Создадим там еще один Сopy, ко входу которого подсоединим Each1. Зададим следующие параметры:

Количество копий равно floor(2*$PI*stamp("..","FORVALUE",0)/ch("../../width")) . Это целое от ( 2* «пи»* текущий радиус/ ширину плитки). Дело в том, что переменная FORVALUE передает внутрь ForEach значение, по которому идет цикл, для каждого шага цикла. В нашем случае – значение радиуса. Шаг поворота – 360/количество копий. Я хочу заметить, что мы могли делать ForEach не по значению атрибута, а для каждой группы, например. А радиус при этом высчитывать внутри ForEach тем же способом, что и раньше. Просто мне хотелось увидеть значения этих радиусов в Details View, поэтому я создал атрибут.

Выходим на уровень сцены, видим полусферу из «плиток». Меняем радиус, ширину и высоту плиток – все работает. Но плитки у верха шара немного перекрывают друг друга. Почему?

Потому что мы радиус каждого ряда высчитывали для центра «плитки», но они наклоняются, причем, чем ближе к верхушке, тем больше, поэтому радиус их верхних границ уже меньше. Насколько? На полплитки. Уточняем нашу формулу вычисления радиуса в ноде Copy1, теперь она выглядит так: cos($CY*90/(ch(ncy)-0.5))*ch("../radius") «Дырка» у верхушки немного увеличилась, но зато плитки больше не пересекаются.
Осталось сделать зеркальное отражение нашей сферы. Но тут есть один нюанс – если просто сделать mirror, то первый ряд «плиток» сдублируется на себя же, поэтому в нижней полусфере его нужно убрать. Делаем следующим образом – создаем ноду Mirror, direction выставляем в ней 0,1,0, чтобы она скопировала нижнюю половину. Убираем галочку Keep Original – теперь у нас только нижняя половина. Добавляем ноду Blast, в ней вводим в поле Group значение Box0. Удаляется первый ряд «плиток». Именно для этого мы включали галочку Create Output Groups в ноде Copy1 (после этого сначала каждый Box, а после ForEach и каждый ряд стал иметь свою группу, названную Box0, Box1, Box2… и т .д). Теперь делаем ноду Merge и присоединяем к ней Blast и ForEach – две половинки соединились вместе.

Осталось только добавить наш Size для «плиток». Идем в самое начало, и в ноде Box1 в поля ширины и высоты добавляем еще один множитель. Теперь эти поля выглядят так:
ch("../height")*ch("../size") и ch("../width")*ch("../size")

Наш шар завершен, можно менять радиус, высоту и ширину «плиток», и зазор между ними. Создавать «заглушки» сверху и снизу шара я не буду – как это делать, уже описано. Также как и сделать digital asset.

Как видите, получился очень простой сетап.
Xочется также отметить, что можно было бы сразу создать не четверть окружности, а потом половинку шара, а сразу половину окружности и шар целиком. Только после копирования пришлось бы дугу из «плиток» повернуть, чтобы она была симметричной относительно горизонтальной плоскости. Тогда наш проект вообще уместился бы в 4-5 нод, но при четном количестве «плиток» в полуокружности, «плитки», которая стояла бы строго вертикально, у нас бы не было. А мне хотелось, чтобы был именно центральный ряд «плиток»(который мы удаляли у нижней половины шара), который идет по максимальному радиусу R, и все «плитки» которого расположены строго вертикально.

Что если мы захотим немного усложнить задачу, и задаться целью сделать наш шар немного «неправильным», а именно повернуть каждую «плитку» относительно своего положение по двум из трех осей, чтобы они отражали свет более хаотично, как в реальности, а не были бы идеально прилеплены к сфере? Начав сейчас поворачивать «плитки», мы получим вращение относительно центра шара, а не относительно своих мест на сфере. Если же мы начнем поворачивать ноду Box1 до копирования, то будет поворачиваться весь ряд плиток, потому что наш шар мы создаем в два приема. Что же делать? Я бы сделал, например, так – вместо боксов я бы стал копировать точки(points), а затем уже в один прием нодой Copy расставил бы по ним «плитки». Но, как мы видим из предыдущего урока, просто точек нам недостаточно, даже если они имеют правильную нормаль. Почему? Потому что нормаль определяет только два угла поворота, а не три, и не может точно передать ориентацию в пространстве «плитки» шара. Поэтому при создании точек нужно будет сохранить в них информацию об углах поворота, либо как два атрибута по двум осям вращения, либо в виде кватерниона. Но это тема уже не этого урока.
Надеюсь, что изложенный материал пригодится тем, кто недавно начал осваивать Houdini.

ФАЙЛ СЦЕНЫ

729 0 850 6
3
2011-12-23
Только сейчас смог добраться до вашего урока. Отлично! Спасибо ) Видно, что и математическая и техническая стороны дела вам представляются яснее )))
2011-12-27
Да какая уж тут математика - геометрия за 7й класс средней школы :)
2012-07-10
Блестящее решение, а за одно - отличное упражнение для ума. Спасибо!
RENDER.RU