青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

posts - 15,  comments - 0,  trackbacks - 0
 

OpenGL入門學(xué)習(xí)

OpenGL作為當(dāng)前主流的圖形API之一,它在一些場(chǎng)合具有比DirectX更優(yōu)越的特性。

1、與C語言緊密結(jié)合

OpenGL命令最初就是用C語言函數(shù)來進(jìn)行描述的,對(duì)于學(xué)習(xí)過C語言的人來講,OpenGL是容易理解和學(xué)習(xí)的。如果你曾經(jīng)接觸過TCgraphics.h,你會(huì)發(fā)現(xiàn),使用OpenGL作圖甚至比TC更加簡(jiǎn)單

2、強(qiáng)大的可移植性

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

3、高性能的圖形渲染

OpenGL是一個(gè)工業(yè)標(biāo)準(zhǔn),它的技術(shù)緊跟時(shí)代,現(xiàn)今各個(gè)顯卡廠家無一不對(duì)OpenGL提供強(qiáng)力支持,激烈的競(jìng)爭(zhēng)中使得OpenGL性能一直領(lǐng)先。

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

OpenGL官方網(wǎng)站(英文)

http://www.opengl.org

 

linux下編寫OpenGL程序的一些準(zhǔn)備工作

需要用到的軟件包有兩個(gè),glut tmake,分別可以從以下兩個(gè)網(wǎng)址下載:

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

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

下載后的文件假設(shè)都放在/usr/src

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

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如果運(yùn)行成功的話,說明glut已經(jīng)可以用了。

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

此外應(yīng)該注意的是兩個(gè)Makefile文件的修改

  

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

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

 

  OPENGL = -lGL

  GLU = -lGLU

因?yàn)樗付ǖ哪夸浿袥]有libMesaGL.solibMesaGLU.so

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

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

得到tmake-1.8目錄,之后設(shè)置兩個(gè)環(huán)境變量:PATHTMAKEPATH

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

export PATH

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

export TMAKEPATH

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

之后生成一個(gè)pro文件:progen  -o hello.pro

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

編輯生成的Makefile文件,在加載動(dòng)態(tài)連接庫的行里面加入 -lglut -lXi -lXmu

保存,make

./hello 可以看到運(yùn)行結(jié)果就可以了。

 

下面將對(duì)Windows下的OpenGL編程進(jìn)行簡(jiǎn)單介紹。

學(xué)習(xí)OpenGL前的準(zhǔn)備工作

第一步,選擇一個(gè)編譯環(huán)境

現(xiàn)在Windows系統(tǒng)的主流編譯環(huán)境有Visual StudioBroland C++ BuilderDev-C++等,它們都是支持OpenGL的。但這里我們選擇Visual Studio 2005作為學(xué)習(xí)OpenGL的環(huán)境。

第二步,安裝GLUT工具包

GLUT不是OpenGL所必須的,但它會(huì)給我們的學(xué)習(xí)帶來一定的方便,推薦安裝。

Windows VS2005環(huán)境下環(huán)境下安裝GLUT的步驟:

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

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

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

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

 

Windows VS2008環(huán)境下環(huán)境下安裝GLUT的步驟:

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

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

 

第三步,建立一個(gè)OpenGL工程

這里以VisualStudio2005為例。

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

在談出的對(duì)話框左邊點(diǎn)Application Settings,找到Empty project并勾上,選擇Finish

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

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

 

第一個(gè)OpenGL程序

一個(gè)簡(jiǎn)單的OpenGL程序如下:(注意,如果需要編譯并運(yùn)行,需要正確安裝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("第一個(gè)OpenGL程序");

    glutDisplayFunc(&myDisplay);

    glutMainLoop();

    return 0;

}

 

該程序的作用是在一個(gè)黑色的窗口中央畫一個(gè)白色的矩形。下面對(duì)各行語句進(jìn)行說明。

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

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

 

然后看main函數(shù)。

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

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

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

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

3glutInitWindowPosition,這個(gè)簡(jiǎn)單,設(shè)置窗口在屏幕中的位置。

4glutInitWindowSize,這個(gè)也簡(jiǎn)單,設(shè)置窗口的大小。

5glutCreateWindow,根據(jù)前面設(shè)置的信息創(chuàng)建窗口。參數(shù)將被作為窗口的標(biāo)題。注意:窗口被創(chuàng)建后,并不立即顯示到屏幕上。需要調(diào)用glutMainLoop才能看到窗口。

6glutDisplayFunc,設(shè)置一個(gè)函數(shù),當(dāng)需要進(jìn)行畫圖時(shí),這個(gè)函數(shù)就會(huì)被調(diào)用。(這個(gè)說法不夠準(zhǔn)確,但準(zhǔn)確的說法可能初學(xué)者不太好理解,暫時(shí)這樣說吧)。

7glutMainLoop,進(jìn)行一個(gè)消息循環(huán)。(這個(gè)可能初學(xué)者也不太明白,現(xiàn)在只需要知道這個(gè)函數(shù)可以顯示窗口,并且等待窗口關(guān)閉后才會(huì)返回,這就足夠了。)

 

glutDisplayFunc函數(shù)中,我們?cè)O(shè)置了當(dāng)需要畫圖時(shí),請(qǐng)調(diào)用myDisplay函數(shù)。于是myDisplay函數(shù)就用來畫圖。觀察myDisplay中的三個(gè)函數(shù)調(diào)用,發(fā)現(xiàn)它們都以gl開頭。這種以gl開頭的函數(shù)都是OpenGL的標(biāo)準(zhǔn)函數(shù),下面對(duì)用到的函數(shù)進(jìn)行介紹。

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

