用OpenSceneGraph实现的NeHe OpenGL教程 - 第十九课_附录1

时间:2022-09-21 16:08:08
  • 简介

在第十九课中我们使用NeHe教程中的方式实现了一个简单的粒子效果,在osg中也提供了一个专门处理粒子系统的库---osgParticle,下面我们就用osgParticle库来实现同样的效果。

  • 实现

在具体实现之前,我们先了解一下osgParticle,osgParticle的粒子系统主要的模块包括如下部分:


  • ParticleSystem粒子系统类,继承自osg::Drawable可绘制对象,用来管理粒子的绘制,它包含一个Particle的数组,Particle可以理解为很多粒子中其中的一个。

ParticleSystem利用粒子的模板来产生大量的粒子

用OpenSceneGraph实现的NeHe OpenGL教程 - 第十九课_附录1

  • Particle类,刚才已经解释了,Particle可以看做ParticleSystem的一个模板,ParticleSystem依据它产生大量的粒子
  • Emitter类:它包含三个部分(一个组合类):即Shooter(发射器:发射出粒子的速度和方向),Counter(计数器:每帧发射多少粒子)以及Placer(放置器:决定粒子初始位置在哪儿)
  • Program类:Program类里面包含一个Operator类的数组,用来存放一系列的操作,在对粒子进行渲染之前、渲染中以及渲染后都可以进行一些设置,包括粒子的方方面面(颜色、位置、生命、加速度等等),非常的灵活。我们需要自定义粒子的一些操作实际上是继承Operator类并重写里面的operator函数来完成粒子的一些自定义操作

粒子系统的基本操作过程可以参考osg示例解析之osgParticle,里面对粒子系统的设置过程进行了说明。

同样我们还是需要定义粒子结构体用来更新数据:

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;
};
设置particles的初始值与之前的代码一样

	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;	
	}
因为我们需要控制粒子的整个运行过程(产生的1000个粒子需要一直让我们来操作,这些粒子不会增加也不会减少,因此我把粒子的生命设置为永远"存活",并且当粒子数量达到1000个之后,就不在发射粒子(Counter设置为0)),这与 osgParticle示例中的方式不一样,osgParticle会一直产生粒子。

	//设置粒子模板
	osgParticle::Particle particleTemplate;
	particleTemplate.setShape(osgParticle::Particle::QUAD_TRIANGLESTRIP);
	//负值表示粒子永远不会"死亡"
	particleTemplate.setLifeTime(-1);
	particleTemplate.setPosition(osg::Vec3(0.0, 0.0, 0.0));
	particleTemplate.setRadius(1.0f);
	//设置粒子系统的光照,纹理等(与普通的Drawable设置一样)
	osgParticle::ParticleSystem *particleSystem = new osgParticle::ParticleSystem();
	particleSystem->setDefaultAttributes("Data/Particle.bmp", false, false);
	osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE);
	particleSystem->getOrCreateStateSet()->setAttributeAndModes(blendFunc,osg::StateAttribute::ON);
	particleSystem->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
	particleSystem->setDefaultParticleTemplate(particleTemplate);
设置Emitter的各个参数:

	osgParticle::ModularEmitter *emitter = new osgParticle::ModularEmitter;
	emitter->setParticleSystem(particleSystem);

	osgParticle::PointPlacer *placer = new osgParticle::PointPlacer;
	placer->setCenter(0, 0, 0);
	emitter->setPlacer(placer);
	
	osgParticle::RandomRateCounter *counter = new osgParticle::RandomRateCounter;
	counter->setRateRange(1000, 1000);
	emitter->setCounter(counter);
	g_counter = counter; //为了设置1000个粒子之后停止发射

	osgParticle::RadialShooter *shooter = new osgParticle::RadialShooter;
	emitter->setShooter(shooter);
设置Program来定义自定义的操作

	osgParticle::ModularProgram *program = new osgParticle::ModularProgram;
	program->setParticleSystem(particleSystem);

	ColorOption *co = new ColorOption;
	program->addOperator(co);
在ColorOption中完成了对所有粒子的位置和颜色的更新:(更新方式使用NeHe教程中的方式)

void beginOperate(osgParticle::Program *pro)
最后把粒子系统添加到一个Group节点中并设置到场景里面:

	osg::Geode *particleGeode = new osg::Geode;
	particleGeode->addDrawable(particleSystem);

	osg::Group *particleEffect = new osg::Group;
	particleEffect->addChild(emitter);
	particleEffect->addChild(particleGeode);
	particleEffect->addChild(program);
	particleEffect->addChild(psu);
当场景中的粒子数量到达1000之后,停止产生新的粒子:

		osgParticle::ParticleSystem *ps = pro->getParticleSystem();
		if (ps->numParticles() >= MaxParticles)
		{
			g_counter->setRateRange(0,0);
		}

键盘的交互部分和第十九课中的代码类似,详细部分参看本课的完整代码。

编译运行程序:

用OpenSceneGraph实现的NeHe OpenGL教程 - 第十九课_附录1

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

#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/BlendFunc>

