-
简介
这节课我们将使用osg实现一个简单的粒子系统。从NeHe的教程中可以看到,制作一个简单的粒子系统并没有想象中的那么困难,不过粒子系统涉及的细节计算还是有些繁琐的。粒子系统主要用来模拟三维场景中的雨雪、爆炸、火焰、喷泉等效果。主要是对大量的可绘制几何体(如简单的三角形、四边形、三角形条带等)进行它们位置、方向、显示时间(生命)的一些设置。
首先我们按照NeHe教程中,定义了一个粒子的结构体,代表场景中产生的一个粒子的参数:
- struct Particle
- {
- bool active; //是否是激活状态
- float life; //存活时间
- float fade; //消失的速度
- float r;
- float g;
- float b;
- float x;
- float y;
- float z;
- float xi;
- float yi;
- float zi;
- float xg;
- float yg;
- float zg;
- };
- for (int i=0; i<MaxParticles; i++)
- {
- particles[i].active=true;
- particles[i].life=1.0f;
- particles[i].fade=float(rand()%100)/1000.0f+0.003f;
- particles[i].r=colors[int(i*(12.0/MaxParticles))][0];
- particles[i].g=colors[int(i*(12.0/MaxParticles))][1];
- particles[i].b=colors[int(i*(12.0/MaxParticles))][2];
- particles[i].xi=float((rand()%50)-26.0f)*10.0f;
- particles[i].yi=float((rand()%50)-25.0f)*10.0f;
- particles[i].zi=float((rand()%50)-25.0f)*10.0f;
- particles[i].xg=0.0f;
- particles[i].yg=-0.8f;
- particles[i].zg=0.0f;
- }
粒子系统中的每一个粒子实际上就是一个简单的可绘制几何体(Geometry),设置它的各项参数如下:
- osg::Geometry *particle = new osg::Geometry();
- float x=particles[i].x;
- float y=particles[i].y;
- float z=particles[i].z+zoom;
- //设置顶点
- osg::Vec3Array *vertexArray = new osg::Vec3Array;
- vertexArray->push_back(osg::Vec3(x+0.5f,y+0.5f,z));
- vertexArray->push_back(osg::Vec3(x-0.5f,y+0.5f,z));
- vertexArray->push_back(osg::Vec3(x+0.5f,y-0.5f,z));
- vertexArray->push_back(osg::Vec3(x-0.5f,y-0.5f,z));
- //设置纹理坐标
- osg::Vec2Array *texArray = new osg::Vec2Array;
- texArray->push_back(osg::Vec2(1,1));
- texArray->push_back(osg::Vec2(0,1));
- texArray->push_back(osg::Vec2(1,0));
- texArray->push_back(osg::Vec2(0,0));
- //设置颜色
- osg::Vec4Array *colorArray = new osg::Vec4Array;
- colorArray->push_back(osg::Vec4(particles[i].r,particles[i].g,particles[i].b,particles[i].life));
- colorArray->setBinding(osg::Array::BIND_OVERALL);
- //设置纹理
- osg::Texture2D *texture = new osg::Texture2D;
- texture->setImage(osgDB::readImageFile("Data/Particle.bmp"));
- texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- particle->setVertexArray(vertexArray);
- particle->setTexCoordArray(0, texArray);
- particle->setColorArray(colorArray);
- particle->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, 4));
- particle->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture);
- osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE);
- particle->getOrCreateStateSet()->setAttributeAndModes(blendFunc,osg::StateAttribute::ON);
- particle->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
- particle->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
- particle->setUseDisplayList(false);
- particle->setUpdateCallback(new ParticleDrawableCallback(i));
在每一帧中需要更新粒子的位置和颜色等参数,我们设置一个Geode的更新回调用来完成对所有粒子参数的更新
- class ParticleUpdateCallback : public osg::NodeCallback
- class ParticleDrawableCallback : public osg::Drawable::UpdateCallback
- {
- public:
- ParticleDrawableCallback(int index) : _index(index){ }
最后在ParticleEventHandler之中完成粒子系统中一些参数的交互,这部分和NeHe教程中是一样的:
- class ParticleEventHandler : public osgGA::GUIEventHandler
- {
编译运行程序:
附:本课完整代码(代码中可能存在着错误和不足,仅供参考)
- #include "../osgNeHe.h"
- #include <QtCore/QTimer>
- #include <QtGui/QApplication>
- #include <QtGui/QVBoxLayout>
- #include <osgViewer/Viewer>
- #include <osgDB/ReadFile>
- #include <osgQt/GraphicsWindowQt>
- #include <osg/MatrixTransform>
- #include <osg/Texture2D>
- #include <osg/BlendFunc>
- #include <osg/PrimitiveSet>
- //
- const int MaxParticles = 1000;
- float slowdown = 2.0f;
- float xspeed;
- float yspeed;
- float zoom = -40.0f;
- GLuint delay;
- int col;
- struct Particle
- {
- bool active; //是否是激活状态
- float life; //存活时间
- float fade; //消失的速度
- float r;
- float g;
- float b;
- float x;
- float y;
- float z;
- float xi;
- float yi;
- float zi;
- float xg;
- float yg;
- float zg;
- };
- Particle particles[MaxParticles];
- static GLfloat colors[12][3] =
- {
- {1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
- {0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
- {0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
- };
- //
- //Particle Manipulator
- class ParticleEventHandler : public osgGA::GUIEventHandler
- {
- public:
- ParticleEventHandler(){}
- virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
- {
- osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
- if (!viewer)
- return false;
- if (!viewer->getSceneData())
- return false;
- if (ea.getHandled())
- return false;
- osg::Group *root = viewer->getSceneData()->asGroup();
- switch(ea.getEventType())
- {
- case(osgGA::GUIEventAdapter::KEYDOWN):
- {
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_8)
- {
- for(int i = 0; i < MaxParticles; ++i)
- if ((particles[i].yg<1.5f))
- particles[i].yg+=0.01f;
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_2)
- {
- for(int i = 0; i < MaxParticles; ++i)
- if ((particles[i].yg>-1.5f))
- particles[i].yg-=0.01f;
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_6)
- {
- for(int i = 0; i < MaxParticles; ++i)
- if (particles[i].xg<1.5f)
- particles[i].xg+=0.01f;
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_4)
- {
- for(int i = 0; i < MaxParticles; ++i)
- if (particles[i].xg>-1.5f)
- particles[i].xg-=0.01f;
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Tab)
- {
- for(int i = 0; i < MaxParticles; ++i)
- {
- particles[i].x=0.0f;
- particles[i].y=0.0f;
- particles[i].z=0.0f;
- particles[i].xi=float((rand()%50)-26.0f)*10.0f;
- particles[i].yi=float((rand()%50)-25.0f)*10.0f;
- particles[i].zi=float((rand()%50)-25.0f)*10.0f;
- }
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Page_Up)
- {
- zoom += 0.1;
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Page_Down)
- {
- zoom -= 0.1;
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Plus)
- {
- if (slowdown>1.0f)
- slowdown-=0.01f;
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Minus)
- {
- if (slowdown<4.0f)
- slowdown+=0.01f;
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Up)
- {
- if ((yspeed<200))
- yspeed+=1.0f;
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Down)
- {
- if ((yspeed>-200))
- yspeed-=1.0f;
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Left)
- {
- if ((xspeed>-200))
- xspeed-=1.0f;
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Right)
- {
- if ((xspeed<200))
- xspeed+=1.0f;
- }
- }
- default: break;
- }
- return false;
- }
- };
- class ViewerWidget : public QWidget, public osgViewer::Viewer
- {
- public:
- ViewerWidget(osg::Node *scene = NULL)
- {
- QWidget* renderWidget = getRenderWidget( createGraphicsWindow(0,0,100,100), scene);
- QVBoxLayout* layout = new QVBoxLayout;
- layout->addWidget(renderWidget);
- layout->setContentsMargins(0, 0, 0, 1);
- setLayout( layout );
- connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) );
- _timer.start( 10 );
- }
- QWidget* getRenderWidget( osgQt::GraphicsWindowQt* gw, osg::Node* scene )
- {
- osg::Camera* camera = this->getCamera();
- camera->setGraphicsContext( gw );
- const osg::GraphicsContext::Traits* traits = gw->getTraits();
- camera->setClearColor( osg::Vec4(0.0, 0.0, 0.0, 0.0) );
- camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
- camera->setProjectionMatrixAsPerspective(45.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 0.1f, 100.0f );
- camera->setViewMatrixAsLookAt(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, 0), osg::Vec3d(0, 1, 0));
- this->setSceneData( scene );
- addEventHandler(new ParticleEventHandler);
- return gw->getGLWidget();
- }
- osgQt::GraphicsWindowQt* createGraphicsWindow( int x, int y, int w, int h, const std::string& name="", bool windowDecoration=false )
- {
- osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
- osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
- traits->windowName = name;
- traits->windowDecoration = windowDecoration;
- traits->x = x;
- traits->y = y;
- traits->width = w;
- traits->height = h;
- traits->doubleBuffer = true;
- traits->alpha = ds->getMinimumNumAlphaBits();
- traits->stencil = ds->getMinimumNumStencilBits();
- traits->sampleBuffers = ds->getMultiSamples();
- traits->samples = ds->getNumMultiSamples();
- return new osgQt::GraphicsWindowQt(traits.get());
- }
- virtual void paintEvent( QPaintEvent* event )
- {
- frame();
- }
- protected:
- QTimer _timer;
- };
- //
- //Particle Geode's UpdateCallback
- class ParticleUpdateCallback : public osg::NodeCallback
- {
- public:
- virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
- {
- for(int i = 0; i < MaxParticles; ++i)
- {
- particles[i].x+=particles[i].xi/(slowdown*1000);
- particles[i].y+=particles[i].yi/(slowdown*1000);
- particles[i].z+=particles[i].zi/(slowdown*1000);
- particles[i].xi+=particles[i].xg;
- particles[i].yi+=particles[i].yg;
- particles[i].zi+=particles[i].zg;
- particles[i].life-=particles[i].fade;
- if (particles[i].life<0.0f)
- {
- if (col > 11)
- {
- col = 0;
- }
- ++col;
- particles[i].life=1.0f;
- particles[i].fade=float(rand()%100)/1000.0f+0.003f;
- particles[i].x=0.0f;
- particles[i].y=0.0f;
- particles[i].z=0.0f;
- particles[i].xi=xspeed+float((rand()%60)-32.0f);
- particles[i].yi=yspeed+float((rand()%60)-30.0f);
- particles[i].zi=float((rand()%60)-30.0f);
- particles[i].r=colors[col][0];
- particles[i].g=colors[col][1];
- particles[i].b=colors[col][2];
- }
- }
- }
- };
- //
- //ParticleDrawableCallback
- class ParticleDrawableCallback : public osg::Drawable::UpdateCallback
- {
- public:
- ParticleDrawableCallback(int index) : _index(index){ }
- virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
- {
- osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable);
- if (!geometry)
- {
- return;
- }
- osg::Vec4Array *colorArray = dynamic_cast<osg::Vec4Array*>(geometry->getColorArray());
- if (colorArray)
- {
- colorArray->clear();
- colorArray->push_back(osg::Vec4(particles[_index].r,particles[_index].g,particles[_index].b,particles[_index].life));
- colorArray->dirty();
- }
- osg::Vec3Array *vertexArray = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
- if (vertexArray)
- {
- float x=particles[_index].x;
- float y=particles[_index].y;
- float z=particles[_index].z+zoom;
- vertexArray->clear();
- vertexArray->push_back(osg::Vec3(x+0.5f,y+0.5f,z));
- vertexArray->push_back(osg::Vec3(x-0.5f,y+0.5f,z));
- vertexArray->push_back(osg::Vec3(x+0.5f,y-0.5f,z));
- vertexArray->push_back(osg::Vec3(x-0.5f,y-0.5f,z));
- vertexArray->dirty();
- }
- }
- int _index;
- };
- ///
- ///
- osg::Node* buildScene()
- {
- for (int i=0; i<MaxParticles; i++)
- {
- particles[i].active=true;
- particles[i].life=1.0f;
- particles[i].fade=float(rand()%100)/1000.0f+0.003f;
- particles[i].r=colors[int(i*(12.0/MaxParticles))][0];
- particles[i].g=colors[int(i*(12.0/MaxParticles))][1];
- particles[i].b=colors[int(i*(12.0/MaxParticles))][2];
- particles[i].xi=float((rand()%50)-26.0f)*10.0f;
- particles[i].yi=float((rand()%50)-25.0f)*10.0f;
- particles[i].zi=float((rand()%50)-25.0f)*10.0f;
- particles[i].xg=0.0f;
- particles[i].yg=-0.8f;
- particles[i].zg=0.0f;
- }
- osg::Geode *particleGeode = new osg::Geode;
- for (int i = 0; i < MaxParticles; ++i)
- {
- osg::Geometry *particle = new osg::Geometry();
- float x=particles[i].x;
- float y=particles[i].y;
- float z=particles[i].z+zoom;
- //设置顶点
- osg::Vec3Array *vertexArray = new osg::Vec3Array;
- vertexArray->push_back(osg::Vec3(x+0.5f,y+0.5f,z));
- vertexArray->push_back(osg::Vec3(x-0.5f,y+0.5f,z));
- vertexArray->push_back(osg::Vec3(x+0.5f,y-0.5f,z));
- vertexArray->push_back(osg::Vec3(x-0.5f,y-0.5f,z));
- //设置纹理坐标
- osg::Vec2Array *texArray = new osg::Vec2Array;
- texArray->push_back(osg::Vec2(1,1));
- texArray->push_back(osg::Vec2(0,1));
- texArray->push_back(osg::Vec2(1,0));
- texArray->push_back(osg::Vec2(0,0));
- //设置颜色
- osg::Vec4Array *colorArray = new osg::Vec4Array;
- colorArray->push_back(osg::Vec4(particles[i].r,particles[i].g,particles[i].b,particles[i].life));
- colorArray->setBinding(osg::Array::BIND_OVERALL);
- //设置纹理
- osg::Texture2D *texture = new osg::Texture2D;
- texture->setImage(osgDB::readImageFile("Data/Particle.bmp"));
- texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- particle->setVertexArray(vertexArray);
- particle->setTexCoordArray(0, texArray);
- particle->setColorArray(colorArray);
- particle->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, 4));
- particle->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture);
- osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE);
- particle->getOrCreateStateSet()->setAttributeAndModes(blendFunc,osg::StateAttribute::ON);
- particle->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
- particle->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
- particle->setUseDisplayList(false);
- particle->setUpdateCallback(new ParticleDrawableCallback(i));
- particleGeode->addDrawable(particle);
- }
- particleGeode->addUpdateCallback(new ParticleUpdateCallback);
- osg::Group *root = new osg::Group;
- root->addChild(particleGeode);
- return root;
- }
- int main( int argc, char** argv )
- {
- QApplication app(argc, argv);
- ViewerWidget* viewWidget = new ViewerWidget(buildScene());
- viewWidget->setGeometry( 100, 100, 640, 480 );
- viewWidget->show();
- return app.exec();
- }