Равномерные трейлы частиц

Когда возникает задача получить партиклами какие-то гладкие линии, трейлы или струи, обычно надежды возлагаются на 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);
	}
}

Основная частица-генератор (красная) создает за собой равномерный трейл из зеленых частиц:

Uniform Particle Trail (Jagged)

Uniform Particle Trail (Jagged)

Сцена примера: 05_uniPtTrail_01.mb

Разумеется, если частица быстро двигается по гладкой траектории, в результате линейной интерполяции между позициями трейл будет выглядеть изломанным:

Fast movement of particle-generator causes broken trail

Это решается увеличением oversampling динамики (в данном случае с 1 до 2):

With increased oversampling trail is smoother

With increased oversampling trail is smoother

Сцена примера: 05_uniPtTrail_02.mb

Теперь казалось бы все как нужно и можно запускать частицы и генерить ими трейлы… Еще одна проблема становится заметна, если к самим трейлам применить какую-то динамику или рампы от возраста age. В этой сцене на trail_pt действует гравитация – некогда при создании гладкие арки склоняются к земле с какими-то разрывами (верхняя часть следующего изображения). Откуда же они берутся и как от них избавиться (нижняя часть):

Forces affect trail particles in chunks

Forces affect trail particles in chunks

Если рассмотреть более детально чем же отличаются эти разорванные группы частиц, становится ясно, что все части в каждой созданы разом в один и тот же момент времени – birthTime и как результат age у них одинаковы:

Trail particles are chunked by birthTime values

Trail particles are chunked by birthTime values

Таким образом, когда просчитывается действие динамики, созданные за один и тот же фрейм частицы получают одни и те же модификации и в данном случае распадаются жесткими группами. Решение проблемы – заодно с генерацией в экспрешене задавать значение 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. Как результат – и силы и различные рампы, управляемые возрастом частиц, гладко влияют при переходе с группы на группу:

Corrected birthTime smoothes trails

Corrected birthTime smoothes trails

Арки и их цвета становятся гладкими:

Сцена примера: 05_uniPtTrail_03.mb

Posted on May 24, 2009 at 21:37 by Ark · Permalink
In: FX · Tagged with: , , , , , , , , ,

13 Responses

Subscribe to comments via RSS

  1. Written by matt
    on 4 September 2009 at 13:34
    Permalink

    Simply great and very imformative!! thanks a tons

  2. Written by Ryan
    on 24 September 2009 at 5:36
    Permalink

    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!

  3. Written by Paul
    on 19 February 2010 at 12:31
    Permalink

    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));

  4. Written by Sagroth
    on 20 February 2010 at 0:33
    Permalink

    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 🙂

  5. Written by Stefan
    on 8 April 2010 at 16:06
    Permalink

    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.

  6. Written by Sagroth
    on 9 April 2010 at 22:14
    Permalink

    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.

  7. Written by gus
    on 15 October 2013 at 16:08
    Permalink

    Thank you! I’m going to use your script to visualize a few things. Very cool!

  8. Written by HIM
    on 11 October 2014 at 19:44
    Permalink

    Any chance this can somehow work with Maya Fluids correctly?

    I was hoping emission would work from these ‘substepped’ particles without having to actually sim the fluids at .2, etc…. but alas Fluids does not recognize the extra particles…

  9. Written by Steve
    on 25 June 2015 at 8:44
    Permalink

    Hi there, thanks so much for this – very useful. I’m trying to make this work using a softbody to drive the emitting particles – but having no luck. the emitted particles are offset all over the place. essentially im trying to direct the movement of the trails explicitly (without fields or expressions). ideally i’d do this just by animating an emitter, but this approach doesn’t seem to work that way.

    do you have any thoughts about why this wouldn’t work? I may just be missing something obvious. many thanks

  10. Written by Juulien
    on 12 January 2016 at 17:37
    Permalink

    Thanks helps alot,!!!!!

  11. Written by Unai
    on 25 March 2016 at 18:51
    Permalink

    I can confirm that the correction for the forces being evaluated in chunks doesn’t work in Maya 2015 and 2016, legacy Particles or nParticles.

    Even if you use a normal emitter moving fast, which has the birthTime set properly by default, the forces will be applied per frame, resulting in chunks :/

    This is slowly driving me crazy.

  12. Written by Sagroth
    on 26 March 2016 at 22:21
    Permalink

    Increasing old ‘Over Samples’ seems to help (nCache>Edit Oversampling or Cache Settings…), but definitely might slow things down.

  13. Written by Unai
    on 27 March 2016 at 11:48
    Permalink

    You might have forgotten to explain the gradient > arrayMapper > gravity_magnitude, or perhaps thought it was irrelevant, but THAT’s the magic that gets rid of the chunks in your scene, not the birthTime alone 🙂

    Please give this a read: http://unaimb.blogspot.co.uk/2016/03/fixing-maya-particle-dynamics.html

    Thanks!

Subscribe to comments via RSS

Leave a Reply