// osgParticle Related Header Files
#include <osgParticle/Particle>
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/ModularEmitter>
#include <osgParticle/ModularProgram>
#include <osgParticle/RandomRateCounter>
#include <osgParticle/PointPlacer>
#include <osgParticle/SectorPlacer>
#include <osgParticle/RadialShooter>
#include <osgParticle/AccelOperator>
#include <osgParticle/FluidFrictionOperator>


//////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////
const int MaxParticles = 1000;
float slowdown = 2.0f;
float xspeed;
float yspeed;
float zoom = -40.0f;
GLuint delay;
int col;
bool g_restart = false;

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][4] =
{
	{1.0f,0.5f,0.5f, 1.0f},{1.0f,0.75f,0.5f, 1.0f},{1.0f,1.0f,0.5f, 1.0f},{0.75f,1.0f,0.5f, 1.0f},
	{0.5f,1.0f,0.5f, 1.0f},{0.5f,1.0f,0.75f, 1.0f},{0.5f,1.0f,1.0f, 1.0f},{0.5f,0.75f,1.0f, 1.0f},
	{0.5f,0.5f,1.0f, 1.0f},{0.75f,0.5f,1.0f,1.0f},{1.0f,0.5f,1.0f, 1.0f},{1.0f,0.5f,0.75f, 1.0f}
};



//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
//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;

		switch(ea.getEventType())
		{
		case(osgGA::GUIEventAdapter::KEYDOWN):
			{
				if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Tab)
				{	
					g_restart = true;
				}

				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;
};

osgParticle::RandomRateCounter *g_counter = NULL;
//////////////////////////////////////////////////////////////////////////

class ColorOption : public osgParticle::Operator
{
public:
	ColorOption() 
		: osgParticle::Operator(){}

	ColorOption(const ColorOption &copy, const osg::CopyOp &copyop = osg::CopyOp::SHALLOW_COPY)
		: osgParticle::Operator(copy, copyop){}

	META_Object(osgParticle, ColorOption);

	void beginOperate(osgParticle::Program *pro)
	{
		osgParticle::ParticleSystem *ps = pro->getParticleSystem();
		if(!ps)
			return;

		if (ps->numParticles() >= MaxParticles)
		{
			g_counter->setRateRange(0,0);
		}
		//如果按下Tab键
		if (g_restart) {
			osgParticle::ParticleSystem *ps = pro->getParticleSystem();
			if (!ps)
				return;
			
			for (unsigned i = 0; i < ps->numParticles(); ++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;
			}

			g_restart = false;
		} else {

			for (int i = 0; i < ps->numParticles(); ++i)
			{
				float x=particles[i].x;	
				float y=particles[i].y;
				float z=particles[i].z+zoom;
				ps->getParticle(i)->setPosition(osg::Vec3(x,y,z));
				osg::Vec4 color(particles[i].r,particles[i].g,particles[i].b,particles[i].life);
				ps->getParticle(i)->setColorRange(osgParticle::rangev4(color, color));

				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)
				{
					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];
				}
			}
		}
	}

	void operate (osgParticle::Particle *P, double dt)
	{
		
	}
};


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;	
	}

	//////////////////////////////////////////////////////////////////////////

	//设置粒子模板
	osgParticle::Particle particleTemplate;
	particleTemplate.setShape(osgParticle::Particle::QUAD_TRIANGLESTRIP);
	//负值表示粒子永远不会"死亡"
	particleTemplate.setLifeTime(-1);
	particleTemplate.setPosition(osg::Vec3(0.0, 0.0, 0.0));
	particleTemplate.setRadius(1.0f);
	
	//设置粒子系统
	osgParticle::ParticleSystem *particleSystem = new osgParticle::ParticleSystem();
	particleSystem->setDefaultAttributes("Data/Particle.bmp", false, false);
	osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE);
	particleSystem->getOrCreateStateSet()->setAttributeAndModes(blendFunc,osg::StateAttribute::ON);
	particleSystem->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
	particleSystem->setDefaultParticleTemplate(particleTemplate);
	
	osgParticle::ModularEmitter *emitter = new osgParticle::ModularEmitter;
	emitter->setParticleSystem(particleSystem);

	osgParticle::PointPlacer *placer = new osgParticle::PointPlacer;
	placer->setCenter(0, 0, 0);
	emitter->setPlacer(placer);
	
	osgParticle::RandomRateCounter *counter = new osgParticle::RandomRateCounter;
	counter->setRateRange(1000, 1000);
	emitter->setCounter(counter);
	g_counter = counter;

	osgParticle::RadialShooter *shooter = new osgParticle::RadialShooter;
	emitter->setShooter(shooter);

	osgParticle::ModularProgram *program = new osgParticle::ModularProgram;
	program->setParticleSystem(particleSystem);

	ColorOption *co = new ColorOption;
	program->addOperator(co);

	osgParticle::ParticleSystemUpdater *psu = new osgParticle::ParticleSystemUpdater;
	psu->addParticleSystem(particleSystem);

	osg::Geode *particleGeode = new osg::Geode;
	particleGeode->addDrawable(particleSystem);

	osg::Group *particleEffect = new osg::Group;
	particleEffect->addChild(emitter);
	particleEffect->addChild(particleGeode);
	particleEffect->addChild(program);
	particleEffect->addChild(psu);

	osg::Group *root = new osg::Group;
	root->addChild(particleEffect);

	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();
}