Равномерные трейлы частиц
Когда возникает задача получить партиклами какие-то гладкие линии, трейлы или струи, обычно надежды возлагаются на Blobby Surface или новый Output Mesh для nParticles. У обоих есть свои достоинства и недостатки, общий из которых – как добиться ровного трейла – при минимуме использованых частиц получить максимально гладкий результат.
Первым решением наверняка будет – генерить трейлы из основных частиц обычным emitter. Как раз при таком варианте трейлы будут получаться неравномерными – в зависимости от скорости движения основной частицы-генератора участки трейла будут состоять из разного количества частиц, где-то скучкованы, где-то значительные промежутки, для устранения которых придется увеличивать радиус blobby, что в свою очередь приведет к общему утолщению трейла, лишним вычислениям и т.п. Гораздо более эффективный способ, о котором и пойдет речь в данном посте – генерить частицы трейла партикловым экспрешеном в зависимости от расстояния, которое базовая частица-генератор проходит за фрейм, и располагать их равномерно между этими двумя позициями.

Собственно, этот партикловый экспрешен для базовой частицы-генератора вполне прост – берем позицию до динамики/перемещения в этом фрейме, позицию после, и в зависимости от заданного пользователем промежутка между двумя партиклами трейла (я создал scalar float атрибут separ на шейпе частиц) создаем командой emit необходимое количество равномерно расположенных частиц между этими позициями. Более подробное объяснение получения указанных значений позиции есть в посте Before/After Runtime Expressions (упомяну, что тут я воспользовался созданием дополнительного атрибута beforePosition потому что в сцене нет коллиженов и lastPosition/worldPosition будут выдавать не те значения что нам нужны):
runtimeBeforeDynamics:
beforePosition = position;
runtimeAfterDynamics:
string $trail_pt = "trail_pt";
float $separ = separ;
vector $lastPos = beforePosition;
vector $pos = position;
vector $move = <<(($pos.x)-($lastPos.x)), (($pos.y)-($lastPos.y)),
(($pos.z)-($lastPos.z))>>;
int $num = ceil( mag( $move ) / $separ );
if( $num != 0 ) {
vector $step = $move / $num;
for( $i = 1; $i <= $num; $i++ ) {
vector $newPos = $lastPos + $step*$i;
emit -o $trail_pt -pos ($newPos.x) ($newPos.y) ($newPos.z);
}
}
Основная частица-генератор (красная) создает за собой равномерный трейл из зеленых частиц:
Сцена примера: 05_uniPtTrail_01.mb
Разумеется, если частица быстро двигается по гладкой траектории, в результате линейной интерполяции между позициями трейл будет выглядеть изломанным:

Fast movement of particle-generator causes broken trail
Это решается увеличением oversampling динамики (в данном случае с 1 до 2):
Сцена примера: 05_uniPtTrail_02.mb
Теперь казалось бы все как нужно и можно запускать частицы и генерить ими трейлы… Еще одна проблема становится заметна, если к самим трейлам применить какую-то динамику или рампы от возраста age. В этой сцене на trail_pt действует гравитация – некогда при создании гладкие арки склоняются к земле с какими-то разрывами (верхняя часть следующего изображения). Откуда же они берутся и как от них избавиться (нижняя часть):

Forces affect trail particles in chunks
Если рассмотреть более детально чем же отличаются эти разорванные группы частиц, становится ясно, что все части в каждой созданы разом в один и тот же момент времени – birthTime и как результат age у них одинаковы:
Таким образом, когда просчитывается действие динамики, созданные за один и тот же фрейм частицы получают одни и те же модификации и в данном случае распадаются жесткими группами. Решение проблемы – заодно с генерацией в экспрешене задавать значение birthTime, чтобы у каждой частицы трейла был плавный прирост, сливающий всю группу с соседними:
[...]
for( $i = 1; $i <= $num; $i++ ) {
vector $newPos = $lastPos + $step*$i;
float $life = time - (1.0/24/$num * ($num-$i));
emit -o $trail_pt -pos ($newPos.x) ($newPos.y) ($newPos.z)
-at "birthTime" -fv $life;
}
[...]
Подправив birthTime, подправляется и age. Как результат – и силы и различные рампы, управляемые возрастом частиц, гладко влияют при переходе с группы на группу:
Арки и их цвета становятся гладкими:
Сцена примера: 05_uniPtTrail_03.mb





6 Responses to “Равномерные трейлы частиц”
Simply great and very imformative!! thanks a tons
This is great. Exactly what I was looking for too. The problem I’ve been having is creating a smooth contrail like emission from the wing tips on a plane moving at a high rate. I’ll check this out and link back if this works for my scene.
Thanks for posting this!
Hey man thanks a lot for the tut, its great information. i was wondering if you could help explain a little more what these two lines of code do and why you are using the equations that you are? I kind of understand, but I’m not completely sure yet. Thanks.
int $num = ceil( mag( $move ) / $separ );
and
float $life = time – (1.0/24/$num * ($num-$i));
The first one calculates the number of particles that needs to be generated between two positions of a master one. I find the distance between these two positions – magnitude of the vector of transformation – and divide it by user defined $separ (that’s like density of particles in the trail – a distance we want between a pair). Since this division almost never leaves a whole number, I’ve decided that one more particle is better than one less
so it’s ceil here. Then in the $step variable the final distance between these particular two positions is calculated – it’s like $separ adapted for the distance in current step. As a result that makes sections of the trail a little bit uneven (some have denser particles), but I believe in the end that’s better than gaps around each master-particle position.
Second one is easier to write than to explain
Since I’ve got a bunch of particles born at the exactly same time, I want to fool them that they were born consistently in time from last frame to this frame to make forces affect them accordingly. So, I take the number of particles I generate in current step and find the time delta needed for each one depending on the current fps to evenly stretch in time – 1/24/$num (that’s actually should be 1/$fps/$num to make it more generalized). Then since I generate particles “from the past towards now”, I subtract this delta multiplied by the inverse number of the particle from current time. That creates them with birthTime gradually from previous time to current.
Hope that was understandable
Thank you very much for this high level tutorial Sagroth! Its a great contributrion and very appreciated.
About your last post, yes, it´s clearly understandable what the script does in terms of birthTime and for lifespan related ramps, this works fine. I´m using Maya2009 right now and i got the problem, that with fields (tested gravity and uniform), i still get the “chunk” drop. I guess we need to add the trick to active the field per particle a short amount of time after its creation.
You can access the magnitude of a specific field via .inputForce[#] attribute. More details in another thread here about the checking particle presence in a volume. Hence you can set the magnitude precisely for each particle when you create it, just like birthTime. That all is becoming pretty tricky though. That’s strange birthTime doesn’t work for you ’cause it does just that – modifies the magnitude of forces. Trails in my example are falling with gravity also.
Leave a Reply