6 авг. 2010 г.

рандомные вращения с умом

Пожалуй всем, кто сталкивался с ParticleInstancer, приходилось задаваться вопросом: "А как бы так повращать мои инстансы, да еще чтобы выглядело неплохо?" Решений немало, но не все одинаково хороши. Предлагаю рассмотеть наиболее часто встречающиеся подробнее, покритиковать и предложить свой вариант. И, как всегда, скрипт. Итак поехали!


Самый популярный

Самый популярный - это создать векторный Per Particle атрибут rotPP, и напрямую назначить его на вращение, т.е. rotation в параметрах узла ParticleShape, в секции Instancer(Geometry Replacement).

Затем создать creation expression:
particleShape1.rotPP = rand (0,360);
и runtime expression:
particleShape1.rotPP += 1;
Как видим, во время создания частицы ей назначается некий непредсказуемый поворот, и далее в каждом кадре постепенно прибавляются значения к каждой компоненте вектора поворота. Прием действительно часто используется и рекомендуется, например в этом уроке ресурса http://www.learning-maya.com.

Сразу же хочется отметить особенность использования псевдослучайных функций, они дают непредсказуемый результат от раза к разу, т.е. проигрывая анимацию в очередной раз вы никогда не получите предыдущий результат, что совершенно неприемлемо при просчете по пассам, или распределенном рендеринге. Выход таков: нужно создать обычный (не партикл) экспрешн...
if (frame == 1)
    seed(0);
Это не минус рассмотренного способа, а общая особенность, и решаться она будет во всех рассматриваемых случаях одинаково.

Теперь о минусах.

Проведем эксперимент: проанимируем у подопытного объекта по очереди сначала вращение по одной оси, затем по двум, и по трем.

В первом случае все привычно, все работает, но выглядит скучновато, во втором присутствует некая необоснованность поведения, ну и в третьем его просто мотает, как на аттракционе.

Рассмотренный выше пример вращения инстансных объектов повторяет в точности смоделированную ситуацию. Внимательный зритель заподозрит неестественность поведения, а зритель, еще не забывший школьный курс физики, обвинит меня в незнании закона сохранения момента импульса. Который в самой простой формулировке гласит, что вращающееся тело при отсутствии внешних сил не изменит ни скорость вращения, ни направление оси вращения.

Плюсы же вытекают из минусов, рассмотренный способ вкупе с полем турбулентности смоделирует поведение легких листьев на ветру. Неравномерность движения воздушного потока, обоснует постоянные изменения оси вращения объектов.


Решения основанные на векторе скорости партикла

Если на aim direction назначить velocity, то объект будет всегда ориентирован в сторону движения, что характерно для стрел, ракет, снарядов и т.п.

также ориентацию можно преобразовать и в поворот (подсмотрено в Sigillarium).
float $vel[] = velocity;
float $ang[] = `angleBetween -euler -v1 1 0 0 -v2 $vel[0] $vel[1] $vel[2]`;
rotPP = <<$ang[0], $ang[1], $ang[2]>>;
Следующий пример http://joshuabocko.com/blog/tutorials/particle-instancing-tricks/ тоже использует вектор скорости и повторяет предыдущую ошибку, ось и скорость вращения все время меняются, но зато зрелищности добавляет зависимость скорости вращения от массы (для этого примера атрибут .mass был добавлен частицам пропорционально размеру управляемого объекта).
float $vel = (particleShape1.velocity / particleShape1.mass); 
particleShape1.rotPP += $vel;

А что же делать, например, когда инстансером управляются увесистые осколки кирпичей, ведь им не пристало так беззаботно колыхаться на ветру?


Cпособ поинтеллектуальнее

Cценарий на MEL - km_randomInstanceRotation.mel (можно скачать здесь), товарища по имени Kevin Mannens, автора уроков по скриптованию от Gnomon, и технического директора проектов: G-Force, Alice in Wonderland, 10,000BC и The Chronicles of Narnia: prince Caspian. Скрипт создает узлам сцены необходимые атрибуты и выражения.
Вот creation expression:
particleShape1.axis = floor(rand(3)); 
particleShape1.rot = <<rand(360),rand(360),rand(360)>>;  
particleShape1.rotMaxRand = rand(0 - particleShape1.rotMax, particleShape1.rotMax) / 1000;
И runtime expression:
if (particleShape1.axis == 0) 
    particleShape1.rot = particleShape1.rot + << particleShape1.rotMaxRand, 0, 0 >>; 
