首先我得說(shuō)我非常喜歡這一章節(jié).Jonathan de Blok使我產(chǎn)生了用OpenGL編寫(xiě)AVI播放器的想法,可那時(shí),我跟本不知如何打開(kāi)AVI文件,更不必說(shuō)去寫(xiě)一個(gè)播放器了.于是我瀏覽了搜藏的編程書(shū)籍,沒(méi)有一本講到AVI文件的.我又閱讀了MSDN上和AVI文件格式有關(guān)的一切內(nèi)容,上面有很多有用的信息,但我需要更多的.
花了幾小時(shí)在網(wǎng)上搜到AVI范例,只找到兩個(gè)網(wǎng)站.我的搜索技巧不能說(shuō)很棒吧,但99.9%的情況,我能找到我要尋找的東西.了解到AVI范例竟如此之少時(shí),我完全震驚了.大多數(shù)范例并不能編譯通過(guò)...有一些則用了太復(fù)雜的的方法(至少對(duì)我如此),剩下的不錯(cuò),可是用VB,Delphi等寫(xiě)的(不是用vc++).
找到的第一個(gè)網(wǎng)頁(yè)是Jonathan Nix寫(xiě)的題為"AVI 文件"的文章.網(wǎng)址是http://www.gamedev.net/reference/programming/features/avifile.感謝Jonathan寫(xiě)了這片關(guān)于AVI格式的好文章.雖然我用不同的做法,但他的代碼片斷和清晰的注解讓人學(xué)得很輕松!第二個(gè)網(wǎng)站標(biāo)題為"AVI 總體觀"(John F. McGowan, Ph.D寫(xiě)的)..我可以大肆贊美John的網(wǎng)葉有多么驚奇,但你最好自己去看看.他的網(wǎng)址是http://www.jmcgowan.com/avi.html.這個(gè)網(wǎng)站講到了和AVI格式有關(guān)的幾乎所有內(nèi)容.感謝John做了一個(gè)這么有用的網(wǎng)站.
最后要提到是我沒(méi)有借鑒任何代碼,沒(méi)有抄襲任何代碼.我的代碼是花了三天時(shí)間了解到上述網(wǎng)站和文章的信息后才寫(xiě)成的.我是想說(shuō)我的代碼也許不是播放AVI文件的最好代碼,他也許不是放AVI文件的正確代碼,但他管用而且使用方便.如果你不喜歡這些代碼和我的編程風(fēng)格,或者覺(jué)得我的言論傷害到整個(gè)編程界,你有以下選擇:1)在網(wǎng)上找到替換的資源2)寫(xiě)自己的AVI播放器3)寫(xiě)一篇更好的文章.任何訪問(wèn)本網(wǎng)站的人現(xiàn)在應(yīng)該知道我只是一名中級(jí)程序員(這一點(diǎn)我在網(wǎng)站里很多文章的開(kāi)頭都提到過(guò))!我編寫(xiě)代碼自樂(lè)而已.本網(wǎng)站的目的在于讓非精英程序員更輕松的開(kāi)始OpenGl編程.這些文章只是關(guān)于我實(shí)現(xiàn)的幾個(gè)特殊的效果...沒(méi)有其他的.
開(kāi)始講代碼首先你要注意的是我們要包括和連接到視頻頭文件和庫(kù)文件.非常感謝微軟(窩不敢相信我說(shuō)了什么).庫(kù)文件使打開(kāi),播放AVI文件都很簡(jiǎn)便.現(xiàn)在你要知道的是必須包括頭文件vfw.h而且要連接到vfw32.lib庫(kù)文件如果想編譯你的代碼的話:)
#include <vfw.h> // Video For Windows頭文件
#include "NeHeGL.h" // NeHeGL頭文件
#pragma comment( lib, "opengl32.lib" )
#pragma comment( lib, "glu32.lib" )
#pragma comment( lib, "vfw32.lib" ) // 鏈接到VFW32.lib
GL_Window* g_window;
Keys* g_keys;
現(xiàn)在定義變量.angle是用來(lái)根據(jù)時(shí)間來(lái)旋轉(zhuǎn)物體的.為簡(jiǎn)單起見(jiàn)我們用angle來(lái)控制所有的旋轉(zhuǎn).
接下來(lái)是一個(gè)整型變量是用來(lái)計(jì)算經(jīng)過(guò)的時(shí)間(以毫秒計(jì)).它使幀速保持一個(gè)速度.
后面細(xì)講!
frame是動(dòng)畫(huà)要顯示的當(dāng)前幀,初始值為0(第一幀).我想如果成功打開(kāi)AVI,他至少有一幀吧,這樣假定比較安全:)
effect是當(dāng)前屏幕上的效果(有:立方體,球體,圓柱體).env是布爾值.若它為true則環(huán)境映射啟動(dòng),若為假,則物體沒(méi)有環(huán)境映射.若bg為true,你會(huì)看到物體后有全屏的動(dòng)畫(huà);若為假,你只會(huì)看到物體(沒(méi)有背景).
sp,ep和bp用來(lái)確定使用者沒(méi)有按著鍵不放.
float angle; // 旋轉(zhuǎn)用
int next; // 動(dòng)畫(huà)用
int frame=0; // 幀計(jì)數(shù)器
int effect; // 當(dāng)前效果
bool sp; // 空格鍵按下?
bool env=TRUE; // 環(huán)境映射(默認(rèn)開(kāi))
bool ep; //’E’ 按下?
bool bg=TRUE; // 背景(默認(rèn)開(kāi))
bool bp; // ’B’ 按下?
psi結(jié)構(gòu)體包含AVI文件信息.pavi緩沖的指針,緩沖用來(lái)接受AVI文件打開(kāi)時(shí)的流句柄.pgf是指向GetFrame對(duì)象的指針.bmih在后面的代碼中將被用來(lái)把動(dòng)畫(huà)的每一幀轉(zhuǎn)換為我們需要的格式(保存位圖的頭信息).lastframe保存AVI動(dòng)畫(huà)最后一幀的序號(hào).width和height保存AVI流的維信息,最后...pdata是圖象數(shù)據(jù)的指針(每次在從AVI中獲得一幀后返回).mpf用來(lái)計(jì)算每幀需要多少毫秒.后面細(xì)談這個(gè)變量.
AVISTREAMINFO psi; // 包含流信息的結(jié)構(gòu)體的指針
PAVISTREAM pavi; // 流句柄
PGETFRAME pgf; // GetFrame對(duì)象的指針
BITMAPINFOHEADER bmih; // 頭信息 For DrawDibDraw
long lastframe; // 流中最后一幀
int width; // 視頻寬
int height; // 視頻高
char *pdata; // 紋理數(shù)據(jù)指針
int mpf; // 控制每幀顯示時(shí)間
在本章中我們用GLU庫(kù)創(chuàng)建兩個(gè)二次曲面(球體和圓柱體).quadratic是曲面對(duì)象的指針.
hdd是DrawDib設(shè)備上下文的句柄.hdc是設(shè)備上下文的句柄.
hBitmap是設(shè)備無(wú)關(guān)位圖的句柄(在后面位圖轉(zhuǎn)換時(shí)用到).
data是最后指向轉(zhuǎn)換后位圖的圖象數(shù)據(jù)的指針,在后面的代碼中會(huì)有意義,往下讀:)
GLUquadricObj *quadratic; // 存儲(chǔ)二次曲面對(duì)象
HDRAWDIB hdd; // Dib句柄
HBITMAP hBitmap; // 設(shè)備無(wú)關(guān)位圖的句柄
HDC hdc = CreateCompatibleDC(0); // 創(chuàng)建一個(gè)兼容的設(shè)備上下文
unsigned char* data = 0; // 調(diào)整后的圖象數(shù)據(jù)指針
下面使用到匯編語(yǔ)言.那些從來(lái)沒(méi)有用過(guò)匯編的不要被嚇倒了.他看起來(lái)神秘,實(shí)際上非常簡(jiǎn)單!
在寫(xiě)本章是我發(fā)現(xiàn)了十分奇怪的事.第一次做出來(lái)的可以播放,但色彩混亂了.本來(lái)是紅色的變成藍(lán)色的了,本來(lái)是藍(lán)色的變成紅色的了.我簡(jiǎn)直要發(fā)狂了!我相信我的代碼某處有問(wèn)題.看了一邊代碼還是找不到bug于是又讀了MSDN.為什么紅色與藍(lán)色互換了!?!MSDN明明說(shuō)24比特位圖是RGB啊!又讀了一些東西,我找到了答案.在WINDOWS圖形系統(tǒng)中,RGB數(shù)據(jù)是倒著存儲(chǔ)的(BGR).而在OpenGL中,要用的RGB數(shù)據(jù)就是RGB的順序!
在抱怨了微軟之后:)我決定加一條注解!我不因?yàn)镽GB數(shù)據(jù)倒過(guò)來(lái)存放而打算罵微軟.只是覺(jué)得很奇怪--他叫做RGB實(shí)際上在文件中是按BGR存的!
另:這一點(diǎn)和"little endian"和"big endian"有關(guān).Intel以及Intel兼容產(chǎn)品用little endian--LSB(數(shù)據(jù)最低位)首先存.OpenGL是產(chǎn)生于Silicon Graphics的機(jī)器的,用的是big endian,所以標(biāo)準(zhǔn)的OpenGL要位圖格式是big endian格式.這是我的理解.
棒極了!所以說(shuō)這第一個(gè)播放器就是一個(gè)垃圾!我的解決方法是用一個(gè)循環(huán)把數(shù)據(jù)交換過(guò)來(lái).這能行,但太慢.我又在紋理生成代碼中用GL_BGR_EXT代替了GL_RGB,速度暴升,色彩顯示也對(duì)了!問(wèn)題解決了...原來(lái)我是這樣想!后來(lái)發(fā)現(xiàn)一些OpenGL驅(qū)動(dòng)不支持GL_BGR... :(
與好友Maxwell Sayles討論后,他推薦我用匯編代碼來(lái)交換數(shù)據(jù).一分鐘后,他用icq發(fā)來(lái)下面的代碼!也許不是最優(yōu)化的,但他很快也很有效!
動(dòng)畫(huà)的每一幀存在一個(gè)緩沖里.圖象256像素寬,256像素高,每個(gè)色彩一字節(jié)(一像素3字節(jié)).下面的代碼會(huì)掃描整個(gè)緩沖并交換紅與藍(lán)的字節(jié).紅存在ebx+0,藍(lán)存在ebx+2.我們一次向前走3字節(jié)(因?yàn)橐粋€(gè)像素3字節(jié)).不斷掃描直到所有數(shù)據(jù)交換過(guò)來(lái).
你們有些人不喜歡用匯編代碼,所以我想有必要在本章里解釋一下.本來(lái)計(jì)劃用GL_BGR_EXT,他管用,但不是所有的顯卡都支持!我又用異或交換法,這在所有機(jī)器上都是有效的,但不十分快.用了匯編后速度相當(dāng)快.考慮到我們?cè)谔幚韺?shí)時(shí)視頻,你需要最快的交換方法.權(quán)衡了以上選擇,匯編是最好的!如果你有更好的辦法,就用你自己的吧!我并不是告訴你必須如何去做,只是告訴你我的做法.我也會(huì)細(xì)致的解釋代碼.如果你要用更好的代碼來(lái)作替換,你要清楚這些代碼是來(lái)干什么的,自己寫(xiě)代碼時(shí),要為日后的優(yōu)化提供方便.
void flipIt(void* buffer) // 交換紅藍(lán)數(shù)據(jù)(256x256)
{
void* b = buffer; // 緩沖指針
__asm // 匯編代碼
{
mov ecx, 256*256 // 設(shè)置計(jì)數(shù)器
mov ebx, b // ebx存數(shù)據(jù)指針
label: // 循環(huán)標(biāo)記
mov al,[ebx+0] // 把ebx位置的值賦予al
mov ah,[ebx+2] // 把ebx+2位置的值賦予ah
mov [ebx+2],al // 把a(bǔ)l的值存到ebx+2的位置
mov [ebx+0],ah // 把a(bǔ)h的值存到ebx+0的位置
add ebx,3 // 向前走3個(gè)字節(jié)
dec ecx // 循環(huán)計(jì)數(shù)器減1
jnz label // ecx非0則跳至label
}
}
下面的代碼以只讀方式打開(kāi)AVI文件.szFile是打開(kāi)文件的名字.title[100]用來(lái)修改window標(biāo)題(顯示AVI文件信息).
首先調(diào)用AVIFileInit().他初始化AVI文件庫(kù)(使東西能用?鵠?).
打開(kāi)AVI文件有很多方法.我采用AVIStreamOpenFromFile(...).他能打開(kāi)AVI文件中單獨(dú)一個(gè)流(AVI文件可以包含多個(gè)流).它的參數(shù)如下:pavi是接收流句柄的緩沖的指針,szFile是打開(kāi)文件的名字(包括路徑).第三參數(shù)是打開(kāi)的流的類型.在這個(gè)工程里,我們只對(duì)視頻流感興趣(streamtypeVIDEO).第四參數(shù)是0,這表示我們需要第一次讀到的視頻流(一個(gè)AVI文件里會(huì)有多個(gè)視頻流,我們要第一個(gè)).OF_READ表示以只讀方式打開(kāi)文件.最后一個(gè)參數(shù)是一個(gè)類標(biāo)識(shí)句柄的指針.說(shuō)實(shí)話,我也不清楚他是干嗎的.我讓windows自己設(shè)定,于是把NULL傳過(guò)去.
void OpenAVI(LPCSTR szFile) // 打開(kāi)AVI文件szFile
{
TCHAR title[100]; // 包含修改了的window標(biāo)題
AVIFileInit(); // 打開(kāi)AVI文件庫(kù)
// 打開(kāi)AVI流
if (AVIStreamOpenFromFile(&pavi, szFile, streamtypeVIDEO, 0, OF_READ, NULL) !=0)
{
// 打開(kāi)流時(shí)的出錯(cuò)處理
MessageBox (HWND_DESKTOP, "打開(kāi)AVI流失敗", "錯(cuò)誤", MB_OK | MB_ICONEXCLAMATION);
}
到目前為止,我們假定文件被正確打開(kāi),流被正確定位!然后用AVIStreamInfo(...)從AVI文件里抓取一些信息.
先前我們創(chuàng)建了叫psi的結(jié)構(gòu)體來(lái)保存AVI流的信息.下面第一行,我們把AVI信息填入該結(jié)構(gòu)體.從流的寬度(以像素計(jì))到動(dòng)畫(huà)的幀速等所有的信息都會(huì)存到psi中.那些想要精確控制播放速度的要記住我剛才說(shuō)的.更多的信息參閱MSDN.
我們通過(guò)右邊位置減左邊位置算出幀寬.這個(gè)結(jié)果是以像素記的精確的幀寬.至于高度,可以用底邊位置減頂邊位置得到.這樣得到高度的像素值.
然后用AVIStreamLength(...)得到AVI文件最后一幀的序號(hào).AVIStreamLength(...)返回動(dòng)畫(huà)最后一幀的序號(hào).結(jié)果存在lastframe里.
計(jì)算幀速很簡(jiǎn)單.每秒幀速(fps)= psi.dwRate/psi,dwScale.返回的值應(yīng)該匹配顯示幀的速度(你在AVI動(dòng)畫(huà)中右擊鼠標(biāo)可以看到).你會(huì)問(wèn)那么這和mpf有什么關(guān)系呢?第一次寫(xiě)這個(gè)代碼時(shí),我試著用fps來(lái)選擇動(dòng)畫(huà)了正確的幀面.我遇到一個(gè)問(wèn)題...視頻放的太快!于是我看了一下視頻屬性.face2.avi文件有3.36秒長(zhǎng).幀速是29.974fps.視頻動(dòng)畫(huà)共有91幀.而3.36*29.974 = 100.71.非常奇怪!!
所以我采用一些不同的方法.不是計(jì)算幀速,我計(jì)算每一幀播放所需時(shí)間.AVIStreamSampleToTime()把在動(dòng)畫(huà)中的位置轉(zhuǎn)換位你到達(dá)該位置所需的時(shí)間(毫秒計(jì)).所以通過(guò)計(jì)算到達(dá)最后一幀的時(shí)間就得到整個(gè)動(dòng)畫(huà)的播放時(shí)間.再拿這個(gè)結(jié)果除以動(dòng)畫(huà)總幀數(shù)(lastframe).這樣就給出了每幀的顯示時(shí)間(毫秒計(jì)).結(jié)果存在mpf(milliseconds per frame)里.你也能通過(guò)獲取動(dòng)畫(huà)中一幀的時(shí)間來(lái)算每幀的毫秒數(shù),代碼為:AVIStreamSampleToTime(pavi,1).兩種方法都不錯(cuò)!非常感謝Albert Chaulk提供思路!
我說(shuō)每幀的毫秒數(shù)不精確是因?yàn)閙pf是一個(gè)整型值,所以所有的浮點(diǎn)數(shù)都會(huì)被取整.
AVIStreamInfo(pavi, &psi, sizeof(psi)); // 把流信息讀進(jìn)psi
width=psi.rcFrame.right-psi.rcFrame.left; // 寬度為右邊減左邊
height=psi.rcFrame.bottom-psi.rcFrame.top; // 高為底邊減頂邊
lastframe=AVIStreamLength(pavi); // 最后一幀的序號(hào)
mpf=AVIStreamSampleToTime(pavi,lastframe)/lastframe; // mpf的不精確值
因?yàn)镺penGL需要紋理數(shù)據(jù)是2的冪,而大多視頻是160*120,320*240等等,所以需要一種把視頻格式重調(diào)整為能用作紋理的格式.我們可利用Windows Dib函數(shù)去做.
首先要做的是描述我們想要的圖像的類型.于是我們要以所需參數(shù)填好bmih這個(gè)BitmapInfoHeader結(jié)構(gòu).
首先設(shè)定該結(jié)構(gòu)體的大小.再把位平面數(shù)設(shè)為1.3字節(jié)的數(shù)據(jù)有24比特(RGB).要使圖像位256像素寬,256像素高,最后要讓數(shù)據(jù)返回為UNCOMPRESSED(非壓縮)的RGB數(shù)據(jù)(BI_RGB).
CreateDIBSection創(chuàng)建一個(gè)可直接寫(xiě)的設(shè)備無(wú)關(guān)位圖(dib).如果一切順利,hBitmap會(huì)指向該dib的比特值.hdc是設(shè)備上下文(DC)的句柄第二參數(shù)是BitmapInfo結(jié)構(gòu)體的指針.該結(jié)構(gòu)體包含了上述dib文件的信息.第三參數(shù)(DIB_RGB_COLORS)設(shè)定數(shù)據(jù)是RGB值.data是指向DIB比特值位置的指針的指針(嗚,真繞口).第五參數(shù)設(shè)為NULL,我們的DIB已被分配好內(nèi)存.末了,最后一個(gè)參數(shù)可忽略(設(shè)為NULL).
引自MSDN:SelecObject函數(shù)選一個(gè)對(duì)象進(jìn)入設(shè)備上下文(DC).
現(xiàn)在我們建好一個(gè)能直接寫(xiě)的DIB,yeah:)
bmih.biSize = sizeof (BITMAPINFOHEADER); // BitmapInfoHeader的大小
bmih.biPlanes = 1; // 位平面
bmih.biBitCount = 24; //比特格式(24 Bit, 3 Bytes)
bmih.biWidth = 256; // 寬度(256 Pixels)
bmih.biHeight = 256; // 高度 (256 Pixels)
bmih.biCompression = BI_RGB; // 申請(qǐng)的模式 = RGB
hBitmap = CreateDIBSection (hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL);
SelectObject (hdc, hBitmap); // 選hBitmap進(jìn)入設(shè)備上下文(hdc)
在從AVI中讀取幀面前還有幾件事要做.接下來(lái)使程序做好從AVI文件中解出幀面的準(zhǔn)備.用AVIStreamGetFrameOpen(...)函數(shù)做這一點(diǎn).
你能給這個(gè)函數(shù)傳一個(gè)結(jié)構(gòu)體作為第二參數(shù)(它會(huì)返回一個(gè)特定的視頻格式).糟糕的是,你能改變的唯一數(shù)據(jù)是返回的圖像的寬度和高度.MSDN也提到能傳AVIGETFRAMEF_BESTDISPLAYFMT為參數(shù)來(lái)選擇一個(gè)最佳顯示格式.奇怪的是,我的編譯器沒(méi)有定義這玩藝兒.
如果一切順利,一個(gè)GETFRAME對(duì)象被返回(用來(lái)讀幀數(shù)據(jù)).有問(wèn)題的話,提示框會(huì)出現(xiàn)在屏幕上告訴你有錯(cuò)誤!
pgf=AVIStreamGetFrameOpen(pavi, NULL); // 用要求的模式建PGETFRAME
if (pgf==NULL)
{
// 解幀出錯(cuò)
MessageBox (HWND_DESKTOP, "不能打開(kāi)AVI幀", "錯(cuò)誤", MB_OK | MB_ICONEXCLAMATION);
}
下面的代碼把視頻寬,高和幀數(shù)傳給window標(biāo)題.用函數(shù)SetWindowText(...)在window頂部顯示標(biāo)題.以窗口模式運(yùn)行程序看看以下代碼的作用.
// bt標(biāo)題欄信息(寬 / 高/ 幀數(shù))
wsprintf (title, "NeHe's AVI Player: Width: %d, Height: %d, Frames: %d", width, height, lastframe);
SetWindowText(g_window->hWnd, title); // 修改標(biāo)題欄
}
下面是有趣的東西...從AVI中抓取一幀,把它轉(zhuǎn)為大小和色深可用的圖象.lpbi包含一幀的BitmapInfoHeader信息.我們?cè)谙旅娴诙型瓿闪藥准?先是抓了動(dòng)畫(huà)的一幀...我們需要的幀面由這些幀確定.這會(huì)讓動(dòng)畫(huà)走掉這一幀,lpbi會(huì)指向這一幀的頭信息.
下面是有趣的東西...我們要指向圖像數(shù)據(jù)了.要跳過(guò)頭信息(lpbi->biSize).一件事直到寫(xiě)本文時(shí)我才意識(shí)到:也要跳過(guò)任何的色彩信息.所以要跳過(guò)biClrUsed*sizeof(RGBQUAD)(譯者:我想他是說(shuō)要跳過(guò)調(diào)色板信息).做完這一切,我們就得到圖像數(shù)據(jù)的指針了(pdata).
也要把動(dòng)畫(huà)的每一幀的大小轉(zhuǎn)為紋理能用的大小,還要把數(shù)據(jù)轉(zhuǎn)為RGB數(shù)據(jù).這用到DrawDibDraw(...).
一個(gè)大概的解釋.我們能直接寫(xiě)設(shè)定的DIB圖像.那就是DrawDibDraw(...)所做的.第一參數(shù)是DrawDib DC的句柄.第二參數(shù)是DC的句柄.接下來(lái)用左上角(0,0)和右下角(256,256)構(gòu)成目標(biāo)矩形.
lpbi指向剛讀的幀的bitmapinfoheader信息.pdata是剛讀的幀的圖像數(shù)據(jù)指針.
再把源圖象(剛讀的幀)的左上角設(shè)為(0,0),右下角設(shè)為(幀寬,幀高).最后的參數(shù)應(yīng)設(shè)為0.
這個(gè)方法可把任何大小、色深的圖像轉(zhuǎn)為256*256*24bit的圖像.
void GrabAVIFrame(int frame) // 從流中抓取一幀
{
LPBITMAPINFOHEADER lpbi; // 存位圖的頭信息
lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, frame); // 從AVI流中得到數(shù)據(jù)
pdata=(char *)lpbi+lpbi->biSize+lpbi->biClrUsed * sizeof(RGBQUAD); // 數(shù)據(jù)指針,由AVIStreamGetFrame返回(跳過(guò)頭
//信息和色彩信息)
// 把數(shù)據(jù)轉(zhuǎn)為所需格式
DrawDibDraw (hdd, hdc, 0, 0, 256, 256, lpbi, pdata, 0, 0, width, height, 0);
我們得到動(dòng)畫(huà)的每幀數(shù)據(jù)(紅藍(lán)數(shù)據(jù)顛倒的).為解決這個(gè)問(wèn)題,我們的高速代碼flipIt(...).記住,data是指向DIB比特值位置的指針的指針變量.這意味著調(diào)用DrawDibDraw后,data指向一個(gè)調(diào)整過(guò)大小(256*256),修改過(guò)色深(24bits)的位圖數(shù)據(jù).
原來(lái)我通過(guò)重建動(dòng)畫(huà)的每一幀來(lái)更新紋理.我收到幾封email建議我用glTexSubImage2D().翻閱了OpenGL紅寶書(shū)后,我磕磕絆絆的寫(xiě)出下面注釋:"創(chuàng)建紋理的計(jì)算消耗比修改紋理要大.在OpenGL1.1版本中,有幾條調(diào)用能更新全部或部分紋理圖像信息.這對(duì)某些應(yīng)用程序有用,比如實(shí)時(shí)的抓取視頻圖像作紋理.對(duì)于這些程序,用glTexSubImage2D()根據(jù)新視頻圖像來(lái)創(chuàng)建單個(gè)紋理以代替舊的紋理數(shù)據(jù)是行得通的."
在我個(gè)人并沒(méi)有發(fā)現(xiàn)速度明顯加快,也許在低端顯卡上才會(huì).glTexSubImage2D()的參數(shù)是:目標(biāo)是一個(gè)二維紋理(GL_TEXTURE_2D).細(xì)節(jié)級(jí)別(0),mipmapping用.x(0),y(0)告訴OpenGL開(kāi)始拷貝的位置(0,0是紋理的左下角).然后是圖像的寬度,我們要拷貝的圖像是256像素寬,256像素高.GL_RGB是我們的數(shù)據(jù)格式.我們?cè)诳截悷o(wú)符號(hào)byte.最后...圖像數(shù)據(jù)指針----data.非常簡(jiǎn)單!
Kevin Rogers 另加:我想指出使用glTexSubImage2D()另一個(gè)重要原因.不僅因?yàn)樵谠S多OpenGL實(shí)現(xiàn)中它很快,還因?yàn)槟繕?biāo)區(qū)不必是2的冪.這對(duì)視頻重放很方便,因?yàn)橐粠木S通常不是2的冪(而是像320*200之類的).這樣給了你很大機(jī)動(dòng)性,你可以按視頻流原本的樣子播放,而不是扭曲或剪切每一幀來(lái)適應(yīng)紋理的維.
重要的是你不能更新一個(gè)紋理如果你第一次沒(méi)有創(chuàng)建他!我們?cè)贗nitialize()中創(chuàng)建紋理.
還要提到的是...如果你計(jì)劃在工程里使用多個(gè)紋理,務(wù)必綁住你要更新的紋理.否則,更新出來(lái)的紋理也許不是你想要的!
flipIt(data); // 交換紅藍(lán)數(shù)據(jù)
// 更新紋理
glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 256, 256, GL_RGB, GL_UNSIGNED_BYTE, data);
}
接下來(lái)的部分當(dāng)程序退出時(shí)調(diào)用,我們關(guān)掉DrawDib DC,釋放占用的資源.然后釋放AVI GetFrame資源.最后釋放AVI流和文件.
void CloseAVI(void) // 關(guān)掉AVI資源
{
DeleteObject(hBitmap); //釋放設(shè)備無(wú)關(guān)位圖信息
DrawDibClose(hdd); // 關(guān)掉DrawDib DC
AVIStreamGetFrameClose(pgf); // 釋放AVI GetFrame資源
AVIStreamRelease(pavi); // 釋放AVI流
AVIFileExit(); // 釋放AVI文件
}
初始化很簡(jiǎn)明.設(shè)初始的angle為0.再打開(kāi)DrawDib庫(kù)(得到一個(gè)DC).一切順利的話,hdd會(huì)是新創(chuàng)建的dc的句柄.
以黑色清屏,開(kāi)啟深度測(cè)試,等等.
然后建一個(gè)新的二次曲面.quadratic是這個(gè)新對(duì)象的指針.設(shè)置光滑的法線,允許紋理坐標(biāo)的生成.
BOOL Initialize (GL_Window* window, Keys* keys)
{
g_window = window;
g_keys = keys;
// 開(kāi)始用戶的初始
angle = 0.0f; // angle為0先
hdd = DrawDibOpen(); // 得到Dib的DC
glClearColor (0.0f, 0.0f, 0.0f, 0.5f); // 黑色背景
glClearDepth (1.0f); // 深度緩沖初始
glDepthFunc (GL_LEQUAL); // 深度測(cè)試的類型(小于或等于)
glEnable(GL_DEPTH_TEST); // 開(kāi)啟深度測(cè)試
glShadeModel (GL_SMOOTH); // 平滑效果
glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 透視圖計(jì)算設(shè)為 //最高精度
quadratic=gluNewQuadric(); // 建二次曲面的指針
gluQuadricNormals(quadratic, GLU_SMOOTH); // 設(shè)置光滑的法線
gluQuadricTexture(quadratic, GL_TRUE); // 創(chuàng)建紋理坐標(biāo)
下面的代碼中,我們開(kāi)啟2D紋理映射,紋理濾鏡設(shè)為GLNEAREST(最快,但看起來(lái)很糙),建立球面映射(為了實(shí)現(xiàn)環(huán)境映射效果).試試其它濾鏡,如果你有條件,可以試試GLLINEAR得到一個(gè)平滑的動(dòng)畫(huà)效果.
設(shè)完紋理和球面映射,我們打開(kāi).AVI文件.我盡量使事情簡(jiǎn)單化...你能看出來(lái)么:)我們要打開(kāi)的文件叫作facec2.avi
glEnable(GL_TEXTURE_2D); // 開(kāi)啟2D紋理映射
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);// 設(shè)置紋理濾鏡
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // 設(shè)紋理坐標(biāo)生成模式為s
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // 設(shè)紋理坐標(biāo)生成模式為t
OpenAVI("data/face2.avi"); // 打開(kāi)AVI文件
// 創(chuàng)建紋理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
return TRUE; // 初始化成功返回TRUE
}
關(guān)閉時(shí)調(diào)用CloseAVI().他正確的關(guān)閉AVI文件,并釋放所有占用資源.
void Deinitialize (void) // 做所有的釋放工作
{
CloseAVI(); // 關(guān)閉AVI文件
}
到了檢查按鍵和更新旋轉(zhuǎn)角度的地方了.我知道再?zèng)]有必要詳細(xì)解釋這些代碼了.我們檢查空格鍵是否按下,若是,則增加effect值.有3種效果(立方,球,圓柱)第四個(gè)效果被選時(shí)(effect = 3)不畫(huà)任何對(duì)象...僅顯示背景!如果選了第四效果,空格又按下了,就重設(shè)為第一個(gè)效果(effect = 0).Yeah,我本該叫他對(duì)象:)
然后檢查’b’鍵是否按下,若是,則改變背景(bg從ON到OFF或從OFF到ON).
環(huán)境映射的鍵設(shè)置也一樣.檢查’E’是否按下,若是則改變env從TRUE到FALSE或從FALSE到TRUE.僅僅是關(guān)閉或開(kāi)啟環(huán)境映射!
每次調(diào)用Updata()時(shí)angle都加上一個(gè)小分?jǐn)?shù).我用經(jīng)過(guò)的時(shí)間除以60.0f使速度降一點(diǎn).
void Update (DWORD milliseconds) // 動(dòng)畫(huà)更新
{
if (g_keys->keyDown [VK_ESCAPE] == TRUE) //ESC按下?
{
TerminateApplication (g_window); // 關(guān)閉程序
}
if (g_keys->keyDown [VK_F1] == TRUE) // F1按下?
{
ToggleFullscreen (g_window); // 改變顯示模式
}
if ((g_keys->keyDown [' ']) && !sp) // 空格按下并已松開(kāi)
{
sp=TRUE; // 設(shè)sp為T(mén)rue
effect++; // 增加effect
if (effect>3) // 超出界限?
effect=0; // 重設(shè)為0
}
if (!g_keys->keyDown[' ']) // 空格沒(méi)按下?
sp=FALSE; // 設(shè)sp為False
if ((g_keys->keyDown ['B']) && !bp) // ’B’按下并已松開(kāi)
{
bp=TRUE; // 設(shè)bp為T(mén)rue
bg=!bg; // 改變背景 Off/On
}
if (!g_keys->keyDown['B']) // ’B’沒(méi)按下?
bp=FALSE; // 設(shè)bp為False
if ((g_keys->keyDown ['E']) && !ep) // ’E’按下并已松開(kāi)
{
ep=TRUE; // 設(shè)ep為T(mén)rue
env=!env; // 改變環(huán)境映射 Off/On
}
if (!g_keys->keyDown['E']) //’E’沒(méi)按下
ep=FALSE; // 設(shè)ep為False
angle += (float)(milliseconds) / 60.0f; // 根據(jù)時(shí)間更新angle
在原來(lái)的文章里,所有的AVI文件都以相同的速度播放.于是,我重寫(xiě)了本文讓視頻以正常的速度播放.next增加經(jīng)過(guò)的毫秒數(shù).如果你記得文章的前面,我們算出了顯示每幀的毫秒數(shù)(mpf).為了計(jì)算當(dāng)前幀,我們拿經(jīng)過(guò)的時(shí)間除以顯示每幀的毫秒數(shù)(mpf).
還要檢查確定當(dāng)前幀沒(méi)有超過(guò)視頻的最后幀.若超過(guò)了,則將frame設(shè)為0,動(dòng)畫(huà)計(jì)時(shí)器設(shè)為0,于是動(dòng)畫(huà)從頭開(kāi)始.
下面的代碼會(huì)丟掉一些幀,若果你的計(jì)算機(jī)太慢的話,
或者另一個(gè)程序占用了CPU.如果想顯示每一幀而不管計(jì)算機(jī)有多慢的話,你要檢查next是否比mpf大,若是,你要把next設(shè)為0,frame增1.兩種方法都行,雖然下面的代碼更有利于跑的快的機(jī)器.
如果你有干勁,試著加上循環(huán),快速播放,暫停或倒放等功能.
next+= milliseconds; // 根據(jù)時(shí)間增加next
frame=next/mpf; // 計(jì)算當(dāng)前幀號(hào)
if (frame>=lastframe) // 超過(guò)最后一幀?
{
frame=0; // Frame設(shè)為0
next=0; // 重設(shè)動(dòng)畫(huà)計(jì)時(shí)器
}
}
下面是畫(huà)屏代碼:)我們清屏和深度緩沖.再抓取動(dòng)畫(huà)的一幀.我將使這更簡(jiǎn)單!把你想要的幀數(shù)傳給GrabAVIFrame().非常簡(jiǎn)單!當(dāng)然,如果是多個(gè)AVI,你要傳一個(gè)紋理標(biāo)號(hào).(你要做更多的事)
void Draw (void) // 繪制我們的屏幕
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清屏和深度緩沖
GrabAVIFrame(frame); // 抓取動(dòng)畫(huà)的一幀
下面檢查我們是否想畫(huà)一個(gè)背景圖.若bg是TRUE,重設(shè)模型視角矩陣,畫(huà)一個(gè)單紋理映射的能蓋住整個(gè)屏幕的矩形(紋理是從AVI從得到的一幀).矩形距離屏面向里20個(gè)單位,這樣它看起來(lái)在對(duì)象之后(距離更遠(yuǎn)).
if (bg) // 背景可見(jiàn)?
{
glLoadIdentity(); // 重設(shè)模型視角矩陣
glBegin(GL_QUADS); // 開(kāi)始畫(huà)背景(一個(gè)矩形)
// 正面
glTexCoord2f(1.0f, 1.0f); glVertex3f( 11.0f, 8.3f, -20.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-11.0f, 8.3f, -20.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-11.0f, -8.3f, -20.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 11.0f, -8.3f, -20.0f);
glEnd();
}
畫(huà)完背景(或沒(méi)有),重設(shè)模型視角矩陣(使視角中心回到屏幕中央).視角中心再向屏內(nèi)移進(jìn)10個(gè)單位.然后檢查env是否為T(mén)RUE.若是,開(kāi)啟球面映射來(lái)實(shí)現(xiàn)環(huán)境映射效果.
glLoadIdentity (); // 重設(shè)模型視角矩陣
glTranslatef (0.0f, 0.0f, -10.0f); // 視角中心再向屏內(nèi)移進(jìn)10個(gè)單位
if (env) // 環(huán)境映射開(kāi)啟?
{
glEnable(GL_TEXTURE_GEN_S); // 開(kāi)啟紋理坐標(biāo)生成S坐標(biāo)
glEnable(GL_TEXTURE_GEN_T); // 開(kāi)啟紋理坐標(biāo)生成T坐標(biāo)
}
在最后關(guān)頭我加了以下代碼.他繞X軸和Y軸旋轉(zhuǎn)(根據(jù)angle的值)然后在Z軸方向移動(dòng)2單位.這使我們離開(kāi)了屏幕中心.如果刪掉下面三行,對(duì)象會(huì)在屏幕中心打轉(zhuǎn).有了下面三行,對(duì)象旋轉(zhuǎn)時(shí)看起來(lái)離我們遠(yuǎn)一些:)
如果你不懂旋轉(zhuǎn)和平移...你就不該讀這一章:)
glRotatef(angle*2.3f,1.0f,0.0f,0.0f); // 加旋轉(zhuǎn)讓東西動(dòng)起來(lái)
glRotatef(angle*1.8f,0.0f,1.0f,0.0f); // 加旋轉(zhuǎn)讓東西動(dòng)起來(lái)
glTranslatef(0.0f,0.0f,2.0f); // 旋轉(zhuǎn)后平移到新位置
下面的代碼檢查我們要畫(huà)哪一個(gè)對(duì)象.若effect為0,我們做一些旋轉(zhuǎn)在畫(huà)一個(gè)立方體.這個(gè)旋轉(zhuǎn)使立方體繞X,Y,Z軸旋轉(zhuǎn).現(xiàn)在你腦中該烙下建一個(gè)立方體的方法了吧:)
switch (effect) // 哪個(gè)效果?
{
case 0: // 效果 0 - 立方體
glRotatef (angle*1.3f, 1.0f, 0.0f, 0.0f);
glRotatef (angle*1.1f, 0.0f, 1.0f, 0.0f);
glRotatef (angle*1.2f, 0.0f, 0.0f, 1.0f);
glBegin(GL_QUADS);
glNormal3f( 0.0f, 0.0f, 0.5f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glNormal3f( 0.0f, 0.0f,-0.5f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glNormal3f( 0.0f, 0.5f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glNormal3f( 0.0f,-0.5f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glNormal3f( 0.5f, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glNormal3f(-0.5f, 0.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
break;
下面是畫(huà)球體的地方.開(kāi)始先繞X,Y,Z軸旋轉(zhuǎn),再畫(huà)球體.球體半徑為1.3f,20經(jīng)線,20緯線.我用20是因?yàn)槲覜](méi)打算讓球體非常光滑.少用些經(jīng)緯數(shù),使球看起來(lái)不那么光滑,這樣球轉(zhuǎn)起來(lái)時(shí)就能看到球面映射的效果(當(dāng)然球面映射必須開(kāi)啟).試著嘗試其它值!要知道,使用更多的經(jīng)緯數(shù)需要更強(qiáng)的計(jì)算能力!
case 1: // 效果1,球體
glRotatef (angle*1.3f, 1.0f, 0.0f, 0.0f);
glRotatef (angle*1.1f, 0.0f, 1.0f, 0.0f);
glRotatef (angle*1.2f, 0.0f, 0.0f, 1.0f);
gluSphere(quadratic,1.3f,20,20);
break;
下面畫(huà)圓柱.開(kāi)始先繞X,Y,Z軸旋轉(zhuǎn),圓柱頂和底的半徑都為1.0f.高3.0f,32經(jīng)線,32緯線.若減少經(jīng)緯數(shù),圓柱的組成多邊形會(huì)減少,他看起來(lái)就沒(méi)那么圓.
case 2: // 效果2,圓柱
glRotatef (angle*1.3f, 1.0f, 0.0f, 0.0f);
glRotatef (angle*1.1f, 0.0f, 1.0f, 0.0f);
glRotatef (angle*1.2f, 0.0f, 0.0f, 1.0f);
glTranslatef(0.0f,0.0f,-1.5f);
gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);
break;
}
下面檢查env是否為T(mén)RUE,若是,關(guān)閉球面映射.調(diào)用glFlush()清空渲染流水線(使在下一幀開(kāi)始前一切都渲染了).
if (env) // 是否開(kāi)啟了環(huán)境渲染
{
glDisable(GL_TEXTURE_GEN_S); // 關(guān)閉紋理坐標(biāo)S
glDisable(GL_TEXTURE_GEN_T); // 關(guān)閉紋理坐標(biāo)T
}
glFlush (); // 清空渲染流水線
}
希望你們喜歡這一章.現(xiàn)在已經(jīng)凌晨?jī)牲c(diǎn)了(譯者oak:譯到這時(shí)剛好也是2:00am!)...寫(xiě)這章花了我6小時(shí)了.聽(tīng)起來(lái)不可思議,可要把東西寫(xiě)通不是件容易的事.本文我讀了三邊,我力圖使文章好懂.不管你信還是不信,對(duì)我最重要的是你們能明白代碼是怎樣運(yùn)作的,它為什么能行.那就是我喋喋不休并且加了過(guò)量注解的原因.
無(wú)論如何,我都想聽(tīng)到本文的反饋.如果你找到文章的錯(cuò)誤,并想幫我做一些改進(jìn),請(qǐng)聯(lián)系我.就像我說(shuō)的那樣,這是我第一次寫(xiě)和AVI有關(guān)的代碼.通常我不會(huì)寫(xiě)一個(gè)我才接觸到的主題,但我太興奮了,并且考慮到關(guān)于這方面的文章太少了.我所希望的是,我打開(kāi)了編寫(xiě)高質(zhì)量AVI demo和代碼的一扇門(mén)!也許成功,也許沒(méi)有.不管怎樣,你可以任意處理我的代碼.
非常感謝 Fredster提供face AVI文件.Face是他發(fā)來(lái)的六個(gè)AVI動(dòng)畫(huà)中的一個(gè).他沒(méi)提出任何問(wèn)題和條件.他以他的方式幫助了我,謝謝他!
更要感謝Jonathan de Blok,要沒(méi)要她,本文就不會(huì)有.他給我發(fā)來(lái)他的AVI播放器的代碼,這使我對(duì)AVI格式產(chǎn)生了興趣.他也回答了我問(wèn)的關(guān)于他的代碼的問(wèn)題.但重要的是我并沒(méi)有借鑒或抄襲他的代碼,他的代碼只是幫助我理解AVI播放器的運(yùn)行機(jī)制.我的播放器的打開(kāi),解幀和播放AVI文件用的是不同的代碼!
感謝給予幫助的所有人,包括所有參觀者!若沒(méi)有你們,我的網(wǎng)站不值一文!!!