用OpenInventor實現的NeHe OpenGL教程-第十九課
這節課我們將討論怎樣使用OpenInventor實現一個簡單的粒子系統。我們在NeHe教程中可以看到,一個簡單的粒子系統實現起來不像想象中那樣的困難。只要能計算好一個粒子的當前狀態(速度,加速度,顏色),就很容易完成一個粒子系統的效果。
NeHe教程中使用三角形帶來顯示每個粒子(GL_TRIANGLE_STRIP),OpenInventor提供了一個相對應的節點類SoTriangleStripSet。SoTriangleStripSet節點是OpenInventor中顯示速度最快的節點,特別適合顯示大量粒子效果。
下面的代碼首先定義一些全局變量。
SoMaterial* g_pParticleColor = NULL; //所有粒子的顏色
SoCoordinate3* g_pParticleCoord = NULL; //所有粒子的位置座標
SoTextureCoordinate2* g_pParticleTexCoord = NULL; //所有粒子的材質座標
SoTriangleStripSet* g_pParticleStripSet = NULL; //所有粒子的三角形面集
下面定義的變量和NeHe教程定義的變量名稱和含義完全相同,不在贅述
#define MAX_PARTICLES 1000 // Number Of Particles To Create
bool rainbow = true; // Rainbow Mode?
float slowdown =
float xspeed = 0; // Base X Speed (To Allow Keyboard Direction Of Tail)
float yspeed = 0; // Base Y Speed (To Allow Keyboard Direction Of Tail)
float zoom =
int col; // Current Color Selection
int delay; // Rainbow Effect Delay
typedef struct // Create A Structure For Particle
{
bool active; // Active (Yes/No)
float life; // Particle Life
float fade; // Fade Speed
float r; // Red Value
float g; // Green Value
float b; // Blue Value
float x; // X Position
float y; // Y Position
float z; // Z Position
float xi; // X Direction
float yi; // Y Direction
float zi; // Z Direction
float xg; // X Gravity
float yg; // Y Gravity
float zg; // Z Gravity
} particles; // Particles Structure
particles particle[MAX_PARTICLES]; // Particle Array (Room For Particle Info)
static float colors[12][3] = // Rainbow Of Colors
{
{
{
{
};
float ParticleColorData[MAX_PARTICLES][3];
float ParticleLifeData[MAX_PARTICLES];
SbVec
SbVec
int ParticleNumVertices[MAX_PARTICLES];
下面的函數用來更新粒子系統中每個粒子的顏色,位置,材質等信息。計算方法和NeHe教程相同。
void UpdateParticle(void)
{
for (int i = 0; i < MAX_PARTICLES; i++)
{
if (particle[i].active) // If The Particle Is Active
{
float x = particle[i].x; // Grab Our Particle X Position
float y = particle[i].y; // Grab Our Particle Y Position
float z = particle[i].z + zoom; // Particle Z Pos + Zoom
ParticleCoordData[i * 4 + 0].setValue(x +
ParticleCoordData[i * 4 + 1].setValue(x -
ParticleCoordData[i * 4 + 2].setValue(x +
ParticleCoordData[i * 4 + 3].setValue(x -
ParticleTexCoordData[i * 4 + 0].setValue(1,1);
ParticleTexCoordData[i * 4 + 1].setValue(0,1);
ParticleTexCoordData[i * 4 + 2].setValue(1,0);
ParticleTexCoordData[i * 4 + 3].setValue(0,0);
ParticleColorData[i][0] = particle[i].r;
ParticleColorData[i][1] = particle[i].g;
ParticleColorData[i][2] = particle[i].b;
ParticleLifeData[i] = 1 - particle[i].life;
particle[i].x += particle[i].xi / (slowdown * 1000);// Move On The X Axis By X Speed
particle[i].y += particle[i].yi / (slowdown * 1000);// Move On The Y Axis By Y Speed
particle[i].z += particle[i].zi / (slowdown * 1000);// Move On The Z Axis By Z Speed
particle[i].xi += particle[i].xg; // Take Pull On X Axis Into Account
particle[i].yi += particle[i].yg; // Take Pull On Y Axis Into Account
particle[i].zi += particle[i].zg; // Take Pull On Z Axis Into Account
particle[i].life -= particle[i].fade; // Reduce Particles Life By 'Fade'
if (particle[i].life <
{
particle[i].life =
particle[i].fade = float(rand() % 100) /
particle[i].x =
particle[i].y =
particle[i].z =
particle[i].xi = xspeed + float((rand() % 60) -
particle[i].yi = yspeed + float((rand() % 60) -
particle[i].zi = float((rand() % 60) -
particle[i].r = colors[col][0]; // Select Red From Color Table
particle[i].g = colors[col][1]; // Select Green From Color Table
particle[i].b = colors[col][2]; // Select Blue From Color Table
}
}
}
g_pParticleColor->diffuseColor.setValuesPointer(MAX_PARTICLES,(float *)ParticleColorData);
g_pParticleColor->transparency.setValuesPointer(MAX_PARTICLES,ParticleLifeData);
g_pParticleCoord->point.setValuesPointer(MAX_PARTICLES * 4,ParticleCoordData);
g_pParticleTexCoord->point.setValuesPointer(MAX_PARTICLES * 4,ParticleTexCoordData);
}
定時回調函數,用來不斷更新粒子系統中每個粒子的顏色,位置等狀態
void TimerSensorCB(void * data, SoSensor *)
{
UpdateParticle();
if(rainbow && (delay > 25))
{
delay = 0;
col++; // Change The Particle Color
if (col > 11)
col = 0; // If Color Is To High Reset It
}
delay++;
}
創建場景數據
void BuildScene(void)
{
//添加鍵盤迴調事件
SoEventCallback* pEventCallback = new SoEventCallback;
pEventCallback->addEventCallback(SoKeyboardEvent::getClassTypeId(),
KeyboardEventCB,g_pOivSceneRoot);
g_pOivSceneRoot->addChild(pEventCallback);
//OpenGL回調事件
SoCallback *pGlCallback = new SoCallback();
pGlCallback->setCallback(GlRenderCB, NULL);
g_pOivSceneRoot->addChild(pGlCallback);
SoComplexity *pComplexity = new SoComplexity;
pComplexity->textureQuality = 0.6;
g_pOivSceneRoot->addChild(pComplexity);
//加載材質位圖
SoTexture2 *Texture = new SoTexture2;
Texture->filename.setValue("../Data/Particle.png");
Texture->model = SoTexture2::MODULATE;
g_pOivSceneRoot->addChild(Texture);
SoMaterialBinding *pMaterialBinding = new SoMaterialBinding;
pMaterialBinding->value = SoMaterialBindingElement::PER_PART;
g_pOivSceneRoot->addChild(pMaterialBinding);
g_pParticleColor = new SoMaterial;
g_pOivSceneRoot->addChild(g_pParticleColor);
g_pParticleCoord = new SoCoordinate3;
g_pOivSceneRoot->addChild(g_pParticleCoord);
g_pParticleTexCoord = new SoTextureCoordinate2;
g_pOivSceneRoot->addChild(g_pParticleTexCoord);
g_pParticleStripSet = new SoTriangleStripSet;
for (int i = 0; i < MAX_PARTICLES; i++)
ParticleNumVertices[i] = 4;
g_pParticleStripSet->numVertices.setValues(0,MAX_PARTICLES,ParticleNumVertices);
g_pOivSceneRoot->addChild(g_pParticleStripSet);
//初始化粒子狀態數組
for (int i = 0; i < MAX_PARTICLES; i++) // Initials All The Textures
{
particle[i].active = true; // Make All The Particles Active
particle[i].life =
particle[i].fade = float(rand() % 100) /
particle[i].zi = float((rand() % 50) -
particle[i].yg =
particle[i].zg =
particle[i].x = 0;
particle[i].y = 0;
particle[i].z = 0;
}
UpdateParticle();
//定義粒子更新定時器
SoTimerSensor *pTimerSensor = new SoTimerSensor(TimerSensorCB, NULL);
pTimerSensor->setInterval(0.001);
pTimerSensor->schedule();
}
下面的代碼是鍵盤響應函數。
void KeyboardEventCB(void *userData, SoEventCallback *pEventCB)
{
const SoEvent *pEvent = pEventCB->getEvent();
if(SO_KEY_PRESS_EVENT(pEvent,LEFT_ARROW))
{
if(xspeed > -200)
xspeed -=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,RIGHT_ARROW))
{
if(xspeed < 200)
xspeed +=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,UP_ARROW))
{
if(yspeed < 200)
yspeed +=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,DOWN_ARROW))
{
if(yspeed > -200)
yspeed -=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAGE_UP))
{
zoom +=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAGE_DOWN))
{
zoom -=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAD_ADD))
{
if(slowdown >
slowdown -=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAD_SUBTRACT))
{
if(slowdown <
slowdown +=
}
else
if(SO_KEY_PRESS_EVENT(pEvent,TAB))
{
for (int i = 0; i < MAX_PARTICLES; i++)
{
if (particle[i].active) // If The Particle Is Active
{
particle[i].x =
particle[i].y =
particle[i].z =
particle[i].xi = float((rand() % 50) -
}
}
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAD_8))
{
for (int i = 0; i < MAX_PARTICLES; i++)
{
if (particle[i].active && particle[i].yg <
particle[i].yg +=
}
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAD_2))
{
for (int i = 0; i < MAX_PARTICLES; i++)
{
if (particle[i].active && particle[i].yg >
particle[i].yg -=
}
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAD_6))
{
for (int i = 0; i < MAX_PARTICLES; i++)
{
if (particle[i].active && particle[i].xg <
particle[i].xg +=
}
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAD_4))
{
for (int i = 0; i < MAX_PARTICLES; i++)
{
if (particle[i].active && particle[i].xg >
particle[i].xg -=
}
}
else
if(SO_KEY_PRESS_EVENT(pEvent,RETURN))
{
rainbow = !rainbow;
}
else
if(SO_KEY_PRESS_EVENT(pEvent,SPACE))
{
rainbow = false;
delay = 0;
col++; // Change The Particle Color
if (col > 11)
col = 0; // If Color Is To High Reset It
}
pEventCB->setHandled();
}
現在編譯運行我們程序,屏幕上顯示出一個效果非常華麗的噴泉。按下左右方向鍵,噴泉可以左右轉動。按下上下方向鍵,噴泉可以上下轉動。按下PnUp/PnDn鍵,噴泉將放大或縮小。按下TAB鍵,噴泉將初始化一次。按下空格鍵,噴泉將修改顏色。效果和NeHe第十九課是相同的。
本課的完整代碼下載。(VC 2003 + Coin2.5)
後記
OpenInventor是一種基於OpenGL的面向對象的三維圖形軟件開發包。使用這個開發包,程序員可以快速、簡潔地開發出各種類型的交互式三維圖形軟件。這裏不對OpenInventor做詳細的介紹,讀者如果感興趣,可以閱讀我的blog中的這篇文章《OpenInventor 簡介》。
NeHe教程是目前針對初學者來說最好的OpenGL教程,它可以帶領讀者由淺入深,循序漸進地掌握OpenGL編程技巧。到目前爲止(2007年11月),NeHe教程一共有48節。我的計劃是使用OpenInventor來實現所有48節課程同樣的效果。目的是複習和鞏固OpenGL的知識,同時與各位讀者交流OpenInventor的使用技巧。
因爲篇幅的限制,我不會介紹NeHe教程中OpenGL的實現過程,因爲NeHe的教程已經講解的很清楚了,目前網絡中也有NeHe的中文版本。我將使用VC 2003作爲主要的編譯器。程序框架採用和NeHe一樣的Win32程序框架,不使用MFC。程序也可以在VC Express,VC 2005/2008中編譯。我採用的OpenInventor開發環境是Coin,這是一個免費開源的OpenInventor開發庫。文章 《OpenInventor-Coin3D開發環境》 介紹瞭如何在VC中使用Coin。我使用的Coin版本是2.5。讀者可以到 www.coin3d.org 中免費下載。
讀者可以在遵循GNU協議的條件下自由使用、修改本文的代碼。水平的原因,代碼可能不是最優化的,我隨時期待讀者的指正和交流。轉載請註明。謝謝。
我的聯繫方式:
E-mail: < [email protected] > < [email protected] >
Blog: < http://blog.csdn.net/RobinHao >
Site: < http://www.openinventor.cn >