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

  • 简介

这节课我们将使用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;
};
同样在初始化的时候给所有的粒子(1000个)一些初始值

	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){ }
每一个粒子的序号绑定到_index之上

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

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

编译运行程序:

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


你可能感兴趣的:(C++,qt,OpenGL,nehe,OSG)