设置如图。
1、所有的砖都需要freeze transform。
2、我们需要建粒子去发射它。可以运行这个命令://create particle node particle -n wall_front_PTL;
3、创建一个属性,当开始播放场景的时候我们将用这个属性旋转砖块。打开粒子的属性栏,增加一个per-particle的属性,名称为rotationPP。
4、粒子将从每一块砖的位置发射。我们首先从所有的节点中捕获一个列表,然后循环每一个节点,用emit命令从每个节点的位置发射粒子。老外用了这样一段脚本来完成这个操作。emit_brick_particles.mel:// 获取前墙砖的名称列表string $nodeList[] = `ls -dag -type transform "o_frontWall_LOC|*PLY" "i_frontWall_LOC|*PLY" "side_frontWall_LOC|*PLY"`;
// 用循环语句让每一块砖发射一个粒子for( $node in $nodeList ) { // 得到每一块砖的位置 float $pos[] = `xform -q -ws -t $node`; // 确定每一块砖的大致体积 // 当我们破碎砖块的时候就会用到:
float $bb[] = `xform -q -ws -bb $node`;
float $x = abs( $bb[3] - $bb[0] );
float $y = abs( $bb[4] - $bb[1] );
float $z = abs( $bb[5] - $bb[2] );
vector $v = <<$x, $y, $z>>;
// 得到砖的长度 float $vol = mag( $v );
//发射一个粒子 emit -o wall_front_PTL
-pos $pos[0] $pos[1] $pos[2]
-at mass -fv $vol;
} // for $node in $nodeList
// 将此时的粒子保存为初始状态
saveInitialState wall_front_PTL;
emit_brick_particles.mel (1.08k)
[page]
5.记录粒子产生的次序。因为砖和粒子之间没有真正的关联,我们不得不依靠maya的物体列表清单。按字母顺序获取$nodelist变量里的列表,这是非常重要的一点,这将帮助我们正确的对砖块进行instance。
6.我们现在为每个粒子建立instance.我们将像前面发射粒子一样定义这个列表。
//选择砖块
select -r `ls -dag -type transform "o_frontWall_LOC|*PLY" "i_frontWall_LOC|*PLY"
"side_frontWall_LOC|*PLY"`;
7.选择菜单particles>instancer(Replacement)>optionBox.在instanced objects列表里,你将会看到你所选择的按字母顺序排列的所有砖块。单击create 按钮,你会看到instance物体已经出现了。
你会注意到有些砖的位置不正确。当你使用默认值创建instance时,instance会使用列表中的第一个物体。我们将指定instance使每个粒子使用它相同位置的砖。因为在前面我们已经正确的设置了我们的粒子和instance,所以现在就可以简单的将粒子的ID作为砖的列表索引。
8.选择粒子wall_front_PTL打开属性编辑器找到instancer一栏。
9.打开objectindex下拉菜单,设置为particleID,我们将看到砖块已经恢复到了正确的位置。
10.先添加一个per-particle属性,名称为rotationPP,打开rotation下拉菜单,设置为rotationPP。
当这些砖移动的时候,我们将用这个属性使砖产生翻滚的效果。
开始模拟
有趣的事马上就要发生了,我们会从内部炸毁这面墙。我们遇到的第一个问题是重力,怎样使砖块受到重力影响呢,在它们被推出去之前不让它们发生移动。有许多方法:
味 1)在粒子的runtime expression里创建一个重力,当粒子的速度不为零的时候它将受到重力影响,如果粒子已经接触到了地面,将它所受的重力设置为0。
//获取位置
vector $pos=position;
//如果粒子已经移动,并且没有掉到地面上,受重力影响。
if(mag(velocity)>0&&$pos.y>0.01)
velocity+=<<0,-32.17,0>>;
2)创建一个真正的重力场,将它的volume shape设置为cube。将它的位置放在墙的前面,当粒子被推出去以后它就会受到重力的影响。
第二种方法比较实用,如果你的场景物体不是很规则,那可能需要建立很多个重力场,并确保他们互相不交迭。
[page]
现在我们打开2_2_instance_brickwall_sim_base.mb,前面砖墙已经设置好了instance,一些场和碰撞物体将在适当的时间将砖墙碰出去。如果现在播放,会看到砖被推了出去掉在了地上。这是因为有许多的场和碰撞物体来模拟这个爆炸动画。可以看到在砖飞出去的时候并没有旋转,现在我们来加这个效果,让砖旋转着飞出去。
我们将根据一些因素来旋转这些砖:
a.砖的速度。
b.砖的质量(大的砖相对重一些,因此它们转的会慢一些)。
c.上面的砖朝一个方向旋转,下面的砖会朝另一个方向旋转,距离爆炸中心的会旋转的快一些。
d.用一个随机数值去乘以旋转值,让它们的速度有些变化。
现在我们要为粒子增加一些属性。因为我们是使用emit命令来发射的粒子,所以它们的创建表达式将被忽略。我发现可以在运行表达式中创建一个“假的”创建表达式。这个创建表达式将出现在第二帧,是在爆炸前运行。
我们也可以用epicentre_LOC这个locator的位置来决定爆炸什么时候发生。
1.首先,我们给wall_front_PTL的粒子增加几个per-particle的属性。3个per-particle float属性的,名称为rotXPP,rotYPP和rotZPP,1个per-particle float属性的,名称为magVelPP。
2.接下来,我们创建这个“假的”创建表达式。它将运行在模拟之前,在runtime expression中输入下列表达式:
//假的创建表达式if(frame==`playbackOptions -q -min`+1){
//获取位置
vector $pos = position;
//获取质量,并且取它的倒数
float $mass=1/(mass);
//获取爆炸中心的位置
float $epicentreF[]=`xform -q -ws -t epicentre_LOC`;
vector $epicentre=<<$epicentreF[0],$epicentreF[1],$epicentreF[2]>>;
//确定粒子和爆炸中心的距离
float $distToCentre=mag($pos-$epicentre);
//定义旋转值
float $rx=deg_to_rad(deg_to_rad(rand(-10)));
float $ry=deg_to_rad(deg_to_rad(rand(5)));
float $rz=deg_to_rad(deg_to_rad(rand(2)));
//根据粒子与爆炸中心的关系修改旋转值
if($pos.y<$epicentre.y)$rx*=-1;
if($pos.x<$epicentre.x)$ry*=-1;
//根据粒子与爆炸中心的距离关系修改旋转速度
float $distNorm=1-smoothstep(0,10,$distToCentre);
//最后为粒子指定旋转值
rotXPP=$rx*$distNorm*$mass;
rotXPP=$ry*$distNorm*$mass;
rotXPP=$rz*$distNorm*$mass;
}//假的创建表达式结束
3.现在我们继续给增加运行表达式
//获取旋转值
vector $rot=rotationPP;
//获取速度
//确保当粒子没有移动的时候不旋转。
magVelPP=smoothstep(1,30,mag(velocity));
//加上现有的旋转值
float $rx=$rot.x+rotXPP*magVelPP;
float $ry=$rot.y+rotYPP*magVelPP;
float $rz=$rot.z+rotZPP*magVelPP;
//指定给旋转值
rotationPP=<<$rx,$ry,$rz>>;
在粒子的属性中找到instance栏,将rotation改成rotationPP现在重新播放,可以看到砖块旋转着飞出去了。
[page]
下面我们来做砖块被炸碎的效果
首先打开2_3_instance_brickwall_broken_base.mb文件,打开outline可以看到,我们已经准备好了,20种破碎成不同形态的砖,每块砖都把它碎成了几块。
每个碎块的轴心点都在它的中心。并不在世界坐标的中心,如果让这些碎块的轴心都处于世界坐标的中心,可能设置起来会容易一些,可是在我们这个效果中旋转起来会出现问题。这就是为什么让他们的轴心点都处在自身中心的原因。
[page]
我给大家提供一个脚本,它能使这些碎砖instance好的砖块:emit_brick_chunks.mel
1.首先我们选择所有的碎块,我们使用我已经提供的一个脚本assign_brick_ids.mel,在命令行输入
source assign_brick_ids.mel;assign_brick_ids();
assign_brick_ids.mel (0.64k)
2.接下来,我们选择一个砖测量它与爆炸中心的距离。靠近爆炸中心的将会被炸成小碎片。如果你注意观察了,你就会发现名称代号小的是碎块多的砖,名称代号大的是碎块少的砖。我们将在距离爆炸中心近的地方用一些碎块多的砖,远的地方用一些碎块少的砖。实现这个效果的所有代码都在emit_brick_chunks.mel。
emit_brick_chunks.mel (4.3k)
global proc emit_brick_chunks() {
// all nodes under which bricks sit
string $nodeList[] = { "o_frontWall_LOC", "i_frontWall_LOC", "side_frontWall_LOC" };
// 初始化变量
vector $epicentre = f2v( `xform -q -ws -t epicentre_LOC` );
string $brickName = "brokenBrick";
// 循环便历所有节点
for( $node in $nodeList ) {
// 显示出状态
print( "// WORKING on group "+$node+"...\n" );
// 列出所有的子项
string $children[] = `listRelatives -c -type transform $node`;
//为朝向旁边的砖进行一个不同的设置.
// 可以在 'brokenBricksR_LOC'下找到
if( $node == "side_frontWall_LOC" )
$brickName = "brokenBrickR";
// 循环遍历每一块砖
for( $brick in $children ) {
// 确定距离
vector $brickPos = f2v( `xform -q -ws -t $brick` );
float $dist = mag( $brickPos - $epicentre );
// 选择一块砖
// 我们需要选择一块砖. 有20块。
// 根据距爆炸中心的距离
// 按大块和小块分布
float $minDist = 1.4;
float $maxDist = 9.1;
float $diffDist = $maxDist - $minDist;
// determine normalized distance
float $normDist = linstep( $minDist, $maxDist, $dist );
float $weight = $dist / $diffDist;
float $gauss = gauss( $weight );
$gauss = abs( $gauss ) * 20;
$gauss = int( $gauss );
$gauss = clamp( 1, 20, $gauss );
// $brickIndex 是实际已经选择的砖
int $brickIndex = $gauss;
// 在每个碎块的中心发射粒子
//列出所有的碎块
string $chunks[] = `listRelatives -c -type transform ($brickName+$brickIndex+"_LOC")`;
// 得到砖块的世界坐标位置
vector $brickPos = f2v( `xform -q -ws -t $brick` );
// 设置发射器
string $emitStr = "emit -object wall_front_PTL ";
// 在每个碎块上建一个发射器
for ($chunk in $chunks) {
// 得到每一个碎块的index号 -- 我们用这个设置粒子的objectIndexPP
int $chunkIndex = `getAttr ($chunk+".index")`;
// 得到每一个碎块的世界坐标位置.
vector $chunkPos = f2v( `xform -q -ws -t $chunk` );
// 碎块的位置加上砖的位置
$chunkPos += $brickPos;
// 确定每一个碎块的体积用'bb_calculateBB'
float $vol = `bb_calculateBB( $chunk )`;
// 连接 $emitStr
$emitStr += "-pos "+$chunkPos.x+" "+$chunkPos.y+" "+$chunkPos.z+" ";
$emitStr += "-at initPosPP -vv "+$chunkPos.x+" "+$chunkPos.y+" "+$chunkPos.z+" ";
$emitStr += "-at scalePP -vv 1 1 1 ";
$emitStr += "-at rotationPP -vv 0 0 0 ";
$emitStr += "-at initMassPP -fv "+$vol+" ";
$emitStr += "-at mass -fv "+$vol+" ";
$emitStr += "-at objectIndexPP -fv "+$chunkIndex+" ";
$emitStr += "-at visibilityPP -fv 1 ";
} // for chunk in chunks
//在每一块上执行发射命令
eval($emitStr);
} // for brick
} // for node
// 将粒子保存为初始状态
saveInitialState wall_front_PTL;
// 显示状态
print("// DONE emitting chunks.\n");
} // emit_brick_chunks
[page]
3.上面的操作让我们为每一个碎块的位置都放置了一个粒子。
4.然后我们对粒子进行instance,可以用objectIndexPP来做这个,首先选择砖块。select -r `ls -tr,rokenBrick*_*PLY"`。
5.particle>instancer>optionbox如下设置:
scale 设置为scalePP
visibility 设置为visibilityPP
objectIndex 设置为objectindexPP
rotation 设置为rotationPP
单击create 如果没有做错的话就已经好了,重新播放。