MayaParticles

From cgwiki

So this is mostly porting across the particles notes from the old wiki to this one. I must say its disheartening to see how little the workflow has changed for particles in maya in 10 (ten! TEN!!!) years. Hopefully Bifrost will fix all this in future releases, but for now, either look to SOuP to save you, or roll up your sleeves to swear at the particle expression editor, or do it properly and go learn Houdini.

Convert spriteNumPP so it can be software rendered

To my rage and disappointment, was amazed to find that in the year 2014, you still can't render particle sprites in a software render without massive effort. I thought it was simply impossible until I found this helpful old post on the mentalray forums: http://forum.nvidia-arc.com/showthread.php?4078-Rendering-particles

Summary is to feed all your sprite textures into a single ramp, and drive the ramp vCoord with a duplicated spriteNumPP variable.

  1. create one file node per texture frame
  2. create a ramp
  3. crate as many points on the ramp as you have textures
  4. start from the bottom, connect the first image to the bottom point, second to the second point etc
  5. set the interpolation type to none
  6. on your particleshape, create userScalarPP1
  7. in the particle expression, copy the spritenum to userscalar, and normalise it, ( eg, userScalarPP1 = spriteNumPP / 10 )
  8. now that we have this as a 0-1, we can use this to drive the vCoord of your ramp.
  9. create a particleSamplerInfo
  10. connect particleSamplerInfo.userScalarPP1 to ramp.vCoord
  11. now you can connect this into your shading network.


In my case, I was using rgbPP and opacityPP to get the look I wanted. As such, I had to create a few multiplyDivide nodes, and multiply the output of the ramp with particleSamplerInfo.rgb, and that to say the self-illum of a vray material, and a similar dance to get opacityPP to the cutout attribute. Its not fun.

Oh, if using vray, add vray attributes to the particleshape, and export rgbPP, opacityPP, userScalar1PP.

In my tests, this works in mentalray and vray, fails in maya software renderer, fails any of the hardware render modes. From what I've read you shouldn't need to do any of this in renderman for maya, there's posts on the 3delight forums explaining how to get this to work, haven't tried arnold yet.

Range of random colours for rgbPP

Setting rgbPP with rand(1,1,1) is probably too random for most use cases. Instead use hsv_to_rgb to allow you to set a range of huge, saturation and value. This little example does variations on yellow, in a city lights kind of vibe.

$hsv = <<0.13, rand(1,0.1), rand(1,0.7)>>;
spritesShape.rgbPP = `hsv_to_rgb($hsv)`;


Those components on the second line:

  • H = 0.13. If the colour wheel starts at 0 as red, and 1 as red, 0.13 is sorta yellow (use the colour picker to help you here)
  • S = rand(1,0.1) meaning choose a random saturation between full saturated (1) to almost desaturated (0.1)
  • V = rand(1, 0.7) meaning chose a random value/brightness between 1 and 0.7.