2glRectf,畫一個(gè)矩形。四個(gè)參數(shù)分別表示了位于對(duì)角線上的兩個(gè)點(diǎn)的橫、縱坐標(biāo)。

3glFlush,保證前面的OpenGL命令立即執(zhí)行(而不是讓它們?cè)诰彌_區(qū)中等待)。其作用跟fflush(stdout)類似。


 

 

OpenGL入門學(xué)習(xí)(二)(轉(zhuǎn))

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

 

一、點(diǎn)、直線和多邊形

我們知道數(shù)學(xué)(具體的說,是幾何學(xué))中有點(diǎn)、直線和多邊形的概念,但這些概念在計(jì)算機(jī)中會(huì)有所不同。

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

同樣的,數(shù)學(xué)上的直線沒有寬度,但OpenGL的直線則是有寬度的。同時(shí),OpenGL的直線必須是有限長(zhǎng)度,而不是像數(shù)學(xué)概念那樣是無限的。可以認(rèn)為,OpenGL直線概念與數(shù)學(xué)上的線段接近,它可以由兩個(gè)端點(diǎn)來確定。

多邊形是由多條線段首尾相連而形成的閉合區(qū)域。OpenGL規(guī)定,一個(gè)多邊形必須是一個(gè)凸多邊形(其定義為:多邊形內(nèi)任意兩點(diǎn)所確定的線段都在多邊形內(nèi),由此也可以推導(dǎo)出,凸多邊形不能是空心的)。多邊形可以由其邊的端點(diǎn)(這里可稱為頂點(diǎn))來確定。(注意:如果使用的多邊形不是凸多邊形,則最后輸出的效果是未定義的——OpenGL為了效率,放寬了檢查,這可能導(dǎo)致顯示錯(cuò)誤。要避免這個(gè)錯(cuò)誤,盡量使用三角形,因?yàn)槿切味际峭苟噙呅危?/span>

 

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

 

二、在OpenGL中指定頂點(diǎn)

由以上的討論可以知道,點(diǎn)是一切的基礎(chǔ)。

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

glVertex2d

glVertex2f

glVertex3f

glVertex3fv

等等。

數(shù)字表示參數(shù)的個(gè)數(shù),2表示有兩個(gè)參數(shù),3表示三個(gè),4表示四個(gè)(我知道有點(diǎn)羅嗦~)。

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

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

                  f表示32位浮點(diǎn)數(shù)(OpenGL中將這個(gè)類型定義為GLfloatGLclampf),

                  d表示64位浮點(diǎn)數(shù)(OpenGL中將這個(gè)類型定義為GLdoubleGLclampd)。

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

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

(一)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*來表示這一系列函數(shù)。

注意:OpenGL的很多函數(shù)都是采用這樣的形式,一個(gè)相同的前綴再加上參數(shù)說明標(biāo)記,這一點(diǎn)會(huì)隨著學(xué)習(xí)的深入而有更多的體會(huì)。

 

 

三、開始繪制

假設(shè)現(xiàn)在我已經(jīng)指定了若干頂點(diǎn),那么OpenGL是如何知道我想拿這些頂點(diǎn)來干什么呢?是一個(gè)一個(gè)的畫出來,還是連成線?或者構(gòu)成一個(gè)多邊形?或者做其它什么事情?

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

例如我寫:

glBegin(GL_POINTS);

    glVertex2f(0.0f, 0.0f);

    glVertex2f(0.5f, 0.0f);

glEnd();

則這兩個(gè)點(diǎn)將分別被畫出來。如果將GL_POINTS替換成GL_LINES,則兩個(gè)點(diǎn)將被認(rèn)為是直線的兩個(gè)端點(diǎn),OpenGL將會(huì)畫出一條直線。

我們還可以指定更多的頂點(diǎn),然后畫出更復(fù)雜的圖形。

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

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

 

程序代碼:

void myDisplay(void)

{

    glClear(GL_COLOR_BUFFER_BIT);

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

        /* 在這里使用glVertex*系列函數(shù) */

        /* 指定你所希望的頂點(diǎn)位置 */

    glEnd();

    glFlush();

}

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

 

兩個(gè)例子

例一、畫一個(gè)圓

/*

正四邊形,正五邊形,正六邊形,……,直到正n邊形,當(dāng)n越大時(shí),這個(gè)圖形就越接近圓

當(dāng)n大到一定程度后,人眼將無法把它跟真正的圓相區(qū)別

這時(shí)我們已經(jīng)成功的畫出了一個(gè)

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

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

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();

}

 

 

例二、畫一個(gè)五角星

/*

設(shè)五角星的五個(gè)頂點(diǎn)分布位置關(guān)系如下:

     A

 E       B

 

   D   C

首先,根據(jù)余弦定理列方程,計(jì)算五角星的中心到頂點(diǎn)的距離a

(假設(shè)五角星對(duì)應(yīng)正五邊形的邊長(zhǎng)為.0

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

然后,根據(jù)正弦和余弦的定義,計(jì)算Bx坐標(biāo)bxy坐標(biāo)by,以及Cy坐標(biāo)

(假設(shè)五角星的中心在坐標(biāo)原點(diǎn))

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

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

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

五個(gè)點(diǎn)的坐標(biāo)就可以通過以上四個(gè)量和一些常數(shù)簡(jiǎn)單的表示出來

*/

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

}

 

 

例三、畫出正弦函數(shù)的圖形

