向场景中添加自定义的osgParticle实例,模拟坦克模型在地形上运动时产生的烟尘。
-----------------------------
添加粒子效果可以有效提高仿真程序的外观和真实性。粒子引擎一般用于模拟烟雾,火焰,尘埃以及其他一些类似的效果。如果要向OSG场景中添加粒子效果,通常可以使用下面的一些类:
粒子系统(osgParticle::ParticleSystem)- 维护并管理一系列粒子的生成,更新,渲染和销毁。粒子系统类继承自Drawable类。粒子的渲染控制因此与其它Drawable对象的渲染类似:控制其渲染属性StateAttribute即可。OSG提供了一个方便的函数以允许用户控制三个常用的渲染状态属性。方法setDefaultAttributes可以用于指定材质(或者指定为NULL以禁用材质),允许/禁止附加的图像融合,允许/禁止光照。
粒子(osgParticle::Particle)- 粒子系统的基本单元。粒子类同时具有物理属性和图像属性。它的形状可以是任意的点(POINT),四边形(QUAD),四边形带(QUAD_TRIPSTRIP),六角形(HEXAGON)或者线(LINE)。每个粒子都有自己的生命周期。生命周期也就是每个粒子可以存活的秒数。(生命周期为负数的粒子可以存活无限长时间)所有的粒子都具有大小(SIZE),ALPHA值和颜色(COLOR)属性。每一组粒子都可以指定其最大和最小值。为了便于粒子生命周期的管理,粒子系统通过改变生命周期的最大和最小值来控制单个粒子的渲染。(根据已经消耗的时间,在最小和最大值之间进行线性插值)
程序员也可以自行指定最小到最大值的插值方法。(参见osgParticle::Interpolator的代码)
放置器(osgParticle::Placer)- 设置粒子的初始位置。用户可以使用预定义的放置器或者定义自己的放置器。已定义的放置器包括:点放置器PointPlacer(所有的粒子从同一点出生),扇面放置器SectorPlacer(所有的粒子从一个指定中心点,半径范围和角度范围的扇面出生),以及多段放置器MultiSegmentPlacer(用户指定一系列的点,粒子沿着这些点定义的线段出生)。
发射器(osgParticle::Shooter)- 指定粒子的初始速度。RadialShooter类允许用户指定一个速度范围(米/秒)以及弧度值表示的方向。方向由两个角度指定:theta角 - 与Z轴夹角,phi角 - 与XY平面夹角。
计数器(osgParticle::Counter)- 控制每一帧产生的粒子数。RandomRateCounter类允许用户指定每帧产生粒子的最大和最小数目。
标准放射极(osgParticle::ModularEmitter)- 一个标准放射极包括一个计数器,一个放置器和一个发射器。它为用户控制粒子系统中多个元素提供了一个标准机制。
粒子系统更新器(osgParticle::ParticleSystemUpdater)- 用于自动更新粒子。将其置于场景中时,它会在拣选遍历中调用所有“存活”粒子的更新方法。
标准编程器(osgParticle::ModularProgram)- 在单个粒子的生命周期中,用户可以使用ModularProgram实例控制粒子的位置。ModularProgram需要与Operator对象组合使用。
计算器(osgParticle::Operator)- 提供了控制粒子在其生命周期中的运动特性的方法。用户可以改变现有Operator类实例的参数,或者定义自己的Operator类。OSG提供的Operator类包括:AccelOperator(应用常加速度),AngularAccelOperator(应用常角加速度),FluidFrictionOperator(基于指定密度和粘性的流体运动进行计算),以及ForceOperator(应用常力)。
代码:
为了使用上面的类创建高度自定义化的粒子系统,我们可以遵循以下的步骤。(括号中的步骤是可选的,如果所建立的粒子系统比较基本,也可以忽略)
下面的代码将完成上述的步骤。首先,我们需要建立基本的坦克和地形模型。(稍后我们再添加坦克模型到场景中)
osg::Group* rootNode = new osg::Group();
osg::Node* terrainNode = new osg::Node();
osgViewer::Viewer viewer;
terrainNode = osgDB::readNodeFile("//Models//JoeDirt//JoeDirt.flt");
if (! terrainNode)
{
std::cout << "Couldn't load models, quitting." << std::endl;
return -1;
}
rootNode->addChild(terrainNode);
osg::Node* tankNode = osgDB::readNodeFile("//Models//T72-Tank//T72-tank_des.flt");
if ( ! tankNode)
{
std::cout << "no tank" << std::endl;
return -1;
}
建立粒子系统的基本步骤是:创建一个粒子系统对象,一个更新器对象,以及一个粒子对象,同时设置场景。
// 创建并初始化粒子系统。
osgParticle::ParticleSystem *dustParticleSystem = new osgParticle::ParticleSystem;
// 设置材质,是否放射粒子,以及是否使用光照。
dustParticleSystem->setDefaultAttributes(dust2.rgb", false, false);
// 由于粒子系统类继承自Drawable类,因此我们可以将其作为Geode的子节点加入场景。
osg::Geode *geode = new osg::Geode;
rootNode->addChild(geode);
geode->addDrawable(dustParticleSystem);
// 添加更新器,以实现每帧的粒子管理。
osgParticle::ParticleSystemUpdater *dustSystemUpdater = new osgParticle::ParticleSystemUpdater;
// 将更新器与粒子系统对象关联。
dustSystemUpdater->addParticleSystem(dustParticleSystem);
// 将更新器节点添加到场景中。
rootNode->addChild(dustSystemUpdater);
// 创建粒子对象,设置其属性并交由粒子系统使用。
osgParticle::Particle smokeParticle;
smokeParticle.setSizeRange(osgParticle::rangef(0.01,20.0)); // 单位:米
smokeParticle.setLifeTime(4); // 单位:秒
smokeParticle.setMass(0.01); // 单位:千克
// 设置为粒子系统的缺省粒子对象。
dustParticleSystem->setDefaultParticleTemplate(smokeParticle);
下面的代码将使用标准放射极对象来设置粒子的一些基本参数:例如每帧创建的粒子数,新生粒子的产生位置,以及新生粒子的速度。
// 创建标准放射极对象。(包括缺省的计数器,放置器和发射器)
osgParticle::ModularEmitter *emitter = new osgParticle::ModularEmitter;
// 将放射极对象与粒子系统关联。
emitter->setParticleSystem(dustParticleSystem);
// 获取放射极中缺省计数器的句柄,调整每帧增加的新粒子数目。
osgParticle::RandomRateCounter *dustRate =
static_cast<osgParticle::RandomRateCounter *>(emitter->getCounter());
dustRate->setRateRange(5, 10); // 每秒新生成5到10个新粒子。
// 自定义一个放置器,这里我们创建并初始化一个多段放置器。
osgParticle::MultiSegmentPlacer* lineSegment = new osgParticle::MultiSegmentPlacer();
// 向放置器添加顶点,也就是定义粒子产生时所处的线段位置。
// (如果将坦克和标准放射极定义与同一位置,那么我们可以实现一种
// 灰尘粒子从坦克模型后下方的延长线上产生的效果。)
lineSegment->addVertex(0,0,-2);
lineSegment->addVertex(0,-2,-2);
lineSegment->addVertex(0,-16,0);
// 为标准放射极设置放置器。
emitter->setPlacer(lineSegment);
// 自定义一个发射器,这里我们创建并初始化一个RadialShooter弧度发射器。
osgParticle::RadialShooter* smokeShooter = new osgParticle::RadialShooter();
// 设置发射器的属性。
smokeShooter->setThetaRange(0.0, 3.14159/2); // 弧度值,与Z轴夹角。
smokeShooter->setInitialSpeedRange(50,100); // 单位:米/秒
// 为标准放射极设置发射器。
emitter->setShooter(smokeShooter);
现在我们将把放射极和坦克模型作为Transform变换节点的子节点添加到场景中。放射极和坦克均由变换节点决定其位置。刚才定义的放置器将会根据变换的参量安排粒子的位置。
osg::MatrixTransform * tankTransform = new osg::MatrixTransform();
tankTransform->setUpdateCallback( new orbit() ); // 回调函数,使节点环向运动。
// 把放射极和坦克模型添加为变换节点的子节点。
tankTransform->addChild(emitter);
tankTransform->addChild(tankNode);
rootNode->addChild(tankTransform);
下面的代码将创建一个标准编程器ModularProgram实例,用于控制粒子在生命周期中的更新情况。标准编程器对象使用Operator计算器来实现对粒子的控制。
// 创建标准编程器对象并与粒子系统相关联。
osgParticle::ModularProgram *moveDustInAir = new osgParticle::ModularProgram;
moveDustInAir->setParticleSystem(dustParticleSystem);
// 创建计算器对象,用于模拟重力的作用,调整其参数并添加给编程器对象。
osgParticle::AccelOperator *accelUp = new osgParticle::AccelOperator;
accelUp->setToGravity(-1); // 设置重力加速度的放缩因子。
moveDustInAir->addOperator(accelUp);
// 向编程器再添加一个计算器对象,用于计算空气阻力。
osgParticle::FluidFrictionOperator *airFriction = new osgParticle::FluidFrictionOperator;
airFriction->setFluidToAir();
moveDustInAir->addOperator(airFriction);
// 最后,将编程器添加到场景中。
rootNode->addChild(moveDustInAir);
下面的代码就是仿真循环的内容了。
viewer.setCameraManipulator(new osgGA::TrackballManipulator());
viewer.setSceneData(rootNode);
viewer.realize();
while( !viewer.done() )
{
viewer.frame();
}
return 0;