程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> VC++編程實現仿真火焰的視覺效果

VC++編程實現仿真火焰的視覺效果

編輯:關於VC++

引言

計算機仿真技術的基本原理都是一樣的,神秘復雜的核爆同水波、火焰、煙霧等非常平常的自然現象在仿真處理過程中並沒有什麼太大的區別。都是經歷了從實體對象到物理特性的總結,再由此建立數學模型並在數學模型基礎之上提出仿真算法,最後通過計算機將其動態仿真出來等一系列步驟。本文以火焰作為仿真對象,通過對熱源、熱擴散以及對流等特性的分析對其建立了數學模型及仿真算法,為了能充分發揮計算機對圖形的硬件加速,使用DirectDraw技術對仿真結果顯示進行了加速,使之能逼真、流暢地對火焰的燃燒過程實行動態模擬。

簡單近似模型設計

雖然火焰在自然界是一種極普通的自然現象,但根據流體力學的相關知識,火焰可以表達為一個相當復雜的三維動態流體系統。如要在計算機中對這樣一個復雜的流體系統做出精確的仿真將需要有相當龐大復雜的數學模型為基礎,而且運算量將非常巨大,在現有的微型計算機中幾乎很難保證其動態實時性,這也就失去了仿真的意義。因此,在仿真時應用盡可能簡單的模型來實現盡可能逼真的效果。

從物理角度分析,要產生火焰,首先要有火源,其次為了產生"焰"的效果,需要以火源為中心向上、向四周擴散,而且由於在擴散過程中逐漸遠離火源,溫度會逐漸下降,表現在視覺上就是火焰的冷卻變暗。另外,由於火焰的高溫使周圍空氣受熱膨脹比重下降,因此會有空氣的對流出現,這將把火焰向上"吹"起,使火焰向四周擴散的距離要遠小於向上擴散的距離。基於以上幾點認識,可以采取對應的仿真措施:對火源的設置可以用一幅二值位圖來標識,非火源以低亮度像素填充、火源點則設以高亮度像素,通過對位圖像素值的判別可以斷定當前點是否為火源。

對於火源的溫度高低可用其所在點的亮度來描述;對於火焰擴散的模擬,為盡量減少運算量,在此簡單地用某火源點(x,y)及其前後左右鄰近四點的均值來近似,即Pixel(x,y)=(Pixel(x,y)+Pixel(x,y-1)+Pixel(x,y+1)+Pixel(x-1,y)+Pixel(x+1,y))/5,雖然該近似算法沒有采取正余弦的方法精確,但運算速度極快,而且在後續的實驗效果上同采用正余弦的方法幾乎沒有任何差別;由於在仿真過程中對火焰的溫度是通過改變其亮度值來實現的,因此對於擴散過程的冷卻可對像素點降低一個固定的亮度值來實現。衰減值的大小需要視所希望火焰冷卻速度的快慢而定;對流對火焰產生的直接影響就是使火焰始終保持向上燃燒,因此可通過將當前火焰上滾一至兩個像素來加以實現。根據前面描述的仿真運算法則,可將火焰的擴散和對流融合在一起實現,這將在一定程度上減少運算量,使產生的火焰在視覺上更加真實。實現上述近似模型的偽代碼可大致設計如下:

ARRAY_OF_BYTES: buffer1(xsize*ysize),buffer2(xsize*ysize)
While(TRUE){
 for(y=1;y  for(x=1;x   n1 = buffer1(x+1, y) //讀取4相臨像素值
   n2 = buffer1(x-1, y)
   n3 = buffer1(x, y+1)
   n4 = buffer1(x, y-1)
   p = ((n1+n2+n3+n4+p) /5); //四臨像素均值
   p = p-c; //同一固定冷卻衰減值相減
   if(p<0)
    p=0
   buffer2(x,y-1)=p
  }
 }
 copy buffer2 to the screen ; //顯示下一幀
 copy buffer2 to buffer1; //更新Buffer1
}

火焰非均勻冷卻的改進模型

根據上述近似模型可對火焰進行一定程度上的仿真,但由於沒有引入隨機分布火焰往往看上去相當單調規則,而且火焰總呈線性上升,冷卻速度也嚴格地保持統一速率。要消除以上問題,可通過引入隨機非均勻因素來解決。一種途徑是隨機布置各點冷卻值使火焰冷卻過程非均勻化。但由於火焰的模擬過程是實時進行的,為確保動態模擬過程中能順暢進行,最好用預先創建的冷卻位圖(見右圖)來代替。一般采用在屏幕上隨機撒布幾千個亮度不同的點並對其應用平滑處理等方法對冷卻位圖加以填充。通過冷卻圖中獲取的數值來代替原來固定的冷卻衰減值效果要好的多,此時的冷卻過程改進為Pixel(x,y)=Pixel(x,y)-Coolingmap(x,y) 這樣的衰減結果將使火焰的冷卻衰減效果更加真實:

p = lightBuf2+imgWidth*2;
pp = coolMap + coolMapWidth*2;
p1 = lightBuf1+imgWidth*2;
p2 = p1 - imgWidth;
p3 = p1 - 1;
p4 = p1 + 1;
p5 = p1 + imgWidth;
for(i=0;i //計算某點及其四鄰像素均值
 c1=(unsigned char)(((UINT)*p1+(UINT)*p2+(UINT)*p3+(UINT)*p4+(UINT)*p5)/5);
 c2 = *pp;
 if(c1>c2)
  c1 -= c2;
 *p = c1;
 pp++,p++,p1++,p2++,p3++,p4++,p5++; //內存指針修正
}

