• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

              C++博客 :: 首頁 :: 聯系 ::  :: 管理
              163 Posts :: 4 Stories :: 350 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(48)

            我參與的團隊

            搜索

            •  

            積分與排名

            • 積分 - 398977
            • 排名 - 59

            最新評論

            閱讀排行榜

            評論排行榜

            首先我得說我非常喜歡這一章節.Jonathan de Blok使我產生了用OpenGL編寫AVI播放器的想法,可那時,我跟本不知如何打開AVI文件,更不必說去寫一個播放器了.于是我瀏覽了搜藏的編程書籍,沒有一本講到AVI文件的.我又閱讀了MSDN上和AVI文件格式有關的一切內容,上面有很多有用的信息,但我需要更多的.
            花了幾小時在網上搜到AVI范例,只找到兩個網站.我的搜索技巧不能說很棒吧,但99.9%的情況,我能找到我要尋找的東西.了解到AVI范例竟如此之少時,我完全震驚了.大多數范例并不能編譯通過...有一些則用了太復雜的的方法(至少對我如此),剩下的不錯,可是用VB,Delphi等寫的(不是用vc++).

            找到的第一個網頁是Jonathan Nix寫的題為"AVI 文件"的文章.網址是http://www.gamedev.net/reference/programming/features/avifile.感謝Jonathan寫了這片關于AVI格式的好文章.雖然我用不同的做法,但他的代碼片斷和清晰的注解讓人學得很輕松!第二個網站標題為"AVI 總體觀"(John F. McGowan, Ph.D寫的)..我可以大肆贊美John的網葉有多么驚奇,但你最好自己去看看.他的網址是http://www.jmcgowan.com/avi.html.這個網站講到了和AVI格式有關的幾乎所有內容.感謝John做了一個這么有用的網站.

            最后要提到是我沒有借鑒任何代碼,沒有抄襲任何代碼.我的代碼是花了三天時間了解到上述網站和文章的信息后才寫成的.我是想說我的代碼也許不是播放AVI文件的最好代碼,他也許不是放AVI文件的正確代碼,但他管用而且使用方便.如果你不喜歡這些代碼和我的編程風格,或者覺得我的言論傷害到整個編程界,你有以下選擇:1)在網上找到替換的資源2)寫自己的AVI播放器3)寫一篇更好的文章.任何訪問本網站的人現在應該知道我只是一名中級程序員(這一點我在網站里很多文章的開頭都提到過)!我編寫代碼自樂而已.本網站的目的在于讓非精英程序員更輕松的開始OpenGl編程.這些文章只是關于我實現的幾個特殊的效果...沒有其他的.

            開始講代碼首先你要注意的是我們要包括和連接到視頻頭文件和庫文件.非常感謝微軟(窩不敢相信我說了什么).庫文件使打開,播放AVI文件都很簡便.現在你要知道的是必須包括頭文件vfw.h而且要連接到vfw32.lib庫文件如果想編譯你的代碼的話:)

             
              

            #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;

              
             現在定義變量.angle是用來根據時間來旋轉物體的.為簡單起見我們用angle來控制所有的旋轉.
            接下來是一個整型變量是用來計算經過的時間(以毫秒計).它使幀速保持一個速度.
            后面細講!
            frame是動畫要顯示的當前幀,初始值為0(第一幀).我想如果成功打開AVI,他至少有一幀吧,這樣假定比較安全:)
            effect是當前屏幕上的效果(有:立方體,球體,圓柱體).env是布爾值.若它為true則環境映射啟動,若為假,則物體沒有環境映射.若bg為true,你會看到物體后有全屏的動畫;若為假,你只會看到物體(沒有背景).
            sp,ep和bp用來確定使用者沒有按著鍵不放. 
              

            float        angle;                            // 旋轉用
            int        next;                            // 動畫用
            int        frame=0;                            // 幀計數器
            int        effect;                            // 當前效果
            bool        sp;                            // 空格鍵按下?
            bool        env=TRUE;                            // 環境映射(默認開)
            bool        ep;                            //’E’ 按下?
            bool        bg=TRUE;                            // 背景(默認開)
            bool        bp;                            // ’B’ 按下?

              
             psi結構體包含AVI文件信息.pavi緩沖的指針,緩沖用來接受AVI文件打開時的流句柄.pgf是指向GetFrame對象的指針.bmih在后面的代碼中將被用來把動畫的每一幀轉換為我們需要的格式(保存位圖的頭信息).lastframe保存AVI動畫最后一幀的序號.width和height保存AVI流的維信息,最后...pdata是圖象數據的指針(每次在從AVI中獲得一幀后返回).mpf用來計算每幀需要多少毫秒.后面細談這個變量. 
              

            AVISTREAMINFO        psi;                        // 包含流信息的結構體的指針
            PAVISTREAM        pavi;                        // 流句柄
            PGETFRAME        pgf;                            // GetFrame對象的指針
            BITMAPINFOHEADER    bmih;                            // 頭信息 For DrawDibDraw
            long            lastframe;                    // 流中最后一幀
            int            width;                        // 視頻寬
            int            height;                        // 視頻高
            char            *pdata;                        // 紋理數據指針
            int            mpf;                        // 控制每幀顯示時間

              
             在本章中我們用GLU庫創建兩個二次曲面(球體和圓柱體).quadratic是曲面對象的指針.
            hdd是DrawDib設備上下文的句柄.hdc是設備上下文的句柄.
            hBitmap是設備無關位圖的句柄(在后面位圖轉換時用到).
            data是最后指向轉換后位圖的圖象數據的指針,在后面的代碼中會有意義,往下讀:) 
              

            GLUquadricObj *quadratic;                        // 存儲二次曲面對象

            HDRAWDIB hdd;                            // Dib句柄
            HBITMAP hBitmap;                            // 設備無關位圖的句柄
            HDC hdc = CreateCompatibleDC(0);                    // 創建一個兼容的設備上下文
            unsigned char* data = 0;                        // 調整后的圖象數據指針

              
             下面使用到匯編語言.那些從來沒有用過匯編的不要被嚇倒了.他看起來神秘,實際上非常簡單!
            在寫本章是我發現了十分奇怪的事.第一次做出來的可以播放,但色彩混亂了.本來是紅色的變成藍色的了,本來是藍色的變成紅色的了.我簡直要發狂了!我相信我的代碼某處有問題.看了一邊代碼還是找不到bug于是又讀了MSDN.為什么紅色與藍色互換了!?!MSDN明明說24比特位圖是RGB啊!又讀了一些東西,我找到了答案.在WINDOWS圖形系統中,RGB數據是倒著存儲的(BGR).而在OpenGL中,要用的RGB數據就是RGB的順序!

            在抱怨了微軟之后:)我決定加一條注解!我不因為RGB數據倒過來存放而打算罵微軟.只是覺得很奇怪--他叫做RGB實際上在文件中是按BGR存的!

            另:這一點和"little endian"和"big endian"有關.Intel以及Intel兼容產品用little endian--LSB(數據最低位)首先存.OpenGL是產生于Silicon Graphics的機器的,用的是big endian,所以標準的OpenGL要位圖格式是big endian格式.這是我的理解.

            棒極了!所以說這第一個播放器就是一個垃圾!我的解決方法是用一個循環把數據交換過來.這能行,但太慢.我又在紋理生成代碼中用GL_BGR_EXT代替了GL_RGB,速度暴升,色彩顯示也對了!問題解決了...原來我是這樣想!后來發現一些OpenGL驅動不支持GL_BGR... :(

            與好友Maxwell Sayles討論后,他推薦我用匯編代碼來交換數據.一分鐘后,他用icq發來下面的代碼!也許不是最優化的,但他很快也很有效!

            動畫的每一幀存在一個緩沖里.圖象256像素寬,256像素高,每個色彩一字節(一像素3字節).下面的代碼會掃描整個緩沖并交換紅與藍的字節.紅存在ebx+0,藍存在ebx+2.我們一次向前走3字節(因為一個像素3字節).不斷掃描直到所有數據交換過來.

            你們有些人不喜歡用匯編代碼,所以我想有必要在本章里解釋一下.本來計劃用GL_BGR_EXT,他管用,但不是所有的顯卡都支持!我又用異或交換法,這在所有機器上都是有效的,但不十分快.用了匯編后速度相當快.考慮到我們在處理實時視頻,你需要最快的交換方法.權衡了以上選擇,匯編是最好的!如果你有更好的辦法,就用你自己的吧!我并不是告訴你必須如何去做,只是告訴你我的做法.我也會細致的解釋代碼.如果你要用更好的代碼來作替換,你要清楚這些代碼是來干什么的,自己寫代碼時,要為日后的優化提供方便.

             
              

            void flipIt(void* buffer)                        // 交換紅藍數據(256x256)
            {
                void* b = buffer;                        // 緩沖指針
                __asm                            // 匯編代碼
                {
                    mov ecx, 256*256                    // 設置計數器
                    mov ebx, b                    // ebx存數據指針
                    label:                        // 循環標記
                        mov al,[ebx+0]                // 把ebx位置的值賦予al
                        mov ah,[ebx+2]                // 把ebx+2位置的值賦予ah
                        mov [ebx+2],al                // 把al的值存到ebx+2的位置
                        mov [ebx+0],ah                // 把ah的值存到ebx+0的位置

                        add ebx,3                    // 向前走3個字節
                        dec ecx                    // 循環計數器減1
                        jnz label                    // ecx非0則跳至label
                }
            }

              
             下面的代碼以只讀方式打開AVI文件.szFile是打開文件的名字.title[100]用來修改window標題(顯示AVI文件信息).
            首先調用AVIFileInit().他初始化AVI文件庫(使東西能用?鵠?).

            打開AVI文件有很多方法.我采用AVIStreamOpenFromFile(...).他能打開AVI文件中單獨一個流(AVI文件可以包含多個流).它的參數如下:pavi是接收流句柄的緩沖的指針,szFile是打開文件的名字(包括路徑).第三參數是打開的流的類型.在這個工程里,我們只對視頻流感興趣(streamtypeVIDEO).第四參數是0,這表示我們需要第一次讀到的視頻流(一個AVI文件里會有多個視頻流,我們要第一個).OF_READ表示以只讀方式打開文件.最后一個參數是一個類標識句柄的指針.說實話,我也不清楚他是干嗎的.我讓windows自己設定,于是把NULL傳過去.

             
              

            void OpenAVI(LPCSTR szFile)                        // 打開AVI文件szFile
            {
                TCHAR    title[100];                    // 包含修改了的window標題

                AVIFileInit();                        // 打開AVI文件庫

                // 打開AVI流
                if (AVIStreamOpenFromFile(&pavi, szFile, streamtypeVIDEO, 0, OF_READ, NULL) !=0)
                {
                    // 打開流時的出錯處理
                    MessageBox (HWND_DESKTOP, "打開AVI流失敗", "錯誤", MB_OK | MB_ICONEXCLAMATION);
                }

              
             到目前為止,我們假定文件被正確打開,流被正確定位!然后用AVIStreamInfo(...)從AVI文件里抓取一些信息.
            先前我們創建了叫psi的結構體來保存AVI流的信息.下面第一行,我們把AVI信息填入該結構體.從流的寬度(以像素計)到動畫的幀速等所有的信息都會存到psi中.那些想要精確控制播放速度的要記住我剛才說的.更多的信息參閱MSDN.

            我們通過右邊位置減左邊位置算出幀寬.這個結果是以像素記的精確的幀寬.至于高度,可以用底邊位置減頂邊位置得到.這樣得到高度的像素值.

            然后用AVIStreamLength(...)得到AVI文件最后一幀的序號.AVIStreamLength(...)返回動畫最后一幀的序號.結果存在lastframe里.

            計算幀速很簡單.每秒幀速(fps)= psi.dwRate/psi,dwScale.返回的值應該匹配顯示幀的速度(你在AVI動畫中右擊鼠標可以看到).你會問那么這和mpf有什么關系呢?第一次寫這個代碼時,我試著用fps來選擇動畫了正確的幀面.我遇到一個問題...視頻放的太快!于是我看了一下視頻屬性.face2.avi文件有3.36秒長.幀速是29.974fps.視頻動畫共有91幀.而3.36*29.974 = 100.71.非常奇怪!!

            所以我采用一些不同的方法.不是計算幀速,我計算每一幀播放所需時間.AVIStreamSampleToTime()把在動畫中的位置轉換位你到達該位置所需的時間(毫秒計).所以通過計算到達最后一幀的時間就得到整個動畫的播放時間.再拿這個結果除以動畫總幀數(lastframe).這樣就給出了每幀的顯示時間(毫秒計).結果存在mpf(milliseconds per frame)里.你也能通過獲取動畫中一幀的時間來算每幀的毫秒數,代碼為:AVIStreamSampleToTime(pavi,1).兩種方法都不錯!非常感謝Albert Chaulk提供思路!

            我說每幀的毫秒數不精確是因為mpf是一個整型值,所以所有的浮點數都會被取整.

             
              

                AVIStreamInfo(pavi, &psi, sizeof(psi));            // 把流信息讀進psi
                width=psi.rcFrame.right-psi.rcFrame.left;            // 寬度為右邊減左邊
                height=psi.rcFrame.bottom-psi.rcFrame.top;            // 高為底邊減頂邊

                lastframe=AVIStreamLength(pavi);                // 最后一幀的序號

                mpf=AVIStreamSampleToTime(pavi,lastframe)/lastframe;        // mpf的不精確值

              
             因為OpenGL需要紋理數據是2的冪,而大多視頻是160*120,320*240等等,所以需要一種把視頻格式重調整為能用作紋理的格式.我們可利用Windows Dib函數去做.
            首先要做的是描述我們想要的圖像的類型.于是我們要以所需參數填好bmih這個BitmapInfoHeader結構.
            首先設定該結構體的大小.再把位平面數設為1.3字節的數據有24比特(RGB).要使圖像位256像素寬,256像素高,最后要讓數據返回為UNCOMPRESSED(非壓縮)的RGB數據(BI_RGB).

            CreateDIBSection創建一個可直接寫的設備無關位圖(dib).如果一切順利,hBitmap會指向該dib的比特值.hdc是設備上下文(DC)的句柄第二參數是BitmapInfo結構體的指針.該結構體包含了上述dib文件的信息.第三參數(DIB_RGB_COLORS)設定數據是RGB值.data是指向DIB比特值位置的指針的指針(嗚,真繞口).第五參數設為NULL,我們的DIB已被分配好內存.末了,最后一個參數可忽略(設為NULL).

            引自MSDN:SelecObject函數選一個對象進入設備上下文(DC).

            現在我們建好一個能直接寫的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;                        // 申請的模式 = RGB

                hBitmap = CreateDIBSection (hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL);
                SelectObject (hdc, hBitmap);                    // 選hBitmap進入設備上下文(hdc)

              
             在從AVI中讀取幀面前還有幾件事要做.接下來使程序做好從AVI文件中解出幀面的準備.用AVIStreamGetFrameOpen(...)函數做這一點.
            你能給這個函數傳一個結構體作為第二參數(它會返回一個特定的視頻格式).糟糕的是,你能改變的唯一數據是返回的圖像的寬度和高度.MSDN也提到能傳AVIGETFRAMEF_BESTDISPLAYFMT為參數來選擇一個最佳顯示格式.奇怪的是,我的編譯器沒有定義這玩藝兒.

            如果一切順利,一個GETFRAME對象被返回(用來讀幀數據).有問題的話,提示框會出現在屏幕上告訴你有錯誤!

             
              

                pgf=AVIStreamGetFrameOpen(pavi, NULL);                // 用要求的模式建PGETFRAME
                if (pgf==NULL)
                {
                    // 解幀出錯
                    MessageBox (HWND_DESKTOP, "不能打開AVI幀", "錯誤", MB_OK | MB_ICONEXCLAMATION);
                }

              
             下面的代碼把視頻寬,高和幀數傳給window標題.用函數SetWindowText(...)在window頂部顯示標題.以窗口模式運行程序看看以下代碼的作用. 
              

                // bt標題欄信息(寬 / 高/ 幀數)
                wsprintf (title, "NeHe's AVI Player: Width: %d, Height: %d, Frames: %d", width, height, lastframe);
                SetWindowText(g_window->hWnd, title);                // 修改標題欄
            }

              
             下面是有趣的東西...從AVI中抓取一幀,把它轉為大小和色深可用的圖象.lpbi包含一幀的BitmapInfoHeader信息.我們在下面第二行完成了幾件事.先是抓了動畫的一幀...我們需要的幀面由這些幀確定.這會讓動畫走掉這一幀,lpbi會指向這一幀的頭信息.
            下面是有趣的東西...我們要指向圖像數據了.要跳過頭信息(lpbi->biSize).一件事直到寫本文時我才意識到:也要跳過任何的色彩信息.所以要跳過biClrUsed*sizeof(RGBQUAD)(譯者:我想他是說要跳過調色板信息).做完這一切,我們就得到圖像數據的指針了(pdata).

            也要把動畫的每一幀的大小轉為紋理能用的大小,還要把數據轉為RGB數據.這用到DrawDibDraw(...).

            一個大概的解釋.我們能直接寫設定的DIB圖像.那就是DrawDibDraw(...)所做的.第一參數是DrawDib DC的句柄.第二參數是DC的句柄.接下來用左上角(0,0)和右下角(256,256)構成目標矩形.

            lpbi指向剛讀的幀的bitmapinfoheader信息.pdata是剛讀的幀的圖像數據指針.

            再把源圖象(剛讀的幀)的左上角設為(0,0),右下角設為(幀寬,幀高).最后的參數應設為0.

            這個方法可把任何大小、色深的圖像轉為256*256*24bit的圖像.

             
              

            void GrabAVIFrame(int frame)                        // 從流中抓取一幀
            {
                LPBITMAPINFOHEADER lpbi;                        // 存位圖的頭信息
                lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, frame);        // 從AVI流中得到數據
                pdata=(char *)lpbi+lpbi->biSize+lpbi->biClrUsed * sizeof(RGBQUAD);    // 數據指針,由AVIStreamGetFrame返回(跳過頭
            //信息和色彩信息)
            // 把數據轉為所需格式
                DrawDibDraw (hdd, hdc, 0, 0, 256, 256, lpbi, pdata, 0, 0, width, height, 0);

              
             我們得到動畫的每幀數據(紅藍數據顛倒的).為解決這個問題,我們的高速代碼flipIt(...).記住,data是指向DIB比特值位置的指針的指針變量.這意味著調用DrawDibDraw后,data指向一個調整過大小(256*256),修改過色深(24bits)的位圖數據.
            原來我通過重建動畫的每一幀來更新紋理.我收到幾封email建議我用glTexSubImage2D().翻閱了OpenGL紅寶書后,我磕磕絆絆的寫出下面注釋:"創建紋理的計算消耗比修改紋理要大.在OpenGL1.1版本中,有幾條調用能更新全部或部分紋理圖像信息.這對某些應用程序有用,比如實時的抓取視頻圖像作紋理.對于這些程序,用glTexSubImage2D()根據新視頻圖像來創建單個紋理以代替舊的紋理數據是行得通的."

            在我個人并沒有發現速度明顯加快,也許在低端顯卡上才會.glTexSubImage2D()的參數是:目標是一個二維紋理(GL_TEXTURE_2D).細節級別(0),mipmapping用.x(0),y(0)告訴OpenGL開始拷貝的位置(0,0是紋理的左下角).然后是圖像的寬度,我們要拷貝的圖像是256像素寬,256像素高.GL_RGB是我們的數據格式.我們在拷貝無符號byte.最后...圖像數據指針----data.非常簡單!

            Kevin Rogers 另加:我想指出使用glTexSubImage2D()另一個重要原因.不僅因為在許多OpenGL實現中它很快,還因為目標區不必是2的冪.這對視頻重放很方便,因為一幀的維通常不是2的冪(而是像320*200之類的).這樣給了你很大機動性,你可以按視頻流原本的樣子播放,而不是扭曲或剪切每一幀來適應紋理的維.

            重要的是你不能更新一個紋理如果你第一次沒有創建他!我們在Initialize()中創建紋理.

            還要提到的是...如果你計劃在工程里使用多個紋理,務必綁住你要更新的紋理.否則,更新出來的紋理也許不是你想要的!

             
              

                flipIt(data);                            // 交換紅藍數據

                // 更新紋理
                glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 256, 256, GL_RGB, GL_UNSIGNED_BYTE, data);
            }

              
             接下來的部分當程序退出時調用,我們關掉DrawDib DC,釋放占用的資源.然后釋放AVI GetFrame資源.最后釋放AVI流和文件. 
              

            void CloseAVI(void)                            // 關掉AVI資源
            {
                DeleteObject(hBitmap);                        //釋放設備無關位圖信息
                DrawDibClose(hdd);                            // 關掉DrawDib DC
                AVIStreamGetFrameClose(pgf);                    // 釋放AVI GetFrame資源
                AVIStreamRelease(pavi);                        // 釋放AVI流
                AVIFileExit();                            // 釋放AVI文件
            }

              
             初始化很簡明.設初始的angle為0.再打開DrawDib庫(得到一個DC).一切順利的話,hdd會是新創建的dc的句柄.
            以黑色清屏,開啟深度測試,等等.

            然后建一個新的二次曲面.quadratic是這個新對象的指針.設置光滑的法線,允許紋理坐標的生成.

             
              

            BOOL Initialize (GL_Window* window, Keys* keys)
            {
                g_window    = window;
                g_keys        = keys;

                // 開始用戶的初始
                angle = 0.0f;                            // angle為0先
                hdd = DrawDibOpen();                        // 得到Dib的DC
                glClearColor (0.0f, 0.0f, 0.0f, 0.5f);                // 黑色背景
                glClearDepth (1.0f);                        // 深度緩沖初始
                glDepthFunc (GL_LEQUAL);                        // 深度測試的類型(小于或等于)
                glEnable(GL_DEPTH_TEST);                        // 開啟深度測試
                glShadeModel (GL_SMOOTH);                        // 平滑效果
                glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // 透視圖計算設為 //最高精度

                quadratic=gluNewQuadric();                        // 建二次曲面的指針
                gluQuadricNormals(quadratic, GLU_SMOOTH);                // 設置光滑的法線
                gluQuadricTexture(quadratic, GL_TRUE);                // 創建紋理坐標

              
             下面的代碼中,我們開啟2D紋理映射,紋理濾鏡設為GLNEAREST(最快,但看起來很糙),建立球面映射(為了實現環境映射效果).試試其它濾鏡,如果你有條件,可以試試GLLINEAR得到一個平滑的動畫效果.
            設完紋理和球面映射,我們打開.AVI文件.我盡量使事情簡單化...你能看出來么:)我們要打開的文件叫作facec2.avi

             
              

                glEnable(GL_TEXTURE_2D);                    // 開啟2D紋理映射
                glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);// 設置紋理濾鏡
                glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

                glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);        // 設紋理坐標生成模式為s
                glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);        // 設紋理坐標生成模式為t

                OpenAVI("data/face2.avi");                    // 打開AVI文件

                // 創建紋理
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

                return TRUE;                        // 初始化成功返回TRUE
            }

              
             關閉時調用CloseAVI().他正確的關閉AVI文件,并釋放所有占用資源. 
              

            void Deinitialize (void)                        // 做所有的釋放工作
            {
                CloseAVI();                        // 關閉AVI文件
            }

              
             到了檢查按鍵和更新旋轉角度的地方了.我知道再沒有必要詳細解釋這些代碼了.我們檢查空格鍵是否按下,若是,則增加effect值.有3種效果(立方,球,圓柱)第四個效果被選時(effect = 3)不畫任何對象...僅顯示背景!如果選了第四效果,空格又按下了,就重設為第一個效果(effect = 0).Yeah,我本該叫他對象:)
            然后檢查’b’鍵是否按下,若是,則改變背景(bg從ON到OFF或從OFF到ON).

            環境映射的鍵設置也一樣.檢查’E’是否按下,若是則改變env從TRUE到FALSE或從FALSE到TRUE.僅僅是關閉或開啟環境映射!

            每次調用Updata()時angle都加上一個小分數.我用經過的時間除以60.0f使速度降一點.

             
              

            void Update (DWORD milliseconds)                    // 動畫更新
            {
                if (g_keys->keyDown [VK_ESCAPE] == TRUE)            //ESC按下?
                {
                    TerminateApplication (g_window);            // 關閉程序
                }

                if (g_keys->keyDown [VK_F1] == TRUE)                // F1按下?
                {
                    ToggleFullscreen (g_window);            // 改變顯示模式
                }

                if ((g_keys->keyDown [' ']) && !sp)                // 空格按下并已松開
                {
                    sp=TRUE;                        // 設sp為True
                    effect++;                        // 增加effect
                    if (effect>3)                    // 超出界限?
                        effect=0;                    // 重設為0
                }

                if (!g_keys->keyDown[' '])                    // 空格沒按下?
                    sp=FALSE;                        // 設sp為False

                if ((g_keys->keyDown ['B']) && !bp)                // ’B’按下并已松開
                {
                    bp=TRUE;                        // 設bp為True
                    bg=!bg;                        // 改變背景 Off/On
                }

                if (!g_keys->keyDown['B'])                    // ’B’沒按下?
                    bp=FALSE;                        // 設bp為False

                if ((g_keys->keyDown ['E']) && !ep)                //  ’E’按下并已松開
                {
                    ep=TRUE;                        // 設ep為True
                    env=!env;                        // 改變環境映射 Off/On
                }

                if (!g_keys->keyDown['E'])                    //’E’沒按下
                    ep=FALSE;                        // 設ep為False

                angle += (float)(milliseconds) / 60.0f;            // 根據時間更新angle

              
             在原來的文章里,所有的AVI文件都以相同的速度播放.于是,我重寫了本文讓視頻以正常的速度播放.next增加經過的毫秒數.如果你記得文章的前面,我們算出了顯示每幀的毫秒數(mpf).為了計算當前幀,我們拿經過的時間除以顯示每幀的毫秒數(mpf).
            還要檢查確定當前幀沒有超過視頻的最后幀.若超過了,則將frame設為0,動畫計時器設為0,于是動畫從頭開始.

            下面的代碼會丟掉一些幀,若果你的計算機太慢的話,
            或者另一個程序占用了CPU.如果想顯示每一幀而不管計算機有多慢的話,你要檢查next是否比mpf大,若是,你要把next設為0,frame增1.兩種方法都行,雖然下面的代碼更有利于跑的快的機器.

            如果你有干勁,試著加上循環,快速播放,暫停或倒放等功能.

             
              

                next+= milliseconds;                        // 根據時間增加next
                frame=next/mpf;                            // 計算當前幀號

                if (frame>=lastframe)                        // 超過最后一幀?
                {
                    frame=0;                            // Frame設為0
                    next=0;                            // 重設動畫計時器
                }
            }

              
             下面是畫屏代碼:)我們清屏和深度緩沖.再抓取動畫的一幀.我將使這更簡單!把你想要的幀數傳給GrabAVIFrame().非常簡單!當然,如果是多個AVI,你要傳一個紋理標號.(你要做更多的事) 
              

            void Draw (void)                            // 繪制我們的屏幕
            {
                glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        // 清屏和深度緩沖

                GrabAVIFrame(frame);                    // 抓取動畫的一幀

              
             下面檢查我們是否想畫一個背景圖.若bg是TRUE,重設模型視角矩陣,畫一個單紋理映射的能蓋住整個屏幕的矩形(紋理是從AVI從得到的一幀).矩形距離屏面向里20個單位,這樣它看起來在對象之后(距離更遠). 
              

                if (bg)                            // 背景可見?
                {
                    glLoadIdentity();                    // 重設模型視角矩陣
                    glBegin(GL_QUADS);                    // 開始畫背景(一個矩形)
                        // 正面
                        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();                       
                }

              
             畫完背景(或沒有),重設模型視角矩陣(使視角中心回到屏幕中央).視角中心再向屏內移進10個單位.然后檢查env是否為TRUE.若是,開啟球面映射來實現環境映射效果.
             
              

                glLoadIdentity ();                        // 重設模型視角矩陣
                glTranslatef (0.0f, 0.0f, -10.0f);                // 視角中心再向屏內移進10個單位

                if (env)                            // 環境映射開啟?
                {
                    glEnable(GL_TEXTURE_GEN_S);                // 開啟紋理坐標生成S坐標
                    glEnable(GL_TEXTURE_GEN_T);                // 開啟紋理坐標生成T坐標
                }

              
             在最后關頭我加了以下代碼.他繞X軸和Y軸旋轉(根據angle的值)然后在Z軸方向移動2單位.這使我們離開了屏幕中心.如果刪掉下面三行,對象會在屏幕中心打轉.有了下面三行,對象旋轉時看起來離我們遠一些:)
            如果你不懂旋轉和平移...你就不該讀這一章:)

             
              

                glRotatef(angle*2.3f,1.0f,0.0f,0.0f);                // 加旋轉讓東西動起來
                glRotatef(angle*1.8f,0.0f,1.0f,0.0f);                // 加旋轉讓東西動起來
                glTranslatef(0.0f,0.0f,2.0f);                    // 旋轉后平移到新位置

              
             下面的代碼檢查我們要畫哪一個對象.若effect為0,我們做一些旋轉在畫一個立方體.這個旋轉使立方體繞X,Y,Z軸旋轉.現在你腦中該烙下建一個立方體的方法了吧:) 
              

                switch (effect)                            // 哪個效果?
                {
                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;                       
              
             下面是畫球體的地方.開始先繞X,Y,Z軸旋轉,再畫球體.球體半徑為1.3f,20經線,20緯線.我用20是因為我沒打算讓球體非常光滑.少用些經緯數,使球看起來不那么光滑,這樣球轉起來時就能看到球面映射的效果(當然球面映射必須開啟).試著嘗試其它值!要知道,使用更多的經緯數需要更強的計算能力! 
              

                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;                           
              
             下面畫圓柱.開始先繞X,Y,Z軸旋轉,圓柱頂和底的半徑都為1.0f.高3.0f,32經線,32緯線.若減少經緯數,圓柱的組成多邊形會減少,他看起來就沒那么圓. 
              

                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是否為TRUE,若是,關閉球面映射.調用glFlush()清空渲染流水線(使在下一幀開始前一切都渲染了). 
              

                if (env)                                // 是否開啟了環境渲染
                {
                    glDisable(GL_TEXTURE_GEN_S);                // 關閉紋理坐標S
                    glDisable(GL_TEXTURE_GEN_T);                // 關閉紋理坐標T
                }

                glFlush ();                            // 清空渲染流水線
            }

              
             希望你們喜歡這一章.現在已經凌晨兩點了(譯者oak:譯到這時剛好也是2:00am!)...寫這章花了我6小時了.聽起來不可思議,可要把東西寫通不是件容易的事.本文我讀了三邊,我力圖使文章好懂.不管你信還是不信,對我最重要的是你們能明白代碼是怎樣運作的,它為什么能行.那就是我喋喋不休并且加了過量注解的原因.
            無論如何,我都想聽到本文的反饋.如果你找到文章的錯誤,并想幫我做一些改進,請聯系我.就像我說的那樣,這是我第一次寫和AVI有關的代碼.通常我不會寫一個我才接觸到的主題,但我太興奮了,并且考慮到關于這方面的文章太少了.我所希望的是,我打開了編寫高質量AVI demo和代碼的一扇門!也許成功,也許沒有.不管怎樣,你可以任意處理我的代碼.

            非常感謝 Fredster提供face AVI文件.Face是他發來的六個AVI動畫中的一個.他沒提出任何問題和條件.他以他的方式幫助了我,謝謝他!

            更要感謝Jonathan de Blok,要沒要她,本文就不會有.他給我發來他的AVI播放器的代碼,這使我對AVI格式產生了興趣.他也回答了我問的關于他的代碼的問題.但重要的是我并沒有借鑒或抄襲他的代碼,他的代碼只是幫助我理解AVI播放器的運行機制.我的播放器的打開,解幀和播放AVI文件用的是不同的代碼!

            感謝給予幫助的所有人,包括所有參觀者!若沒有你們,我的網站不值一文!!!

             
             
            posted on 2007-12-24 16:18 sdfasdf 閱讀(1549) 評論(0)  編輯 收藏 引用 所屬分類: OPENGL
            中文字幕无码精品亚洲资源网久久| 91精品久久久久久无码| 狠狠色丁香婷婷久久综合 | 思思久久99热只有频精品66| 色婷婷久久久SWAG精品| 人妻无码中文久久久久专区| 亚洲午夜久久影院| 亚洲色大成网站www久久九| 69国产成人综合久久精品| 精品久久国产一区二区三区香蕉| 久久人做人爽一区二区三区| 精品久久久久香蕉网| 久久激情五月丁香伊人| 精品无码久久久久国产| 一本久久精品一区二区| 香蕉久久一区二区不卡无毒影院| 狠狠色婷婷久久综合频道日韩| 99久久亚洲综合精品网站| 777午夜精品久久av蜜臀| 久久高潮一级毛片免费| 精品免费久久久久久久| 久久久久久久精品成人热色戒| 国产精品综合久久第一页 | 一级做a爰片久久毛片人呢| 国产成人久久精品一区二区三区| 草草久久久无码国产专区| 国内精品久久久久影院免费| 久久亚洲AV无码精品色午夜麻豆| 久久久国产一区二区三区| 一级做a爰片久久毛片人呢| 精品久久久久久| av午夜福利一片免费看久久| 亚洲午夜久久久久久久久久| 亚洲欧洲精品成人久久曰影片| www亚洲欲色成人久久精品| 成人久久精品一区二区三区| 国内精品久久人妻互换| 久久天堂AV综合合色蜜桃网| 精品熟女少妇av免费久久| www.久久热.com| 国产午夜精品久久久久九九|