/*

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

所以我們?cè)O(shè)置一個(gè)因子factor,把所有的坐標(biāo)值等比例縮小,

這樣就可以畫出更多個(gè)正弦周期

試修改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);        // 以上兩個(gè)點(diǎn)可以畫x

        glVertex2f(0.0f, -1.0f);

        glVertex2f(0.0f, 1.0f);        // 以上兩個(gè)點(diǎn)可以畫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();

}

 

 

小結(jié)

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

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

 

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

OpenGL入門學(xué)習(xí)(三)(轉(zhuǎn))

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

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

 

下面就點(diǎn)、直線、多邊形分別討論。

 

 

1、關(guān)于點(diǎn)

點(diǎn)的大小默認(rèn)為1個(gè)像素,但也可以改變之。改變的命令為glPointSize,其函數(shù)原型如下:

void glPointSize(GLfloat size);

size必須大于0.0f,默認(rèn)值為1.0f,單位為像素

注意:對(duì)于具體的OpenGL實(shí)現(xiàn),點(diǎn)的大小都有個(gè)限度的,如果設(shè)置的size超過最大值,則設(shè)置可能會(huì)有問題。

例子:

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、關(guān)于直線

1)直線可以指定寬度:

void glLineWidth(GLfloat width);

其用法跟glPointSize類似。

2)畫虛線。

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

然后,使用glLineStipple來設(shè)置虛線的樣式。

void glLineStipple(GLint factor, GLushort pattern);

pattern是由10組成的長(zhǎng)度為16的序列,從最低位開始看,如果為1,則直線上接下來應(yīng)該畫的factor個(gè)點(diǎn)將被畫為實(shí)的;如果為0,則直線上接下來應(yīng)該畫的factor個(gè)點(diǎn)將被畫為虛的。

以下是一些例子:

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

示例代碼:

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、關(guān)于多邊形

多邊形的內(nèi)容較多,我們將講述以下四個(gè)方面。

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

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

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

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

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

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

 

 

2)反轉(zhuǎn)

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

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

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

glFrontFace(GL_CCW);  // 設(shè)置CCW方向?yàn)?/span>正面CCWCounterClockWise,逆時(shí)針

glFrontFace(GL_CW);   // 設(shè)置CW方向?yàn)?/span>正面CWClockWise,順時(shí)針

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

void myDisplay(void)

{

    glClear(GL_COLOR_BUFFER_BIT);

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

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

    glFrontFace(GL_CCW);              // 設(shè)置逆時(shí)針方向?yàn)檎?/span>

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

        glVertex2f(-0.5f, -0.5f);

        glVertex2f(0.0f, -0.5f);

        glVertex2f(0.0f, 0.0f);

        glVertex2f(-0.5f, 0.0f);

    glEnd();

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

        glVertex2f(0.0f, 0.0f);

        glVertex2f(0.0f, 0.5f);

        glVertex2f(0.5f, 0.5f);

        glVertex2f(0.5f, 0.0f);

    glEnd();

    glFlush();

}

3)剔除多邊形表面

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

首先,使用glEnable(GL_CULL_FACE);來啟動(dòng)剔除功能(使用glDisable(GL_CULL_FACE)可以關(guān)閉之)

然后,使用glCullFace來進(jìn)行剔除。

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

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

 

4)鏤空多邊形

直線可以被畫成虛線,而多邊形則可以進(jìn)行鏤空。

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

然后,使用glPolygonStipple來設(shè)置鏤空的樣式。

void glPolygonStipple(const GLubyte *mask);

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

但是,如果我們直接定義這個(gè)mask數(shù)組,像這樣:

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   // 這是最上面的一行

};

這樣一堆數(shù)據(jù)非常缺乏直觀性,我們需要很費(fèi)勁的去分析,才會(huì)發(fā)現(xiàn)它表示的竟然是一只蒼蠅。

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

 

 

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

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

然后,就可以使用以下代碼來獲得這個(gè)Mask數(shù)組了。

static GLubyte Mask[128];

FILE *fp;

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

if( !fp )

    exit(0);

// 移動(dòng)文件指針到這個(gè)位置,使得再讀sizeof(Mask)個(gè)字節(jié)就會(huì)遇到文件結(jié)束

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

// 如果直接寫-sizeof(Mask)的話,因?yàn)?/span>sizeof取得的是一個(gè)無符號(hào)數(shù),取負(fù)號(hào)會(huì)有問題

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

    exit(0);

// 讀取sizeof(Mask)個(gè)字節(jié)到Mask

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

    exit(0);

fclose(fp);

 

 

好的,現(xiàn)在請(qǐng)自己編輯一個(gè)圖片作為mask,并用上述方法取得Mask數(shù)組,運(yùn)行后觀察效果。

說明:繪制虛線時(shí)可以設(shè)置factor因子,但多邊形的鏤空無法設(shè)置factor因子。請(qǐng)用鼠標(biāo)改變窗口的大小,觀察鏤空效果的變化情況。

#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);  // 在左下方繪制一個(gè)有鏤空效果的正方形

    glDisable(GL_POLYGON_STIPPLE);

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

    glFlush();

}

 

 

小結(jié)

本課學(xué)習(xí)了繪制幾何圖形的一些細(xì)節(jié)。

點(diǎn)可以設(shè)置大小。

直線可以設(shè)置寬度;可以將直線畫成虛線。

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

了解這些細(xì)節(jié)會(huì)使我們?cè)谝恍﹫D象繪制中更加得心應(yīng)手。

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

 

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

OpenGL入門學(xué)習(xí)(四)(轉(zhuǎn))

本次學(xué)習(xí)的是顏色的選擇。終于要走出黑白的世界了~~

 

 

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

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

 

1. RGBA顏色

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

RGBA模式下選擇顏色是十分簡(jiǎn)單的事情,只需要一個(gè)函數(shù)就可以搞定。

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

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

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

(還記得嗎?3f表示有三個(gè)浮點(diǎn)參數(shù)~請(qǐng)看第二課中關(guān)于glVertex*函數(shù)的敘述。)

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

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

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

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

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

 

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

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系列函數(shù),在參數(shù)類型不同時(shí),表示最大顏色的值也不同。

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

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

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

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

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

這些規(guī)則看似麻煩,但熟悉后實(shí)際使用中不會(huì)有什么障礙。

 

2、索引顏色

在索引顏色模式中,OpenGL需要一個(gè)顏色表。這個(gè)表就相當(dāng)于畫家的調(diào)色板:雖然可以調(diào)出很多種顏色,但同時(shí)存在于調(diào)色板上的顏色種數(shù)將不會(huì)超過調(diào)色板的格數(shù)。試將顏色表的每一項(xiàng)想象成調(diào)色板上的一個(gè)格子:它保存了一種顏色。

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

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

 

2.1、選擇顏色

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

void glIndexi(GLint c);

是的,這的確很簡(jiǎn)單。

 

2.2、設(shè)置顏色表

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

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

#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函數(shù)就可以了。首先,使用auxSetOneColor設(shè)置顏色表中的一格。循環(huán)八次就可以設(shè)置八格。

glShadeModel等下再講,這里不提。

然后在循環(huán)中用glVertex設(shè)置頂點(diǎn),同時(shí)用glIndexi改變頂點(diǎn)代表的顏色。

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

 

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

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

 

 

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

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

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

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

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

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

void myDisplay(void)

{

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

    glClear(GL_COLOR_BUFFER_BIT);

    glFlush();

}

呵,這個(gè)還真簡(jiǎn)單~

 

 

4、指定著色模型

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

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

}

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

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

glShadeModel的使用方法:

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

glShadeModel(GL_FLAT);     // 單色方式

 

小結(jié):

本課學(xué)習(xí)了如何設(shè)置顏色。其中RGB顏色方式是目前PC機(jī)上的常用方式。

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

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

 

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

OpenGL入門學(xué)習(xí)(五)(轉(zhuǎn))

今天要講的是三維變換的內(nèi)容,課程比較枯燥。主要是因?yàn)楹芏嗪瘮?shù)在單獨(dú)使用時(shí)都不好描述其效果,我只好在最后舉一個(gè)比較綜合的例子。希望大家能一口氣看到底了。只看一次可能不夠,如果感覺到迷糊,不妨多看兩遍。有疑問可以在下面跟帖提出。

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

 

 

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

 

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

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

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

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

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

這些,都可以在OpenGL中實(shí)現(xiàn)。

 

OpenGL變換實(shí)際上是通過矩陣乘法來實(shí)現(xiàn)。無論是移動(dòng)、旋轉(zhuǎn)還是縮放大小,都是通過在當(dāng)前矩陣的基礎(chǔ)上乘以一個(gè)新的矩陣來達(dá)到目的。關(guān)于矩陣的知識(shí),這里不詳細(xì)介紹,有興趣的朋友可以看看線性代數(shù)(大學(xué)生的話多半應(yīng)該學(xué)過的)。

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

 

 

1、模型變換和視圖變換

相對(duì)移動(dòng)的觀點(diǎn)來看,改變觀察點(diǎn)的位置與方向和改變物體本身的位置與方向具有等效性。在OpenGL中,實(shí)現(xiàn)這兩種功能甚至使用的是同樣的函數(shù)。

由于模型和視圖的變換都通過矩陣運(yùn)算來實(shí)現(xiàn),在進(jìn)行變換前,應(yīng)先設(shè)置當(dāng)前操作的矩陣為模型視圖矩陣。設(shè)置的方法是以GL_MODELVIEW為參數(shù)調(diào)用glMatrixMode函數(shù),像這樣:

glMatrixMode(GL_MODELVIEW);

通常,我們需要在進(jìn)行變換前把當(dāng)前矩陣設(shè)置為單位矩陣。這也只需要一行代碼:

glLoadIdentity();

 

然后,就可以進(jìn)行模型變換和視圖變換了。進(jìn)行模型和視圖變換,主要涉及到三個(gè)函數(shù):

glTranslate*,把當(dāng)前矩陣和一個(gè)表示移動(dòng)物體的矩陣相乘。三個(gè)參數(shù)分別表示了在三個(gè)坐標(biāo)上的位移值。

glRotate*,把當(dāng)前矩陣和一個(gè)表示旋轉(zhuǎn)物體的矩陣相乘。物體將繞著(0,0,0)(x,y,z)的直線以逆時(shí)針旋轉(zhuǎn),參數(shù)angle表示旋轉(zhuǎn)的角度。

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

 

注意我都是說XX相乘,而不是直接說這個(gè)函數(shù)就是旋轉(zhuǎn)或者這個(gè)函數(shù)就是移動(dòng),這是有原因的,馬上就會(huì)講到。

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

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

讓我們想象,坐標(biāo)并不是固定不變的。旋轉(zhuǎn)的時(shí)候,坐標(biāo)系統(tǒng)隨著物體旋轉(zhuǎn)。移動(dòng)的時(shí)候,坐標(biāo)系統(tǒng)隨著物體移動(dòng)。如此一來,就不需要考慮代碼的順序反轉(zhuǎn)的問題了。

 

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

 

 

2、投影變換

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

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

glMatrixMode(GL_PROJECTION);

通常,我們需要在進(jìn)行變換前把當(dāng)前矩陣設(shè)置為單位矩陣。

glLoadIdentity();

 

透視投影所產(chǎn)生的結(jié)果類似于照片,有近大遠(yuǎn)小的效果,比如在火車頭內(nèi)向前照一個(gè)鐵軌的照片,兩條鐵軌似乎在遠(yuǎn)處相交了。

使用glFrustum函數(shù)可以將當(dāng)前的可視空間設(shè)置為透視投影空間。其參數(shù)的意義如下圖:

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

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

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

 

正投影相當(dāng)于在無限遠(yuǎn)處觀察得到的結(jié)果,它只是一種理想狀態(tài)。但對(duì)于計(jì)算機(jī)來說,使用正投影有可能獲得更好的運(yùn)行速度。

使用glOrtho函數(shù)可以將當(dāng)前的可視空間設(shè)置為正投影空間。其參數(shù)的意義如下圖:

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

 

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

 

 

3、視口變換

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

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

 

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

 

4、操作矩陣堆棧

介于是入門教程,先簡(jiǎn)單介紹一下堆棧。你可以把堆棧想象成一疊盤子。開始的時(shí)候一個(gè)盤子也沒有,你可以一個(gè)一個(gè)往上放,也可以一個(gè)一個(gè)取下來。每次取下的,都是最后一次被放上去的盤子。通常,在計(jì)算機(jī)實(shí)現(xiàn)堆棧時(shí),堆棧的容量是有限的,如果盤子過多,就會(huì)出錯(cuò)。當(dāng)然,如果沒有盤子了,再要求取一個(gè)盤子,也會(huì)出錯(cuò)。

我們?cè)谶M(jìn)行矩陣操作時(shí),有可能需要先保存某個(gè)矩陣,過一段時(shí)間再恢復(fù)它。當(dāng)我們需要保存時(shí),調(diào)用glPushMatrix函數(shù),它相當(dāng)于把矩陣(相當(dāng)于盤子)放到堆棧上。當(dāng)需要恢復(fù)最近一次的保存時(shí),調(diào)用glPopMatrix函數(shù),它相當(dāng)于把矩陣從堆棧上取下。OpenGL規(guī)定堆棧的容量至少可以容納32個(gè)矩陣,某些OpenGL實(shí)現(xiàn)中,堆棧的容量實(shí)際上超過了32個(gè)。因此不必過于擔(dān)心矩陣的容量問題。

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

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

 

5、綜合舉例

好了,視圖變換的入門知識(shí)差不多就講完了。但我們不能就這樣結(jié)束。因?yàn)楸敬握n程的內(nèi)容實(shí)在過于枯燥,如果分別舉例,可能效果不佳。我只好綜合的講一個(gè)例子,算是給大家一個(gè)參考。至于實(shí)際的掌握,還要靠大家自己花功夫。閑話少說,現(xiàn)在進(jìn)入正題。

 

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

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

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

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

為了讓地球和月亮在離我們很近時(shí),我們?nèi)匀徊恍枰儞Q觀察點(diǎn)和觀察方向就可以觀察它們,我們把觀察點(diǎn)放在這個(gè)位置:(0, -200000000, 0)  ——因?yàn)榈厍蜍壍腊霃綖?/span>150000000,咱們就湊個(gè)整,取-200000000就可以了。觀察目標(biāo)設(shè)置為原點(diǎn)(即太陽中心),選擇Z軸正方向作為  “方。當(dāng)然我們還可以把觀察點(diǎn)往方移動(dòng)一些,得到(0, -200000000, 200000000),這樣可以得到45度角的俯視效果。

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

 

 

5、綜合舉例

好了,視圖變換的入門知識(shí)差不多就講完了。但我們不能就這樣結(jié)束。因?yàn)楸敬握n程的內(nèi)容實(shí)在過于枯燥,如果分別舉例,可能效果不佳。我只好綜合的講一個(gè)例子,算是給大家一個(gè)參考。至于實(shí)際的掌握,還要靠大家自己花功夫。閑話少說,現(xiàn)在進(jìn)入正題。

 

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

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

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

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

為了讓地球和月亮在離我們很近時(shí),我們?nèi)匀徊恍枰儞Q觀察點(diǎn)和觀察方向就可以觀察它們,我們把觀察點(diǎn)放在這個(gè)位置:(0, -200000000, 0) ——因?yàn)榈厍蜍壍腊霃綖?/span>150000000,咱們就湊個(gè)整,取-200000000就可以了。觀察目標(biāo)設(shè)置為原點(diǎn)(即太陽中心),選擇Z軸正方向作為 方。當(dāng)然我們還可以把觀察點(diǎn)往方移動(dòng)一些,得到(0, -200000000, 200000000),這樣可以得到45度角的俯視效果。

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

 

 

現(xiàn)在我們來看看如何繪制這三個(gè)天體。

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

太陽在坐標(biāo)原點(diǎn),所以不需要經(jīng)過任何變換,直接繪制就可以了。

地球則要復(fù)雜一點(diǎn),需要變換坐標(biāo)。由于今年已經(jīng)經(jīng)過的天數(shù)已知為day,則地球轉(zhuǎn)過的角度為day/一年的天數(shù)*360度。前面已經(jīng)假定每年都是360天,因此地球轉(zhuǎn)過的角度恰好為day。所以可以通過下面的代碼來解決:

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

/* 注意地球公轉(zhuǎn)是自西向東的,因此是饒著Z軸負(fù)方向進(jìn)行逆時(shí)針旋轉(zhuǎn) */

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

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

