• <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>
            posts - 15,  comments - 0,  trackbacks - 0
             

            OpenGL入門學習

            OpenGL作為當前主流的圖形API之一,它在一些場合具有比DirectX更優越的特性。

            1、與C語言緊密結合

            OpenGL命令最初就是用C語言函數來進行描述的,對于學習過C語言的人來講,OpenGL是容易理解和學習的。如果你曾經接觸過TCgraphics.h,你會發現,使用OpenGL作圖甚至比TC更加簡單

            2、強大的可移植性

            微軟的Direct3D雖然也是十分優秀的圖形API,但它只用于Windows系統(現在還要加上一個XBOX游戲機)。而OpenGL不僅用于 Windows,還可以用于Unix/Linux等其它系統,它甚至在大型計算機、各種專業計算機(如:醫療用顯示設備)上都有應用。并且,OpenGL 的基本命令都做到了硬件無關,甚至是平臺無關。

            3、高性能的圖形渲染

            OpenGL是一個工業標準,它的技術緊跟時代,現今各個顯卡廠家無一不對OpenGL提供強力支持,激烈的競爭中使得OpenGL性能一直領先。

            總之,OpenGL是一個很NB的圖形軟件接口。至于究竟有多NB,去看看DOOM3QUAKE4等專業游戲就知道了。

            OpenGL官方網站(英文)

            http://www.opengl.org

             

            linux下編寫OpenGL程序的一些準備工作

            需要用到的軟件包有兩個,glut tmake,分別可以從以下兩個網址下載:

            http://www.opengl.org/resources/libraries/glut/glut-3.7.tar.gz

            ftp://ftp.trolltech.com/freebies/tmake/tmake-1.8.tar.gz

            下載后的文件假設都放在/usr/src

            首先是安裝glut庫,以下是從www.linux.com找到的編譯glut庫的手冊。

            Install GLUT 3.7 Distribution (optional)

            If you installed the MesaDemos/MesaLib package, then you have already installed GLUT 3.7 since it is included with MesaDemos. However, you may be interested in installing the GLUT

            manpages and you can skip right to the "Install GLUT manual pages", below ...

            Installing GLUT is a bit tricky. I'm not too familiar with imake, the program that it uses to manage the Makefiles, and didn't quite see how to get GLUT to install to where I wanted it (/usr/lib,

            but MesaDemos will do this without any trouble though). It can be done manually anyhow:

             cd /usr/src

             tar -xvzf glut-3.7.tar.gz

             cd glut-3.7

             Read the file: README.linux

             cd linux

             READ the file: README

             cp Glut.cf ..

             cd ..

             Edit Glut.cf: remove any Mesa references.

             Replace any -lMesaGL -lMesaGLU with -lGL -lGLU if needed.

             In particular, replace:

              OPENGL = $(TOP)/../lib/libMesaGL.so

              GLU = $(TOP)/../lib/libMesaGLU.so

             with:

              OPENGL = -lGL

              GLU = -lGLU

             ./mkmkfiles.imake

             cd lib/glut

             cp /usr/src/glut-3.7/linux/Makefile .

             Edit the Makefile: remove any Mesa references.

             Replace any -lMesaGL -lMesaGLU with -lGL -lGLU if needed.

             In particular, replace:

              OPENGL = $(TOP)/../lib/libMesaGL.so

              GLU = $(TOP)/../lib/libMesaGLU.so

             with:

              OPENGL = -lGL

              GLU = -lGLU

             make

             ln -s libglut.so.3.7 libglut.so

             ln -s libglut.so.3.7 libglut.so.3

             cp -d libglut.* /usr/lib

             cd ..

             cd gle

             # make a shared lib for libgle

             make

             gcc -shared -o libgle.so.3.7 *.o

             ln -s libgle.so.3.7 libgle.so

             ln -s libgle.so.3.7 libgle.so.3

             cp -d libgle.* /usr/lib

             cd ..

             cd mui

             # make a shared lib for libmui

             make

             gcc -shared -o libmui.so.3.7 *.o

             ln -s libmui.so.3.7 libmui.so

             ln -s libmui.so.3.7 libmui.so.3

             cp -d libmui.* /usr/lib

                    # Install the GLUT manual pages (not included with MesaDemos)

             cd /usr/src/glut-3.7

             make SUBDIRS=man Makefile

             cd man/glut

             make install.man

             ldconfig

             cd ../../progs/demos/ideas

             # edit the Makefile, change OPENGL = -lGL and GLU = -lGLU

             make

             ./ideas

             # test compiling some demos

             # take a look at which libraries have to be linked (-lX11 ...) in

             # the Makefiles.  Qt's tmake program available at www.troll.no

             # is a quick way to make a Makefile but you have to edit it

             # and add the -l needed.

            ideas如果運行成功的話,說明glut已經可以用了。

            上面的幾步中,下載的glut包放在/usr/src目錄下,如果放在其他目錄下,將/usr/src改為相應的目錄即可。

            此外應該注意的是兩個Makefile文件的修改

              

            ·  OPENGL = $(TOP)/../lib/libMesaGL.so

              GLU = $(TOP)/../lib/libMesaGLU.so

             

              OPENGL = -lGL

              GLU = -lGLU

            因為所指定的目錄中沒有libMesaGL.solibMesaGLU.so

            之后是tmake的配置,后面我們可以用它來生成pro工程文件和makefile文件。

            先將下載的tmake解壓縮,tar  -zxvf  tmake-1.8.tar.gz

            得到tmake-1.8目錄,之后設置兩個環境變量:PATHTMAKEPATH

            PATH=$PATH:/usr/src/tmake-1.8/bin

            export PATH

            TMAKEPATH=/usr/src/tmake-1.8/lib/linux-g++

            export TMAKEPATH

            新建一個測試目錄test,將glut-3.7目錄下的progs/redbook目錄下的hello.c復制到test目錄中

            之后生成一個pro文件:progen  -o hello.pro

            然后生成makefile文件:tmake hello.pro -o Makefile

            編輯生成的Makefile文件,在加載動態連接庫的行里面加入 -lglut -lXi -lXmu

            保存,make

            ./hello 可以看到運行結果就可以了。

             

            下面將對Windows下的OpenGL編程進行簡單介紹。

            學習OpenGL前的準備工作

            第一步,選擇一個編譯環境

            現在Windows系統的主流編譯環境有Visual StudioBroland C++ BuilderDev-C++等,它們都是支持OpenGL的。但這里我們選擇Visual Studio 2005作為學習OpenGL的環境。

            第二步,安裝GLUT工具包

            GLUT不是OpenGL所必須的,但它會給我們的學習帶來一定的方便,推薦安裝。

            Windows VS2005環境下環境下安裝GLUT的步驟:

            1、將下載的壓縮包解開,將得到5個文件

            2、在我的電腦中搜索“gl.h”,并找到其所在文件夾(如果是VisualStudio2005,則應該是其安裝目錄下面的“VC\PlatformSDK\include\gl文件夾)。把解壓得到的glut.h放到這個文件夾。

            3、把解壓得到的glut.libglut32.lib放到靜態函數庫所在文件夾(如果是VisualStudio2005,則應該是其安裝目錄下面的“VC\lib”文件夾)。

            4、把解壓得到的glut.dllglut32.dll放到操作系統目錄下面的system32文件夾內。(典型的位置為:C:\Windows\System32

             

            Windows VS2008環境下環境下安裝GLUT的步驟:

            如果是VisualStudio2008,把解壓得到的glut.h放到“Microsoft Visual Studio 9.0\VC\include”;

            如果是VisualStudio2008,把解壓得到的glut.dllglut32.dll放到“Microsoft Visual Studio 9.0\VC\lib”。

             

            第三步,建立一個OpenGL工程

            這里以VisualStudio2005為例。

            選擇File->New->Project,然后選擇Win32 Console Application,選擇一個名字,然后按OK

            在談出的對話框左邊點Application Settings,找到Empty project并勾上,選擇Finish

            然后向該工程添加一個代碼文件,取名為“OpenGL.c”,注意用.c來作為文件結尾。

            搞定了,就跟平時的工程沒什么兩樣的。

             

            第一個OpenGL程序

            一個簡單的OpenGL程序如下:(注意,如果需要編譯并運行,需要正確安裝GLUT,安裝方法如上所述)

            #include <GL/glut.h>

            void myDisplay(void)

            {

                glClear(GL_COLOR_BUFFER_BIT);

                glRectf(-0.5f, -0.5f, 0.5f, 0.5f);

                glFlush();

            }

             

            int main(int argc, char *argv[])

            {

                glutInit(&argc, argv);

                glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);

                glutInitWindowPosition(100, 100);

                glutInitWindowSize(400, 400);

                glutCreateWindow("第一個OpenGL程序");

                glutDisplayFunc(&myDisplay);

                glutMainLoop();

                return 0;

            }

             

            該程序的作用是在一個黑色的窗口中央畫一個白色的矩形。下面對各行語句進行說明。

            首先,需要包含頭文件#include <GL/glut.h>,這是GLUT的頭文件。

            本來OpenGL程序一般還要包含<GL/gl.h><GL/glu.h>,但GLUT的頭文件中已經自動將這兩個文件包含了,不必再次包含。

             

            然后看main函數。

            int main(int argc, char *argv[]),這個是帶命令行參數的main函數,各位應該見過吧?沒見過的同志們請多翻翻書,等弄明白了再往下看。

            注意main函數中的各語句,除了最后的return之外,其余全部以glut開頭。這種以glut開頭的函數都是GLUT工具包所提供的函數,下面對用到的幾個函數進行介紹。

            1glutInit,對GLUT進行初始化,這個函數必須在其它的GLUT使用之前調用一次。其格式比較死板,一般照抄這句glutInit(&argc, argv)就可以了。

            2 glutInitDisplayMode,設置顯示方式,其中GLUT_RGB表示使用RGB顏色,與之對應的還有GLUT_INDEX(表示使用索引顏色)。GLUT_SINGLE表示使用單緩沖,與之對應的還有GLUT_DOUBLE(使用雙緩沖)。更多信息,請自己Google。當然以后的教程也會有一些講解。

            3glutInitWindowPosition,這個簡單,設置窗口在屏幕中的位置。

            4glutInitWindowSize,這個也簡單,設置窗口的大小。

            5glutCreateWindow,根據前面設置的信息創建窗口。參數將被作為窗口的標題。注意:窗口被創建后,并不立即顯示到屏幕上。需要調用glutMainLoop才能看到窗口。

            6glutDisplayFunc,設置一個函數,當需要進行畫圖時,這個函數就會被調用。(這個說法不夠準確,但準確的說法可能初學者不太好理解,暫時這樣說吧)。

            7glutMainLoop,進行一個消息循環。(這個可能初學者也不太明白,現在只需要知道這個函數可以顯示窗口,并且等待窗口關閉后才會返回,這就足夠了。)

             

            glutDisplayFunc函數中,我們設置了當需要畫圖時,請調用myDisplay函數。于是myDisplay函數就用來畫圖。觀察myDisplay中的三個函數調用,發現它們都以gl開頭。這種以gl開頭的函數都是OpenGL的標準函數,下面對用到的函數進行介紹。

            1glClear,清除。GL_COLOR_BUFFER_BIT表示清除顏色,glClear函數還可以清除其它的東西,但這里不作介紹。

            2glRectf,畫一個矩形。四個參數分別表示了位于對角線上的兩個點的橫、縱坐標。

            3glFlush,保證前面的OpenGL命令立即執行(而不是讓它們在緩沖區中等待)。其作用跟fflush(stdout)類似。


             

             

            OpenGL入門學習(二)(轉)

            本次課程所要講的是繪制簡單的幾何圖形,在實際繪制之前,讓我們先熟悉一些概念。

             

            一、點、直線和多邊形

            我們知道數學(具體的說,是幾何學)中有點、直線和多邊形的概念,但這些概念在計算機中會有所不同。

            數學上的點,只有位置,沒有大小。但在計算機中,無論計算精度如何提高,始終不能表示一個無窮小的點。另一方面,無論圖形輸出設備(例如,顯示器)如何精確,始終不能輸出一個無窮小的點。一般情況下,OpenGL中的點將被畫成單個的像素(像素的概念,請自己搜索之~),雖然它可能足夠小,但并不會是無窮小。同一像素上,OpenGL可以繪制許多坐標只有稍微不同的點,但該像素的具體顏色將取決于OpenGL的實現。當然,過度的注意細節就是鉆牛角尖,我們大可不必花費過多的精力去研究多個點如何畫到同一像素上

            同樣的,數學上的直線沒有寬度,但OpenGL的直線則是有寬度的。同時,OpenGL的直線必須是有限長度,而不是像數學概念那樣是無限的。可以認為,OpenGL直線概念與數學上的線段接近,它可以由兩個端點來確定。

            多邊形是由多條線段首尾相連而形成的閉合區域。OpenGL規定,一個多邊形必須是一個凸多邊形(其定義為:多邊形內任意兩點所確定的線段都在多邊形內,由此也可以推導出,凸多邊形不能是空心的)。多邊形可以由其邊的端點(這里可稱為頂點)來確定。(注意:如果使用的多邊形不是凸多邊形,則最后輸出的效果是未定義的——OpenGL為了效率,放寬了檢查,這可能導致顯示錯誤。要避免這個錯誤,盡量使用三角形,因為三角形都是凸多邊形)

             

            可以想象,通過點、直線和多邊形,就可以組合成各種幾何圖形。甚至于,你可以把一段弧看成是很多短的直線段相連,這些直線段足夠短,以至于其長度小于一個像素的寬度。這樣一來弧和圓也可以表示出來了。通過位于不同平面的相連的小多邊形,我們還可以組成一個曲面

             

            二、在OpenGL中指定頂點

            由以上的討論可以知道,是一切的基礎。

            如何指定一個點呢?OpenGL提供了一系列函數。它們都以glVertex開頭,后面跟一個數字和1~2個字母。例如:

            glVertex2d

            glVertex2f

            glVertex3f

            glVertex3fv

            等等。

            數字表示參數的個數,2表示有兩個參數,3表示三個,4表示四個(我知道有點羅嗦~)。

            字母表示參數的類型,s表示16位整數(OpenGL中將這個類型定義為GLshort),

                              i表示32位整數(OpenGL中將這個類型定義為GLintGLsizei),

                              f表示32位浮點數(OpenGL中將這個類型定義為GLfloatGLclampf),

                              d表示64位浮點數(OpenGL中將這個類型定義為GLdoubleGLclampd)。

                              v表示傳遞的幾個參數將使用指針的方式,見下面的例子。

            這些函數除了參數的類型和個數不同以外,功能是相同的。例如,以下五個代碼段的功能是等效的:

            (一)glVertex2i(1, 3);

            (二)glVertex2f(1.0f, 3.0f);

            (三)glVertex3f(1.0f, 3.0f, 0.0f);

            (四)glVertex4f(1.0f, 3.0f, 0.0f, 1.0f);

            (五)GLfloat VertexArr3[] = {1.0f, 3.0f, 0.0f};

                 glVertex3fv(VertexArr3);

            以后我們將用glVertex*來表示這一系列函數。

            注意:OpenGL的很多函數都是采用這樣的形式,一個相同的前綴再加上參數說明標記,這一點會隨著學習的深入而有更多的體會。

             

             

            三、開始繪制

            假設現在我已經指定了若干頂點,那么OpenGL是如何知道我想拿這些頂點來干什么呢?是一個一個的畫出來,還是連成線?或者構成一個多邊形?或者做其它什么事情?

            為了解決這一問題,OpenGL要求:指定頂點的命令必須包含在glBegin函數之后,glEnd函數之前(否則指定的頂點將被忽略)。并由glBegin來指明如何使用這些點。

            例如我寫:

            glBegin(GL_POINTS);

                glVertex2f(0.0f, 0.0f);

                glVertex2f(0.5f, 0.0f);

            glEnd();

            則這兩個點將分別被畫出來。如果將GL_POINTS替換成GL_LINES,則兩個點將被認為是直線的兩個端點,OpenGL將會畫出一條直線。

            我們還可以指定更多的頂點,然后畫出更復雜的圖形。

            另一方面,glBegin支持的方式除了GL_POINTSGL_LINES,還有GL_LINE_STRIPGL_LINE_LOOPGL_TRIANGLESGL_TRIANGLE_STRIPGL_TRIANGLE_FAN等,每種方式的大致效果見下圖:

            我并不準備在glBegin的各種方式上大作文章。大家可以自己嘗試改變glBegin的方式和頂點的位置,生成一些有趣的圖案。

             

            程序代碼:

            void myDisplay(void)

            {

                glClear(GL_COLOR_BUFFER_BIT);

                glBegin( /* 在這里填上你所希望的模式 */ );

                    /* 在這里使用glVertex*系列函數 */

                    /* 指定你所希望的頂點位置 */

                glEnd();

                glFlush();

            }

            把這段代碼改成你喜歡的樣子,然后用它替換第一課中的myDisplay函數,編譯后即可運行。

             

            兩個例子

            例一、畫一個圓

            /*

            正四邊形,正五邊形,正六邊形,……,直到正n邊形,當n越大時,這個圖形就越接近圓

            n大到一定程度后,人眼將無法把它跟真正的圓相區別

            這時我們已經成功的畫出了一個

            (注:畫圓的方法很多,這里使用的是比較簡單,但效率較低的一種)

            試修改下面的const int n的值,觀察當n=3,4,5,8,10,15,20,30,50等不同數值時輸出的變化情況

            GL_POLYGON改為GL_LINE_LOOPGL_POINTS等其它方式,觀察輸出的變化情況

            */

            #include <math.h>

            const int n = 20;

            const GLfloat R = 0.5f;

            const GLfloat Pi = 3.1415926536f;

            void myDisplay(void)

            {

                int i;

                glClear(GL_COLOR_BUFFER_BIT);

                glBegin(GL_POLYGON);

                for(i=0; i<n; ++i)

                    glVertex2f(R*cos(2*Pi/n*i), R*sin(2*Pi/n*i));

                glEnd();

                glFlush();

            }

             

             

            例二、畫一個五角星

            /*

            設五角星的五個頂點分布位置關系如下:

                 A

             E       B

             

               D   C

            首先,根據余弦定理列方程,計算五角星的中心到頂點的距離a

            (假設五角星對應正五邊形的邊長為.0

            a = 1 / (2-2*cos(72*Pi/180));

            然后,根據正弦和余弦的定義,計算Bx坐標bxy坐標by,以及Cy坐標

            (假設五角星的中心在坐標原點)

            bx = a * cos(18 * Pi/180);

            by = a * sin(18 * Pi/180);

            cy = -a * cos(18 * Pi/180);

            五個點的坐標就可以通過以上四個量和一些常數簡單的表示出來

            */

            #include <math.h>

            const GLfloat Pi = 3.1415926536f;

            void myDisplay(void)

            {

                GLfloat a = 1 / (2-2*cos(72*Pi/180));

                GLfloat bx = a * cos(18 * Pi/180);

                GLfloat by = a * sin(18 * Pi/180);

                GLfloat cy = -a * cos(18 * Pi/180);

                GLfloat

                    PointA[2] = { 0, a },

                    PointB[2] = { bx, by },

                    PointC[2] = { 0.5, cy },

                    PointD[2] = { -0.5, cy },

                    PointE[2] = { -bx, by };

             

                glClear(GL_COLOR_BUFFER_BIT);

                // 按照A->C->E->B->D->A的順序,可以一筆將五角星畫出

                glBegin(GL_LINE_LOOP);

                    glVertex2fv(PointA);

                    glVertex2fv(PointC);

                    glVertex2fv(PointE);

                    glVertex2fv(PointB);

                    glVertex2fv(PointD);

                glEnd();

                glFlush();

            }

             

             

            例三、畫出正弦函數的圖形

            /*

            由于OpenGL默認坐標值只能從-11,(可以修改,但方法留到以后講)

            所以我們設置一個因子factor,把所有的坐標值等比例縮小,

            這樣就可以畫出更多個正弦周期

            試修改factor的值,觀察變化情況

            */

            #include <math.h>

            const GLfloat factor = 0.1f;

            void myDisplay(void)

            {

                GLfloat x;

                glClear(GL_COLOR_BUFFER_BIT);

                glBegin(GL_LINES);

                    glVertex2f(-1.0f, 0.0f);

                    glVertex2f(1.0f, 0.0f);        // 以上兩個點可以畫x

                    glVertex2f(0.0f, -1.0f);

                    glVertex2f(0.0f, 1.0f);        // 以上兩個點可以畫y

                glEnd();

                glBegin(GL_LINE_STRIP);

                for(x=-1.0f/factor; x<1.0f/factor; x+=0.01f)

                {

                    glVertex2f(x*factor, sin(x)*factor);

                }

                glEnd();

                glFlush();

            }

             

             

            小結

            本課講述了點、直線和多邊形的概念,以及如何使用OpenGL來描述點,并使用點來描述幾何圖形。

            大家可以發揮自己的想象,畫出各種幾何圖形,當然,也可以用GL_LINE_STRIP把很多位置相近的點連接起來,構成函數圖象。如果有興趣,也可以去找一些圖象比較美觀的函數,自己動手,用OpenGL把它畫出來。

             

            =====================   第二課    =====================

            OpenGL入門學習(三)(轉)

            在第二課中,我們學習了如何繪制幾何圖形,但大家如果多寫幾個程序,就會發現其實還是有些郁悶之處。例如:點太小,難以看清楚;直線也太細,不舒服;或者想畫虛線,但不知道方法只能用許多短直線,甚至用點組合而成。

            這些問題將在本課中被解決。

             

            下面就點、直線、多邊形分別討論。

             

             

            1、關于點

            點的大小默認為1個像素,但也可以改變之。改變的命令為glPointSize,其函數原型如下:

            void glPointSize(GLfloat size);

            size必須大于0.0f,默認值為1.0f,單位為像素

            注意:對于具體的OpenGL實現,點的大小都有個限度的,如果設置的size超過最大值,則設置可能會有問題。

            例子:

            void myDisplay(void)

            {

                glClear(GL_COLOR_BUFFER_BIT);

                glPointSize(5.0f);

                glBegin(GL_POINTS);

                    glVertex2f(0.0f, 0.0f);

                    glVertex2f(0.5f, 0.5f);

                glEnd();

                glFlush();

            }

             

            2、關于直線

            1)直線可以指定寬度:

            void glLineWidth(GLfloat width);

            其用法跟glPointSize類似。

            2)畫虛線。

            首先,使用glEnable(GL_LINE_STIPPLE);來啟動虛線模式(使用glDisable(GL_LINE_STIPPLE)可以關閉之)。

            然后,使用glLineStipple來設置虛線的樣式。

            void glLineStipple(GLint factor, GLushort pattern);

            pattern是由10組成的長度為16的序列,從最低位開始看,如果為1,則直線上接下來應該畫的factor個點將被畫為實的;如果為0,則直線上接下來應該畫的factor個點將被畫為虛的。

            以下是一些例子:

            聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,1994年)已經流傳于網絡,我希望沒有觸及到版權問題。

            示例代碼:

            void myDisplay(void)

            {

                glClear(GL_COLOR_BUFFER_BIT);

                glEnable(GL_LINE_STIPPLE);

                glLineStipple(2, 0x0F0F);

                glLineWidth(10.0f);

                glBegin(GL_LINES);

                    glVertex2f(0.0f, 0.0f);

                    glVertex2f(0.5f, 0.5f);

                glEnd();

                glFlush();

            }

             

             

            3、關于多邊形

            多邊形的內容較多,我們將講述以下四個方面。

            1)多邊形的兩面以及繪制方式。

            雖然我們目前還沒有真正的使用三維坐標來畫圖,但是建立一些三維的概念還是必要的。

            從三維的角度來看,一個多邊形具有兩個面。每一個面都可以設置不同的繪制方式:填充、只繪制邊緣輪廓線、只繪制頂點,其中填充是默認的方式。可以為兩個面分別設置不同的方式。

            glPolygonMode(GL_FRONT, GL_FILL);           // 設置正面為填充方式

            glPolygonMode(GL_BACK, GL_LINE);            // 設置反面為邊緣繪制方式

            glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); // 設置兩面均為頂點繪制方式

             

             

            2)反轉

            一般約定為頂點以逆時針順序出現在屏幕上的面正面,另一個面即成為反面。生活中常見的物體表面,通常都可以用這樣的正面反面合理的被表現出來(請找一個比較透明的礦泉水瓶子,在正對你的一面沿逆時針畫一個圓,并標明畫的方向,然后將背面轉為正面,畫一個類似的圓,體會一下正面反面。你會發現正對你的方向,瓶的外側是正面,而背對你的方向,瓶的內側才是正面。正對你的內側和背對你的外側則是反面。這樣一來,同樣屬于瓶的外側這個表面,但某些地方算是正面,某些地方卻算是反面了)。

            但也有一些表面比較特殊。例如麥比烏斯帶(請自己Google一下),可以全部使用正面或全部使用背面來表示。

            可以通過glFrontFace函數來交換正面反面的概念。

            glFrontFace(GL_CCW);  // 設置CCW方向為正面CCWCounterClockWise,逆時針

            glFrontFace(GL_CW);   // 設置CW方向為正面CWClockWise,順時針

            下面是一個示例程序,請用它替換第一課中的myDisplay函數,并將glFrontFace(GL_CCW)修改為glFrontFace(GL_CW),并觀察結果的變化。

            void myDisplay(void)

            {

                glClear(GL_COLOR_BUFFER_BIT);

                glPolygonMode(GL_FRONT, GL_FILL); // 設置正面為填充模式

                glPolygonMode(GL_BACK, GL_LINE);  // 設置反面為線形模式

                glFrontFace(GL_CCW);              // 設置逆時針方向為正面

                glBegin(GL_POLYGON);              // 按逆時針繪制一個正方形,在左下方

                    glVertex2f(-0.5f, -0.5f);

                    glVertex2f(0.0f, -0.5f);

                    glVertex2f(0.0f, 0.0f);

                    glVertex2f(-0.5f, 0.0f);

                glEnd();

                glBegin(GL_POLYGON);              // 按順時針繪制一個正方形,在右上方

                    glVertex2f(0.0f, 0.0f);

                    glVertex2f(0.0f, 0.5f);

                    glVertex2f(0.5f, 0.5f);

                    glVertex2f(0.5f, 0.0f);

                glEnd();

                glFlush();

            }

            3)剔除多邊形表面

            在三維空間中,一個多邊形雖然有兩個面,但我們無法看見背面的那些多邊形,而一些多邊形雖然是正面的,但被其他多邊形所遮擋。如果將無法看見的多邊形和可見的多邊形同等對待,無疑會降低我們處理圖形的效率。在這種時候,可以將不必要的面剔除。

            首先,使用glEnable(GL_CULL_FACE);來啟動剔除功能(使用glDisable(GL_CULL_FACE)可以關閉之)

            然后,使用glCullFace來進行剔除。

            glCullFace的參數可以是GL_FRONTGL_BACK或者GL_FRONT_AND_BACK,分別表示剔除正面、剔除反面、剔除正反兩面的多邊形。

            注意:剔除功能只影響多邊形,而對點和直線無影響。例如,使用glCullFace(GL_FRONT_AND_BACK)后,所有的多邊形都將被剔除,所以看見的就只有點和直線。

             

            4)鏤空多邊形

            直線可以被畫成虛線,而多邊形則可以進行鏤空。

            首先,使用glEnable(GL_POLYGON_STIPPLE);來啟動鏤空模式(使用glDisable(GL_POLYGON_STIPPLE)可以關閉之)。

            然后,使用glPolygonStipple來設置鏤空的樣式。

            void glPolygonStipple(const GLubyte *mask);

            其中的參數mask指向一個長度為128字節的空間,它表示了一個32*32的矩形應該如何鏤空。其中:第一個字節表示了最左下方的從左到右(也可以是從右到左,這個可以修改)8個像素是否鏤空(1表示不鏤空,顯示該像素;0表示鏤空,顯示其后面的顏色),最后一個字節表示了最右上方的8個像素是否鏤空。

            但是,如果我們直接定義這個mask數組,像這樣:

            static GLubyte Mask[128] =

            {

                0x00, 0x00, 0x00, 0x00,   //  這是最下面的一行

                0x00, 0x00, 0x00, 0x00,

                0x03, 0x80, 0x01, 0xC0,   //  

                0x06, 0xC0, 0x03, 0x60,   //  

                0x04, 0x60, 0x06, 0x20,   //  

                0x04, 0x30, 0x0C, 0x20,   //  

                0x04, 0x18, 0x18, 0x20,   //  

                0x04, 0x0C, 0x30, 0x20,   //  

                0x04, 0x06, 0x60, 0x20,   //  

                0x44, 0x03, 0xC0, 0x22,   //  

                0x44, 0x01, 0x80, 0x22,   //  

                0x44, 0x01, 0x80, 0x22,   //  

                0x44, 0x01, 0x80, 0x22,   //  使

                0x44, 0x01, 0x80, 0x22,   //  

                0x44, 0x01, 0x80, 0x22,

                0x44, 0x01, 0x80, 0x22,

                0x66, 0x01, 0x80, 0x66,

                0x33, 0x01, 0x80, 0xCC,

                0x19, 0x81, 0x81, 0x98,

                0x0C, 0xC1, 0x83, 0x30,

                0x07, 0xE1, 0x87, 0xE0,

                0x03, 0x3F, 0xFC, 0xC0,

                0x03, 0x31, 0x8C, 0xC0,

                0x03, 0x3F, 0xFC, 0xC0,

                0x06, 0x64, 0x26, 0x60,

                0x0C, 0xCC, 0x33, 0x30,

                0x18, 0xCC, 0x33, 0x18,

                0x10, 0xC4, 0x23, 0x08,

                0x10, 0x63, 0xC6, 0x08,

                0x10, 0x30, 0x0C, 0x08,

                0x10, 0x18, 0x18, 0x08,

                0x10, 0x00, 0x00, 0x08   // 這是最上面的一行

            };

            這樣一堆數據非常缺乏直觀性,我們需要很費勁的去分析,才會發現它表示的竟然是一只蒼蠅。

            如果將這樣的數據保存成圖片,并用專門的工具進行編輯,顯然會方便很多。下面介紹如何做到這一點。

             

             

            首先,用Windows自帶的畫筆程序新建一副圖片,取名為mask.bmp,注意保存時,應該選擇單色位圖。在圖象”->“屬性對話框中,設置圖片的高度和寬度均為32

            用放大鏡觀察圖片,并編輯之。黑色對應二進制零(鏤空),白色對應二進制一(不鏤空),編輯完畢后保存。

            然后,就可以使用以下代碼來獲得這個Mask數組了。

            static GLubyte Mask[128];

            FILE *fp;

            fp = fopen("mask.bmp", "rb");

            if( !fp )

                exit(0);

            // 移動文件指針到這個位置,使得再讀sizeof(Mask)個字節就會遇到文件結束

            // 注意-(int)sizeof(Mask)雖然不是什么好的寫法,但這里它確實是正確有效的

            // 如果直接寫-sizeof(Mask)的話,因為sizeof取得的是一個無符號數,取負號會有問題

            if( fseek(fp, -(int)sizeof(Mask), SEEK_END) )

                exit(0);

            // 讀取sizeof(Mask)個字節到Mask

            if( !fread(Mask, sizeof(Mask), 1, fp) )

                exit(0);

            fclose(fp);

             

             

            好的,現在請自己編輯一個圖片作為mask,并用上述方法取得Mask數組,運行后觀察效果。

            說明:繪制虛線時可以設置factor因子,但多邊形的鏤空無法設置factor因子。請用鼠標改變窗口的大小,觀察鏤空效果的變化情況。

            #include <stdio.h>

            #include <stdlib.h>

            void myDisplay(void)

            {

                static GLubyte Mask[128];

                FILE *fp;

                fp = fopen("mask.bmp", "rb");

                if( !fp )

                    exit(0);

                if( fseek(fp, -(int)sizeof(Mask), SEEK_END) )

                    exit(0);

                if( !fread(Mask, sizeof(Mask), 1, fp) )

                    exit(0);

                fclose(fp);

                glClear(GL_COLOR_BUFFER_BIT);

                glEnable(GL_POLYGON_STIPPLE);

                glPolygonStipple(Mask);

                glRectf(-0.5f, -0.5f, 0.0f, 0.0f);  // 在左下方繪制一個有鏤空效果的正方形

                glDisable(GL_POLYGON_STIPPLE);

                glRectf(0.0f, 0.0f, 0.5f, 0.5f);    // 在右上方繪制一個無鏤空效果的正方形

                glFlush();

            }

             

             

            小結

            本課學習了繪制幾何圖形的一些細節。

            點可以設置大小。

            直線可以設置寬度;可以將直線畫成虛線。

            多邊形的兩個面的繪制方法可以分別設置;在三維空間中,不可見的多邊形可以被剔除;可以將填充多邊形繪制成鏤空的樣式。

            了解這些細節會使我們在一些圖象繪制中更加得心應手。

            另外,把一些數據寫到程序之外的文件中,并用專門的工具編輯之,有時可以顯得更方便。

             

            =====================   第三課    =====================

            OpenGL入門學習(四)(轉)

            本次學習的是顏色的選擇。終于要走出黑白的世界了~~

             

             

            OpenGL支持兩種顏色模式:一種是RGBA,一種是顏色索引模式。

            無論哪種顏色模式,計算機都必須為每一個像素保存一些數據。不同的是,RGBA模式中,數據直接就代表了顏色;而顏色索引模式中,數據代表的是一個索引,要得到真正的顏色,還必須去查索引表。

             

            1. RGBA顏色

            RGBA模式中,每一個像素會保存以下數據:R值(紅色分量)、G值(綠色分量)、B值(藍色分量)和A值(alpha分量)。其中紅、綠、藍三種顏色相組合,就可以得到我們所需要的各種顏色,而alpha不直接影響顏色,它將留待以后介紹。

            RGBA模式下選擇顏色是十分簡單的事情,只需要一個函數就可以搞定。

            glColor*系列函數可以用于設置顏色,其中三個參數的版本可以指定RGB的值,而A值采用默認;四個參數的版本可以分別指定RGBA的值。例如:

            void glColor3f(GLfloat red, GLfloat green, GLfloat blue);

            void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);

            (還記得嗎?3f表示有三個浮點參數~請看第二課中關于glVertex*函數的敘述。)

            將浮點數作為參數,其中0.0表示不使用該種顏色,而1.0表示將該種顏色用到最多。例如:

            glColor3f(1.0f, 0.0f, 0.0f);   表示不使用綠、藍色,而將紅色使用最多,于是得到最純凈的紅色。

            glColor3f(0.0f, 1.0f, 1.0f);   表示使用綠、藍色到最多,而不使用紅色。混合的效果就是淺藍色。

            glColor3f(0.5f, 0.5f, 0.5f);   表示各種顏色使用一半,效果為灰色。

            注意:浮點數可以精確到小數點后若干位,這并不表示計算機就可以顯示如此多種顏色。實際上,計算機可以顯示的顏色種數將由硬件決定。如果OpenGL找不到精確的顏色,會進行類似四舍五入的處理。

             

            大家可以通過改變下面代碼中glColor3f的參數值,繪制不同顏色的矩形。

            void myDisplay(void)

            {

                glClear(GL_COLOR_BUFFER_BIT);

                glColor3f(0.0f, 1.0f, 1.0f);

                glRectf(-0.5f, -0.5f, 0.5f, 0.5f);

                glFlush();

            }

             

            注意:glColor系列函數,在參數類型不同時,表示最大顏色的值也不同。

            采用fd做后綴的函數,以1.0表示最大的使用。

            采用b做后綴的函數,以127表示最大的使用。

            采用ub做后綴的函數,以255表示最大的使用。

            采用s做后綴的函數,以32767表示最大的使用。

            采用us做后綴的函數,以65535表示最大的使用。

            這些規則看似麻煩,但熟悉后實際使用中不會有什么障礙。

             

            2、索引顏色

            在索引顏色模式中,OpenGL需要一個顏色表。這個表就相當于畫家的調色板:雖然可以調出很多種顏色,但同時存在于調色板上的顏色種數將不會超過調色板的格數。試將顏色表的每一項想象成調色板上的一個格子:它保存了一種顏色。

            在使用索引顏色模式畫圖時,我說我把第i種顏色設置為某某,其實就相當于將調色板的第i格調為某某顏色。我需要第k種顏色來畫圖,那么就用畫筆去蘸一下第k格調色板。

            顏色表的大小是很有限的,一般在256~4096之間,且總是2的整數次冪。在使用索引顏色方式進行繪圖時,總是先設置顏色表,然后選擇顏色。

             

            2.1、選擇顏色

            使用glIndex*系列函數可以在顏色表中選擇顏色。其中最常用的可能是glIndexi,它的參數是一個整形。

            void glIndexi(GLint c);

            是的,這的確很簡單。

             

            2.2、設置顏色表

            OpenGL 并直接沒有提供設置顏色表的方法,因此設置顏色表需要使用操作系統的支持。我們所用的Windows和其他大多數圖形操作系統都具有這個功能,但所使用的函數卻不相同。正如我沒有講述如何自己寫代碼在Windows下建立一個窗口,這里我也不會講述如何在Windows下設置顏色表。

            GLUT工具包提供了設置顏色表的函數glutSetColor,但我測試始終有問題。現在為了讓大家體驗一下索引顏色,我向大家介紹另一個OpenGL工具包: aux。這個工具包是VisualStudio自帶的,不必另外安裝,但它已經過時,這里僅僅是體驗一下,大家不必深入。

            #include <windows.h>

            #include <GL/gl.h>

            #include <GL/glaux.h>

             

            #pragma comment (lib, "opengl32.lib")

            #pragma comment (lib, "glaux.lib")

             

            #include <math.h>

            const GLdouble Pi = 3.1415926536;

            void myDisplay(void)

            {

                int i;

                for(i=0; i<8; ++i)

                    auxSetOneColor(i, (float)(i&0x04), (float)(i&0x02), (float)(i&0x01));

                glShadeModel(GL_FLAT);

                glClear(GL_COLOR_BUFFER_BIT);

                glBegin(GL_TRIANGLE_FAN);

                glVertex2f(0.0f, 0.0f);

                for(i=0; i<=8; ++i)

                {

                    glIndexi(i);

                    glVertex2f(cos(i*Pi/4), sin(i*Pi/4));

                }

                glEnd();

                glFlush();

            }

             

            int main(void)

            {

                auxInitDisplayMode(AUX_SINGLE|AUX_INDEX);

                auxInitPosition(0, 0, 400, 400);

                auxInitWindow(L"");

                myDisplay();

                Sleep(10 * 1000);

                return 0;

            }

             

            其它部分大家都可以不管,只看myDisplay函數就可以了。首先,使用auxSetOneColor設置顏色表中的一格。循環八次就可以設置八格。

            glShadeModel等下再講,這里不提。

            然后在循環中用glVertex設置頂點,同時用glIndexi改變頂點代表的顏色。

            最終得到的效果是八個相同形狀、不同顏色的三角形。

             

            索引顏色雖然講得多了點。索引顏色的主要優勢是占用空間小(每個像素不必單獨保存自己的顏色,只用很少的二進制位就可以代表其顏色在顏色表中的位置),花費系統資源少,圖形運算速度快,但它編程稍稍顯得不是那么方便,并且畫面效果也會比RGB顏色差一些。星際爭霸可能代表了256色的顏色表的畫面效果,雖然它在一臺很爛的PC上也可以運行很流暢,但以目前的眼光來看,其畫面效果就顯得不足了。

            目前的PC機性能已經足夠在各種場合下使用RGB顏色,因此PC程序開發中,使用索引顏色已經不是主流。當然,一些小型設備例如GBA、手機等,索引顏色還是有它的用武之地。

             

             

            3、指定清除屏幕用的顏色

            我們寫:glClear(GL_COLOR_BUFFER_BIT);意思是把屏幕上的顏色清空。

            但實際上什么才叫呢?在宇宙中,黑色代表了;在一張白紙上,白色代表了;在信封上,信封的顏色才是

            OpenGL用下面的函數來定義清楚屏幕后屏幕所擁有的顏色。

            RGB模式下,使用glClearColor來指定的顏色,它需要四個參數,其參數的意義跟glColor4f相似。

            在索引顏色模式下,使用glClearIndex來指定的顏色所在的索引,它需要一個參數,其意義跟glIndexi相似。

            void myDisplay(void)

            {

                glClearColor(1.0f, 0.0f, 0.0f, 0.0f);

                glClear(GL_COLOR_BUFFER_BIT);

                glFlush();

            }

            呵,這個還真簡單~

             

             

            4、指定著色模型

            OpenGL允許為同一多邊形的不同頂點指定不同的顏色。例如:

            #include <math.h>

            const GLdouble Pi = 3.1415926536;

            void myDisplay(void)

            {

                int i;

                // glShadeModel(GL_FLAT);

                glClear(GL_COLOR_BUFFER_BIT);

                glBegin(GL_TRIANGLE_FAN);

                glColor3f(1.0f, 1.0f, 1.0f);

                glVertex2f(0.0f, 0.0f);

                for(i=0; i<=8; ++i)

                {

                    glColor3f(i&0x04, i&0x02, i&0x01);

                    glVertex2f(cos(i*Pi/4), sin(i*Pi/4));

                }

                glEnd();

                glFlush();

            }

            在默認情況下,OpenGL會計算兩點頂點之間的其它點,并為它們填上合適的顏色,使相鄰的點的顏色值都比較接近。如果使用的是RGB模式,看起來就具有漸變的效果。如果是使用顏色索引模式,則其相鄰點的索引值是接近的,如果將顏色表中接近的項設置成接近的顏色,則看起來也是漸變的效果。但如果顏色表中接近的項顏色卻差距很大,則看起來可能是很奇怪的效果。

            使用glShadeModel函數可以關閉這種計算,如果頂點的顏色不同,則將頂點之間的其它點全部設置為與某一個點相同。(直線以后指定的點的顏色為準,而多邊形將以任意頂點的顏色為準,由實現決定。)為了避免這個不確定性,盡量在多邊形中使用同一種顏色。

            glShadeModel的使用方法:

            glShadeModel(GL_SMOOTH);   // 平滑方式,這也是默認方式

            glShadeModel(GL_FLAT);     // 單色方式

             

            小結:

            本課學習了如何設置顏色。其中RGB顏色方式是目前PC機上的常用方式。

            可以設置glClear清除后屏幕所剩的顏色。

            可以設置顏色填充方式:平滑方式或單色方式。

             

            =====================   第四課    =====================

            OpenGL入門學習(五)(轉)

            今天要講的是三維變換的內容,課程比較枯燥。主要是因為很多函數在單獨使用時都不好描述其效果,我只好在最后舉一個比較綜合的例子。希望大家能一口氣看到底了。只看一次可能不夠,如果感覺到迷糊,不妨多看兩遍。有疑問可以在下面跟帖提出。

            我也使用了若干圖形,希望可以幫助理解。

             

             

            在前面繪制幾何圖形的時候,大家是否覺得我們繪圖的范圍太狹隘了呢?坐標只能從-11,還只能是X軸向右,Y軸向上,Z軸垂直屏幕。這些限制給我們的繪圖帶來了很多不便。

             

            我們生活在一個三維的世界——如果要觀察一個物體,我們可以:

            1、從不同的位置去觀察它。(視圖變換)

            2、移動或者旋轉它,當然了,如果它只是計算機里面的物體,我們還可以放大或縮小它。(模型變換)

            3、如果把物體畫下來,我們可以選擇:是否需要一種近大遠小的透視效果。另外,我們可能只希望看到物體的一部分,而不是全部(剪裁)。(投影變換)

            4、我們可能希望把整個看到的圖形畫下來,但它只占據紙張的一部分,而不是全部。(視口變換)

            這些,都可以在OpenGL中實現。

             

            OpenGL變換實際上是通過矩陣乘法來實現。無論是移動、旋轉還是縮放大小,都是通過在當前矩陣的基礎上乘以一個新的矩陣來達到目的。關于矩陣的知識,這里不詳細介紹,有興趣的朋友可以看看線性代數(大學生的話多半應該學過的)。

            OpenGL可以在最底層直接操作矩陣,不過作為初學,這樣做的意義并不大。這里就不做介紹了。

             

             

            1、模型變換和視圖變換

            相對移動的觀點來看,改變觀察點的位置與方向和改變物體本身的位置與方向具有等效性。在OpenGL中,實現這兩種功能甚至使用的是同樣的函數。

            由于模型和視圖的變換都通過矩陣運算來實現,在進行變換前,應先設置當前操作的矩陣為模型視圖矩陣。設置的方法是以GL_MODELVIEW為參數調用glMatrixMode函數,像這樣:

            glMatrixMode(GL_MODELVIEW);

            通常,我們需要在進行變換前把當前矩陣設置為單位矩陣。這也只需要一行代碼:

            glLoadIdentity();

             

            然后,就可以進行模型變換和視圖變換了。進行模型和視圖變換,主要涉及到三個函數:

            glTranslate*,把當前矩陣和一個表示移動物體的矩陣相乘。三個參數分別表示了在三個坐標上的位移值。

            glRotate*,把當前矩陣和一個表示旋轉物體的矩陣相乘。物體將繞著(0,0,0)(x,y,z)的直線以逆時針旋轉,參數angle表示旋轉的角度。

            glScale*,把當前矩陣和一個表示縮放物體的矩陣相乘。x,y,z分別表示在該方向上的縮放比例。

             

            注意我都是說XX相乘,而不是直接說這個函數就是旋轉或者這個函數就是移動,這是有原因的,馬上就會講到。

            假設當前矩陣為單位矩陣,然后先乘以一個表示旋轉的矩陣R,再乘以一個表示移動的矩陣T,最后得到的矩陣再乘上每一個頂點的坐標矩陣v。所以,經過變換得到的頂點坐標就是((RT)v)。由于矩陣乘法的結合率,((RT)v) = (R(Tv)),換句話說,實際上是先進行移動,然后進行旋轉。即:實際變換的順序與代碼中寫的順序是相反的。由于先移動后旋轉先旋轉后移動得到的結果很可能不同,初學的時候需要特別注意這一點。

            OpenGL之所以這樣設計,是為了得到更高的效率。但在繪制復雜的三維圖形時,如果每次都去考慮如何把變換倒過來,也是很痛苦的事情。這里介紹另一種思路,可以讓代碼看起來更自然(寫出的代碼其實完全一樣,只是考慮問題時用的方法不同了)。

            讓我們想象,坐標并不是固定不變的。旋轉的時候,坐標系統隨著物體旋轉。移動的時候,坐標系統隨著物體移動。如此一來,就不需要考慮代碼的順序反轉的問題了。

             

            以上都是針對改變物體的位置和方向來介紹的。如果要改變觀察點的位置,除了配合使用glRotate*glTranslate*函數以外,還可以使用這個函數:gluLookAt。它的參數比較多,前三個參數表示了觀察點的位置,中間三個參數表示了觀察目標的位置,最后三個參數代表從(0,0,0) (x,y,z)的直線,它表示了觀察者認為的方向。

             

             

            2、投影變換

            投影變換就是定義一個可視空間,可視空間以外的物體不會被繪制到屏幕上。(注意,從現在起,坐標可以不再是-1.01.0了!)

            OpenGL支持兩種類型的投影變換,即透視投影和正投影。投影也是使用矩陣來實現的。如果需要操作投影矩陣,需要以GL_PROJECTION為參數調用glMatrixMode函數。

            glMatrixMode(GL_PROJECTION);

            通常,我們需要在進行變換前把當前矩陣設置為單位矩陣。

            glLoadIdentity();

             

            透視投影所產生的結果類似于照片,有近大遠小的效果,比如在火車頭內向前照一個鐵軌的照片,兩條鐵軌似乎在遠處相交了。

            使用glFrustum函數可以將當前的可視空間設置為透視投影空間。其參數的意義如下圖:

            聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,1994年)已經流傳于網絡,我希望沒有觸及到版權問題。

            也可以使用更常用的gluPerspective函數。其參數的意義如下圖:

            聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,1994年)已經流傳于網絡,我希望沒有觸及到版權問題。

             

            正投影相當于在無限遠處觀察得到的結果,它只是一種理想狀態。但對于計算機來說,使用正投影有可能獲得更好的運行速度。

            使用glOrtho函數可以將當前的可視空間設置為正投影空間。其參數的意義如下圖:

            聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,1994年)已經流傳于網絡,我希望沒有觸及到版權問題。

             

            如果繪制的圖形空間本身就是二維的,可以使用gluOrtho2D。他的使用類似于glOrgho

             

             

            3、視口變換

            當一切工作已經就緒,只需要把像素繪制到屏幕上了。這時候還剩最后一個問題:應該把像素繪制到窗口的哪個區域呢?通常情況下,默認是完整的填充整個窗口,但我們完全可以只填充一半。(即:把整個圖象填充到一半的窗口內)

            聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,1994年)已經流傳于網絡,我希望沒有觸及到版權問題。

             

            使用glViewport來定義視口。其中前兩個參數定義了視口的左下腳(0,0表示最左下方),后兩個參數分別是寬度和高度。

             

            4、操作矩陣堆棧

            介于是入門教程,先簡單介紹一下堆棧。你可以把堆棧想象成一疊盤子。開始的時候一個盤子也沒有,你可以一個一個往上放,也可以一個一個取下來。每次取下的,都是最后一次被放上去的盤子。通常,在計算機實現堆棧時,堆棧的容量是有限的,如果盤子過多,就會出錯。當然,如果沒有盤子了,再要求取一個盤子,也會出錯。

            我們在進行矩陣操作時,有可能需要先保存某個矩陣,過一段時間再恢復它。當我們需要保存時,調用glPushMatrix函數,它相當于把矩陣(相當于盤子)放到堆棧上。當需要恢復最近一次的保存時,調用glPopMatrix函數,它相當于把矩陣從堆棧上取下。OpenGL規定堆棧的容量至少可以容納32個矩陣,某些OpenGL實現中,堆棧的容量實際上超過了32個。因此不必過于擔心矩陣的容量問題。

            通常,用這種先保存后恢復的措施,比先變換再逆變換要更方便,更快速。

            注意:模型視圖矩陣和投影矩陣都有相應的堆棧。使用glMatrixMode來指定當前操作的究竟是模型視圖矩陣還是投影矩陣。

             

            5、綜合舉例

            好了,視圖變換的入門知識差不多就講完了。但我們不能就這樣結束。因為本次課程的內容實在過于枯燥,如果分別舉例,可能效果不佳。我只好綜合的講一個例子,算是給大家一個參考。至于實際的掌握,還要靠大家自己花功夫。閑話少說,現在進入正題。

             

            我們要制作的是一個三維場景,包括了太陽、地球和月亮。假定一年有12個月,每個月30天。每年,地球繞著太陽轉一圈。每個月,月亮圍著地球轉一圈。即一年有360天。現在給出日期的編號(0~359),要求繪制出太陽、地球、月亮的相對位置示意圖。(這是為了編程方便才這樣設計的。如果需要制作更現實的情況,那也只是一些數值處理而已,與OpenGL關系不大)

            首先,讓我們認定這三個天體都是球形,且他們的運動軌跡處于同一水平面,建立以下坐標系:太陽的中心為原點,天體軌跡所在的平面表示了X軸與Y軸決定的平面,且每年第一天,地球在X軸正方向上,月亮在地球的正X軸方向。

            下一步是確立可視空間。注意:太陽的半徑要比太陽到地球的距離短得多。如果我們直接使用天文觀測得到的長度比例,則當整個窗口表示地球軌道大小時,太陽的大小將被忽略。因此,我們只能成倍的放大幾個天體的半徑,以適應我們觀察的需要。(百度一下,得到太陽、地球、月亮的大致半徑分別是:696000km  6378km1738km。地球到太陽的距離約為1.5km=150000000km,月亮到地球的距離約為380000km。)

            讓我們假想一些數據,將三個天體的半徑分別修改為:69600000(放大100倍),15945000(放大2500倍),4345000(放大5000倍)。將地球到月亮的距離修改38000000(放大100倍)。地球到太陽的距離保持不變。

            為了讓地球和月亮在離我們很近時,我們仍然不需要變換觀察點和觀察方向就可以觀察它們,我們把觀察點放在這個位置:(0, -200000000, 0)  ——因為地球軌道半徑為150000000,咱們就湊個整,取-200000000就可以了。觀察目標設置為原點(即太陽中心),選擇Z軸正方向作為  “方。當然我們還可以把觀察點往方移動一些,得到(0, -200000000, 200000000),這樣可以得到45度角的俯視效果。

            為了得到透視效果,我們使用gluPerspective來設置可視空間。假定可視角為60度(如果調試時發現該角度不合適,可修改之。我在最后選擇的數值是75。),高寬比為1.0。最近可視距離為1.0,最遠可視距離為200000000*2=400000000。即:gluPerspective  (60, 1, 1, 400000000);

             

             

            5、綜合舉例

            好了,視圖變換的入門知識差不多就講完了。但我們不能就這樣結束。因為本次課程的內容實在過于枯燥,如果分別舉例,可能效果不佳。我只好綜合的講一個例子,算是給大家一個參考。至于實際的掌握,還要靠大家自己花功夫。閑話少說,現在進入正題。

             

            我們要制作的是一個三維場景,包括了太陽、地球和月亮。假定一年有12個月,每個月30天。每年,地球繞著太陽轉一圈。每個月,月亮圍著地球轉一圈。即一年有360天。現在給出日期的編號(0~359),要求繪制出太陽、地球、月亮的相對位置示意圖。(這是為了編程方便才這樣設計的。如果需要制作更現實的情況,那也只是一些數值處理而已,與OpenGL關系不大)

            首先,讓我們認定這三個天體都是球形,且他們的運動軌跡處于同一水平面,建立以下坐標系:太陽的中心為原點,天體軌跡所在的平面表示了X軸與Y軸決定的平面,且每年第一天,地球在X軸正方向上,月亮在地球的正X軸方向。

            下一步是確立可視空間。注意:太陽的半徑要比太陽到地球的距離短得多。如果我們直接使用天文觀測得到的長度比例,則當整個窗口表示地球軌道大小時,太陽的大小將被忽略。因此,我們只能成倍的放大幾個天體的半徑,以適應我們觀察的需要。(百度一下,得到太陽、地球、月亮的大致半徑分別是:696000km 6378km1738km。地球到太陽的距離約為1.5km=150000000km,月亮到地球的距離約為380000km。)

            讓我們假想一些數據,將三個天體的半徑分別修改為:69600000(放大100倍),15945000(放大2500倍),4345000(放大2500倍)。將地球到月亮的距離修改38000000(放大100倍)。地球到太陽的距離保持不變。

            為了讓地球和月亮在離我們很近時,我們仍然不需要變換觀察點和觀察方向就可以觀察它們,我們把觀察點放在這個位置:(0, -200000000, 0) ——因為地球軌道半徑為150000000,咱們就湊個整,取-200000000就可以了。觀察目標設置為原點(即太陽中心),選擇Z軸正方向作為 方。當然我們還可以把觀察點往方移動一些,得到(0, -200000000, 200000000),這樣可以得到45度角的俯視效果。

            為了得到透視效果,我們使用gluPerspective來設置可視空間。假定可視角為60度(如果調試時發現該角度不合適,可修改之。我在最后選擇的數值是75。),高寬比為1.0。最近可視距離為1.0,最遠可視距離為200000000*2=400000000。即:gluPerspective (60, 1, 1, 400000000);

             

             

            現在我們來看看如何繪制這三個天體。

            為了簡單起見,我們把三個天體都想象成規則的球體。而我們所使用的glut實用工具中,正好就有一個繪制球體的現成函數:glutSolidSphere,這個函數在原點繪制出一個球體。由于坐標是可以通過glTranslate*glRotate*兩個函數進行隨意變換的,所以我們就可以在任意位置繪制球體了。函數有三個參數:第一個參數表示球體的半徑,后兩個參數代表了的數目,簡單點說就是球體的精確程度,數值越大越精確,當然代價就是速度越緩慢。這里我們只是簡單的設置后兩個參數為20

            太陽在坐標原點,所以不需要經過任何變換,直接繪制就可以了。

            地球則要復雜一點,需要變換坐標。由于今年已經經過的天數已知為day,則地球轉過的角度為day/一年的天數*360度。前面已經假定每年都是360天,因此地球轉過的角度恰好為day。所以可以通過下面的代碼來解決:

            glRotatef(day, 0, 0, -1);

            /* 注意地球公轉是自西向東的,因此是饒著Z軸負方向進行逆時針旋轉 */

            glTranslatef(地球軌道半徑, 0, 0);

            glutSolidSphere(地球半徑, 20, 20);

            月亮是最復雜的。因為它不僅要繞地球轉,還要隨著地球繞太陽轉。但如果我們選擇地球作為參考,則月亮進行的運動就是一個簡單的圓周運動了。如果我們先繪制地球,再繪制月亮,則只需要進行與地球類似的變換:

            glRotatef(月亮旋轉的角度, 0, 0, -1);

            glTranslatef(月亮軌道半徑, 0, 0);

            glutSolidSphere(月亮半徑, 20, 20);

            但這個月亮旋轉的角度,并不能簡單的理解為day/一個月的天數30*360度。因為我們在繪制地球時,這個坐標已經是旋轉過的。現在的旋轉是在以前的基礎上進行旋轉,因此還需要處理這個差值。我們可以寫成:day/30*360 - day,即減去原來已經轉過的角度。這只是一種簡單的處理,當然也可以在繪制地球前用glPushMatrix保存矩陣,繪制地球后用glPopMatrix恢復矩陣。再設計一個跟地球位置無關的月亮位置公式,來繪制月亮。通常后一種方法比前一種要好,因為浮點的運算是不精確的,即是說我們計算地球本身的位置就是不精確的。拿這個不精確的數去計算月亮的位置,會導致 不精確的成分累積,過多的不精確會造成錯誤。我們這個小程序沒有去考慮這個,但并不是說這個問題不重要。

            還有一個需要注意的細節: OpenGL把三維坐標中的物體繪制到二維屏幕,繪制的順序是按照代碼的順序來進行的。因此后繪制的物體會遮住先繪制的物體,即使后繪制的物體在先繪制的物體的后面也是如此。使用深度測試可以解決這一問題。使用的方法是:1、以GL_DEPTH_TEST為參數調用glEnable函數,啟動深度測試。2、在必要時(通常是每次繪制畫面開始時),清空深度緩沖,即:glClear(GL_DEPTH_BUFFER_BIT);其中,glClear (GL_COLOR_BUFFER_BIT)glClear(GL_DEPTH_BUFFER_BIT)可以合并寫為:

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            且后者的運行速度可能比前者快。

             

             

            到此為止,我們終于可以得到整個太陽,地球和月亮系統的完整代碼。

             

             

            Code:

            --------------------------------------------------------------------------------

            // 太陽、地球和月亮

            // 假設每個月都是30

            // 一年12個月,共是360

            static int day = 200; // day的變化:從0359

            void myDisplay(void)

            {

                glEnable(GL_DEPTH_TEST);

                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

             

                glMatrixMode(GL_PROJECTION);

                glLoadIdentity();

                gluPerspective(75, 1, 1, 400000000);

                glMatrixMode(GL_MODELVIEW);

                glLoadIdentity();

                gluLookAt(0, -200000000, 200000000, 0, 0, 0, 0, 0, 1);

             

                // 繪制紅色的太陽

                glColor3f(1.0f, 0.0f, 0.0f);

                glutSolidSphere(69600000, 20, 20);

                // 繪制藍色的地球

                glColor3f(0.0f, 0.0f, 1.0f);

                glRotatef(day/360.0*360.0, 0.0f, 0.0f, -1.0f);

                glTranslatef(150000000, 0.0f, 0.0f);

                glutSolidSphere(15945000, 20, 20);

                // 繪制黃色的月亮

                glColor3f(1.0f, 1.0f, 0.0f);

                glRotatef(day/30.0*360.0 - day/360.0*360.0, 0.0f, 0.0f, -1.0f);

                glTranslatef(38000000, 0.0f, 0.0f);

                glutSolidSphere(4345000, 20, 20);

             

                glFlush();

            }

            --------------------------------------------------------------------------------

             

             

             

            試修改day的值,看看畫面有何變化。

             

             

            小結:本課開始,我們正式進入了三維的OpenGL世界。

            OpenGL通過矩陣變換來把三維物體轉變為二維圖象,進而在屏幕上顯示出來。為了指定當前操作的是何種矩陣,我們使用了函數glMatrixMode

            我們可以移動、旋轉觀察點或者移動、旋轉物體,使用的函數是glTranslate*glRotate*

            我們可以縮放物體,使用的函數是glScale*

            我們可以定義可視空間,這個空間可以是正投影的(使用glOrthogluOrtho2D),也可以是透視投影的(使用glFrustumgluPerspective)。

            我們可以定義繪制到窗口的范圍,使用的函數是glViewport

            矩陣有自己的堆棧,方便進行保存和恢復。這在繪制復雜圖形時很有幫助。使用的函數是glPushMatrixglPopMatrix

             

            好了,艱苦的一課終于完畢。我知道,本課的內容十分枯燥,就連最后的例子也是。但我也沒有更好的辦法了,希望大家能堅持過去。不必擔心,熟悉本課內容后,以后的一段時間內,都會是比較輕松愉快的了。

             

            =====================   第五課    =====================

            OpenGL入門學習(六)(轉)

            今天要講的是動畫制作——可能是各位都很喜歡的。除了講授知識外,我們還會讓昨天那個太陽、地球和月亮天體圖畫動起來。緩和一下枯燥的氣氛。本次課程,我們將進入激動人心的計算機動畫世界。

             

            想必大家都知道電影和動畫的工作原理吧?是的,快速的把看似連續的畫面一幅幅的呈現在人們面前。一旦每秒鐘呈現的畫面超過24幅,人們就會錯以為它是連續的。我們通常觀看的電視,每秒播放2530幅畫面。但對于計算機來說,它可以播放更多的畫面,以達到更平滑的效果。如果速度過慢,畫面不夠平滑。如果速度過快,則人眼未必就能反應得過來。對于一個正常人來說,每秒60~120幅圖畫是比較合適的。具體的數值因人而異。

             

            假設某動畫一共有n幅畫面,則它的工作步驟就是:

            顯示第1幅畫面,然后等待一小段時間,直到下一個1/24

            顯示第2幅畫面,然后等待一小段時間,直到下一個1/24

            ……

            顯示第n幅畫面,然后等待一小段時間,直到下一個1/24

            結束

            如果用C語言偽代碼來描述這一過程,就是:

            for(i=0; i<n; ++i)

            {

                DrawScene(i);

                Wait();

            }

             

            1、雙緩沖技術

            在計算機上的動畫與實際的動畫有些不同:實際的動畫都是先畫好了,播放的時候直接拿出來顯示就行。計算機動畫則是畫一張,就拿出來一張,再畫下一張,再拿出來。如果所需要繪制的圖形很簡單,那么這樣也沒什么問題。但一旦圖形比較復雜,繪制需要的時間較長,問題就會變得突出。

            讓我們把計算機想象成一個畫圖比較快的人,假如他直接在屏幕上畫圖,而圖形比較復雜,則有可能在他只畫了某幅圖的一半的時候就被觀眾看到。而后面雖然他把畫補全了,但觀眾的眼睛卻又沒有反應過來,還停留在原來那個殘缺的畫面上。也就是說,有時候觀眾看到完整的圖象,有時卻又只看到殘缺的圖象,這樣就造成了屏幕的閃爍。

            如何解決這一問題呢?我們設想有兩塊畫板,畫圖的人在旁邊畫,畫好以后把他手里的畫板與掛在屏幕上的畫板相交換。這樣以來,觀眾就不會看到殘缺的畫了。這一技術被應用到計算機圖形中,稱為雙緩沖技術。即:在存儲器(很有可能是顯存)中開辟兩塊區域,一塊作為發送到顯示器的數據,一塊作為繪畫的區域,在適當的時候交換它們。由于交換兩塊內存區域實際上只需要交換兩個指針,這一方法效率非常高,所以被廣泛的采用。

            注意:雖然絕大多數平臺都支持雙緩沖技術,但這一技術并不是OpenGL標準中的內容。OpenGL為了保證更好的可移植性,允許在實現時不使用雙緩沖技術。當然,我們常用的PC都是支持雙緩沖技術的。

            要啟動雙緩沖功能,最簡單的辦法就是使用GLUT工具包。我們以前在main函數里面寫:

            glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);

            其中GLUT_SINGLE表示單緩沖,如果改成GLUT_DOUBLE就是雙緩沖了。

            當然還有需要更改的地方——每次繪制完成時,我們需要交換兩個緩沖區,把繪制好的信息用于屏幕顯示(否則無論怎么繪制,還是什么都看不到)。如果使用GLUT工具包,也可以很輕松的完成這一工作,只要在繪制完成時簡單的調用glutSwapBuffers函數就可以了。

             

            2、實現連續動畫

            似乎沒有任何疑問,我們應該把繪制動畫的代碼寫成下面這個樣子:

            for(i=0; i<n; ++i)

            {

                DrawScene(i);

                glutSwapBuffers();

                Wait();

            }

            但事實上,這樣做不太符合窗口系統的程序設計思路。還記得我們的第一個OpenGL程序嗎?我們在main函數里寫:glutDisplayFunc(&myDisplay);

            意思是對系統說:如果你需要繪制窗口了,請調用myDisplay這個函數。為什么我們不直接調用myDisplay,而要采用這種看似舍近求遠的做法呢?原因在于——我們自己的程序無法掌握究竟什么時候該繪制窗口。因為一般的窗口系統——拿我們熟悉一點的來說——WindowsX窗口系統,都是支持同時顯示多個窗口的。假如你的程序窗口碰巧被別的窗口遮住了,后來用戶又把原來遮住的窗口移開,這時你的窗口需要重新繪制。很不幸的,你無法知道這一事件發生的具體時間。因此這一切只好委托操作系統來辦了。

            現在我們再看上面那個循環。既然DrawScene都可以交給操作系統來代辦了,那讓整個循環運行起來的工作是否也可以交給操作系統呢?答案是肯定的。我們先前的思路是:繪制,然后等待一段時間;再繪制,再等待一段時間。但如果去掉等待的時間,就變成了繪制,繪制,……,不停的繪制。——當然了,資源是公用的嘛,殺毒軟件總要工作吧?我的下載不能停下來吧?我的mp3播放還不能給耽擱了。總不能因為我們的動畫,讓其他的工作都停下來。因此,我們需要在CPU空閑的時間繪制。

            這里的CPU空閑的時間繪制和我們在第一課講的在需要繪制的時候繪制有些共通,都是XX時間做XXGLUT工具包也提供了一個比較類似的函數:glutIdleFunc,表示在CPU空閑的時間調用某一函數。其實GLUT還提供了一些別的函數,例如在鍵盤按下時做某事等。

             

            到現在,我們已經可以初步開始制作動畫了。好的,就拿上次那個太陽、地球和月亮的程序開刀,讓地球和月亮自己動起來。

             

            Code:


            #include <GL/glut.h>

            // 太陽、地球和月亮

            // 假設每個月都是30

            // 一年12個月,共是360

            static int day = 200; // day的變化:從0359

            void myDisplay(void)

            {

                /****************************************************

                 這里的內容照搬上一課的,只因為使用了雙緩沖,補上最后這句

                *****************************************************/

                glutSwapBuffers();

            }

             

            void myIdle(void)

            {

                /* 新的函數,在空閑時調用,作用是把日期往后移動一天并重新繪制,達到動畫效果 */

                ++day;

                if( day >= 360 )

                    day = 0;

                myDisplay();

            }

             

            int main(int argc, char *argv[])

            {

                glutInit(&argc, argv);

                glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); // 修改了參數為GLUT_DOUBLE

                glutInitWindowPosition(100, 100);

                glutInitWindowSize(400, 400);

                glutCreateWindow("太陽,地球和月亮");   // 改了窗口標題

                glutDisplayFunc(&myDisplay);

                glutIdleFunc(&myIdle);               // 新加入了這句

                glutMainLoop();

                return 0;

            }

            3、關于垂直同步

            代碼是寫好了,但相信大家還有疑問。某些朋友可能在運行時發現,雖然CPU幾乎都用上了,但運動速度很快,根本看不清楚,另一些朋友在運行時發現CPU使用率很低,根本就沒有把空閑時間完全利用起來。但對于上面那段代碼來說,這些現象都是合理的。這里就牽涉到關于垂直同步的問題。

            大家知道顯示器的刷新率是比較有限的,一般為60~120Hz,也就是一秒鐘刷新60~120次。但如果叫計算機繪制一個簡單的畫面,例如只有一個三角形,則一秒鐘可以繪制成千上萬次。因此,如果最大限度的利用計算機的處理能力,繪制很多幅畫面,但顯示器的刷新速度卻跟不上,這不僅造成性能的浪費,還可能帶來一些負面影響(例如,顯示器只刷新到一半時,需要繪制的內容卻變化了,由于顯示器是逐行刷新的,于是顯示器上半部分和下半部分實際上是來自兩幅畫面)。采用垂直同步技術可以解決這一問題。即,只有在顯示器刷新時,才把繪制好的圖象傳輸出去供顯示。這樣一來,計算機就不必去繪制大量的根本就用不到的圖象了。如果顯示器的刷新率為85Hz,則計算機一秒鐘只需要繪制85幅圖象就足夠,如果場景足夠簡單,就會造成比較多的CPU空閑。

            幾乎所有的顯卡都支持垂直同步這一功能。

            垂直同步也有它的問題。如果刷新頻率為60Hz,則在繪制比較簡單的場景時,繪制一幅圖畫需要的時間很段,幀速可以恒定在60FPS(即60/秒)。如果場景變得復雜,繪制一幅圖畫的時間超過了1/60秒,則幀速將急劇下降。

            如果繪制一幅圖畫的時間為1/50,則在第一個1/60秒時,顯示器需要刷新了,但由于新的圖畫沒有畫好,所以只能顯示原來的圖畫,等到下一個1/60秒時才顯示新的圖畫。于是顯示一幅圖畫實際上用了1/30秒,幀速為30FPS。(如果不采用垂直同步,則幀速應該是50FPS

            如果繪制一幅圖畫的時間更長,則下降的趨勢就是階梯狀的:60FPS30FPS20FPS……60/160/260/3……

            如果每一幅圖畫的復雜程度是不一致的,且繪制它們需要的時間都在1/60上下。則在1/60時間內畫完時,幀速為60FPS,在1/60時間未完成時,幀速為30FPS,這就造成了幀速的跳動。這是很麻煩的事情,需要避免它——要么想辦法簡化每一畫面的繪制時間,要么都延遲一小段時間,以作到統一。

            回過頭來看前面的問題。如果使用了大量的CPU而且速度很快無法看清,則打開垂直同步可以解決該問題。當然如果你認為垂直同步有這樣那樣的缺點,也可以關閉它。——至于如何打開和關閉,因操作系統而異了。具體步驟請自己搜索之。

            當然,也有其它辦法可以控制動畫的幀速,或者盡量讓動畫的速度盡量和幀速無關。不過這里面很多內容都是與操作系統比較緊密的,況且它們跟OpenGL關系也不太大。這里就不做介紹了。

            4、計算幀速

            不知道大家玩過3D Mark這個軟件沒有,它可以運行各種場景,測出幀速,并且為你的系統給出評分。這里我也介紹一個計算幀速的方法。

            根據定義,幀速就是一秒鐘內播放的畫面數目(FPS)。我們可以先測量繪制兩幅畫面之間時間t,然后求它的倒數即可。假如t=0.05s,則FPS的值就是1/0.05=20

            理論上是如此了,可是如何得到這個時間呢?通常C語言的time函數精確度一般只到一秒,肯定是不行了。clock函數也就到十毫秒左右,還是有點不夠。因為FPS60FPS100的時候,t的值都是十幾毫秒。

            你知道如何測量一張紙的厚度嗎?一個粗略的辦法就是:用很多張紙疊在一起測厚度,計算平均值就可以了。我們這里也可以這樣辦。測量繪制50幅畫面(包括垂直同步等因素的等待時間)需要的時間t',由t'=t*50很容易的得到FPS=1/t=50/t'

            下面這段代碼可以統計該函數自身的調用頻率,(原理就像上面說的那樣),程序并不復雜,并且這并不屬于OpenGL的內容,所以我不打算詳細講述它。

            Code:


            #include <time.h>

            double CalFrequency()

            {

                static int count;

                static double save;

                static clock_t last, current;

                double timegap;

             

                ++count;

                if( count <= 50 )

                    return save;

                count = 0;

                last = current;

                current = clock();

                timegap = (current-last)/(double)CLK_TCK;

                save = 50.0/timegap;

                return save;

            }


             

            最后,要把計算的幀速顯示出來,但我們并沒有學習如何使用OpenGL把文字顯示到屏幕上。——但不要忘了,在我們的圖形窗口背后,還有一個命令行窗口~使用printf函數就可以輕易的輸出文字了。

            #include <stdio.h>

            double FPS = CalFrequency();

            printf("FPS = %f\n", FPS);

            最后的一步,也被我們解決了——雖然做法不太雅觀,沒關系,以后我們還會改善它的。

            時間過得太久,每次給的程序都只是一小段,一些朋友難免會出問題。

            現在,我給出一個比較完整的程序,供大家參考。

            Code:


            #include <GL/glut.h>

            #include <stdio.h>

            #include <time.h>

             

            // 太陽、地球和月亮

            // 假設每個月都是12

            // 一年12個月,共是360

            static int day = 200; // day的變化:從0359

             

            double CalFrequency()

            {

                static int count;

                static double save;

                static clock_t last, current;

                double timegap;

             

                ++count;

                if( count <= 50 )

                    return save;

                count = 0;

                last = current;

                current = clock();

                timegap = (current-last)/(double)CLK_TCK;

                save = 50.0/timegap;

                return save;

            }

             

            void myDisplay(void)

            {

                double FPS = CalFrequency();

                printf("FPS = %f\n", FPS);

             

                glEnable(GL_DEPTH_TEST);

                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

             

                glMatrixMode(GL_PROJECTION);

                glLoadIdentity();

                gluPerspective(75, 1, 1, 400000000);

                glMatrixMode(GL_MODELVIEW);

                glLoadIdentity();

                gluLookAt(0, -200000000, 200000000, 0, 0, 0, 0, 0, 1);

             

                // 繪制紅色的太陽

                glColor3f(1.0f, 0.0f, 0.0f);

                glutSolidSphere(69600000, 20, 20);

                // 繪制藍色的地球

                glColor3f(0.0f, 0.0f, 1.0f);

                glRotatef(day/360.0*360.0, 0.0f, 0.0f, -1.0f);

                glTranslatef(150000000, 0.0f, 0.0f);

                glutSolidSphere(15945000, 20, 20);

                // 繪制黃色的月亮

                glColor3f(1.0f, 1.0f, 0.0f);

                glRotatef(day/30.0*360.0 - day/360.0*360.0, 0.0f, 0.0f, -1.0f);

                glTranslatef(38000000, 0.0f, 0.0f);

                glutSolidSphere(4345000, 20, 20);

             

                glFlush();

                glutSwapBuffers();

            }

             

            void myIdle(void)

            {

                ++day;

                if( day >= 360 )

                    day = 0;

                myDisplay();

            }

             

            int main(int argc, char *argv[])

            {

                glutInit(&argc, argv);

                glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);

                glutInitWindowPosition(100, 100);

                glutInitWindowSize(400, 400);

                glutCreateWindow("太陽,地球和月亮");

                glutDisplayFunc(&myDisplay);

                glutIdleFunc(&myIdle);

                glutMainLoop();

                return 0;

            }


             

            小結:

            OpenGL動畫和傳統意義上的動畫相似,都是把畫面一幅一幅的呈現在觀眾面前。一旦畫面變換的速度快了,觀眾就會認為畫面是連續的。

            雙緩沖技術是一種在計算機圖形中普遍采用的技術,絕大多數OpenGL實現都支持雙緩沖技術。

            通常都是利用CPU空閑的時候繪制動畫,但也可以有其它的選擇。

            介紹了垂直同步的相關知識。

            介紹了一種簡單的計算幀速(FPS)的方法。

            最后,我們列出了一份完整的天體動畫程序清單。

             

            =====================   第六課    =====================

            posted on 2010-09-27 14:52 王秋林 閱讀(1031) 評論(0)  編輯 收藏 引用
            <2011年4月>
            272829303112
            3456789
            10111213141516
            17181920212223
            24252627282930
            1234567

            常用鏈接

            留言簿(1)

            隨筆檔案(15)

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            色天使久久综合网天天| 久久综合伊人77777| 欧美大香线蕉线伊人久久| 中文无码久久精品| 99久久久精品免费观看国产| 亚洲综合精品香蕉久久网97| 久久久久无码中| 久久亚洲春色中文字幕久久久| 99久久综合狠狠综合久久| 亚洲欧美成人久久综合中文网| 久久精品中文无码资源站| 久久国产福利免费| 久久精品国产亚洲AV无码麻豆| 99久久精品久久久久久清纯| 久久天天躁狠狠躁夜夜不卡| 久久99免费视频| 日本强好片久久久久久AAA| 国产精品女同一区二区久久| 久久久久人妻一区二区三区| 久久国产香蕉一区精品| 久久精品国产清高在天天线| 一极黄色视频久久网站| 99久久er这里只有精品18| 少妇无套内谢久久久久| 久久精品国产亚洲精品| 久久精品国产99国产精品澳门| 亚洲а∨天堂久久精品| 精品人妻伦九区久久AAA片69| 久久精品国产亚洲AV高清热 | 久久毛片免费看一区二区三区| 久久久亚洲欧洲日产国码二区| 日韩欧美亚洲国产精品字幕久久久| 久久精品国产精品国产精品污| 久久天天躁狠狠躁夜夜躁2O2O| 四虎影视久久久免费| 久久久免费观成人影院| 国产精品99久久久久久猫咪 | 久久青青国产| 免费一级欧美大片久久网| 久久中文字幕无码专区| 久久精品免费大片国产大片|