else if (particleShape1.axis == 1) 
    particleShape1.rot = particleShape1.rot + << 0, particleShape1.rotMaxRand, 0 >>; 
else 
    particleShape1.rot = particleShape1.rot + << 0, 0, particleShape1.rotMaxRand >>;
В момент рождения частичке присваивается атрибут .axis, который, как мы видим может иметь лишь три значения и определяет к какой из координат будет добавляться инкремент. Все выглядит пристойно, не противоречит физическим законам, но... хоть начальный разворот и выбирается случайным образом, дальнейшее вращение происходит только по одному из трех возможных сценариев.

Для теста специально были аннулированы перемещения, а инстансы подкрашены в зависимости от атрибута .axis. Как видно из анимации, все синие объекты (.axis = 0) вращаются вокруг локальной оси x, все зеленые (.axis = 2) - вокруг оси z инстансера (подсвечена желтым вектором). Понастоящему хаотично вращаются лишь красные (.axis = 1), те, для которых у вектора .rot изменялся параметр y. Разумеется это верно для для ситуации, когда Rotation Order инстансера выставлен в XYZ.

Можно сделать вывод, что достаточно лишь инкремента по оси, второй в очереди вращения чтобы получить хаотичные движения. Делаем проверку:

У этого решения один лишь минус, для его реализации необходим изначальный хаотичный разворот. Ситуация, например, с разрушением объекта, где каждый фрагмент будет управляться частицей, потребует в начале анимации упорядоченной ориентации всех элементов, да и явное управление осью вращения не помешает.


Свой вариант

Попробуем справиться с этой задачей следующим образом: возьмем объект, случайным образом выберем ось и далее зададим вращение вокруг этой оси, примерно так:

Этот простой и эффективный способ я реализовал с помощью функции rot, поворачивающей один вектор вокруг другого на требуемый угол. Объекты ориентированы векторами Aim Direction и Aim World Up. Объекту частиц добавлены векторные атрибуты upVector, aimVector, rotAxis и скалярный rotForce.

в самом кратком виде выражение выглядит так.
if (frame==1)
{
.upVector = <<0,1,0>>;
.aimVector = <<1,0,0>>;
.rotAxis = sphrand(1);
.rotForce = rand(0, 1);
};
.upVector = rot (.upVector, .rotAxis , .rotForce);
.aimVector = rot (.aimVector, .rotAxis, .rotForce);

В данном примере симуляция начинается от исходного разворота,ось вращения (желтая) задается явно, и далее в каждом кадре вектора up (зеленый) и aim (красный) поворачиваются вокруг нее. Мы получаем больше гибкости, и возможность изменять ось вращения, например в моменты соударения с другими объектами.

Напоследок предлагаю скрипт, автоматизирующий создание выражения (несколько более замысловатого, нем в примере) и добавление атрибутов. Cреди них: кадр в котором следует начать вращение, seed, порог скорости (чтобы не вращать неподвижные объекты), коэффицент влияния массы партикла на скорость его вращения, минимум и максимум - разброс значений. Все они доступны в Extra Attributes партикл шейпа.

Для запуска необходимо скопировать файл в директорию со скриптами, выделить объект частиц, инстансер, и выполнить команду:
rehash;
source SanctusRandomRotation.mel;
Скачать

3 комментария:

  1. Спасибо. Очень полезная информация.

    ОтветитьУдалить
  2. Анонимный2 окт. 2010 г., 5:17:00

    В отношении последнего примера - чтобы объект при падении и ударении о поверхность мог по ней реалистично катиться, советую использовать cross product вектора скорости и вектора вертикали cross(<<0,1,0>>,$velocity). Это даст горизонтальную ось вращения перпендикулярную перемещению и параллельную земле. Очень полезный прием. В случае если "земля" не ровная, то вектором вертикали становится surface normal.

    ОтветитьУдалить

счетчик посещений