(rendered with prman dof cos it's cool)

Hsv particles.jpg

How do I make surface emitted particles stick to their surface?

Kept thinking I was over-engineering this and a lot would/should happen automatically, apparently not. You need to emit from surface (obviously), make the same surface a goal, add all relevant goal and parentUV properties, then set a creation expression linking it all together.

  1. emit particles from surface
  2. check emitter is in surface mode (often defaults back to omni), check 'need parent uvs'
  3. add general particle attributes goalU goalV parentU parentV goalPP
  4. set goal weight to 1
  5. set creation expression goalU = parentU; goalV = parentV;

See? Simple! Kinda. I wondered if there was an advantage to using this rather than just goalU = rand(1); goalV = rand(1);. Turns out there is; if your uv's aren't laid out evenly you'll get particles clustering on the edges of your uv borders. The 'proper' emit method above avoids this.

Make particle instances orient to surface normal

'image here: normal_instances.jpg'

An extension of the above trick. Once you have your goal object defined, go into the 'goal weights' section of the particleshape, and click 'create goal world position 0 PP'. Create the shape(s) you wish to instance (remember the should point down the x-axis), create your instancer node, and set aim direction to goalWorldNormal0PP. This has the advantage of being quite fast, and will update if you deform or transform your goal object. Here's an example scene:

'scene link here: normal_orient_instances.ma: normal_orient_instances.ma'

How do I uninstance particle objects ?

Unfortunately you can't simply duplicate the instancer to retrieve the mesh. Fortunately there are plugins & scripts around.

How do I have some particles stick to a collision surface and some collide?

goalPP and modulus are your friends.

In creation:

// red particles will stick, black will bounce
 if (particleShape1.particleId %2 == 0)
 	particleShape1.rgbPP = <<1, 0, 0>>;
 else 
 	particleShape1.rgbPP = <<0, 0, 0>>;
 
 // initial goalPP setting, no attraction
 particleShape1.goalPP = 0;


In runtime: (all the collision PP attrs need to be added first for this to work)

if (particleShape1.particleId %2 == 0)
 {
 
 	if (particleShape1.collisionTime != -1) 
 	{
 	particleShape1.goalU = particleShape1.collisionU;
 	particleShape1.goalV = particleShape1.collisionV;
 	particleShape1.goalPP = 1;
 	}
 }


-Kevin Mannens - 17 Sep 2008

How do I control ratio of particle instanced objects?

(adapted from a thread on highend) First, have a look at the bottom of this page for info about the modulus (%) operator. Based on this information, you should be able to understand the following particle expression.

// loop through an array of 10
for ($x = 0; $x <= 10; $x++)
{   
// if the remainder is 0
// i.e. if $x is dividable by 10, print "One"
if ( $x % 10 == 0 )
    {
    print ("One\n");
     }
 
// If the remainder is smaller or equal to
// 2 AND if the remainder of $x is bigger then 0,
// then print 2
else if ($x % 10 <= 2  && $x % 10 > 0 )
     {
    print ("Two\n");
      }
 
// if the remainder is bigger then 2,
// print Three     
else
     {
        print ("Three\n");
     }
 
}

You could then write an expression to control the relative ration (e.g. 1-2-7) of particle instances:

if ( particleShape1.particleId % 10 == 0 )
          particleShape1.indexPP = 0; 
 
else if ( particleShape1.particleId % 10 <= 2  && particleShape1.particleId % 10 > 0 )
         particleShape1.indexPP = 1; 
 
else
         particleShape1.indexPP = 2;

-- Maya.KevinMannens - 17 Sep 2008

How do I get particles to collide with only one side of a collision object?

(added by kmannens) Brain teaser from cgtalk: By default, particles in maya collide with connected surfaces on both sides not taking into account face normals or double-sided attribute. Is there a way or a node able to solve the one-sided particle-object collision? Answer: You could do some fancy vector math, or you could be lazy and assign a white texture to one side of the object and black to the other side. Then use colorAtPoint and traceDepthPP. Like so:

//upon collision
if (particleShape1.collisionU != -1 )
 
{
// get collisionU
float $colU = particleShape1.collisionU;
//get collisionV
float $colV = particleShape1.collisionV;
// get the color at the UV
vector $colRGB = `colorAtPoint -o RGB -u $colU  -v $colV ramp1`;
 
if ($colRGB != <<1, 1, 1>>)
{
	//line below to fix collision evaluation problem
	particleShape1.velocity = particleShape1.collisionIncomingVelocity;
	particleShape1.traceDepthPP = 0;
}
else 
	particleShape1.traceDepthPP = 10;
 
}

How do I get (and use) the exact location of a particle collision?

(added by kmannens) collisionWorldPosition is your friend. It's a PP attr that is a bit hidden, and I couldn't find any docs on it - but it does the trick. It does however, need another PP attr to work: collisionTime. So, set up your collisions as your normally would and then add collisionTime and collisionWorldPosition.

Say you want to create a locator at the point of collision. In runtime, add something like this:

//get the collision position
vector $collPos = particleShape1.collisionWorldPosition;
// get collision frame
float $collFrame = (particleShape1.collisionTime*24);
 
//if there is a collision...
if (particleShape1.collisionTime != -1)
 
{	//verbose
	print ("particle ID " + particleShape1.particleId + " colliding at frame " +  $collFrame + ".\n");
	print ("Creating locator at: " + ($collPos.x) +" , " + ($collPos.y) + " , " + ($collPos.z) + ".\n");
 
	//create locator
	string $loc[] = `spaceLocator`;
	// center pivot on locator
	xform -cp $loc[0];
 
	// move locator to collision position
	setAttr ($loc[0]+".translateX") ($collPos.x);
	setAttr ($loc[0]+".translateY") ($collPos.y);
	setAttr ($loc[0]+".translateZ") ($collPos.z);
}

How do I place an annotation at the point of collision?

Say I want to have an annotation per collision that tells me the exact position in xyz of the collision. This turned out to be a bit trickier then expected. Thanks to Chris Armsden for helping me out with the syntax. The trick was not try and do it all in one go in the annotation command. Rather then using the built in -text and -position flags, I created a "naked" annotation first and then adjusted that node using setAttr. Like this:

vector $collPos = particleShape1.collisionWorldPosition;
float $collFrame = (particleShape1.collisionTime*24);
 
if (particleShape1.collisionTime != -1)
{	
	//########### locator stuff #################
 
	string $loc[] = `spaceLocator`;
	xform -cp $loc[0];
 
	setAttr ($loc[0]+".translateX") ($collPos.x);
	setAttr ($loc[0]+".translateY") ($collPos.y);
	setAttr ($loc[0]+".translateZ") ($collPos.z);
 
	//########### annotation stuff #################
 
	// create annotation	with dummy text and pos
	string $annotator = `annotate -tx "THIS SUCKS" -p 0 0 0`; //this returns a shape
	// get transform of annotation node, because command returns shape	
	string $annTrans[] = `listRelatives -p $annotator`;
 
	// position annotation at collision
	setAttr ($annTrans[0]+ ".translateX") ($collPos.x);
	setAttr ($annTrans[0]+ ".translateY") ($collPos.y+5);
	setAttr ($annTrans[0]+ ".translateZ") ($collPos.z);
 
	// cut of fractional part of collPos by re-declaring as int
	int $annX = $collPos.x;
	int $annY = $collPos.y;
	int $annZ = $collPos.z;
 
	// set the text and pos of annotation
	setAttr -type "string" ($annotator + ".text") ($annX + "|" + $annY + "|" + $annZ)  ;
	// parent annotation under locator
	parent $annTrans[0] $loc[0];
 
}

Is it possible that particles from one particle node have different effects on different collision objects?

(added by kmannens) Interesting question on CGTalk, so I gave it a whirl: The key to your success is an extremely obscure attr called collisionGeometryIndex (which not mentioned in the docs, but can be added with the general button as a PP attr), which provides you with an index of the pieces of geo the particles collide with. (Can be seen in the partShape AE under collision attrs) Say you want the particles to go red when they collide with object1 and blue when they hit object 2:

int $idx = particleShape1.collisionGeometryIndex;
if( $idx != -1 )
{ 
	if ($idx ==1)
	particleShape1.rgbPP = <<1,0,0>>;
	if ($idx ==2)
	particleShape1.rgbPP = <<0,0,1>>;
 
}
It gets even more interesting if you want to extract the geo name from that. Thanks to Rob Pieke for the tip:
int $idx = particleShape1.collisionGeometryIndex;
if( $idx != -1 )
 
{ 
          string $geoC[] = `listConnections particleShape1.collisionGeometry[$idx]`;  
          string $shape[] = `listConnections ( $geoC[0] + ".localGeometry" )`; 
          print( $shape[0] + "\n"); 
}

Make particle expressions easier to edit

If you're tweaking expressions a lot, you end up staring at a dense expression editor like this:

particlesLowerShape.dU = (1+rand(-particlesLowerShape.uSpeedRange, particlesLowerShape.uSpeedRange)) * particlesLowerShape.uSpeed * 0.017;
particlesLowerShape.goalOffset = particlesLowerShape.goalWorldNormal0PP * (( noise(particlesLowerShape.particleId * particlesLowerShape.offsetSpeed + time) - 1));


You can refer to variables by the local name (using just 'rgbPP' rather than 'myParticleShape.rgbPP' ), but each time you update the expression, maya fills in the full name again. Instead, close the expression editor, and rename your particleShape to a single character, say 'p'. Your expression now looks like this:

p.dU = (1+rand(-p.uSpeedRange, p.uSpeedRange)) * p.uSpeed * 0.017;
p.goalOffset = p.goalWorldNormal0PP * (( noise(p.particleId * p.offsetSpeed + time) - 1));


Much easier to read! You can even leave it like this if you like, but if you have multiple particleshapes in a scene, you might want to rename it after you finish the expressions, just to avoid naming conflicts later on.

Instance rotation mel

I'd always planned to write a little melscript to make particle instance rotation easier. Kevin Mannens beat me to it. :) 'link here: km_randomInstanceRotation.mel'

Scaling emission rate based on emitter speed

(I believe there's now an option on emitters specifically labelled 'scale emission based on speed', leaving this here for legacy and because it's clever.)

This is based on a reply I gave to a question on cgtalk. The question was: How to scale particle emission rate based on the velocity of the surface emitter?

1. Create one particle (call it velocityParticle) with the particle tool and goal (with goalweight 1 so it snaps immediately) it to the animated emitter

2. Add a PP attribute called speed and have this in runtime of the velocityParticle:

velocityParticleShape.speed = mag (velocityParticleShape.velocity);
if (velocityParticleShape.speed < 15) {
   emitter1.rate =0;
} else {
   emitter1.rate =100;
}


(Change the expression to get the rate you need)

3. After you goaled your particle to the emitter (or whatever transform), you might want to do a Set Initial State.

4. (Optional) If you have a lot of emitters, you could try something like this:

string $emitter[] =`ls -sl -type "pointEmitter"`;
for ($eachEmitter in $emitter) {
   vector $emitterPosition = `xform -q -t -ws $eachEmitter`;
   float $posX =  $emitterPosition.x;
   float $posY =  $emitterPosition.y;
   float $posZ =  $emitterPosition.z;
 
   string $createP[] = `particle -p $posX $posY $posZ`;
   xform -cp $createP[0];
   goal -w 1 -utr 0   -g $eachEmitter  $createP[0];
}

Change particle colour on collision

Again via mr particle, Kevin Mannens:

global proc ParEvent(string $ParName,int $ParID,string
$ObjName)
{
print("ObjName:"+$ObjName+"\n");
 
//Get Par Position 
vector $ParPos;
$ParPos=`particle -id $ParID -at position -q
$ParName`;
 
//convert part pos into UV
setAttr cps.inPositionX ($ParPos.x);
setAttr cps.inPositionY ($ParPos.y);
setAttr cps.inPositionZ ($ParPos.z);
 
 
//get UV
float $U = `getAttr cps.parameterU`;
float $V = `getAttr cps.parameterV`;
 
//get color at collision UV
vector $Color=`colorAtPoint -u $U -v $V -o RGB file1`;
 
float $R=$Color.x;
float $G=$Color.y;
float $B=$Color.z;
 
//assign color of collision UV to particle
particle -e -id $ParID -attribute rgbPP  -vectorValue
$R $G $B $ParName;
 
};


Uber-Mayan Tom Kluyskens hinted me (Kevin Mannens) to a more elegant and faster way of achieving the same effect.

  1. Set up a collision between particle and the textured surface as normal. (in the example scene I have just used a plane with a ramp on it)
  2. Create a particle collision event
  3. Add collisionU and collisionV as PP attributes
  4. Add a custom PP attribute called tempColorPP
  5. Create a ramp on tempColorPP, but click on the option box and set these options for the array mapper
inputU -> collisionU inputV -> collisionV Map To -> (the name of the ramp you have applied on your collision surface)

Most of the time, you wont use a ramp, but a file texture. To use a texure instead of a ramp, give your ramp only one color input, and map your texture to that color.

  1. Finally, create this runtime expression on rgbPP:
if (particleShape1.collisionU>0) {
   particleShape1.rgbPP = particleShape1.tempColorPP;
}

Driving softbody goals via textures

Adapted from this thread on cgtalk, the solution coming from Kevin Mannens: Use an array mapper on the goal PP. When you create a ramp on the goalPP, set these settings:

  • inputU: goalU
  • inputV: goalV
  • Map to: the ramp you have mapped on your surface (you will need to use a ramp, but you can connect a texture into one of the ramp swatches if you need a texture instead).

In the creation expression I have this:

particleShape1.goalU = particleShape1.parentU;
particleShape1.goalV = particleShape1.parentV;
//I added this to show the independent colorization of the particles 
particleShape1.rgbPP = <<rand(0,1),rand(0,1),rand(0,1)>>;

Here's 2 examples scenes Kevin created, in goalPP2.mb I keyframed the ramp so you can see the goalPP being affected.:

'link here: texGoalPP.zip'

Particle intercollision

Kevin Mannens via highend3d, though possibly redundant now that nParticles (finally) have a self-collide attribute.

My preferred way to doing that is using the “use selected as source of field” functionality. Basically what you do is create a negative radial field with a small max distance and use each individual particle as source for that radial field. In doing do, particles will repel each other – as if they have opposing magnetic fields. For this to work, you need to tick on “apply per vertex” in the fields AE.

Matte opacity for volume particles

Duncan via highend3d: The particle volume render does not support matte opacity (to my knowledge). While particle volume shaders seem the same as other volume shaders, internally they are handled a bit differently. The fluids shader is modified internally to handle the case when it is invoked for particle rendering( the fluid shader is the only shader other than the particle volume shader than can render particle volumes in Maya ). The fluid shader does handle matte opacity, so I tried it with particles, however the matte opacity does not work in this context. Thus as far as I can tell you are out of luck... no shading network will make this possible. The shading engine ignores matte opacity in the particle volume shading context.

As a workaround you can render an extra pass, setting colors to your desired matte values, in order to get the matte you want.

Vibrating particles on collision

Using a volume axis turbulence with a snow sim made the particles vibrate on the floor despite doing a bunch of event tests. Turns out the expression I needed on runtime after dynamics was

if (particleShape1.event > 0) {
   particleShape1.mass = 6000;
}

Not exactly elegant, but it works.

Animation takes with rigid body sims

Not really particles, but I'll put it here while I think about making a dynamics page or not... Anyways, brilliant tip from Adrian Graham, thanks Adrian!

You can't cache to disk, but you can do multiple animation takes by baking the rigidBodies and using the choiceNode that's created. Once you bake a rigidBody, look in the graphEditor and you'll see a 'Bake Simulation Index' curve. Your first baked animation will set the curve's value to 1, but if you set it back to -1, you can re-simulate and bake again, and it'll add to the index each time. Do 10 different 'takes', and you can hop back and forth among all of them by adjusting the index.

Shading particles with fluids using overburn in SOuP

SOuP comes with a script to do this, it used to go by a few other names, its pretty cool.

http://petershipkov.com/development/afterburn/afterburn.htm

Lots of other really nice experiments on his site, have a browse, but again, since I originally wrote this Peter has pushed most of his awesome into SOuP.

http://petershipkov.com/

Emit particles from outside of a ring

Question from gary@fuzzygoat, answer by Elvira Pinkhas.

You can write an expression so that the particles have lifespanPP=0 when they are within the boundaries of the circle. The equation for a circle is x^2+y^2=radius^2 I just tried the following and it worked; I created a circle of radius 1 and used a curve emitter, with 0 in direction X. Set the lifespan mode to lifespanPP. Now the following expressions. Creation:

particleShape1.lifespanPP=5;

(or however long you think they should live) Runtime:

vector $pos = particleShape1.position;
float $radius = sqrt(($pos.x*$pos.x)+($pos.z*$pos.z));
if ($radius<=1)
 particleShape1.lifespanPP=0;
else
 particleShape1.lifespanPP = 5;

Random particle rotation

Built into nparticles now isn't it? Will keep this here until I have time to check.

Here's one way to make particle instances rotate in individually set random directions and speeds.

1. Create new custom dynamic attributes for your particle: particleShape1.rotMax (scalar, float) particleShape1.axis (float PP) particleShape1.rot (vector PP) particleShape1.rotMaxRand (float PP)

2. CREATION expression:

particleShape1.axis = floor(rand(3)); 
particleShape1.rot = <<rand(360),rand(360),rand(360)>>;  
particleShape1.rotMaxRand = rand( 0 - particleShape1.rotMax, particleShape1.rotMax) / 1000;

3. 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 >>;

4. In the Att Editor for particleShape1 -> Instancer -> Rotation Options -> Rotation = rot

Change the value of particleShape1.rotMax to control the speed of rotation. Each instance will start out with a random rotation, and each will rotate in a different direction at its own speed. If consistency is a problem, like if it never plays back the same way twice, you may need to make a general expression using the "seed" function. For example:

if (frame == 1) 
seed(10);

Instanced particles and rotation

It looks like using degrees for instanced particle rotation doesn't work. Use radians instead. For example, the creation expression to set particles to either 0 or 90 degrees on y would look like

particleShape1.rot = <<0, trunc(rand(0,2))*1.57, 0>>

Because 180 degrees = pi radians, 90 degrees is pi / 2 or about 1.57. Then when you create your instance, set the rotation mode to radians.

Making instanced particles scale over time

Handy way to scale instanced objects, with controls over speed.

1. Select your particleShape (not the transform!), add a float attribute called scaleStep, with a default of 0.1 2. Again on the particleShape, create a PP vector attribute called scalePP. 3. Add the following runtime expression:

if (particleShape1.scalePP <= <<1,1,1>>) {
    float $scaleStepScaled = particleShape1.scaleStep * 0.1;  // cheers dave.
    vector $temp = particleShape1.scalePP;
    particleShape1.scalePP = <<$temp.x + $scaleStepScaled,$temp.y + $scaleStepScaled, $temp.y + $scaleStepScaled>>;
}

4. And this creation expression to set the scalePP to 0:

particleShape1.scalePP = <<0,0,0>>;

5. In the geometry instance controls, set scale to be scalePP.

Render every 5th particle

We can use modulus (%) on the particle id to work out which ones to kill. Set lifespan type to be lifespanPP only, and use this runtime expression:

if (particleId % 5 != 0) lifeSpanPP = 0;

Modulus?

Modulus. It's a cooler word for remainder. Remember division in primary school? 5 / 2 is 2, with remainder 1.

So 5 % 2 = 1.

Because 5 goes into 5 exactly with no remainder:

5 % 5 = 0

So does 10, 15, 20, 25 etc... thats how we get every 5th particle.

To illustrate this, execute the following:

// loop through an array of 10
for ($a = 0; $a <= 10; $a++)
{
    float $r = $a % 10;
    print ("The remainder of " + $a + " is " + $r + ".\n");
 
}

This should give you:

The remainder of 0 is 0.
The remainder of 1 is 1.
The remainder of 2 is 2.
The remainder of 3 is 3.
The remainder of 4 is 4.
The remainder of 5 is 5.
The remainder of 6 is 6.
The remainder of 7 is 7.
The remainder of 8 is 8.
The remainder of 9 is 9.
The remainder of 10 is 0.

See above for a more advanced (expression) example with particle instances -- Maya.KevinMannens - 17 Sep 2008