耿植
摘要:用C語言實(shí)現(xiàn)計(jì)算機(jī)動(dòng)畫時(shí),往往使用一個(gè)大概時(shí)長作為畫面之間的延時(shí)。該做法會(huì)使得不同畫面的幀時(shí)長不相等,從而無法實(shí)現(xiàn)對動(dòng)畫速度的準(zhǔn)確控制。針對該問題,對延時(shí)函數(shù)進(jìn)行了改進(jìn),能按指定的幀時(shí)長進(jìn)行自適應(yīng)延時(shí)。以該理論為基礎(chǔ),進(jìn)一步提出一種以固定幀率更新畫面的編程方法,適用于編寫需要按時(shí)更新畫面的動(dòng)畫、游戲和應(yīng)用程序。最后,以C語言編寫控制臺(tái)窗口文本界面下的“英文對話動(dòng)態(tài)演示”應(yīng)用程序?yàn)槔故玖嗽摲椒ǖ膽?yīng)用。
關(guān)鍵詞:計(jì)算機(jī)動(dòng)畫;幀率;延時(shí);C語言;編程方法
中圖分類號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2017)01-0046-03
Abstract: To implement computer animation with C language, often use approximate duration as the delay between the frames. This will make the different time length between different frames, making it impossible to achieve accurate control over the speed of the animation. To solve this problem, the sleep function is improved and the adaptive delay can be carried out according to the specified frame time length. On this basis, further proposed a programming method to update frames with fixed frame rate, suitable for animation, games and applications. Finally, the application of this method is demonstrated by the example of "English Dialogue Dynamic Demonstration" under textual interface of the windows console in C language.
Key words: computer animation; frame rate; delay; C language; programming method
1 背景
利用人類視覺系統(tǒng)的視覺殘留特性[1],計(jì)算機(jī)動(dòng)畫中采用:“繪制一幀畫面”、“延時(shí)”、“繪制下一幀畫面”、“延時(shí)”的方式,在短時(shí)間內(nèi)快速切換畫面,能使人產(chǎn)生“畫面是連續(xù)變化”的感覺。有大量的文章探討了使用C語言在圖形界面下進(jìn)行畫面繪制、擦除與切換的技術(shù)手段,如:屏幕重畫、雙緩沖、異或、調(diào)色板[2]、圖形頁面、掩膜[3]等。但卻都只是簡單地使用如delay(10)[3]、delay(200)[4]等大概延時(shí)作為控制動(dòng)畫速度的手段。這僅能實(shí)現(xiàn) “按某個(gè)大概速度動(dòng)起來”,而無法實(shí)現(xiàn)“按一個(gè)指定的速度動(dòng)起來”和“從某時(shí)開始動(dòng)起來,一直到某時(shí)為止”;也沒有討論一個(gè)畫面中若同時(shí)存在多個(gè)不同速度變化的畫面元素如何處理。以下提出一種以固定幀率更新畫面的編程方法,可用于實(shí)現(xiàn)圖形界面和文本界面下的各種類型的動(dòng)畫[6]、游戲和應(yīng)用程序。下文中的C源代碼均在Windows系統(tǒng)中Visual C++編譯環(huán)境調(diào)試通過。
2 幀率
幀率(frame rate)是影視、動(dòng)畫和游戲領(lǐng)域中的概念,表示畫面切換的速度,單位為fps(frames per second,即每秒幀數(shù))。fps數(shù)值越大則畫面連貫性越強(qiáng),但同時(shí)數(shù)據(jù)量也越大,不同場合下需要綜合考慮容量與視覺效果的平衡。通常,電影的幀率為24fps(高幀率電影[5]可以達(dá)到48fps),電視(PAL制)為25fps,游戲?qū)Ξ嬅媪鲿承?、操作反?yīng)靈敏性要求較高,通常需要高于30fps。
幀時(shí)長是指兩幀畫面先后出現(xiàn)的間隔時(shí)間長度,其值是幀率的倒數(shù)。為敘述方便,下文以Tfps=n表示幀率為n時(shí)的理想幀時(shí)長,單位為毫秒。即:
Tfps=n = 1000/n (ms)
在幀率確定后,即可通過幀時(shí)長來計(jì)算動(dòng)畫所需的幀數(shù)、同時(shí)控制畫面元素的變化速度。
3 控制畫面變化速度
以幀率為25fps的動(dòng)畫為例,其理想幀時(shí)長為:
Tfps=n = 1000/25 = 40(ms) (n=25)
若在繪制一幀畫面后立刻更新下一幀畫面,如下代碼所示:
//程序段1:無延時(shí)畫面更新
while (!kbhit())
DrawOneFrame(); //更新1幀畫面
此時(shí),第i幀的實(shí)際幀時(shí)長Tf(fi)等于該幀的代碼執(zhí)行時(shí)長Tr(fi),由于現(xiàn)代電腦性能強(qiáng)勁,Tr(fi)通常小于Tfps=25,即:
Tf(fi) = Tr(fi) < Tfps=n (n=25)
因此,上面代碼生成的動(dòng)畫幀率大于25fps,畫面會(huì)像快進(jìn)的視頻一樣飛速播放。為控制畫面更新速度,需要在更新完一幀畫面后,進(jìn)行適當(dāng)時(shí)長的延時(shí)。
3.1 使用等時(shí)長延時(shí)粗略控制幀時(shí)長
要使動(dòng)畫的時(shí)長與速度準(zhǔn)確,實(shí)際幀時(shí)長應(yīng)等于理想幀時(shí)長,這需要為每一幀增加一個(gè)延時(shí)Td(fi),即:
Tfps=n = Tf(fi) = Tr(fi) + Td(fi)
如引言中所述,許多文章討論動(dòng)畫實(shí)現(xiàn)時(shí)忽略代碼執(zhí)行耗時(shí)Tr(fi),將Td(fi)簡化為一個(gè)固定時(shí)長的Td,即:
Tfps=n = Tf(fi) = Tr(fi) + Td ( Tr(fi)>0, 0
加入Td延時(shí)后程序段1改寫為:
//程序段2:等時(shí)長延時(shí)畫面更新
#define FPS 25 /* 使用宏定義指定幀率,以25fps為例 */
#define FRAME_TIME (1000/FPS) /* 按幀率換算理想幀時(shí)長,單位為ms */
#define DELTA_T 0 /* Tr(fi)的一個(gè)粗略值,大部分文章取0值 */
#include
while (!kbhit())
{
DrawOneFrame(); //更新1幀畫面
Sleep(FRAME_TIME - DELTA_T); //等時(shí)長延時(shí)FRAME_TIME-DELTA_T ms
}
顯然,由于每幀畫面的Tr(fi)的都不相同,Td無論取何值也無法使任意兩幀的實(shí)際幀時(shí)長相等,即:
Tf(fi) ≠ Tf(fj) (1≤i,j≤m,且i≠j,m為動(dòng)畫最大幀數(shù))
因此,使用等時(shí)長延時(shí)控制動(dòng)畫速度有兩個(gè)無法解決的問題:
第一, 同一段動(dòng)畫,無論Td取何值也無法得到穩(wěn)定的幀率。如圖1所示,電腦A無論采用40ms延時(shí)和25ms延時(shí)(或其他任何值)均無法使每一幀都得到40ms的幀時(shí)長;
第二, 采用相同延時(shí)的同一段動(dòng)畫,在不同性能的電腦上播放時(shí)幀率也不同。如圖1所示,性能較弱的電腦B使用25ms延時(shí),相比電腦A,其幀時(shí)長更長(即動(dòng)畫速度更慢)。
3.2 使用等幀長延時(shí)準(zhǔn)確控制幀時(shí)長
為解決等時(shí)長延時(shí)的兩個(gè)問題,必須知道第i幀畫面的代碼執(zhí)行時(shí)長Tr(fi),計(jì)算得到第i幀的Td(fi)值,使得:
Tfps=n = Tf(fi) (1≤i≤m, m為動(dòng)畫最大幀數(shù))
如圖2所示。
為此,必須要記錄每次延時(shí)執(zhí)行時(shí)的具體時(shí)刻,作為計(jì)算Td(fi)的依據(jù),這可以通過使用clock()函數(shù)實(shí)現(xiàn)。以下為庫函數(shù)clock()的函數(shù)原型。
clock_t clock(void);
clock()函數(shù)返回自本程序運(yùn)行起到調(diào)用此函數(shù)為止,CPU產(chǎn)生的clock tick(CPU時(shí)鐘計(jì)時(shí)單元)數(shù)。在VC++6.0的time.h里,對每秒clock tick數(shù)有如下宏定義:
#define CLOCKS_PER_SEC 1000
由此可知1個(gè)clock tick時(shí)長為1毫秒。因此,使用clock()可以實(shí)現(xiàn)計(jì)時(shí)精度為±1ms的自適應(yīng)等幀長延時(shí)函數(shù)。代碼如下:
//程序段3:改進(jìn)的自適應(yīng)等幀長延時(shí)函數(shù)
#include
int pastFrames = 0 ; //全局變量,記錄從程序運(yùn)行到目前已經(jīng)過的幀數(shù)
void FixedFrameTimeSleep(int frameTime)
{
static clock_t endClock = 0; //存放本次延時(shí)結(jié)束時(shí)刻的clock tick數(shù)
pastFrames++; //經(jīng)過的幀數(shù)加1
endClock += frameTime * CLOCKS_PER_SEC / 1000; //計(jì)算本幀結(jié)束時(shí)刻
if (clock() > endClock) //若已超時(shí)則不延時(shí)
endClock = clock(); //以當(dāng)前時(shí)刻為本幀的結(jié)束時(shí)刻
else
while (clock() < endClock) //循環(huán)1ms的延時(shí)至本幀結(jié)束,實(shí)現(xiàn)Td(fi) ms的延時(shí)
Sleep(1); //延時(shí)并降低CPU占用率
}
使用此函數(shù)替換程序段1中的Sleep()函數(shù),即得到以固定幀率FPS運(yùn)行的畫面更新程序框架:
//程序段4:自適應(yīng)等幀長延時(shí)函數(shù)實(shí)現(xiàn)固定幀率更新畫面
while (!kbhit())
{
DrawOneFrame();
FixedFrameTimeSleep(FRAME_TIME);
}
4 實(shí)現(xiàn)畫面元素按時(shí)更新的函數(shù)框架
在實(shí)現(xiàn)了固定幀率后,畫面元素的更新頻率可以換算為每幾幀更新一次,出現(xiàn)時(shí)刻、結(jié)束時(shí)刻可以通過pastFrames來控制。如下函數(shù)框架實(shí)現(xiàn):以指定fps(fps≤FPS),從startTime時(shí)刻到endTime時(shí)刻更新畫面元素。
//程序段5:畫面元素按時(shí)更新函數(shù)框架
void DrawAnimation(int fps,int startTime, int endTime)
{
int curTime, updateFrames ;
curTime = pastFrames * FRAME_TIME ; //計(jì)算當(dāng)前時(shí)間
if ( curTime >= startTime && curTime <= endTime ) //判斷是否在指定的動(dòng)畫時(shí)間段
{
updateFrames = FPS / fps ; //將畫面元素的fps換算成每幾幀更新一次
if ( pastFrames % updateFrames == 0 ) //判斷是否應(yīng)在本幀更新
{
//更新畫面元素的代碼
}
}
}
6 實(shí)現(xiàn)多個(gè)畫面元素按時(shí)更新的程序框架
若畫面中有多個(gè)畫面元素,只需為它們分別編寫合適的DrawAnimation()函數(shù),再依次加到程序循環(huán)中即可,如下所示:
//程序段6:多個(gè)畫面元素按時(shí)更新的程序框架
while (!kbhit())
{
DrawAnimation1(fps1,ts1,te1);
DrawAnimation2(fps2,ts2,te2);
……
FixedFrameTimeSleep(FRAME_TIME);
}
7 編程方法應(yīng)用演示
使用C編寫一個(gè)控制臺(tái)窗口文本界面下循環(huán)動(dòng)態(tài)演示Tom和Jerry進(jìn)行“英文對話”的程序。為演示編程方法中以固定幀率對多個(gè)畫面元素分別按時(shí)更新的特性,對此應(yīng)用程序做如下設(shè)定:
1)一輪對話分為“問”和“答”;
2)Tom從問候語中隨機(jī)選擇一句發(fā)問,Jerry根據(jù)問候語進(jìn)行相應(yīng)的回答;
3)發(fā)問前、回答前均停頓1秒,回答完成后停頓2秒,使得問答停頓自然;
4)Tom和Jerry的語速不同:Tom為10字符/秒,Jerry為20字符/秒;
5)有一個(gè)按“mmss””格式顯示的數(shù)字時(shí)鐘。
若設(shè)定幀率為40fps(幀時(shí)長為25ms),則根據(jù)應(yīng)用程序設(shè)定可知:
1)數(shù)字時(shí)鐘的更新頻率fps為1,每40幀更新一次;
2)Tom、Jerry所說的話更新頻率fps分別為10、20,每4幀、2幀更新1次;
3)一輪對話的時(shí)間順序?yàn)椋?秒+發(fā)問者說話字符數(shù)/語速+1秒+發(fā)問者說話字符數(shù)/語速+2秒。
為數(shù)字時(shí)鐘、Tom(說的話)和Jerry(說的話)這三個(gè)畫面元素按照DrawAnimation()函數(shù)框架分別編寫更新函數(shù),并依序放入程序框架,以下為部分代碼。
//程序段7:“英文對話演示”部分代碼
#define MAX_TIME 3600 /* 設(shè)定最長演示時(shí)間3600秒 */
int ts1 = 0, te1 = 0; //Tom說話的開始時(shí)間與結(jié)束時(shí)間
int ts2 = 0, te2 = 0; //Jerry說話的開始時(shí)間與結(jié)束時(shí)間
while (!kbhit())
{
CalculateTime(); //在上一輪對話完成后,計(jì)算下一輪對話的時(shí)間安排
DrawTom(10,ts1,te1);
DrawJerry(20,ts2,te2);
DrawDigitalClock(1,0,MAX_TIME);
FixedFrameTimeSleep(FRAME_TIME);
}
最終實(shí)現(xiàn)的效果如圖3所示。
8 結(jié)束語
改進(jìn)的等幀長延時(shí)將每幀中代碼執(zhí)行、數(shù)值計(jì)算、界面更新等消耗的時(shí)長計(jì)算在內(nèi),動(dòng)態(tài)增減剩余延時(shí),保證每幀的時(shí)長相同。通過等幀長延時(shí)獲得的固定幀率是一條時(shí)間軸,可以驅(qū)動(dòng)程序流程準(zhǔn)確地按時(shí)間運(yùn)行。在確定幀率和幀時(shí)長后,可用之計(jì)算畫面中每一個(gè)動(dòng)態(tài)元素在此時(shí)間軸上的起始幀、結(jié)束幀以及需要更新的關(guān)鍵幀。這樣就能保證畫面中所有動(dòng)態(tài)元素都能按各自速度和時(shí)間準(zhǔn)確更新。但是,當(dāng)畫面元素之間、畫面元素與用戶操作之間存在比較復(fù)雜的交互邏輯時(shí)(比如:RPG游戲中的戰(zhàn)斗場景[7]),使用面向過程的編程方式難以實(shí)現(xiàn)。因此,需要在此方法基礎(chǔ)上引入事件驅(qū)動(dòng)機(jī)制[8],以降低程序交互邏輯實(shí)現(xiàn)的難度。
參考文獻(xiàn):
[1] 朱蓉, 鄭建華. C語言實(shí)現(xiàn)動(dòng)畫技術(shù)的探討[J]. 電腦知識(shí)與技術(shù), 2005(35): 145-147.
[2] 趙艷忠, 王鴻銘. C語言平臺(tái)下動(dòng)畫技術(shù)實(shí)現(xiàn)方法淺析[J]. 科技信息, 2008(22): 402-421.
[3] 王進(jìn)華, 章云, 曾歆懿. 基于掩模技術(shù)的C語言動(dòng)畫設(shè)計(jì)與實(shí)現(xiàn)[J]. 科技廣場, 2005(10): 39-41.
[4] 韓潔. 用C語言實(shí)現(xiàn)圖形動(dòng)畫技術(shù)[J]. 計(jì)算機(jī)時(shí)代, 1999(3): 14-15.
[5] 陳曉悅. 淺析高幀率電影制作流程[J]. 現(xiàn)代電影技術(shù), 2016(5): 36-39.
[6] 和青芳. 計(jì)算機(jī)圖形學(xué)原理及算法教程(Visual C++版)[M]. 北京: 清華大學(xué)出版社出版, 2006: 202-220.
[7] 張向娟, 李忠. 一種角色扮演類游戲軟件設(shè)計(jì)方法及應(yīng)用[J]. 電腦知識(shí)與技術(shù), 2012(19): 4641-4644.
[8] 韓志強(qiáng). 關(guān)于C#實(shí)現(xiàn)事件驅(qū)動(dòng)機(jī)制的研究[J]. 赤峰學(xué)院學(xué)報(bào):自然科學(xué)版, 2010(12): 50-51.