月亮是最復(fù)雜的。因?yàn)樗粌H要繞地球轉(zhuǎn),還要隨著地球繞太陽轉(zhuǎn)。但如果我們選擇地球作為參考,則月亮進(jìn)行的運(yùn)動(dòng)就是一個(gè)簡(jiǎn)單的圓周運(yùn)動(dòng)了。如果我們先繪制地球,再繪制月亮,則只需要進(jìn)行與地球類似的變換:

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

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

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

但這個(gè)月亮旋轉(zhuǎn)的角度,并不能簡(jiǎn)單的理解為day/一個(gè)月的天數(shù)30*360度。因?yàn)槲覀冊(cè)诶L制地球時(shí),這個(gè)坐標(biāo)已經(jīng)是旋轉(zhuǎn)過的。現(xiàn)在的旋轉(zhuǎn)是在以前的基礎(chǔ)上進(jìn)行旋轉(zhuǎn),因此還需要處理這個(gè)差值。我們可以寫成:day/30*360 - day,即減去原來已經(jīng)轉(zhuǎn)過的角度。這只是一種簡(jiǎn)單的處理,當(dāng)然也可以在繪制地球前用glPushMatrix保存矩陣,繪制地球后用glPopMatrix恢復(fù)矩陣。再設(shè)計(jì)一個(gè)跟地球位置無關(guān)的月亮位置公式,來繪制月亮。通常后一種方法比前一種要好,因?yàn)楦↑c(diǎn)的運(yùn)算是不精確的,即是說我們計(jì)算地球本身的位置就是不精確的。拿這個(gè)不精確的數(shù)去計(jì)算月亮的位置,會(huì)導(dǎo)致 不精確的成分累積,過多的不精確會(huì)造成錯(cuò)誤。我們這個(gè)小程序沒有去考慮這個(gè),但并不是說這個(gè)問題不重要。

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

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