由於火焰在進行冷卻衰減的同時也在進行著火焰的擴散與對流因此必須使這幾種效果保持同步,這需要以同對流速度相同的速度向上滾動冷卻位圖來實現。為減少不必要的操作,滾動是在內存中通過改變冷卻位圖的垂直偏移量來加以實現:

memcpy(lightBuf1,lightBuf1+imgWidth*3,imgWidth*(imgHeight-3));

經過以上幾步處理雖有一定程度的改善,但仍存在一些缺陷,比如生存期、火焰上升速度恆定、在整個空間燃燒等。為使仿真效果更加逼真,可通過設置種子點來對上述缺陷加以改進。同樣出於處理速度的考慮,最好將種子點也以位圖的形式預先設定,在仿真時直接在內存中通過移動指針來完成對種子點的訪問,其主要代碼大致如下:

int t = RAND_MAX/5;
topX = (imgWidth - seedMapWidth)/2; //seedMapWidth種子位圖寬度
topY = (imgHeight - seedMapHeight)/2; //seedMapHeight種子位圖高度
p = lightBuf1 + (topY+2)*imgWidth + topX; //p, unsigned char型指針
ps = seedMap + seedMapWidth*2; //ps, unsigned char型指針
for(j=0;j<(seedMapHeight-4);j++) {
 p1 = p; //p1, unsigned char型指針
 for(k=0;k  if(*ps != 0){ //ps, unsigned char型指針
   if(rand() < t)
    *p1 = 255;
  }
  p1++,ps++; //指針修正
 }
 p += imgWidth; //指針修正
}

圖形加速顯示

前面的算法設計中一直很注意減少不必要的運算量以期獲得盡可能高的處理速度,但僅靠好的算法遠不能取得滿意的視覺效果。不少大型游戲盡管場景非常復雜,場景變化快,但玩家很少能感覺到游戲有難以忍受的停頓感。這不僅因為游戲采取了好的算法更重要的是游戲在同玩家交互的過程中大量采用了Direct X技術,該技術是Direct Draw、Direct Sound、Direct 3D等諸多技術的總稱。DirectDraw是其中最主要的一個部件,主要負責對圖形的加速,並允許程序員可以直接操作顯存、硬件位圖映射以及硬件覆蓋和換頁技術。而且該技術還支持雙緩沖和圖形換頁、3D z-buffers (z緩存)以及z方向(z-ordering)硬件輔助覆蓋等許多重要功能。可以看出,通過使用Direct Draw技術將極大改善仿真結果的圖形輸出效果,能非常流暢地對火焰進行實時的仿真。使用該技術之前必須先進行初試化等預處理工作:

//創建DirectDraw對象(為突出程序流程,以下均對錯誤檢測進行了省略)
DirectDrawCreate( NULL, &lpDD, NULL );
//取得全屏獨占模式
lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
//設置顯示器顯示模式
lpDD->SetDisplayMode( 640,480, 16 );
//填充主頁面信息
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1; //一個後台頁面
//創建主頁面
lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
lpDDSPrimary->GetAttachedSurface(&ddscaps,&lpDDSBack);
DDPIXELFORMAT pixelFormat;
pixelFormat.dwSize = sizeof(DDPIXELFORMAT);
lpDDSPrimary->GetPixelFormat(&pixelFormat);
……

初始化完成後可以通過在後台頁面繪圖,並在繪制完畢後將後台頁面復制到主頁面完成對一幀圖像的顯示:

lpDDSBack->Blt(NULL,NULL,NULL,DDBLT_COLORFILL|DDBLT_WAIT, &ddbltfx);
ddrval = lpDDSBack->Lock(NULL, &ddsd, 0, NULL) //鎖定後台頁面
while (ddrval== DDERR_WASSTILLDRAWING);
if( ddrval == DD_OK ){
 fire.render((WORD*)ddsd.lpSurface); //完成對一幀火焰的渲染
 lpDDSBack->Unlock(NULL); //解鎖後台頁面
}
while( 1 ) {
 ddrval = lpDDSPrimary->Flip( NULL, 0 ); //換頁
 if( ddrval == DD_OK )
  break;
 if( ddrval == DDERR_SURFACELOST ){
  ddrval = lpDDSPrimary->Restore(); //恢復主頁面
 if( ddrval != DD_OK )
  break;
}
if( ddrval != DDERR_WASSTILLDRAWING )
 break;
}

根據以上程序算法對火焰進行了仿真實驗,在速度和仿真結果在視覺的逼真程度上都獲得了非常好的效果。右圖是從仿真過程中截取的一幀畫面,從圖中可以看出,雖然在前面的算法設計過程中多處采用了看似過分的近似處理,但並未因此產生負面效果。實驗表明,本文采用的在數據緩沖區中對圖象進行處理的方法在程序運算和顯示的速度上與仿真對象--火焰的復雜程度是無關的,因此用類似的方法完全可以比較容易地實現對其他復雜物理、自然現象的仿真模擬。

結論

本文通過對火焰的計算機仿真模擬實現過程,對仿真模擬類程序一般的設計實現過程做了簡要介紹。通過對本文所述程序設計思路與實現方法的理解,可以用類似的方法結合實際情況靈活選用諸如OpenGL、Direct3D等不同的軟件接口對其他一些自然現象進行仿真模擬。本文所述程序在Windows 98下,由Microsoft Visual C++ 6.0調試通過(需要DirectX 5.0支持)。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved