虚拟现实中有很多效果,如雨效、雪效、雾效等,这些都可以通过粒子系统来实现。一个真实的粒子系统的模式能使三维场景达到更好的效果。
本章对OSG粒子系统的使用以及生成自定义粒子系统的方法进行了详细介绍最后还附带说明了阴影的使用方法。在实时的场景中,阴影是非常重要的,是一个很大的范畴,笔者也没有深入研究,因此,这里只是简单介绍一下。
粒子系统是一个非常复杂的粒子模拟过程。在 OSG中专门定义了新的名字空间 osgParticle 来处理粒子系统的模拟。
osgParticle 能够高效地模拟粒子系统,生成非常真实的效果。在OSG 预定义的粒子系统中,大部分的粒子系统模拟都采用的是 Billboard 与色彩融合技术生成粒子。Billboard 技术前面已经讲到过虽然它还存在很多问题,但是总体来说,效果还是非常不错的。色彩融合技术就是在渲染的过程中将各种颜色,如顶点颜色、光照颜色、质颜色和纹理颜色等按照 Alpha 值按一定的比例进行融合,以达到真实的效果。在本书自定义的粒子系统示例中,会向读者展示一个爆炸的效果,当然只是演示一个简单的技术,如果深入的话,还需要重新定义模块。
当打开粒子系统的文档时,读者会发现里面包含很多类,但很多类都是内部操作,在模拟一个粒子系统时,只需要使用其中的一部分就可以完成很好的模拟效果,具体使用的类如图11-1 所示。
11-1子系统成块
对于一个普通的粒子系统的模拟,可以用图 11-1 来显示主要模块。通过图 11-1 让读者明白一个粒子系统所需要的模块。下面分别介绍这些模块。
在OSG中除了这些粒子系统的主要模块以外,还包含其他的已经定义好的模块,如osgParticle::ExplosionDebrisEfect(爆炸碎片)、osgParticle::ExplosionEffect (爆炸模拟)、osgParticle::SmokeEfect(烟雾模拟)和 osgParticle::FireEffect(火光模拟)。
还有一个比较重要的类osgParticle::PrecipitationEfect,它是OSG定义的新类,用来模拟一些在OSG中已经定义好的粒子系统,如雨效和雪效,使用方法很简单,可以直接加入到场景中。
下面将介绍如何模拟一个真实的粒子系统。对于模拟粒子系统的过程可以分为两种,一是OSG中已经定义好的粒子系统模块,二是根据需要自定义粒子系统。预定义粒子系统模块模拟过程如下;
(1) 创建预定义粒子系统模块对象,设置相应的参数。
(2) 作为子节点加到场景节点中。从上面列举的子系统的关系继承图中可以看出,它们继承自osg::Node或osg::Group 节点,因此可以直接作为一个节点加入到场景中。
自定义粒子系统模拟过程如下:
(1)创建粒子系统(osgParticle::ParticleSystem),并将其加入到场景中,设置相应的属性,如材质、放射及光照。
(2)创建粒子模板(osgParticle::Particle),控制场景中每一个粒子的特性并关联到粒子系统,设置粒子模板对应的特性,如大小、颜色、生命周期及重量等。
(3)创建粒子系统放射器(osgParticle::ModularEmitter),标准的放射器包括计数器(Counter)、放置器(Placer)和发射器(Shooter)3 部分,设置相应的属性,如位置、形状、速度和方向等。
(4)创建粒子系统编程器对象(osgParticle::Program),控制粒子在声明周期内的运动。一个标准编程器对象包含各种操作器,如osgParticle::AccelOperator和osgParticle;:FluidFrictionOperator等。
(5)创建粒子系统更新器(osgParticle::ParticleSystemUpdater),用于管理每一帧的粒子的属性如位置、速度和方向等。
通过上面的步骤,可以完成一个简单的粒子系统的模拟。对于一般的需要而言是没有任何问题的。如果需要更高要求的,可以从shader 开始编写属于自己的粒子系统。
雾效其实并不是一种粒子系统,只是一种状态属性,放在这里来演示,因为它本身很像一种粒子系统。
雾效的管理主要是由osg::Fog来控制染的。osg::Fog类直接继承自osg::StateAttribute类继承关系图如图11-2所示。
图11-2 osg::Fog 的继承关系图
从继承关系图中可以看到,它继承自osg::StateAttribute类,因此它同样可以通过设置状态模式来控制雾效的开启或关闭,代码如下:
在OSG中,雾效有两种模式,可以通过下面的方式来获取或设置:
雾的坐标源也有两种,可以通过下面的方式来设置或获取:
雾的坐标源在使用固定管道的顶点处理时,雾效的值可以是眼坐标系中的y坐标值,也可以是经过插值的雾坐标,这是由雾的标源是设置成GL_FRAGMENT_DEPTH还是GL_FOG_COORDINATE决定的,在可编程管线中应用比较多。
雾效的特性还有颜色、浓度和起始位置等,可以调用下列类的成员函数来设置相应的特性:
雾效的特性已经都讲了,解释了雾效可能需要设置所有特性,下面来看一个简单的示例。
代码如程序清单11-1 所示。
// 创建雾效
osg::ref_ptr createFog(bool m_Linear)
{
// 创建Fog对象
osg::ref_ptr fog = new osg::Fog();
// 设置颜色
fog->setColor(osg::Vec4(1.0, 1.0, 1.0, 1.0));
// 设置浓度
fog->setDensity(0.01);
// 设置雾效模式为线性雾
if (!m_Linear)
{
fog->setMode(osg::Fog::LINEAR);
}
else// 设置雾效模式为全局零
{
fog->setMode(osg::Fog::EXP);
}
// 设置雾效近点浓度
fog->setStart(5.0);
// 设置雾效远点浓度
fog->setEnd(2000.0);
return fog.get();
}
void fog_11_1(const string &strDataFolder)
{
osg::ref_ptr viewer = new osgViewer::Viewer();
osg::ref_ptr traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr camera = viewer->getCamera();
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
osg::ref_ptr root = new osg::Group();
// 读取模型
string strDataPath = strDataFolder + "lz.osg";
osg::ref_ptr node = osgDB::readNodeFile(strDataPath);
root->addChild(node.get());
// 启用雾效
root->getOrCreateStateSet()->setAttributeAndModes(createFog(false), osg::StateAttribute::ON);
// 优化场景数据
osgUtil::Optimizer optimize;
optimize.optimize(root.get());
viewer->setSceneData(root.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图11-3所示
图11-3 雾效模拟示例截图