Before/After Runtime Expressions
Довольно долгое время различия между runtimeBeforeDynamics и runtimeAfterDynamics партикловыми экспрешенами оставались для меня делом довольно смутным. Ну т.е. из названий в принципе все понятно – первые исполняются до динамики, а вторые после… Но что, черт возьми, на практике это означает и зачем оно надо, как можно полезно применить?! 🙂
Постараюсь ответить на этот вопрос и в качестве примера сделать сетап, при котором частица формально коллайдится с поверхностью – возвращает время, координаты и другую информацию о столкновении – но продолжает лететь как ни в чем не бывало дальше, будто не сталкивалась вовсе (к примеру, пуля проходит сквозь объект, оставляя отверстие в точке входа и в точке выхода).
В целом, при переходе на очередной фрейм происходит следующее:
– исполняются непартикловые экспрешены и прочая анимация в сцене;
– для каждой уже существующей частицы исполняется runtimeBeforeDynamics экспрешен;
– вся динамика в сцене, включающая генерацию новых частиц эмиттерами, влияние полей, goal, коллизии и т.п.;
– runtimeAfterDynamics;
Таким образом, частицы, рожденные до или во время этапа динамики, подвергнутся ее влиянию в том же фрейме. Т.е. если мы создаем частицу обычным эмиттером или командой emit в непартикловом или runtimeBeforeDynamics экспрешене – она появится сразу в позиции после обработки динамикой. Если же команда emit исполняется в runtimeAfterDynamics – частица появится строго в указанной позиции и только со следующего фрейма подвергнется действию динамики.
Предположим, в сцене существующая частица (id #0), которая в каждом фрейме рождает частицу в координате <<1, 0, 0>>, и на все это действует uniform field, направленный вверх:
if( particleId == 0 ) {
emit -o particle1 -pos 1 0 0;
}
Если этот экспрешен находится в runtimeBeforeDynamics, в каждом фрейме рождается новая частица, затем она обрабатывается uniform field, затем нам выдается результат – частица появляются не в указанной <<1, 0, 0>> координате, а с учетом действия силы – чуть выше. Если поместить команду в runtimeAfterDynamics – каждая новая частица появляется без обработки силой строго в указанной <<1, 0, 0>> координате (только на следующий фрейм попадет под ее воздействие):
Во многих случаях эта разница несущественна, но при определенных условиях или для особых задач может иметь значение. К примеру:
1) rigidBody объект быстро падает вниз, из каких-то точек на его поверхности генерятся частицы, устремляющиеся вверх – при генерации их в runtimeBeforeDynamics получающийся шлейф постоянно будет отставать от точки на поверхности, так как частица родилась и сразу под действием ссилы сдвинулась вверх, а объект будучи rigidBody – вниз. При runtimeAfterDynamics калькуляции и эмиттинг произойдут после динамики rigidBody и влияния сил в этом фрейме – шлейф будет четко из точки на поверхности;
2) с движущимся объектом коллайдятся частицы, и каждая в момент коллижена создает другую частицу, привязанную через goal к этой точке на поверхности (дырка от попадания пули в голову):
if( collidingParticles.collisionTime > 0 ) {
float $collPos[] = collidingParticles.collisionPosition;
float $collUV[] = { collidingParticles.collisionU,
collidingParticles.collisionV };
emit -o stickingParticles -pos $collPos[0] $collPos[1] $collPos[2]
-at "goalU" -fv $collUV[0]
-at "goalV" -fv $collUV[1];
}
Так как коллижены просчитываются как раз между runtimeBeforeDynamics и runtimeAfterDynamics, ранее всего мы можем воспользоваться результатом именно в последнем – тогда в том же фрейме, в котором произошла коллизия, в указанной точке появятся новые частицы, привязанные к ней через goal. Если же экспрешен воткнуть в runtimeBeforeDynamics, эти частицы появятся лишь на следующий фрейм (значения коллизии пересчитаются уже после runtimeBeforeDynamics, так что частицы прилипнут все равно в те же самые места). Все не так плохо, если мы не используем какие-нить анимированные данные из сцены при генерации этих частиц – допустим, позиция упомянутого анимированного объекта по Y определяет прицеплять ли частицы:
if( collidingParticles.collisionTime > 0 ) {
float $sphPos[] = `pointPosition -w pSphere1.rotatePivot`;
if( $sphPos[1] < 10 ) {
float $collPos[] = collidingParticles.collisionPosition;
float $collUV[] = { collidingParticles.collisionU,
collidingParticles.collisionV };
emit -o stickingParticles -pos $collPos[0] $collPos[1] $collPos[2]
-at "goalU" -fv $collUV[0]
-at "goalV" -fv $collUV[1];
}
}
Так как перемещение объекта происходит после runtimeAfterDynamics, но до runtimeBeforeDynamics один фрейм прилепления частиц будет потерян при использовании runtimeBeforeDynamics в данном случае. В общем, все различия тут в нюансах в какой последовательности какие данные просчитываются:
Теперь упомянутый в самом начале пример – пуля не просто сталкивается с объектом, оставляя в точке коллижена дырку, но продолжает неизменно лететь насквозь, оставляя выходное отверстие с другой стороны. Можно сделать это следующим образом – так как коллизии просчитываются между runtimeBeforeDynamics и runtimeAfterDynamics, в первом мы имеем данные о частице до коллижена, во втором – после коллижена. Сохранив позицию до динамики в кастомный атрибут (beforePosition), используем ее для вычисления где частица должна была быть, если бы не коллайдилась и перемещаем ее туда после динамики, а данные от коллизии используем как хотим:
runtimeBeforeDynamics:
beforePosition = position;
runtimeAfterDynamics:
float $beforePos[] = beforePosition;
vector $accel = acceleration;
float $fps = `currentTimeUnitToFPS`;
float $noColl[] = $accel / pow($fps, 2);
if( collisionTime > 0 ) {
position = <<($beforePos[0]+$noColl[0]), ($beforePos[1]+$noColl[1]),
($beforePos[2]+$noColl[2])>>;
}
В данном случае можно поступить даже проще, воспользовавшись специальным атрибутом lastPosition, который возвращает значение position в предпоследней калькуляции и как результат может заменить необходимость в кастомном атрибуте beforePosition и runtimeBeforeDynamics экспрешене:
runtimeAfterDynamics:
float $beforePos[] = lastPosition;
vector $accel = acceleration;
float $fps = `currentTimeUnitToFPS`;
float $noColl[] = $accel / pow($fps, 2);
if( collisionTime > 0 ) {
position = <<($beforePos[0]+$noColl[0]), ($beforePos[1]+$noColl[1]),
($beforePos[2]+$noColl[2])>>;
}
Искомый результат получен – коллижен происходит, все необходимые collision* атрибуты калькулируются, но частица не изменяет своей траектории.
В дополнение хочу еще упомянуть про world* атрибуты (в частности worldPosition). По моим наблюдениям, по дефолту они калькулируются один раз в конце просчетов – после runtimeAfterDynamics, но если к партиклам подключены коллижен-объекты, происходит калькуляция после этапа применения динамики, причем без влияния коллижена 🙂 Разумное объяснение сему явлению дать не могу, но применить:
runtimeAfterDynamics:
if( collisionTime > 0 ) {
position = worldPosition;
}
Прилагаю сцену с сетапом пули, проходящей куб насквозь:
Некоторые примеры возможно не самые интересные и показательные, но надеюсь что мне удалось в той или иной степени разложить по полочкам тему различия before/after runtime экспрешенов 🙂
In: FX · Tagged with: acceleration, collision, emit, expression, field, goal, maya, particle attribute, particles, runtimeAfterDynamics, runtimeBeforeDynamics, uniform
on 4 July 2009 at 1:14
Permalink
Hey these tuts are amazing, but could you make some tutorials for beginners? just getting into scrpting got particles
on 4 July 2009 at 3:25
Permalink
Thanks.
I guess I can, but I think there’re a lot of good tutorials on particle expressions for beginners already. And they are pretty boring to write also 🙂