且后者的運(yùn)行速度可能比前者快。

 

 

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

 

 

Code:

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

// 太陽、地球和月亮

// 假設(shè)每個(gè)月都是30

// 一年12個(gè)月,共是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);

    // 繪制藍(lán)色的地球

    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的值,看看畫面有何變化。

 

 

小結(jié):本課開始,我們正式進(jìn)入了三維的OpenGL世界。

OpenGL通過矩陣變換來把三維物體轉(zhuǎn)變?yōu)槎S圖象,進(jìn)而在屏幕上顯示出來。為了指定當(dāng)前操作的是何種矩陣,我們使用了函數(shù)glMatrixMode

我們可以移動(dòng)、旋轉(zhuǎn)觀察點(diǎn)或者移動(dòng)、旋轉(zhuǎn)物體,使用的函數(shù)是glTranslate*glRotate*

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

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

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

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

 

好了,艱苦的一課終于完畢。我知道,本課的內(nèi)容十分枯燥,就連最后的例子也是。但我也沒有更好的辦法了,希望大家能堅(jiān)持過去。不必?fù)?dān)心,熟悉本課內(nèi)容后,以后的一段時(shí)間內(nèi),都會(huì)是比較輕松愉快的了。

 

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

OpenGL入門學(xué)習(xí)(六)(轉(zhuǎn))

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

 

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

 

