Nehe第19课 粒子系统
生活随笔
收集整理的這篇文章主要介紹了
Nehe第19课 粒子系统
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
源代碼鏈接
#include<windows.h> // Windows的頭文件
#include <gl/glew.h> // 包含最新的gl.h,glu.h庫
#include <gl/glut.h> // 包含OpenGL實用庫
#include <gl/glaux.h> // GLaux庫的頭文件
#include <stdio.h> // 標準輸入/輸出庫的頭文件
/*****************************************************************************/GLuint texture[1]; //存儲紋理,如果紋理大于1個改為相應數值
HGLRC hRC=NULL; //窗口著色描述表句柄
HDC hDC=NULL; //設備渲染描述表句柄
HWND hWND=NULL; //窗口句柄
HINSTANCE hInstance; //保存程序的實例bool keys[256];
bool active=TRUE; //窗口的活動狀態,缺省為true
bool fullscreen=TRUE; //全屏的缺省狀態=trueLRESULT CALLBACK WndProc(HWND, UINT,WPARAM, LPARAM);
AUX_RGBImageRec *LoadBMP(char*Filename);
/************************************************************************/
/* Nehe第19課粒子系統 */
/********************19課新增代碼************************************************/
#define MAX_PARTICLES 1000
bool rainbow=true; //是否為彩虹模式
bool sp; //space空格鍵是否按下
bool rp; //回車鍵是否按下
/*變量slowdown控制粒子移動的快慢.數值愈高,移動越慢.數值越底,移動越快.
如果數值降低,粒子將快速的移動!粒子的速度影響它們在熒屏中移動的距離.
記住速度慢的粒子不會射很遠的.
變量xspeed和yspeed控制尾部的方向.xspeed將會增加粒子在x軸上速度.
如果xspeed是正值粒子將會向右邊移動多.
如果xspeed負價值,粒子將會向左邊移動多.那個值越高,就向那個方向移動比較多.
yspeed工作相同的方法,但是在y軸上.
因為有其它的因素影響粒子的運動,所以我要說"多".
xspeed和yspeed有助于在我們想要的方向上移動粒子.
最后是變量zoom,我們用該變量移入或移出我們的屏幕.在粒子引擎里,有時可看見更多的圖象,而且當接近你時很酷
*/
float slowdown=2.0f; // 減速粒子
float xspeed; // X方向的速度
float yspeed; // Y方向的速度
float zoom=-40.0f; // 沿Z軸縮放
/************************************************************************/
/*我們定義了一個復雜的循環變量叫做Loop.我們用這變量預先定義粒子并在屏幕中畫粒子.
color用來給予粒子不同的顏色.delay用來控制在彩虹模式中圓的顏色變化.
最后,我們設定一個存儲空間(粒子紋理).我用紋理而不用點的重要原因是,
點的速度慢,而且挺麻煩的.其次紋理很酷:)你用一個正方形的粒子,一張你臉的小圖片,
一張星星的圖片等等.很好控制! */
/************************************************************************/GLuint loop; // 循環變量
GLuint col; // 當前的顏色索引0-11
GLuint delay; // 彩虹效果延遲好!現在是有趣的東西.下段程序描述單一粒子結構,這是我們給予粒子的屬性.我們用布爾型變量active開始,如果為true,我們的粒子為活躍的.如果為false則粒子為死的,此時我們就刪除它.在程序中我沒有使用活躍的,因為它很好的實現.變量life和fade來控制粒子顯示多久以及顯示時候的亮度.隨著life數值的降低fade的數值也相應降低.這將導致一些粒子比其他粒子燃燒的時間長.typedef struct // 創建粒子數據結構
{bool active; // 是否激活float life; // 粒子生命float fade; // fade 衰退 衰減速度/************************************************************************/
// 變量r,g和b用來表示粒子的紅色強度,綠色強度和藍色強度. float r; // 紅色值float g; // 綠色值float b; // 藍色值// 變量x,y和z控制粒子在屏幕上顯示的位置 float x; // X 位置float y; // Y 位置float z; // Z 位置// 下面三個變量控制粒子在每個軸上移動的快慢和方向.
// 如果xi是負值粒子將會向左移動,正值將會向右移動如果yi是負值粒子將會向下移動,正值將向上,如果zi負值粒子將會向熒屏內部移動,正植將移向觀察者. float xi; // X 方向float yi; // Y 方向float zi; // Z 方向 // 最后,另外3個變量!每一個變量可被看成加速度.float xg; // X 方向重力加速度float yg; // Y 方向重力加速度float zg; // Z 方向重力加速度// 結構的名字為particles. }particles;
particles particle[MAX_PARTICLES];// 保存1000個粒子的數組//在顏色數組上我們減少一些代碼來存儲12種不同的顏色.對每一個顏色從0到11我們存儲亮紅,亮綠,和亮藍.
//下面的顏色表里包含12個漸變顏色從紅色到紫羅蘭色 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.bmp的位圖 //if (TextureImage[0]=LoadBMP("Data/Particle.bmp")) // 載入粒子紋理/* 在 ReSizeGLScene() 之前,我們增加了下面這一段代碼。這段代碼用來加載位圖文件。* 如果文件不存在,返回 NULL 告知程序無法加載位圖。* 關于用作紋理的圖像。圖像的寬和高必須是2的n次方;寬度和高度最小必須是64象素;* 并且出于兼容性的原因,圖像的寬度和高度不應超過256象素。如果您的原始素材的寬度* 和高度不是64,128,256象素的話,使用圖像處理軟件重新改變圖像的大小。*/
AUX_RGBImageRec *LoadBMP(char*Filename)
{// 首先,我們創建一個文件句柄。句柄是個用來鑒別資源的數值,它使程序能夠//訪問此資源。我們開始先將句柄設為 NULL 。FILE *File=NULL;if (!Filename) //確保文件名已提供{return NULL;}File=fopen(Filename,"r");if (File)// 文件存在么?{fclose(File);return auxDIBImageLoad(Filename); // 載入位圖并返回指針}return NULL;
}
int LoadGLTextures()//載入位圖(調用上面的代碼)并轉換成紋理
{//然后設置一個叫做 Status 的變量。我們使用它來跟蹤是否能夠載入位圖以及能否創建紋理。 //Status 缺省設為 FALSE (表示沒有載入或創建任何東東)。int Status=FALSE;//創建存儲位圖的圖像記錄。次記錄包含位圖的寬度、高度和數據。AUX_RGBImageRec *TextureImage[1]; // 創建紋理的存儲空間//清除圖像記錄,確保其內容為空memset(TextureImage,0,sizeof(void *)*1); // 將指針設為 NULLif (TextureImage[0]=LoadBMP("Data/Particle.bmp")) // 載入粒子紋理{Status=true;/*使用TextureImage[0] 的數據創建紋理。第一行 glGenTextures(1, &texture[0]) 告訴OpenGL我們想生成一個紋理名字(如果您想載入多個紋理,加大數字)。第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告訴OpenGL將紋理名字 texture[0] 綁定到紋理目標上。2D紋理只有高度(在 Y 軸上)和寬度(在 X 軸上)。主函數將紋理名字指派給紋理數據。本例中我們告知OpenGL,&texture[0] 處的內存已經可用。我們創建的紋理將存儲在 &texture[0] 的 指向的內存區域。*/glGenTextures(1,&texture[0]);glBindTexture(GL_TEXTURE_2D,texture[0]);/*下面一行告訴OpenGL此紋理是一個2D紋理 ( GL_TEXTURE_2D )。參數“0”代表圖像的詳細程度,通常就由它為零去了。參數三是數據的成分數。因為圖像是由紅色數據,綠色數據,藍色數據三種組分組成。 TextureImage[0]->sizeX 是紋理的寬度。如果您知道寬度,您可以在這里填入,但計算機可以很容易的為您指出此值。 TextureImage[0]->sizey 是紋理的高度。參數零是邊框的值,一般就是“0”。 GL_RGB 告訴OpenGL圖像數據由紅、綠、藍三色數據組成。GL_UNSIGNED_BYTE 意味著組成圖像的數據是無符號字節類型的。最后... TextureImage[0]->data告訴OpenGL紋理數據的來源。此例中指向存放在 TextureImage[0] 記錄中的數據。*/glTexImage2D(GL_TEXTURE_2D,0,3,TextureImage[0]->sizeX,TextureImage[0]->sizeY,0,GL_RGB,GL_UNSIGNED_BYTE,TextureImage[0]->data);/*下面的兩行告訴OpenGL在顯示圖像時,當它比放大得原始的紋理大 ( GL_TEXTURE_MAG_FILTER )或縮小得比原始得紋理小( GL_TEXTURE_MIN_FILTER )時OpenGL采用的濾波方式。通常這兩種情況下我都采用 GL_LINEAR 。這使得紋理從很遠處到離屏幕很近時都平滑顯示。使用 GL_LINEAR 需要CPU和顯卡做更多的運算。如果您的機器很慢,您也許應該采用 GL_NEAREST 。過濾的紋理在放大的時候,看起來斑駁的很『譯者注:馬賽克啦』。您也可以結合這兩種濾波方式。在近處時使用 GL_LINEAR ,遠處時 GL_NEAREST 。*/glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//線性濾波}/*現在我們釋放前面用來存放位圖數據的內存。我們先查看位圖數據是否存放在此處。如果是的話,再查看數據是否已經存儲。如果已經存儲的話,刪了它。接著再釋放 TextureImage[0] 圖像結構以保證所有的內存都能釋放。*/if (TextureImage[0])//紋理是否存在{if (TextureImage[0]->data)//紋理圖像是否存在{free(TextureImage[0]->data);//釋放紋理圖像占用的內存}free(TextureImage[0]);//釋放圖像結構}return Status;
}GLvoid ReSizeGLScene(GLsizei width,GLsizei height)
{if (height==0){height=1;}glViewport(0,0,width, height); //重置當前視口glMatrixMode(GL_PROJECTION);glLoadIdentity();//設置視口的大小,設置透視投影矩陣gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);//y軸傾斜角,寬高比,z軸近距>0,z軸遠距》近距》0glMatrixMode(GL_MODELVIEW);glLoadIdentity(); //重置模型視圖矩陣
}int InitGL(GLvoid) //開始對GL進行所有設置
{if (!LoadGLTextures()){return FALSE;}glEnable(GL_TEXTURE_2D);//啟用紋理映射//啟用陰影平滑glShadeModel(GL_SMOOTH);//黑色背景glClearColor(0.0f,0.0f,0.0f,0.5f);//設置深度緩存glClearDepth(1.0f);//啟用深度緩存//glEnable(GL_DEPTH_TEST);
/**********19課******************************************************/
/* 我們使用光滑的陰影,清除背景為黑色,關閉深度測試,綁定并映射紋理.啟用映射位圖后我們選擇粒子紋理。唯一的改變就是禁用深度測試和初始化粒子 */
/************************************************************************/glDisable(GL_DEPTH_TEST); //禁止深度測試// 下面代碼初始化每個粒子.我們從活躍的粒子開始.如果粒子不活躍,它在熒屏上將不出現,// 無論它有多少life.當我們使粒子活躍之後,我們給它life.我懷疑給粒子生命和顏色漸變// 是否是最好的方法,但當它運行一次后,效果很好!life滿值是1.0f.這也給粒子完整的光亮.//初始化所有的粒子for (loop=0;loop<MAX_PARTICLES;loop++) {particle[loop].active=true; // 使所有的粒子為激活狀態particle[loop].life=1.0f; // 所有的粒子生命值為最大//我們通過給定的值來設定粒子退色快慢.每次粒子被拉的時候life隨著fade而減小.//結束的數值將是0~99中的任意一個,然后平分1000份來得到一個很小的浮點數0.001-0.1.//最后我們把結果加上0.003f來使fade速度值不為0particle[loop].fade=float(rand()%100)/1000.0f+0.003f; // 隨機生成衰減速率//既然粒子是活躍的,而且我們又給它生命,下面將給它顏色數值.一開始,我們就想每個粒子有不同的顏色.//我怎么做才能使每個粒子與前面顏色箱里的顏色一一對應那?//數學很簡單,我們用loop變量乘以箱子中顏色的數目與粒子最大值(MAX_PARTICLES)的余數.//這樣防止最后的顏色數值大于最大的顏色數值(12).舉例:900*(12/900)=12.1000*(12/1000)=12,等等//archie懷疑 12/MAX_PARTICLES不是為0???particle[loop].r=colors[loop*12/MAX_PARTICLES][0]; // 粒子的紅色顏色particle[loop].g=colors[loop*12/MAX_PARTICLES][1]; // 粒子的綠色顏色particle[loop].b=colors[loop*12/MAX_PARTICLES][2]; // 粒子的藍色顏色//現在設定每個粒子移動的方向和速度.我們通過將結果乘于10.0f來創造開始時的爆炸效果.//我們將會以任意一個正或負值結束.這個數值將以任意速度,任意方向移動粒子. particle[loop].xi=float((rand()%50)-26.0f)*10.0f; // 隨機生成X軸方向速度particle[loop].yi=float((rand()%50)-25.0f)*10.0f; // 隨機生成Y軸方向速度particle[loop].zi=float((rand()%50)-25.0f)*10.0f; // 隨機生成Z軸方向速度//最后,我們設定加速度的數值.不像一般的加速度僅僅把事物拉下,我們的加速度能拉出,拉下,拉左,拉右,拉前和拉后粒子.//開始我們需要強大的向下加速度.為了達到這樣的效果我們將xg設為0.0f.在x方向沒有拉力.//我們設yg為-0.8f來產生一個向下的拉力.如果值為正則拉向上.我們不希望粒子拉近或遠離我們,所以將zg設為0.0fparticle[loop].xg=0.0f; // 設置X軸方向加速度為0particle[loop].yg=-0.8f; // 設置Y軸方向加速度為-0.8particle[loop].zg=0.0f; // 設置Z軸方向加速度為0}//所作深度測試的類型glDepthFunc(GL_LEQUAL);//最好的透視修正glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);return TRUE;}//繪圖工作現在為有趣的部分.下面的部分是我們從哪里拉粒子,檢查加速度等等.
//你要明白它是怎么實現的,因此仔細的看:)我們重置Modelview巨陣.
//在畫粒子位置的時候用glVertex3f()命令來代替tranlations,這樣在我們畫粒子的時候不會改變modelview巨陣 int DrawGLScene(GLvoid)
{//清除屏幕和深度緩存glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//重置模型視圖矩陣glLoadIdentity();
/************************************************************************/
/* 下一行代碼選擇我們使用的紋理。如果您在您的場景中使用多個紋理,您應該使用glBindTexture(GL_TEXTURE_2D, texture[ 所使用紋理對應的數字 ]) 選擇要綁定的紋理。當您想改變紋理時,應該綁定新的紋理。有一點值得指出的是,您不能在glBegin() 和 glEnd() 之間綁定紋理,必須在 glBegin() 之前或 glEnd() 之后綁定。注意我們在后面是如何使用 glBindTexture 來指定和綁定紋理的。 */
/************************************************************************/glBindTexture(GL_TEXTURE_2D,texture[0]);//選擇紋理/************************************************************************//* 為了將紋理正確的映射到四邊形上,您必須將紋理的右上角映射到四邊形的右上角,紋理的左上角映射到四邊形的左上角,紋理的右下角映射到四邊形的右下角,紋理的左下角映射到四邊形的左下角。如果映射錯誤的話,圖像顯示時可能上下顛倒,側向一邊或者什么都不是。glTexCoord2f 的第一個參數是X坐標。0.0f 是紋理的左側. 0.5f 是紋理的中點, 1.0f 是紋理的右側。 glTexCoord2f 的第二個參數是Y坐標。 0.0f 是紋理的底部。0.5f 是紋理的中點, 1.0f 是紋理的頂部。所以紋理的左上坐標是 X:0.0f,Y:1.0f ,四邊形的左上頂點是 X: -1.0f,Y:1.0f 。其余三點依此類推。*//************************************************************************/for (loop=0;loop<MAX_PARTICLES;loop++){if (particle[loop].active==TRUE)// 如果粒子為激活的{//下面三個變量是我們確定x,y和z位置的暫時變量.//注意:在z的位置上我們加上zoom以便我們的場景在以前的基礎上再移入zoom個位置.//particle[loop].x告訴我們要畫的x的位置particle[loop].y告訴我們要畫的y的位置particle[loop].z告訴我們要畫的z的位置 float x=particle[loop].x; // 返回X軸的位置float y=particle[loop].y; // 返回Y軸的位置float z=particle[loop].z+zoom; // 返回Z軸的位置//既然知道粒子位置,就能給粒子上色//particle[loop].r保存粒子的亮紅,particle[loop].g保存粒子的亮綠,particle[loop].b保存粒子的亮藍//注意我給alpha賦值為粒子生命.當粒子要燃盡時,它會越來越透明直到它最后消失.這就是為什么粒子的生命不應該超過1.0f.//如果你想粒子燃燒時間長,可降低fade減小的速度 // 設置粒子顏色glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);//我們有粒子的位置,并設置顏色了.所以現在我們來畫我們的粒子.//我們用一個三角形帶來代替一個四邊形這樣使程序運行快一點.//很多3D card畫三角形帶比畫四邊形要快的多.有些3D card將四邊形分成兩個三角形,而有些不.所以我們按照我們自己的想法來,所以我們來畫一個生動的三角形帶 //畫三角形帶/************************************************************************//* 1 03 2逆時針為正。從紅寶書引述:三角形帶就是畫一連續的三角形(三個邊的多角形)使用vertices V0,V1,V2,然后V2,V1,V3(注意順序),然后V2,V3,V4等等.畫三角形的順序一樣才能保證三角形帶為相同的表面.要求方向是很重要的,例如:第一個三角形使用vertices0,1和2畫.第二個三角形用點vertices2,1和3構造.).注意:兩個三角形畫點順序相同.OpenGL從新整理頂點來保證所有的三角形為同一方向!注意:你在屏幕上看見的三角形個數是你敘述的頂點的個數減2.在程序中在我們有4個頂點,所以我們看見二個三角形 *//************************************************************************/glBegin(GL_TRIANGLE_STRIP);glTexCoord2d(1,1);glVertex3f(x+0.5f,y+0.5f,z);glTexCoord2d(1,0);glVertex3f(x+0.5f,y-0.5f,z);glTexCoord2d(0,1);glVertex3f(x-0.5f,y+0.5f,z);glTexCoord2d(0,0);glVertex3f(x-0.5f,y-0.5f,z);glEnd();//現在我們能移動粒子.下面公式可能看起來很奇怪,其實很簡單.//首先我們取得當前粒子的x位置.然后把x運動速度加上粒子被減速1000倍后的值.slowdown=2.0f//所以如果粒子在x軸(0)上屏幕中心的位置,運動值(xi)為x軸方向+10(移動我們為右),而slowdown等于1,//我們移向右邊以10/(1*1000)或 0.01f速度.如果增加slowdown值到2我們只移動0.005f.//希望能幫助你了解slowdown如何工作.那也是為什么用10.0f乘開始值來叫象素移動快速,創造一個爆發效果//y和z軸用相同的公式來計算附近移動粒子 particle[loop].x+=particle[loop].xi/(slowdown*1000); // 更新X坐標的位置particle[loop].y+=particle[loop].yi/(slowdown*1000); // 更新Y坐標的位置particle[loop].z+=particle[loop].zi/(slowdown*1000); // 更新Z坐標的位置//在計算出下一步粒子移到那里,開始考慮重力和阻力.//在下面的第一行,將阻力(xg)和移動速度(xi)相加.//我們的移動速度是10和阻力是1.每時每刻粒子都在抵抗阻力.//第二次畫粒子時,阻力開始作用,移動速度將會從10掉到9.第三次畫粒子時,阻力再一次作用,移動速度降低到8.//如果粒子燃燒為超過10次重畫,它將會最后結束,并向相反方向移動.因為移動速度會變成負值.//阻力同樣使用于y和z移動速度 particle[loop].xi+=particle[loop].xg; // 更新X軸方向速度大小particle[loop].yi+=particle[loop].yg; // 更新Y軸方向速度大小particle[loop].zi+=particle[loop].zg; // 更新Z軸方向速度大小//下行將粒子的生命減少.如果我們不這么做,粒子無法燒盡.//我們用粒子當前的life減去當前的fade值.每粒子都有不同的fade值,因此他們全部將會以不同的速度燒盡 particle[loop].life-=particle[loop].fade; // 減少粒子的生命值,如果life小于0怎么辦??//現在我們檢查當生命為零的話粒子是否活著 if (particle[loop].life<0.0f) // 如果粒子生命值小于0{// 如果粒子消失(燒盡),我們將會使它復原.我們給它全值生命和新的衰弱速度. particle[loop].life=1.0f; // 產生一個新的粒子particle[loop].fade=float(rand()%100)/1000.0f+0.003f; // 隨機生成衰減速率//我們也重新設定粒子在屏幕中心放置.我們重新設定粒子的x,y和z位置為零 particle[loop].x=0.0f; // 新粒子出現在屏幕的中央particle[loop].y=0.0f; particle[loop].z=0.0f; //在粒子從新設置之后,將給它新的移動速度/方向.//注意:我增加最大和最小值,粒子移動速度為從50到60的任意值,但是這次我們沒將移動速度乘10.//我們這次不想要一個爆發的效果,而要比較慢地移動粒子.//也注意我把xspeed和x軸移動速度相加,y軸移動速度和yspeed相加.這個控制粒子的移動方向. particle[loop].xi=xspeed+float((rand()%60)-32.0f); // 隨機生成粒子速度particle[loop].yi=yspeed+float((rand()%60)-30.0f); particle[loop].zi=float((rand()%60)-30.0f); //最后我們分配粒子一種新的顏色.變量col保存一個數字從1到11(12種顏色),//我們用這個變量去找紅,綠,藍亮度在顏色箱里面.//如果col等于0,我們要看第一個組 particle[loop].r=colors[col][0]; // 設置粒子顏色particle[loop].g=colors[col][1]; particle[loop].b=colors[col][2]; }//下行描述加速度的數值是多少.通過小鍵盤8號鍵,我們增加yg(y 地心引力)值.這引起向上的力.//如果這個程序在循環外面,那么我們必須生成另一個循環做相同的工作,因此我們最好放在這里// 如果小鍵盤8被按住,增加Y軸方向的加速度if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;// 如果小鍵盤2被按住,減少Y軸方向的加速度 ,減小yg值,引起向下的力if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;// 如果小鍵盤6被按住,增加X軸方向的加速度if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;// 最后如果4號鍵被按下則增加向左的拉力.這些按鍵給了我們很酷的結果.//舉例來說:你可以用粒子造一條向上的水流.通過增加向下的引力可以形成泉水// 如果小鍵盤4被按住,減少X軸方向的加速度if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;// 通過按住tab鍵所有粒子都回到屏幕中心.所有的粒子在從新開始運動,再產生一個大的爆發.在粒子變弱之后,你最初的效果會再一次出現 if (keys[VK_TAB]) // 按Tab鍵,使粒子回到原點{particle[loop].x=0.0f; particle[loop].y=0.0f; particle[loop].z=0.0f; particle[loop].xi=float((rand()%50)-26.0f)*10.0f; // 隨機生成速度particle[loop].yi=float((rand()%50)-25.0f)*10.0f; particle[loop].zi=float((rand()%50)-25.0f)*10.0f; }}}return TRUE;}//程序退出之前調用 依次釋放著色描述表RC,設備描述表DC和窗口句柄
GLvoid KillGLWindow(GLvoid)
{if (fullscreen){ChangeDisplaySettings(NULL,0); //Windows API 把缺省顯示設備的設置改變為由第一個參數設定的圖形模式ShowCursor(TRUE); //Window32API 顯示鼠標}if (hRC) //是否擁有渲染描述表{if (!wglMakeCurrent(NULL,NULL)){MessageBox(NULL,"釋放DC或RC失敗","Shutdown Error",MB_OK|MB_ICONINFORMATION);}if (!wglDeleteContext(hRC)) {MessageBox(NULL,"釋放RC失敗","Shutdown Error",MB_OK|MB_ICONINFORMATION);}hRC=NULL; //將hRC設為NULL}if (hDC&&!ReleaseDC(hWND,hDC)){MessageBox(NULL,"不能釋放DC失敗","Shutdown Error",MB_OK|MB_ICONINFORMATION);hDC=NULL;}if (hWND&&!DestroyWindow(hWND)){MessageBox(NULL,"釋放窗口句柄失敗","Shutdown Error",MB_OK|MB_ICONINFORMATION);hWND=NULL;}if (!UnregisterClass("OpenGL",hInstance)){MessageBox(NULL,"不能注銷窗口類","Shutdown Error",MB_OK|MB_ICONINFORMATION);hInstance=NULL;}
}//創建OpenGL窗口 窗口標題 寬 高 顏色位 全屏標志
BOOL CreateGLWindow(char* title,int width,int height,int bits,bool fullscreenflag)
{//保存Windows相匹配的像素格式值變量GLuint PixelFormat;//窗口類結構WNDCLASS wc;DWORD dwStyle;//窗口風格DWORD dwExStyle;//擴展窗口風格RECT WindowRect;WindowRect.left=(long)0;WindowRect.right=(long)width;WindowRect.top=(long)0;WindowRect.bottom=(long)height;fullscreen=fullscreenflag;hInstance=GetModuleHandle(NULL);//獲取一個應用程序或動態鏈接庫的模塊句柄 ,NULL返回自身應用程序句柄wc.style=CS_VREDRAW|CS_HREDRAW|CS_OWNDC;//移動時重畫,并為窗口取得DCwc.lpfnWndProc=(WNDPROC)WndProc; //WndProc處理消息wc.cbClsExtra = 0; // 無額外窗口數據wc.cbWndExtra = 0; // 無額外窗口數據wc.hInstance=hInstance; //設置實例wc.hIcon=LoadIcon(NULL,IDI_WINLOGO); //裝入缺省圖標wc.hCursor=LoadCursor(NULL,IDC_ARROW); //裝入鼠標指針wc.hbrBackground=NULL; //GL不需要背景wc.lpszMenuName=NULL; //不需要菜單wc.lpszClassName="OpenGL"; //設定類名字//注冊窗口類if (!RegisterClass(&wc)){MessageBox(NULL,"注冊窗口失敗"," Error",MB_OK|MB_ICONEXCLAMATION);//EXCLAMATION感嘆號return FALSE;}if (fullscreen)//全屏模式{DEVMODE dmScreenSettings; //設備模式memset(&dmScreenSettings,0,sizeof(dmScreenSettings));//確保內存清空為0dmScreenSettings.dmSize=sizeof(dmScreenSettings); //DEVMODE結構的大小dmScreenSettings.dmPelsWidth=width;dmScreenSettings.dmPelsHeight=height; //所選屏幕高度dmScreenSettings.dmBitsPerPel=bits; //每像素顏色數dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSHEIGHT|DM_PELSWIDTH;if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL){// 若模式失敗,提供兩個選項:退出或在窗口內運行。if (MessageBox(NULL,"全屏模式在當前顯示卡上設置失敗!\n使用窗口模式","Nehe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES){fullscreen=false;}else//用戶選擇退出{MessageBox(NULL,"程序將被關閉","Error",MB_OK|MB_ICONSTOP);return FALSE;}}}if (fullscreen){dwExStyle=WS_EX_APPWINDOW;dwStyle=WS_POPUP;ShowCursor(FALSE); //隱藏鼠標指針}else//不是全屏{dwExStyle=WS_EX_APPWINDOW|WS_EX_WINDOWEDGE;dwStyle=WS_OVERLAPPEDWINDOW;}AdjustWindowRectEx(&WindowRect,dwStyle,FALSE,dwExStyle); //Windows API真正適合窗口if (!(hWND=CreateWindowEx(dwExStyle,"OpenGL",title,WS_CLIPSIBLINGS|WS_CLIPCHILDREN|dwStyle,//必須的窗體風格屬性0,0,WindowRect.right-WindowRect.left,WindowRect.bottom-WindowRect.top,//計算調整好的窗口高度NULL,//無父窗口NULL,//無子菜單hInstance,NULL)))//不向WM_CREATE傳遞消息{KillGLWindow();MessageBox(NULL,"不能創建窗口","Error",MB_OK|MB_ICONEXCLAMATION);return FALSE;}static PIXELFORMATDESCRIPTOR pfd= // /pfd 告訴窗口我們所希望的東東,即窗口使用的像素格式{sizeof(PIXELFORMATDESCRIPTOR), // 上述格式描述符的大小1, // 版本號PFD_DRAW_TO_WINDOW | // 格式支持窗口PFD_SUPPORT_OPENGL | // 格式必須支持OpenGLPFD_DOUBLEBUFFER, // 必須支持雙緩沖PFD_TYPE_RGBA, // 申請 RGBA 格式bits, // 選定色彩深度0, 0, 0, 0, 0, 0, // 忽略的色彩位0, // 無Alpha緩存0, // 忽略Shift Bit0, // 無累加緩存0, 0, 0, 0, // 忽略聚集位16, // 16位 Z-緩存 (深度緩存)0, // 無蒙板緩存0, // 無輔助緩存PFD_MAIN_PLANE, // 主繪圖層0, // Reserved0, 0, 0 // 忽略層遮罩};if (!(hDC=GetDC(hWND))){KillGLWindow(); // 重置顯示區MessageBox(NULL,"不能創建一種相匹配的像素格式","錯誤",MB_OK|MB_ICONEXCLAMATION);return FALSE;}if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))){KillGLWindow();MessageBox(NULL,"不能創建像素格式","錯誤",MB_OK|MB_ICONEXCLAMATION);return FALSE;}if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // 能夠設置象素格式么?{KillGLWindow(); // 重置顯示區MessageBox(NULL,"不能設置像素格式","錯誤",MB_OK|MB_ICONEXCLAMATION);return FALSE; // 返回 FALSE}if (!(hRC=wglCreateContext(hDC))){KillGLWindow(); // 重置顯示區MessageBox(NULL,"不能創建OpenGL渲染描述表","錯誤",MB_OK|MB_ICONEXCLAMATION);return FALSE; }if (!wglMakeCurrent(hDC,hRC)){KillGLWindow(); // 重置顯示區MessageBox(NULL,"不能激活當前的OpenGL渲然描述表","錯誤",MB_OK|MB_ICONEXCLAMATION);return FALSE; }ShowWindow(hWND,SW_SHOW);SetForegroundWindow(hWND); //提高優先級SetFocus(hWND); //設置焦點ReSizeGLScene(width,height);if (!InitGL()) // 初始化新建的GL窗口{KillGLWindow(); // 重置顯示區MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);return FALSE; // 返回 FALSE}return TRUE;}LRESULT CALLBACK WndProc( HWND hWnd, // 窗口的句柄UINT uMsg, // 窗口的消息WPARAM wParam, // 附加的消息內容LPARAM lParam) // 附加的消息內容
{switch (uMsg) // 檢查Windows消息{case WM_ACTIVATE: // 監視窗口激活消息{if (!HIWORD(wParam)) // 檢查最小化狀態{active=TRUE; // 程序處于激活狀態}else{active=FALSE; // 程序不再激活}return 0; // 返回消息循環}case WM_SYSCOMMAND: // 系統中斷命令{switch (wParam) // 檢查系統調用{case SC_SCREENSAVE: // 屏保要運行?case SC_MONITORPOWER: // 顯示器要進入節電模式?return 0; // 阻止發生}break; // 退出}case WM_CLOSE: // 收到Close消息?{PostQuitMessage(0); // 發出退出消息return 0; // 返回} case WM_KEYDOWN: // 有鍵按下么?{keys[wParam] = TRUE; // 如果是,設為TRUEreturn 0; // 返回}case WM_KEYUP: // 有鍵放開么?{keys[wParam] = FALSE; // 如果是,設為FALSEreturn 0; // 返回}case WM_SIZE: // 調整OpenGL窗口大小{ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width,HiWord=Heightreturn 0; // 返回}}return DefWindowProc(hWnd,uMsg,wParam,lParam);
}int WINAPI WinMain( HINSTANCE hInstance, // 當前窗口實例HINSTANCE hPrevInstance, // 前一個窗口實例LPSTR lpCmdLine, // 命令行參數int nCmdShow) // 窗口顯示狀態
{MSG msg; // Windowsx消息結構BOOL done=FALSE; // 用來退出循環的Bool 變量if (MessageBox(NULL,"你想在全屏模式下運行么?", "設置全屏模式",MB_YESNO|MB_ICONQUESTION)==IDNO){fullscreen=FALSE; // FALSE為窗口模式}if (!CreateGLWindow("NeHe's 3D空間",640,480,16,fullscreen)){return 0; // 失敗退出}while(!done) // 保持循環直到 done=TRUE{if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // 有消息在等待嗎?{if (msg.message==WM_QUIT) // 收到退出消息?{done=TRUE; // 是,則done=TRUE}else // 不是,處理窗口消息{TranslateMessage(&msg); // 翻譯消息DispatchMessage(&msg); // 發送消息}}else // 如果沒有消息{if (active) // 程序激活的么?{if (keys[VK_ESCAPE]) // ESC 按下了么?{done=TRUE; // ESC 發出退出信號}else // 不是退出的時候,刷新屏幕{DrawGLScene(); // 繪制場景SwapBuffers(hDC); // 交換緩存 (雙緩存)}}if (keys[VK_F1]) // F1鍵按下了么?{keys[VK_F1]=FALSE; // 若是,使對應的Key數組中的值為 FALSEKillGLWindow(); // 銷毀當前的窗口fullscreen=!fullscreen; // 切換 全屏 / 窗口 模式// 重建 OpenGL 窗口if (!CreateGLWindow("NeHe's 3D空間",640,480,16,fullscreen)){return 0; // 如果窗口未能創建,程序退出}}//下面的代碼檢查"+"是否被按下.如果它和slowdown一起實現則slowdown減少0.01f.粒子就可以較快速地移動.if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f; // 按+號,加速粒子if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f; // 按-號,減速粒子//下面的代碼檢測Page Up是否被按下.如果是,則zoom增加.從而導致粒子靠近我們 if (keys[VK_PRIOR]) zoom+=0.1f; // 按Page Up鍵,讓粒子靠近視點// 下行代碼檢測Page Down是否別按下,如果是,則zoom減小.從而導師粒子離開我們if (keys[VK_NEXT]) zoom-=0.1f; // 按Page Down,讓粒子遠離視點if (keys[VK_RETURN] && !rp) // 按住回車鍵,切換彩虹模式{rp=true; rainbow=!rainbow; }if (!keys[VK_RETURN]) rp=false; //第一行檢查space鍵是否被按下并沒有被一直按著.并檢查彩虹模式是否開始運行,如果是,檢查delay是否大于25//delay是我創建的顯示彩虹效果的數值.如果你曾經改變顏色結構,粒子將顯示不同顏色.//通過創建一個delay,在顏色改變之前,一組粒子將是一種顏色.//如果space按下,彩虹運行,delay值大于25則顏色改變if ((keys[' '] && !sp) || (rainbow && (delay>25))) // 空格鍵,變換顏色{ //下面行是為了當space按下則彩虹關掉而設置的.如果我們不關掉彩虹模式,顏色會繼續變化直到enter再被按下//也就是說人們按下space來代替enter是想叫粒子顏色自己變化 if (keys[' ']) rainbow=false;//如果space鍵被按下,或者彩虹模式已開始,并且delay大于25,我們叫計算機知道space鍵被按下//通過叫sp為true.然后我們將delay設定回0以便它能再到25.//最后我們增加col的值以便它通過顏色箱改變成另一個顏色. sp=true; delay=0; col++; //如果顏色值大于11,我們把它重新設為零if (col>11) col=0;}//最后如果space鍵不被按下,我們將sp設為false。 if (!keys[' ']) sp=false; // 如果釋放空格鍵,記錄這個狀態//現在對粒子增加一些控制.還記得我們從開始定義的2變量么?一個xspeed,一個yspeed.//在粒子燃盡之后,我們給它新的移動速度且把新的速度加入到xspeed和yspeed中.//這樣當粒子被創建時將影響粒子的速度. //舉例來說:粒子在x軸上的速度為5在y軸上的速度為0.//當我們減少xspeed到-10,我們將以-10(xspeed)+5(最初的移動速度)的速度移動.//這樣我們將以5的速度向左移動.明白了么??//無論如何,下面的代碼檢測UP是否被按下.如果yspeed增加這將引起粒子向上運動.//最大速度不超過200.速度再快就不好看了// 按上增加粒子Y軸正方向的速度if (keys[VK_UP] && (yspeed<200)) yspeed+=1.0f;// 按下減少粒子Y軸正方向的速度if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f;// 按右增加粒子X軸正方向的速度if (keys[VK_RIGHT] && (xspeed<200)) xspeed+=1.0f;// 按左減少粒子X軸正方向的速度if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f;delay++; // 增加彩虹模式的顏色切換延遲}}KillGLWindow(); // 銷毀窗口return (msg.wParam); // 退出程序
}
總結
以上是生活随笔為你收集整理的Nehe第19课 粒子系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 看了第一句,有人就哭了?
- 下一篇: 纯js实现俄罗斯方块详解与源码