OpenSceneGraph实现的NeHe OpenGL教程 - 第十九课

    xiaoxiao2026-04-21  5

    简介

    这节课我们将使用osg实现一个简单的粒子系统。从NeHe的教程中可以看到,制作一个简单的粒子系统并没有想象中的那么困难,不过粒子系统涉及的细节计算还是有些繁琐的。粒子系统主要用来模拟三维场景中的雨雪、爆炸、火焰、喷泉等效果。主要是对大量的可绘制几何体(如简单的三角形、四边形、三角形条带等)进行它们位置、方向、显示时间(生命)的一些设置。

    实现

    首先我们按照NeHe教程中,定义了一个粒子的结构体,代表场景中产生的一个粒子的参数:

    [cpp]  view plain  copy   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;   };   同样在初始化的时候给所有的粒子(1000个)一些初始值

    [cpp]  view plain  copy   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),设置它的各项参数如下:

    [cpp]  view plain  copy   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的更新回调用来完成对所有粒子参数的更新

    [cpp]  view plain  copy   class ParticleUpdateCallback : public osg::NodeCallback   另外在绘制粒子的时候需要用到更新的新数据,我们在粒子对象的更新回调中完成它坐标位置、颜色的更新

    [cpp]  view plain  copy   class ParticleDrawableCallback : public osg::Drawable::UpdateCallback   {   public:          ParticleDrawableCallback(int index) : _index(index){ }   每一个粒子的序号绑定到_index之上

    最后在ParticleEventHandler之中完成粒子系统中一些参数的交互,这部分和NeHe教程中是一样的:

    [cpp]  view plain  copy   class ParticleEventHandler : public osgGA::GUIEventHandler   {   代码我只是贴出了一部分,详细的代码参考后面的完整代码部分。

    编译运行程序:

    附:本课完整代码(代码中可能存在着错误和不足,仅供参考)

    [cpp]  view plain  copy   #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;                   }               }           defaultbreak;           }           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();   }  
    转载请注明原文地址: https://ju.6miu.com/read-1309089.html
    最新回复(0)