假設(shè)某動(dòng)畫一共有n幅畫面,則它的工作步驟就是:

顯示第1幅畫面,然后等待一小段時(shí)間,直到下一個(gè)1/24

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

……

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

結(jié)束

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

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

{

    DrawScene(i);

    Wait();

}

 

1、雙緩沖技術(shù)

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

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

如何解決這一問題呢?我們?cè)O(shè)想有兩塊畫板,畫圖的人在旁邊畫,畫好以后把他手里的畫板與掛在屏幕上的畫板相交換。這樣以來,觀眾就不會(huì)看到殘缺的畫了。這一技術(shù)被應(yīng)用到計(jì)算機(jī)圖形中,稱為雙緩沖技術(shù)。即:在存儲(chǔ)器(很有可能是顯存)中開辟兩塊區(qū)域,一塊作為發(fā)送到顯示器的數(shù)據(jù),一塊作為繪畫的區(qū)域,在適當(dāng)?shù)臅r(shí)候交換它們。由于交換兩塊內(nèi)存區(qū)域?qū)嶋H上只需要交換兩個(gè)指針,這一方法效率非常高,所以被廣泛的采用。

注意:雖然絕大多數(shù)平臺(tái)都支持雙緩沖技術(shù),但這一技術(shù)并不是OpenGL標(biāo)準(zhǔn)中的內(nèi)容。OpenGL為了保證更好的可移植性,允許在實(shí)現(xiàn)時(shí)不使用雙緩沖技術(shù)。當(dāng)然,我們常用的PC都是支持雙緩沖技術(shù)的。

要啟動(dòng)雙緩沖功能,最簡(jiǎn)單的辦法就是使用GLUT工具包。我們以前在main函數(shù)里面寫:

glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);

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

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

 

2、實(shí)現(xiàn)連續(xù)動(dòng)畫

似乎沒有任何疑問,我們應(yīng)該把繪制動(dòng)畫的代碼寫成下面這個(gè)樣子:

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

{

    DrawScene(i);

    glutSwapBuffers();

    Wait();

}

但事實(shí)上,這樣做不太符合窗口系統(tǒng)的程序設(shè)計(jì)思路。還記得我們的第一個(gè)OpenGL程序嗎?我們?cè)?/span>main函數(shù)里寫:glutDisplayFunc(&myDisplay);

意思是對(duì)系統(tǒng)說:如果你需要繪制窗口了,請(qǐng)調(diào)用myDisplay這個(gè)函數(shù)。為什么我們不直接調(diào)用myDisplay,而要采用這種看似舍近求遠(yuǎn)的做法呢?原因在于——我們自己的程序無法掌握究竟什么時(shí)候該繪制窗口。因?yàn)橐话愕拇翱谙到y(tǒng)——拿我們熟悉一點(diǎn)的來說——WindowsX窗口系統(tǒng),都是支持同時(shí)顯示多個(gè)窗口的。假如你的程序窗口碰巧被別的窗口遮住了,后來用戶又把原來遮住的窗口移開,這時(shí)你的窗口需要重新繪制。很不幸的,你無法知道這一事件發(fā)生的具體時(shí)間。因此這一切只好委托操作系統(tǒng)來辦了。

現(xiàn)在我們?cè)倏瓷厦婺莻€(gè)循環(huán)。既然DrawScene都可以交給操作系統(tǒng)來代辦了,那讓整個(gè)循環(huán)運(yùn)行起來的工作是否也可以交給操作系統(tǒng)呢?答案是肯定的。我們先前的思路是:繪制,然后等待一段時(shí)間;再繪制,再等待一段時(shí)間。但如果去掉等待的時(shí)間,就變成了繪制,繪制,……,不停的繪制。——當(dāng)然了,資源是公用的嘛,殺毒軟件總要工作吧?我的下載不能停下來吧?我的mp3播放還不能給耽擱了。總不能因?yàn)槲覀兊膭?dòng)畫,讓其他的工作都停下來。因此,我們需要在CPU空閑的時(shí)間繪制。

這里的CPU空閑的時(shí)間繪制和我們?cè)诘谝徽n講的在需要繪制的時(shí)候繪制有些共通,都是XX時(shí)間做XXGLUT工具包也提供了一個(gè)比較類似的函數(shù):glutIdleFunc,表示在CPU空閑的時(shí)間調(diào)用某一函數(shù)。其實(shí)GLUT還提供了一些別的函數(shù),例如在鍵盤按下時(shí)做某事等。

 

到現(xiàn)在,我們已經(jīng)可以初步開始制作動(dòng)畫了。好的,就拿上次那個(gè)太陽、地球和月亮的程序開刀,讓地球和月亮自己動(dòng)起來。

 

Code:


#include <GL/glut.h>

// 太陽、地球和月亮

// 假設(shè)每個(gè)月都是30

// 一年12個(gè)月,共是360

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

void myDisplay(void)

{

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

     這里的內(nèi)容照搬上一課的,只因?yàn)槭褂昧穗p緩沖,補(bǔ)上最后這句

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

    glutSwapBuffers();

}

 

void myIdle(void)

{

    /* 新的函數(shù),在空閑時(shí)調(diào)用,作用是把日期往后移動(dòng)一天并重新繪制,達(dá)到動(dòng)畫效果 */

    ++day;

    if( day >= 360 )

        day = 0;

    myDisplay();

}

 

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

{

    glutInit(&argc, argv);

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

    glutInitWindowPosition(100, 100);

    glutInitWindowSize(400, 400);

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

    glutDisplayFunc(&myDisplay);

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

    glutMainLoop();

    return 0;

}

3、關(guān)于垂直同步

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

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

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

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

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

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

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

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

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

4、計(jì)算幀速

不知道大家玩過3D Mark這個(gè)軟件沒有,它可以運(yùn)行各種場(chǎng)景,測(cè)出幀速,并且為你的系統(tǒng)給出評(píng)分。這里我也介紹一個(gè)計(jì)算幀速的方法。

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

理論上是如此了,可是如何得到這個(gè)時(shí)間呢?通常C語言的time函數(shù)精確度一般只到一秒,肯定是不行了。clock函數(shù)也就到十毫秒左右,還是有點(diǎn)不夠。因?yàn)?/span>FPS60FPS100的時(shí)候,t的值都是十幾毫秒。

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

下面這段代碼可以統(tǒng)計(jì)該函數(shù)自身的調(diào)用頻率,(原理就像上面說的那樣),程序并不復(fù)雜,并且這并不屬于OpenGL的內(nèi)容,所以我不打算詳細(xì)講述它。

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;

}


 

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

#include <stdio.h>

double FPS = CalFrequency();

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

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

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

現(xiàn)在,我給出一個(gè)比較完整的程序,供大家參考。

Code:


#include <GL/glut.h>

#include <stdio.h>

#include <time.h>

 

// 太陽、地球和月亮

// 假設(shè)每個(gè)月都是12

// 一年12個(gè)月,共是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);

    // 繪制藍(lán)色的地球

    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;

}


 

小結(jié):

OpenGL動(dòng)畫和傳統(tǒng)意義上的動(dòng)畫相似,都是把畫面一幅一幅的呈現(xiàn)在觀眾面前。一旦畫面變換的速度快了,觀眾就會(huì)認(rèn)為畫面是連續(xù)的。

雙緩沖技術(shù)是一種在計(jì)算機(jī)圖形中普遍采用的技術(shù),絕大多數(shù)OpenGL實(shí)現(xiàn)都支持雙緩沖技術(shù)。

通常都是利用CPU空閑的時(shí)候繪制動(dòng)畫,但也可以有其它的選擇。

介紹了垂直同步的相關(guān)知識(shí)。

介紹了一種簡(jiǎn)單的計(jì)算幀速(FPS)的方法。

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

 

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

posted on 2010-09-27 14:52 王秋林 閱讀(1058) 評(píng)論(0)  編輯 收藏 引用

只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


<2011年4月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

常用鏈接

留言簿(1)

隨筆檔案(15)

搜索

  •  

最新評(píng)論

閱讀排行榜

評(píng)論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            99国产欧美久久久精品| 欧美国产日韩亚洲一区| 国产伪娘ts一区| 久久成人18免费观看| 久久国产一区| 亚洲理论电影网| 亚洲精品久久久久久久久| 欧美激情视频一区二区三区在线播放| 99精品免费网| 亚洲欧美日韩视频一区| 精久久久久久| 欧美激情综合色| 欧美1区视频| 亚洲一区二区三区乱码aⅴ| 午夜一级在线看亚洲| 亚洲第一黄色网| 日韩亚洲欧美一区| 国产亚洲欧美日韩精品| 欧美国产三级| 亚洲一区免费看| 欧美一区二区三区在线播放| 亚洲国产精品电影| 亚洲视频你懂的| 韩国女主播一区| 理论片一区二区在线| 欧美日韩国产电影| 久久久久国产一区二区三区| 欧美成人综合在线| 亚洲一区二区三区精品动漫| 欧美一区二区视频观看视频| 日韩性生活视频| 羞羞漫画18久久大片| 亚洲国产精品电影| 亚洲欧美另类在线| 亚洲精品乱码视频| 亚洲欧美日韩视频二区| 亚洲美女av黄| 午夜精品久久久久99热蜜桃导演| 亚洲经典一区| 香蕉久久一区二区不卡无毒影院 | 亚洲激情网址| 亚洲主播在线播放| 亚洲理论在线| 久久精品夜夜夜夜久久| 亚洲综合色丁香婷婷六月图片| 性做久久久久久| 在线亚洲激情| 久久久久一区| 性久久久久久| 欧美日韩精品一区二区在线播放 | 国产一区二区毛片| 亚洲国产精品成人一区二区| 国产乱码精品一区二区三区忘忧草 | 亚洲高清av在线| 国产一区二区三区久久| 日韩午夜中文字幕| 亚洲成人中文| 香蕉av福利精品导航| 夜夜躁日日躁狠狠久久88av| 久久综合久久美利坚合众国| 亚洲欧美一区在线| 欧美精品 日韩| 欧美成人dvd在线视频| 国产九九视频一区二区三区| 99视频一区二区三区| 亚洲伦理一区| 噜噜噜在线观看免费视频日韩| 久久精品在线播放| 国产精品区一区二区三区| 亚洲精品一区二区在线| 亚洲黑丝在线| 亚洲综合另类| 亚洲图片在线观看| 欧美搞黄网站| 欧美国产第二页| 伊人成年综合电影网| 欧美主播一区二区三区| 亚洲欧美国产高清| 欧美视频一区二区三区四区| 亚洲人成毛片在线播放女女| 亚洲福利视频一区| 久久精品99无色码中文字幕 | 亚洲免费综合| 欧美日韩视频不卡| 最近中文字幕日韩精品| 日韩小视频在线观看| 99pao成人国产永久免费视频| 猛男gaygay欧美视频| 久久www成人_看片免费不卡| 国产精品入口日韩视频大尺度| 一区二区三区四区国产| 亚洲另类春色国产| 欧美精品一区二区三区在线看午夜 | 免费成人黄色| 免费中文日韩| 亚洲高清免费视频| 久久久精品一区二区三区| 久久国产欧美日韩精品| 国产女人精品视频| 亚洲综合国产| 久久精品国产综合| 国产综合18久久久久久| 欧美在线日韩在线| 久久夜色精品一区| 韩国av一区二区三区在线观看| 羞羞视频在线观看欧美| 久久久夜夜夜| 亚洲第一精品夜夜躁人人爽| 久久中文精品| 欧美多人爱爱视频网站| 91久久久久久国产精品| 欧美1区视频| 亚洲美女中文字幕| 亚洲一区精彩视频| 国产精品magnet| 亚洲视频欧美视频| 久久精品视频va| 极品少妇一区二区| 久久久久久穴| 亚洲国产成人高清精品| 一本不卡影院| 亚洲自拍16p| 国内成+人亚洲| 欧美亚洲一区三区| 免费成人高清在线视频| 亚洲激情中文1区| 欧美精品久久久久久久久老牛影院 | 国产一区二区三区日韩| 久久精品91| 亚洲国产精品第一区二区三区| 夜色激情一区二区| 国产精品一页| 久久高清福利视频| 免费观看一区| 日韩天堂在线观看| 国产精品久久久久影院色老大| 欧美一区二区视频网站| 欧美激情性爽国产精品17p| 一区二区三区日韩欧美| 国产日韩视频| 另类av一区二区| 亚洲欧洲精品天堂一级| 午夜视频久久久久久| 国内精品一区二区三区| 欧美99在线视频观看| 一本久久青青| 欧美中文字幕在线观看| 亚洲第一色中文字幕| 欧美日韩亚洲不卡| 欧美在线观看视频在线| 亚洲激情av在线| 性色一区二区三区| 在线看片欧美| 欧美性猛交视频| 久久久欧美精品sm网站| 亚洲美女av在线播放| 久久精品视频99| 洋洋av久久久久久久一区| 国产一区二区三区视频在线观看 | 国产精品成人v| 久久久国产一区二区| 亚洲成人原创| 另类亚洲自拍| 一区二区欧美激情| 麻豆精品视频在线| 亚洲一区二区三区高清不卡| 亚洲第一主播视频| 国产精品久久久一本精品| 久久亚洲免费| 一区二区三区四区精品| 免费看成人av| 亚洲欧美日韩另类| 亚洲国产成人高清精品| 国产精品亚洲视频| 免费精品99久久国产综合精品| 亚洲综合激情| 日韩亚洲欧美成人| 欧美成人在线免费观看| 亚洲一区视频| 亚洲欧洲日本国产| 国产亚洲一区二区精品| 欧美精品在线一区| 亚洲愉拍自拍另类高清精品| 亚洲激情网址| 久久久国产精品一区二区中文| 亚洲午夜91| 亚洲精品美女久久久久| 国产一区二区丝袜高跟鞋图片| 欧美午夜宅男影院在线观看| 久久天堂国产精品| 亚洲在线一区二区| 91久久精品国产91久久| 久热爱精品视频线路一| 午夜综合激情| 一本久久综合亚洲鲁鲁五月天| 韩国精品在线观看| 国产日韩欧美三区| 国产精品成人v| 欧美日韩精品久久久| 蜜桃精品一区二区三区|