OpenGL基礎(chǔ)圖形編程
一、OpenGL與3D圖形世界
1.1、OpenGL使人們進(jìn)入三維圖形世界
我們生活在一個(gè)充滿三維物體的三維世界中,為了使計(jì)算機(jī)能精確地再現(xiàn)這些物體,我們必須能在三維空間描繪這些物體。我們又生活在一個(gè)充滿信息的世界中,能否盡快地理解并運(yùn)用這些信息將直接影響事業(yè)的成敗,所以我們需要用一種最直接的形式來(lái)表示這些信息。
最近幾年計(jì)算機(jī)圖形學(xué)的發(fā)展使得三維表現(xiàn)技術(shù)得以形成,這些三維表現(xiàn)技術(shù)使我們能夠再現(xiàn)三維世界中的物體,能夠用三維形體來(lái)表示復(fù)雜的信息,這種技術(shù)就是可視化(Visualization)技術(shù)。可視化技術(shù)使人能夠在三維圖形世界中直接對(duì)具有形體的信息進(jìn)行操作,和計(jì)算機(jī)直接交流。這種技術(shù)已經(jīng)把人和機(jī)器的力量以一種直覺而自然的方式加以統(tǒng)一,這種革命性的變化無(wú)疑將極大地提高人們的工作效率。可視化技術(shù)賦予人們一種仿真的、三維的并且具有實(shí)時(shí)交互的能力,這樣人們可以在三維圖形世界中用以前不可想象的手段來(lái)獲取信息或發(fā)揮自己創(chuàng)造性的思維。機(jī)械工程師可以從二維平面圖中得以解放直接進(jìn)入三維世界,從而很快得到自己設(shè)計(jì)的三維機(jī)械零件模型。醫(yī)生可以從病人的三維掃描圖象分析病人的病灶。軍事指揮員可以面對(duì)用三維圖形技術(shù)生成的戰(zhàn)場(chǎng)地形,指揮具有真實(shí)感的三維飛機(jī)、軍艦、坦克向目標(biāo)開進(jìn)并分析戰(zhàn)斗方案的效果。
更令人驚奇的是目前正在發(fā)展的虛擬現(xiàn)實(shí)技術(shù),它能使人們進(jìn)入一個(gè)三維的、多媒體的虛擬世界,人們可以游歷遠(yuǎn)古時(shí)代的城堡,也可以遨游浩翰的太空。所有這些都依賴于計(jì)算機(jī)圖形學(xué)、計(jì)算機(jī)可視化技術(shù)的發(fā)展。人們對(duì)計(jì)算機(jī)可視化技術(shù)的研究已經(jīng)歷了一個(gè)很長(zhǎng)的歷程,而且形成了許多可視化工具,其中SGI公司推出的GL三維圖形庫(kù)表現(xiàn)突出,易于使用而且功能強(qiáng)大。利用GL開發(fā)出來(lái)的三維應(yīng)用軟件頗受許多專業(yè)技術(shù)人員的喜愛,這些三維應(yīng)用軟件已涉及建筑、產(chǎn)品設(shè)計(jì)、醫(yī)學(xué)、地球科學(xué)、流體力學(xué)等領(lǐng)域。隨著計(jì)算機(jī)技術(shù)的繼續(xù)發(fā)展,GL已經(jīng)進(jìn)一步發(fā)展成為OpenGL,OpenGL已被認(rèn)為是高性能圖形和交互式視景處理的標(biāo)準(zhǔn),目前包括ATT公司UNIX軟件實(shí)驗(yàn)室、IBM公司、DEC公司、SUN公司、HP公司、Microsoft公司和 SGI公司在內(nèi)的幾家在計(jì)算機(jī)市場(chǎng)占領(lǐng)導(dǎo)地位的大公司都采用了OpenGL圖形標(biāo)準(zhǔn)。
值得一提的是,由于Microsoft公司在 Windows NT中提供OpenGL圖形標(biāo)準(zhǔn),OpenGL將在微機(jī)中廣泛應(yīng)用,尤其是OpenGL三維圖形加速卡和微機(jī)圖形工作站的推出,人們可以在微機(jī)上實(shí)現(xiàn)三維圖形應(yīng)用,如CAD設(shè)計(jì)、仿真模擬、三維游戲等,從而更有機(jī)會(huì)、更方便地使用OpenGL及其應(yīng)用軟件來(lái)建立自己的三維圖形世界。
1.2、OpenGL提供直觀的三維圖形開發(fā)環(huán)境
OpenGL實(shí)際上是一種圖形與硬件的接口。它包括了120個(gè)圖形函數(shù),開發(fā)者可以用這些函數(shù)來(lái)建立三維模型和進(jìn)行三維實(shí)時(shí)交互。與其他圖形程序設(shè)計(jì)接口不同,OpenGL提供了十分清晰明了的圖形函數(shù),因此初學(xué)的程序設(shè)計(jì)員也能利用OpenGL的圖形處理能力和1670萬(wàn)種色彩的調(diào)色板很快地設(shè)計(jì)出三維圖形以及三維交互軟件。
OpenGL強(qiáng)有力的圖形函數(shù)不要求開發(fā)者把三維物體模型的數(shù)據(jù)寫成固定的數(shù)據(jù)格式,這樣開發(fā)者不但可以直接使用自己的數(shù)據(jù),而且可以利用其他不同格式的數(shù)據(jù)源。這種靈活性極大地節(jié)省了開發(fā)者的時(shí)間,提高了軟件開發(fā)效益。
長(zhǎng)期以來(lái),從事三維圖形開發(fā)的技術(shù)人員都不得不在自己的程序中編寫矩陣變換、外部設(shè)備訪問等函數(shù),這樣為調(diào)制這些與自己的軟件開發(fā)目標(biāo)關(guān)系并不十分密切的函數(shù)費(fèi)腦筋,而OpenGL正是提供一種直觀的編程環(huán)境,它提供的一系列函數(shù)大大地簡(jiǎn)化了三維圖形程序。例如:
- OpenGL提供一系列的三維圖形單元供開發(fā)者調(diào)用。
- OpenGL提供一系列的圖形變換函數(shù)。
- OpenGL提供一系列的外部設(shè)備訪問函數(shù),使開發(fā)者可以方便地訪問鼠標(biāo)、鍵盤、空間球、數(shù)據(jù)手套等這種直觀的三維圖形開發(fā)環(huán)境體現(xiàn)了OpenGL的技術(shù)優(yōu)勢(shì),這也是許多三維圖形開發(fā)者熱衷于OpenGL的緣由所在。
OpenGL成為目前三維圖形開發(fā)標(biāo)準(zhǔn)在計(jì)算機(jī)發(fā)展初期,人們就開始從事計(jì)算機(jī)圖形的開發(fā)。直到計(jì)算機(jī)硬軟件和計(jì)算機(jī)圖形學(xué)高度發(fā)達(dá)的九十年代,人們發(fā)現(xiàn)復(fù)雜的數(shù)據(jù)以視覺的形式表現(xiàn)時(shí)是最易理解的,因而三維圖形得以迅猛發(fā)展,于是各種三維圖形工具軟件包相繼推出,如PHIGS、PEX、 RenderMan等。這些三維圖形工具軟件包有些側(cè)重于使用方便,有些側(cè)重于渲染效果或與應(yīng)用軟件的連接,但沒有一種三維工具軟件包在交互式三維圖形建模能力、外部設(shè)備管理以及編程方便程度上能夠OpenGL相比擬。
OpenGL經(jīng)過對(duì)GL的進(jìn)一步發(fā)展,實(shí)現(xiàn)二維和三維的高級(jí)圖形技術(shù),在性能上表現(xiàn)得異常優(yōu)越,它包括建模、變換、光線處理、色彩處理、動(dòng)畫以及更先進(jìn)的能力,如紋理影射、物體運(yùn)動(dòng)模糊等。OpenGL的這些能力為實(shí)現(xiàn)逼真的三維渲染效果、建立交互的三維景觀提供了優(yōu)秀的軟件工具。OpenGL在硬件、窗口、操作系統(tǒng)方面是相互獨(dú)立的。
許多計(jì)算機(jī)公司已經(jīng)把 OpenGL集成到各種窗口和操作系統(tǒng)中,其中操作系統(tǒng)包括UNIX、Windows NT、DOS等,窗口系統(tǒng)有X窗口、Windows等。為了實(shí)現(xiàn)一個(gè)完整功能的圖形處理系統(tǒng),設(shè)計(jì)一個(gè)與OpenGL相關(guān)的系統(tǒng)結(jié)構(gòu)為:其最底層是圖形硬件,第二層為操作系統(tǒng),第三層為窗口系統(tǒng),第四層為OpenGL,第五層為應(yīng)用軟件。OpenGL是網(wǎng)絡(luò)透明的,在客戶 — 服務(wù)器(Client-Server)體系結(jié)構(gòu)中,OpenGL允許本地和遠(yuǎn)程繪圖。所以在網(wǎng)絡(luò)系統(tǒng)中,OpenGL在X窗口、Windows或其它窗口系統(tǒng)下都可以以一個(gè)獨(dú)立的圖形窗口出現(xiàn)。
OpenGL作為一個(gè)性能優(yōu)越的圖形應(yīng)用程序設(shè)計(jì)界面(API)而適合于廣泛的計(jì)算環(huán)境,從個(gè)人計(jì)算機(jī)到工作站和超級(jí)計(jì)算機(jī),OpenGL都能實(shí)現(xiàn)高性能的三維圖形功能。由于許多在計(jì)算機(jī)界具有領(lǐng)導(dǎo)地位的計(jì)算機(jī)公司紛紛采用OpenGL作為三維圖形應(yīng)用程序設(shè)計(jì)界面,OpenGL應(yīng)用程序具有廣泛的移植性。因此,OpenGL已成為目前的三維圖形開發(fā)標(biāo)準(zhǔn),是從事三維圖形開發(fā)工作的技術(shù)人員所必須掌握的開發(fā)工具。
二、OpenGL概念建立
OpenGL是一個(gè)與硬件圖形發(fā)生器的軟件接口,它包括了100多個(gè)圖形操作函數(shù),開發(fā)者可以利用這些函數(shù)來(lái)構(gòu)造景物模型、進(jìn)行三維圖形交互軟件的開發(fā)。正如上一章所述,OpenGL是一個(gè)高性能的圖形開發(fā)軟件包。OpenGL支持網(wǎng)絡(luò),在網(wǎng)絡(luò)系統(tǒng)中用戶可以在不同的圖形終端上運(yùn)行程序顯示圖形。 OpenGL作為一個(gè)與硬件獨(dú)立的圖形接口,它不提供與硬件密切相關(guān)的設(shè)備操作函數(shù),同時(shí),它也不提供描述類似于飛機(jī)、汽車、分子形狀等復(fù)雜形體的圖形操作函數(shù)。用戶必須從點(diǎn)、線、面等最基本的圖形單元開始構(gòu)造自己的三維模型。當(dāng)然,象OpenInventor那樣更高一級(jí)的基于OpenGL的三維圖形建模開發(fā)軟件包將提供方便的工具。因此OpenGL的圖形操作函數(shù)十分基本、靈活。例如OpenGL中的模型繪制過程就多種多樣,內(nèi)容十分豐富,OpenGL提供了以下的對(duì)三維物體的繪制方式:
- 網(wǎng)格線繪圖方式(wireframe)
這種方式僅繪制三維物體的網(wǎng)格輪廓線。
- 深度優(yōu)先網(wǎng)格線繪圖方式(depth_cued)
用網(wǎng)格線方式繪圖,增加模擬人眼看物體一樣,遠(yuǎn)處的物體比近處的物體要暗些。
- 反走樣網(wǎng)格線繪圖方式(antialiased)
用網(wǎng)格線方式繪圖,繪圖時(shí)采用反走樣技術(shù)以減少圖形線條的參差不齊。
- 平面消隱繪圖方式(flat_shade)
對(duì)模型的隱藏面進(jìn)行消隱,對(duì)模型的平面單元按光照程度進(jìn)行著色但不進(jìn)行光滑處理。
- 光滑消隱繪圖方式(smooth_shade)
對(duì)模型進(jìn)行消隱按光照渲染著色的過程中再進(jìn)行光滑處理,這種方式更接近于現(xiàn)實(shí)。
- 加陰影和紋理的繪圖方式(shadows、textures)
在模型表面貼上紋理甚至于加上光照陰影,使得三維景觀象照片一樣。
- 運(yùn)動(dòng)模糊的繪圖方式(motion-blured)
模擬物體運(yùn)動(dòng)時(shí)人眼觀察所感覺的動(dòng)感現(xiàn)象。
- 大氣環(huán)境效果(atmosphere-effects)
在三維景觀中加入如霧等大氣環(huán)境效果,使人身臨其境。
- 深度域效果(depth-of-effects)
類似于照相機(jī)鏡頭效果,模型在聚焦點(diǎn)處清晰,反之則模糊。
2.2、OpenGL工作流程
整個(gè)OpenGL的基本工作流程如下圖:
其中幾何頂點(diǎn)數(shù)據(jù)包括模型的頂點(diǎn)集、線集、多邊形集,這些數(shù)據(jù)經(jīng)過流程圖的上部,包括運(yùn)算器、逐個(gè)頂點(diǎn)操作等;圖像數(shù)據(jù)包括象素集、影像集、位圖集等,圖像象素?cái)?shù)據(jù)的處理方式與幾何頂點(diǎn)數(shù)據(jù)的處理方式是不同的,但它們都經(jīng)過光柵化、逐個(gè)片元(Fragment)處理直至把最后的光柵數(shù)據(jù)寫入幀緩沖器。在OpenGL中的所有數(shù)據(jù)包括幾何頂點(diǎn)數(shù)據(jù)和象素?cái)?shù)據(jù)都可以被存儲(chǔ)在顯示列表中或者立即可以得到處理。OpenGL中,顯示列表技術(shù)是一項(xiàng)重要的技術(shù)。
OpenGL要求把所有的幾何圖形單元都用頂點(diǎn)來(lái)描述,這樣運(yùn)算器和逐個(gè)頂點(diǎn)計(jì)算操作都可以針對(duì)每個(gè)頂點(diǎn)進(jìn)行計(jì)算和操作,然后進(jìn)行光柵化形成圖形碎片;對(duì)于象素?cái)?shù)據(jù),象素操作結(jié)果被存儲(chǔ)在紋理組裝用的內(nèi)存中,再象幾何頂點(diǎn)操作一樣光柵化形成圖形片元。
整個(gè)流程操作的最后,圖形片元都要進(jìn)行一系列的逐個(gè)片元操作,這樣最后的象素值BZ送入幀緩沖器實(shí)現(xiàn)圖形的顯示。
2.3、OpenGL圖形操作步驟
在上一節(jié)中說(shuō)明了OpenGL的基本工作流程,根據(jù)這個(gè)流程可以歸納出在OpenGL中進(jìn)行主要的圖形操作直至在計(jì)算機(jī)屏幕上渲染繪制出三維圖形景觀的基本步驟:
1)根據(jù)基本圖形單元建立景物模型,并且對(duì)所建立的模型進(jìn)行數(shù)學(xué)描述(OpenGL中把:點(diǎn)、線、多邊形、圖像和位圖都作為基本圖形單元)。
2)把景物模型放在三維空間中的合適的位置,并且設(shè)置視點(diǎn)(viewpoint)以觀察所感興趣的景觀。
3)計(jì)算模型中所有物體的色彩,其中的色彩根據(jù)應(yīng)用要求來(lái)確定,同時(shí)確定光照條件、紋理粘貼方式等。
4)把景物模型的數(shù)學(xué)描述及其色彩信息轉(zhuǎn)換至計(jì)算機(jī)屏幕上的象素,這個(gè)過程也就是光柵化(rasterization)。
在這些步驟的執(zhí)行過程中,OpenGL可能執(zhí)行其他的一些操作,例如自動(dòng)消隱處理等。另外,景物光柵化之后被送入幀緩沖器之前還可以根據(jù)需要對(duì)象素?cái)?shù)據(jù)進(jìn)行操作。
三、WindowsNT下的OpenGL
3.1、Windows NT下的OpenGL函數(shù)
如前面的章節(jié)所述,Windows NT下的OpenGL同樣包含100多個(gè)庫(kù)函數(shù),這些函數(shù)都按一定的格式來(lái)命名,即每個(gè)函數(shù)都以gl開頭。Windows NT下的OpenGL除了具有基本的OpenGL函數(shù)外,還支持其他四類函數(shù):
相應(yīng)函數(shù) | 具體說(shuō)明 |
OpenGL實(shí)用庫(kù) | 43個(gè)函數(shù),每個(gè)函數(shù)以glu開頭。 |
OpenGL輔助庫(kù) | 31個(gè)函數(shù),每個(gè)函數(shù)以aux開頭。 |
Windows專用庫(kù)函數(shù)(WGL) | 6個(gè)函數(shù),每個(gè)函數(shù)以wgl開頭。 |
Win32 API函數(shù) | 5個(gè)函數(shù),函數(shù)前面沒有專用前綴。 |
在OpenGL中有115個(gè)核心函數(shù),這些函數(shù)是最基本的,它們可以在任何OpenGL的工作平臺(tái)上應(yīng)用。這些函數(shù)用于建立各種各樣的形體,產(chǎn)生光照效果,進(jìn)行反走樣以及進(jìn)行紋理映射,進(jìn)行投影變換等等。由于這些核心函數(shù)有許多種形式并能夠接受不同類型的參數(shù),實(shí)際上這些函數(shù)可以派生出300 多個(gè)函數(shù)。
OpenGL的實(shí)用函數(shù)是比OpenGL核心函數(shù)更高一層的函數(shù),這些函數(shù)是通過調(diào)用核心函數(shù)來(lái)起作用的。這些函數(shù)提供了十分簡(jiǎn)單的用法,從而減輕了開發(fā)者的編程負(fù)擔(dān)。OpenGL的實(shí)用函數(shù)包括紋理映射、坐標(biāo)變換、多邊形分化、繪制一些如橢球、圓柱、茶壺等簡(jiǎn)單多邊形實(shí)體(本指南將詳細(xì)講述這些函數(shù)的具體用法)等。這部分函數(shù)象核心函數(shù)一樣在任何OpenGL平臺(tái)都可以應(yīng)用。
OpenGL的輔助庫(kù)是一些特殊的函數(shù),這些函數(shù)本來(lái)是用于初學(xué)者做簡(jiǎn)單的練習(xí)之用,因此這些函數(shù)不能在所有的OpenGL平臺(tái)上使用,在Windows NT環(huán)境下可以使用這些函數(shù)。這些函數(shù)使用簡(jiǎn)單,它們可以用于窗口管理、輸入輸出處理以及繪制一些簡(jiǎn)單的三維形體。為了使OpenGL的應(yīng)用程序具有良好的移植性,在使用OpenGL輔助庫(kù)的時(shí)候應(yīng)謹(jǐn)慎。
6個(gè)WGL函數(shù)是用于連接OpenGL與Windows NT的,這些函數(shù)用于在Windows NT環(huán)境下的OpenGL窗口能夠進(jìn)行渲染著色,在窗口內(nèi)繪制位圖字體以及把文本放在窗口的某一位置等。這些函數(shù)把Windows與OpenGL揉合在一起。最后的5個(gè)Win32函數(shù)用于處理象素存儲(chǔ)格式和雙緩沖區(qū),顯然這些函數(shù)僅僅能夠用于Win32系統(tǒng)而不能用于其它OpenGL平臺(tái)。
3.2、OpenGL基本功能
OpenGL能夠?qū)φ麄€(gè)三維模型進(jìn)行渲染著色,從而繪制出與客觀世界十分類似的三維景象。另外OpenGL還可以進(jìn)行三維交互、動(dòng)作模擬等。具體的功能主要有以下這些內(nèi)容。
- 模型繪制
OpenGL能夠繪制點(diǎn)、線和多邊形。應(yīng)用這些基本的形體,我們可以構(gòu)造出幾乎所有的三維模型。OpenGL通常用模型的多邊形的頂點(diǎn)來(lái)描述三維模型。如何通過多邊形及其頂點(diǎn)來(lái)描述三維模型,在指南的在后續(xù)章節(jié)會(huì)有詳細(xì)的介紹。
- 模型觀察
在建立了三維景物模型后,就需要用OpenGL描述如何觀察所建立的三維模型。觀察三維模型是通過一系列的坐標(biāo)變換進(jìn)行的。模型的坐標(biāo)變換在使觀察者能夠在視點(diǎn)位置觀察與視點(diǎn)相適應(yīng)的三維模型景觀。在整個(gè)三維模型的觀察過程中,投影變換的類型決定觀察三維模型的觀察方式,不同的投影變換得到的三維模型的景象也是不同的。最后的視窗變換則對(duì)模型的景象進(jìn)行裁剪縮放,即決定整個(gè)三維模型在屏幕上的圖象。
- 顏色模式的指定
OpenGL 應(yīng)用了一些專門的函數(shù)來(lái)指定三維模型的顏色。程序員可以選擇二個(gè)顏色模式,即RGBA模式和顏色表模式。在RGBA模式中,顏色直接由RGB值來(lái)指定;在顏色表模式中,顏色值則由顏色表中的一個(gè)顏色索引值來(lái)指定。程序員還可以選擇平面著色和光滑著色二種著色方式對(duì)整個(gè)三維景觀進(jìn)行著色。
- 光照應(yīng)用
用OpenGL繪制的三維模型必須加上光照才能更加與客觀物體相似。OpenGL提供了管理四種光(輻射光、環(huán)境光、鏡面光和漫反射光)的方法,另外還可以指定模型表面的反射特性。
- 圖象效果增強(qiáng)
OpenGL提供了一系列的增強(qiáng)三維景觀的圖象效果的函數(shù),這些函數(shù)通過反走樣、混合和霧化來(lái)增強(qiáng)圖象的效果。反走樣用于改善圖象中線段圖形的鋸齒而更平滑,混合用于處理模型的半透明效果,霧使得影像從視點(diǎn)到遠(yuǎn)處逐漸褪色,更接近于真實(shí)。
- 位圖和圖象處理
OpenGL還提供了專門對(duì)位圖和圖象進(jìn)行操作的函數(shù)。
- 紋理映射
三維景物因缺少景物的具體細(xì)節(jié)而顯得不夠真實(shí),為了更加逼真地表現(xiàn)三維景物,OpenGL提供了紋理映射的功能。OpenGL提供的一系列紋理映射函數(shù)使得開發(fā)者可以十分方便地把真實(shí)圖象貼到景物的多邊形上,從而可以在視窗內(nèi)繪制逼真的三維景觀。
- 實(shí)時(shí)動(dòng)畫
為了獲得平滑的動(dòng)畫效果,需要先在內(nèi)存中生成下一幅圖象,然后把已經(jīng)生成的圖象從內(nèi)存拷貝到屏幕上,這就是OpenGL的雙緩存技術(shù)(double buffer)。OpenGL提供了雙緩存技術(shù)的一系列函數(shù)。
- 交互技術(shù)
目前有許多圖形應(yīng)用需要人機(jī)交互,OpenGL提供了方便的三維圖形人機(jī)交互接口,用戶可以選擇修改三維景觀中的物體。
OpenGL的作用機(jī)制是客戶(client)/服務(wù)器(sever)機(jī)制,即客戶(用OpenGL繪制景物的應(yīng)用程序)向服務(wù)器(即OpenGL內(nèi)核)發(fā)布OpenGL命令,服務(wù)器則解釋這些命令。大多數(shù)情況下,客戶和服務(wù)器在同一機(jī)器上運(yùn)行。正是OpenGL的這種客戶/服務(wù)器機(jī)制,OpenGL可以十分方便地在網(wǎng)絡(luò)環(huán)境下使用。因此Windows NT下的OpenGL是網(wǎng)絡(luò)透明的。正象Windows的圖形設(shè)備接口(GDI)把圖形函數(shù)庫(kù)封裝在一個(gè)動(dòng)態(tài)鏈接庫(kù)(Windows NT下的GDI32.DLL)內(nèi)一樣,OpenGL圖形庫(kù)也被封裝在一個(gè)動(dòng)態(tài)鏈接庫(kù)內(nèi)(OPENGL32.DLL)。受客戶應(yīng)用程序調(diào)用的OpenGL函數(shù)都先在OPENGL32.DLL中處理,然后傳給服務(wù)器WINSRV.DLL。OpenGL的命令再次得到處理并且直接傳給Win32的設(shè)備驅(qū)動(dòng)接口(Device Drive Interface,DDI),這樣就把經(jīng)過處理的圖形命令送給視頻顯示驅(qū)動(dòng)程序。下圖簡(jiǎn)要說(shuō)明這個(gè)過程:
圖3-1 OpenGL在Windows NT下運(yùn)行機(jī)制
在三維圖形加速卡的GLINT圖形加速芯片的加速支持下,二個(gè)附加的驅(qū)動(dòng)程序被加入這個(gè)過程中。一個(gè)OpenGL可安裝客戶驅(qū)動(dòng)程序(Installable Client Driver,ICD)被加在客戶這一邊,一個(gè)硬件指定DDI(Hardware-specific DDI)被加在服務(wù)器這邊,這個(gè)驅(qū)動(dòng)程序與Wind32 DDI是同一級(jí)別的。
圖3-2 在三維圖形加速下OpenGL運(yùn)行機(jī)制
四、OpenGL基礎(chǔ)程序結(jié)構(gòu)
用OpenGL編寫的程序結(jié)構(gòu)類似于用其他語(yǔ)言編寫的程序。實(shí)際上,OpenGL是一個(gè)豐富的三維圖形函數(shù)庫(kù),編寫OpenGL程序并非難事,只需在基本C語(yǔ)言中調(diào)用這些函數(shù),用法同Turbo C、Microsoft C等類似,但也有許多不同之處。
本指南所有的程序都是在Windows NT的Microsoft Visual C++集成環(huán)境下編譯連接的,其中有部分頭文件和函數(shù)是為這個(gè)環(huán)境所用的,例如判別操作系統(tǒng)的頭文件“glos.h”。此外,為便于各類讀者同時(shí)快速入門,在短時(shí)間內(nèi)掌握OpenGL編程的基本方法和技巧,指南中例子盡量采用標(biāo)準(zhǔn)ANSI C調(diào)用OpenGL函數(shù)來(lái)編寫,而且所有例程都只采用OpenGL附帶的輔助庫(kù)中的窗口系統(tǒng)。此外,這樣也便于程序在各平臺(tái)間移植,尤其往工作站UNIX 操作系統(tǒng)移植時(shí),也只需改動(dòng)頭文件等很少很少的部分。下面列出一個(gè)簡(jiǎn)單的OpenGL程序:
例4-1 OpenGL簡(jiǎn)單例程(Simple.c)
#include <GL/gl.h>
#include <GL/glaux.h>
#include "glos.h"
void main(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("simple");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0,0.0,0.0);
glRectf(-0.5,-0.5,0.5,0.5);
glFlush();
_sleep(1000);
}
這個(gè)程序運(yùn)行結(jié)果是在屏幕窗口內(nèi)畫一個(gè)紅色的方塊。
下面具體分析整個(gè)程序結(jié)構(gòu):首先,在程序最開始處是OpenGL頭文件:<GL/gl.h>、<GL/glaux.h>。前一個(gè)是gl庫(kù)的頭文件,后一個(gè)是輔助庫(kù)的頭文件。此外,在以后的幾章中還將說(shuō)明OpenGL的另外兩個(gè)頭文件,一個(gè)是<GL/glu.h>實(shí)用庫(kù)的頭文件,另一個(gè)是<GL/glx.h>X窗口擴(kuò)充庫(kù)的頭文件(這個(gè)常用在工作站上)。接下來(lái)是主函數(shù)main()的定義:一般的程序結(jié)構(gòu)是先定義一個(gè)窗口:
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("simple");
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA)設(shè)置窗口顯示模式為RGBA方式,即彩色方式,并且圖形緩存為單緩存(SINGLE BUFFER)。 auxInitPosition(0, 0, 500, 500)定義窗口的初始位置,前兩個(gè)參數(shù)(0, 0)為窗口的左上角點(diǎn)的屏幕坐標(biāo),后兩個(gè)參數(shù)(500,500)為窗口的寬度和高度。auxInitWindow("simple")是窗口初始化,字符參數(shù)是窗口名稱。
然后是窗口內(nèi)清屏:
glClearColor(0.0,0.0,0.0,0.0); glClear(GL_COLOR_BUFFER_BIT);
第一句將窗口清為黑色,第二句將顏色緩沖區(qū)清為glClearColor(0.0, 0.0, 0.0, 0.0)命令所設(shè)置的顏色,即同窗口背景顏色一致。
再接著是在窗口內(nèi)畫一個(gè)物體:
glColor3f(1.0,0.0,0.0);
glRectf(-0.5,-0.5,0.5,0.5);
很明顯,第一句設(shè)置物體顏色,函數(shù)中前三個(gè)參數(shù)分別為R、G、B值,最后一個(gè)參數(shù)是Alpha值,范圍都從0至1;第二句繪制一個(gè)二維矩形。注意:OpenGL是針對(duì)三維圖形而言,因此用作OpenGL編程繪制物體必須意識(shí)到任何一個(gè)物體都是三維的,具有空間性,而顯示于屏幕上的物體都是三維物體在二維平面上的投影。
從表面上看,上述程序代碼很簡(jiǎn)單,實(shí)際上已經(jīng)用到了缺省的投影形式(正射投影)。再看glFlush()函數(shù),表示強(qiáng)制繪圖完成。最后一句_sleep(1000),參數(shù)單位為毫秒,整句意思是保持現(xiàn)有狀況一秒鐘,然后結(jié)束程序運(yùn)行。這個(gè)函數(shù)是VC++的庫(kù)函數(shù)。
總而言之,OpenGL程序基本結(jié)構(gòu)為定義窗口、清理窗口、繪制物體、結(jié)束運(yùn)行。
五、OpenGL的數(shù)據(jù)類型和函數(shù)名
OpenGL的數(shù)據(jù)類型定義可以與其它語(yǔ)言一致,但建議在ANSI C下最好使用以下定義的數(shù)據(jù)類型,例如GLint、GLfloat等。具體類型見表5-1。
前綴 數(shù)據(jù)類型 相應(yīng)C語(yǔ)言類型 OpenGL類型
================================================================
b 8-bit integer signed char GLbyte
s 16-bit integer short GLshort
i 32-bit integer long GLint,GLsizei
f 32-bit floating-point float GLfloat,GLclampf
d 64-bit floating-point double GLdouble,GLclampd
ub 8-bit unsigned integer unsigned char GLubyte,GLboolean
us 16-bit unsigned integer unsigned short GLushort
ui 32-bit unsigned integer unsigned long GLuint,GLenum,GLbitfield
首先,每個(gè)庫(kù)函數(shù)有前綴gl、glu、glx或aux,表示此函數(shù)分屬于基本庫(kù)、實(shí)用庫(kù)、X窗口擴(kuò)充庫(kù)或輔助庫(kù),其后的函數(shù)名頭字母大寫,后綴是參數(shù)類型的簡(jiǎn)寫,取i、f,參見表5-1。例:
glVertex2i(2,4);
glVertex3f(2.0,4.0,5.0);
注意:有的函數(shù)參數(shù)類型后綴前帶有數(shù)字2、3、4。2代表二維,3代表三維,4代表alpha值(以后介紹)。
有些OpenGL函數(shù)最后帶一個(gè)字母v,表示函數(shù)參數(shù)可用一個(gè)指針指向一個(gè)向量(或數(shù)組)來(lái)替代一系列單個(gè)參數(shù)值。下面兩種格式都表示設(shè)置當(dāng)前顏色為紅色,二者等價(jià)。
glColor3f(1.0,0.0,0.0);
float color_array[]={1.0,0.0,0.0};
glColor3fv(color_array);
除了以上基本命名方式外,還有一種帶“*”星號(hào)的表示方法,例如glColor*(),它表示可以用函數(shù)的各種方式來(lái)設(shè)置當(dāng)前顏色。同理,glVertex*v()表示用一個(gè)指針指向所有類型的向量來(lái)定義一系列頂點(diǎn)坐標(biāo)值。
最后,OpenGL也定義GLvoid類型,如果用C語(yǔ)言編寫,可以用它替代void類型。
六、OpenGL輔組庫(kù)的基本使用
OpenGL是一個(gè)開放的系統(tǒng),它是獨(dú)立于任何窗口系統(tǒng)或操作系統(tǒng)的。盡管它包含了許多圖形函數(shù),但它卻沒有窗口函數(shù),也沒有從鍵盤和鼠標(biāo)讀取事件的函數(shù),所以要初學(xué)者寫出一個(gè)完整的圖形程序是相當(dāng)困難的。另外,OpenGL圖形函數(shù)中只提供基本的幾何原形:點(diǎn)、線、多邊形,因此要?jiǎng)?chuàng)建基本的三維幾何體如球、錐體等,也很不容易。而OpenGL輔助庫(kù)就是為解決這些基本問題專門設(shè)計(jì)的,它提供了一些基本的窗口管理函數(shù)和三維圖形繪制函數(shù),能幫助初學(xué)者盡快進(jìn)入OpenGL世界,掌握關(guān)鍵的三維圖形技術(shù),體會(huì)其中奇妙的樂趣。但是,對(duì)于復(fù)雜的應(yīng)用,這些函數(shù)遠(yuǎn)遠(yuǎn)不夠,只能作為參考。
6.1、輔助庫(kù)函數(shù)分類
這一節(jié)內(nèi)容可以作為手冊(cè)查閱,初學(xué)者不必深究。
輔助庫(kù)函數(shù)大致分為六類:
6.1.1 窗口初始化和退出
相關(guān)函數(shù)有三個(gè),它們?cè)诘谝徽乱烟岬剑@里將詳細(xì)介紹:
void auxInitWindow(GLbyte *titleString)
打開一個(gè)由auxInitDisplayMode()和auxInitPosition()指定的窗口。函數(shù)參數(shù)是窗口標(biāo)題,窗口背景缺省顏色是RGBA下的黑色或顏色表(color_index)下的0號(hào)調(diào)色板的顏色。按下Escape鍵可以完成關(guān)掉窗口、結(jié)束程序、全部清屏三項(xiàng)功能。
void auxInitDisplayMode(GLbitfield mask)
設(shè)置窗口顯示模式。基本模式有RGBA或顏色表、單或雙緩存,也可指定其他附加模式:深度、模板或累積緩存(depth,stencil,and/or accumulation buffer)。參數(shù)mask是一組位標(biāo)志的聯(lián)合(取或),AUX_RGBA或AUX_INDEX、AUX_SINGLE或AUX_DOUBLE,以及其它有效標(biāo)志AUX_DEPTH、AUX_STENCIL或AUX_ACCUM。
void auxInitPosition(GLint x,GLint y,GLsizei width,GLsizei height)
設(shè)置窗口位置及大小。參數(shù)(x, y)為窗口的左上角點(diǎn)的屏幕坐標(biāo),參數(shù)(width, height)為窗口的寬度和高度,單位為象素,缺省值為(0, 0, 100, 100)。
6.1.2 窗口處理和事件輸入
當(dāng)窗口創(chuàng)建后,且在進(jìn)入主函數(shù)循環(huán)之前,應(yīng)當(dāng)?shù)怯浺韵铝谐龅幕卣{(diào)函數(shù)(callback function):
void auxReshapeFunc(void(*function)(GLsizei,GLsizei))
定義窗口改變時(shí)形狀重定函數(shù)。參數(shù)function是一個(gè)函數(shù)指針,這個(gè)函數(shù)帶有兩個(gè)參數(shù),即窗口改變后的新寬度和新高度。通常,function是 glViewport(),顯示裁減后的新尺寸,重定義投影矩陣,以便使投影后圖像的比例與視點(diǎn)匹配,避免比例失調(diào)。若不調(diào)用 auxReshapeFunc(),缺省重定物體形狀的函數(shù)功能是調(diào)用一個(gè)二維的正射投影矩陣。運(yùn)用輔助庫(kù),窗口將在每個(gè)事件改變后自動(dòng)重新繪制。
void auxKeyFunction(GLint key,void(*function)(void))
定義鍵盤響應(yīng)函數(shù)。參數(shù)function就是當(dāng)按下key鍵時(shí)所調(diào)用的函數(shù)指針,輔助庫(kù)為參數(shù)key定義了幾個(gè)常量:AUX_0至AUX_9、 AUX_A至AUX_Z、AUX_a至AUX_z、AUX_LEFT、AUX_RIGHT、AUX_UP、AUX_DOWN(方向鍵)、 AUX_ESCAPE、AUX_SPACE或AUX_RETURN。
void auxMouseFunc(GLint button,Glint mode,void(*function)(AUX_EVENTREC *))
定義鼠標(biāo)響應(yīng)函數(shù)。參數(shù)function就是當(dāng)鼠標(biāo)以mode方式作用于button時(shí)所調(diào)用的函數(shù)。參數(shù)button有 AUX_LEFTBUTTON、AUX_MIDDLEBUTTON或AUX_RIGHTBUTTON(以右手為標(biāo)準(zhǔn))。參數(shù)mode代表鼠標(biāo)觸擊狀態(tài),擊中時(shí)為AUX_MOUSEDOWN,釋放時(shí)為AUX_MOUSEUP。參數(shù)function必須帶一個(gè)參數(shù),它是指向結(jié)構(gòu)AUX_EVENNTREC的指針。當(dāng)函數(shù)auxMouseFunc()被調(diào)用時(shí)將為這個(gè)結(jié)構(gòu)分配相應(yīng)的內(nèi)存。通常用法類似如下:
void function(AUX_EVENTREC *event)
{
GLint x,y;
x=event->data[AUX_MOUSEX];
y=event->data[AUX_MOUSEY];
...
}
6.1.3 顏色表裝入
因?yàn)镺penGL本身沒有窗口系統(tǒng),所以依賴于窗口系統(tǒng)的顏色映射就沒法裝入顏色查找表。如果采用顏色表模式,就要用到輔助庫(kù)提供的用RGB值定義的單個(gè)顏色索引函數(shù):
void auxSetOneColor(GLint index,GLfloat red,GLfloat green,GLfloat blue)
設(shè)置自定義顏色的索引。參數(shù)index即索引號(hào),參數(shù)red、green、blue分別為紅、綠、藍(lán)值,范圍在(0~1)內(nèi)。
6.1.4 三維物體繪制
每組三維物體包括兩種形式:網(wǎng)狀體(wire)和實(shí)心體(solid)。網(wǎng)狀體沒有平面法向,而實(shí)心體有,能進(jìn)行光影計(jì)算,有光照時(shí)采用實(shí)心體模型。下面這些函數(shù)的 參數(shù)都是定義物體大小的,可以改變。
功能
|
函數(shù) |
繪制球
|
void auxWireSphere(GLdouble radius) void auxSolidSphere(GLdouble radius) |
繪制立方體
|
void auxWireCube(GLdouble size) void auxSolidCube(GLdouble size) |
繪制長(zhǎng)方體
|
void auxWireBox(GLdouble width,GLdouble height,GLdouble depth) void auxSolidBox(GLdouble width,GLdouble height,GLdouble depth) |
繪制環(huán)形圓紋面
|
void auxWireTorus(GLdouble innerRadius,GLdouble outerRadius) void auxSolidTorus(GLdouble innerRadius,GLdouble outerRadius) |
繪制圓柱
|
void auxWireCylinder(GLdouble radius,GLdouble height) void auxSolidCylinder(GLdouble radius,GLdouble height) |
繪制二十面體
|
void auxWireIcosahedron(GLdouble radius) void auxSolidIcosahedron(GLdouble radius) |
繪制八面體
|
void auxWireOctahedron(GLdouble radius) void auxSolidOctahedron(GLdouble radius) |
繪制四面體
|
void auxWireTetrahedron(GLdouble radius) void auxSolidTetrahedron(GLdouble radius) |
繪制十二面體
|
void auxWireDodecahedron(GLdouble radius) void auxSolidDodecahedron(GLdouble radius) |
繪制圓錐
|
void auxWireCone(GLdouble radius,GLdouble height) void auxSolidCone(GLdouble radius,GLdouble height) |
繪制茶壺
|
void auxWireTeapot(GLdouble size) void aucSolidTeapot(GLdouble size) |
表6-1
|
以上物體均以各自中心為原點(diǎn)繪制,所有坐標(biāo)都已單位化,可以縮放。
6.1.5 背景過程管理
void auxIdleFunc(void *func)
定義空閑狀態(tài)執(zhí)行函數(shù)。參數(shù)func是一個(gè)指針,指向所要執(zhí)行的函數(shù)功能。當(dāng)它為零時(shí),func執(zhí)行無(wú)效。
6.1.6 程序運(yùn)行
void auxMainLoop(void(*displayFunc)(void))
定義場(chǎng)景繪制循環(huán)函數(shù)。displayFunc指針指向場(chǎng)景繪制函數(shù)。當(dāng)窗口需要更新或場(chǎng)景發(fā)生改變時(shí),程序便調(diào)用它所指的函數(shù),重新繪制場(chǎng)景。
6.2、輔助庫(kù)應(yīng)用示例
下面舉一個(gè)輔助庫(kù)的應(yīng)用例子,testaux.c:
例6-1 輔助庫(kù)應(yīng)用例程 testaux.c
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w,GLsizei h);
void CALLBACK display(void);
void myinit(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
}
void CALLBACK myReshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-1.5,1.5,-10.0,10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void CALLBACK display(void)
{
glColor3f(1.0,1.0,0.0);
auxWireSphere(1.0);
glFlush();
}
void main(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("AUX_SAMPLE");
myinit();
auxReshapeFunc(myReshape);
auxMainLoop(display);
}
圖6-1 網(wǎng)狀球體 |
以上程序運(yùn)行結(jié)果是在屏幕窗口內(nèi)繪制一個(gè)黃色的網(wǎng)狀球體,這個(gè)程序充分體現(xiàn)了輔助庫(kù)的基本應(yīng)用方法。
首先,在主函數(shù)中用輔助庫(kù)函數(shù)定義一個(gè)窗口auxInitWindow(),然后初始化顏色myinit(),這些在第一章中已說(shuō)明。接下來(lái)是兩個(gè)十分重要的函數(shù) auxReshapeFunc()和auxMainLoop(),參數(shù)都是一個(gè)函數(shù)指針,指向的都是回調(diào)函數(shù)(回調(diào)函數(shù)定義用CALLBACK說(shuō)明)。
前者是窗口形狀重定函數(shù),參數(shù)指針指向函數(shù)myReshape(),它的兩個(gè)參數(shù)就是窗口的新寬度和新高度。然后用glViewport(0, 0, w, h)重定視口,并且在新視口內(nèi)重新定義投影矩陣,
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-1.5,1.5,-10.0,10.0);
即先用glMatrixMode()說(shuō)明當(dāng)前矩陣操作與投影有關(guān)GL_PROJECTION,再用glLoadIdentity()將矩陣清為單位矩陣,避免受其它矩陣操作的干擾;然后調(diào)用glOrtho()對(duì)物體進(jìn)行正射投影,并且用判斷語(yǔ)句給出了兩種情況,使投影后圖像的比例與視點(diǎn)匹配,避免比例失調(diào)。
再下來(lái)調(diào)用glMatrixMode()將矩陣操作改為對(duì)觀察物體有關(guān)的方式GL_MODELVIEW,同樣用 glLoadIdentity()清矩陣。后者是主函數(shù)循環(huán)函數(shù),參數(shù)指針指向函數(shù)display(),即繪制物體。當(dāng)窗口需要更新或物體發(fā)生改變時(shí),程序便調(diào)用它重新繪制。以上例子是輔助庫(kù)的最基本應(yīng)用,復(fù)雜的應(yīng)用將在后續(xù)的章節(jié)中詳細(xì)介紹。
七、OpenGL建模
OpenGL基本庫(kù)提供了大量繪制各種類型圖元的方法,輔助庫(kù)也提供了不少描述復(fù)雜三維圖形的函數(shù)。這一章主要介紹基本圖元,如點(diǎn)、線、多邊形,有了這些圖元,就可以建立比較復(fù)雜的模型了。
7.1、描述圖元
OpenGL是三維圖形的函數(shù)庫(kù),它所定義的點(diǎn)、線、多邊形等圖元與一般的定義不太一樣,存在一定的差別。對(duì)編程者來(lái)說(shuō),能否理解二者之間的差別十分重要。一種差別源于基于計(jì)算機(jī)計(jì)算的限制。OpenGL中所有浮點(diǎn)計(jì)算精度有限,故點(diǎn)、線、多邊形的坐標(biāo)值存在一定的誤差。另一種差別源于位圖顯示的限制。以這種方式顯示圖形,最小的顯示圖元是一個(gè)象素,盡管每個(gè)象素寬度很小,但它們?nèi)匀槐葦?shù)學(xué)上所定義的點(diǎn)或線寬要大得多。當(dāng)用OpenGL 進(jìn)行計(jì)算時(shí),雖然是用一系列浮點(diǎn)值定義點(diǎn)串,但每個(gè)點(diǎn)仍然是用單個(gè)象素顯示,只是近似擬合。
OpenGL圖元是抽象的幾何概念,不是真實(shí)世界中的物體,因此須用相關(guān)的數(shù)學(xué)模型來(lái)描述。
7.1.1 齊次坐標(biāo)(Homogeneous Coordinate)
在空間直角坐標(biāo)系中,任意一點(diǎn)可用一個(gè)三維坐標(biāo)矩陣[x y z]表示。如果將該點(diǎn)用一個(gè)四維坐標(biāo)的矩陣[Hx Hy Hz H]表示時(shí),則稱為齊次坐標(biāo)表示方法。在齊次坐標(biāo)中,最后一維坐標(biāo)H稱為比例因子。
在OpenGL中,二維坐標(biāo)點(diǎn)全看作三維坐標(biāo)點(diǎn),所有的點(diǎn)都用齊次坐標(biāo)來(lái)描述,統(tǒng)一作為三維齊次點(diǎn)來(lái)處理。每個(gè)齊次點(diǎn)用一個(gè)向量(x, y, z, w)表示,其中四個(gè)元素全不為零。齊次點(diǎn)具有下列幾個(gè)性質(zhì):
1)如果實(shí)數(shù)a非零,則(x, y, x, w)和(ax, ay, az, aw)表示同一個(gè)點(diǎn),類似于x/y = (ax)/( ay)。
2)三維空間點(diǎn)(x, y, z)的齊次點(diǎn)坐標(biāo)為(x, y, z, 1.0),二維平面點(diǎn)(x,y)的齊次坐標(biāo)為(x, y, 0.0, 1.0)。
3)當(dāng)w不為零時(shí),齊次點(diǎn)坐標(biāo)(x, y, z, w)即三維空間點(diǎn)坐標(biāo)(x/w, y/w, z/w);當(dāng)w為零時(shí),齊次點(diǎn)(x, y, z, 0.0)表示此點(diǎn)位于某方向的無(wú)窮遠(yuǎn)處。
注意:OpenGL中指定w大于或等于0.0。
7.1.2 點(diǎn)(Point)
用浮點(diǎn)值表示的點(diǎn)稱為頂點(diǎn)(Vertex)。所有頂點(diǎn)在OpenGL內(nèi)部計(jì)算時(shí)都作為三維點(diǎn)處理,用二維坐標(biāo)(x, y)定義的點(diǎn)在OpenGL中默認(rèn)z值為0。所有頂點(diǎn)坐標(biāo)用齊次坐標(biāo)(x, y, z, w) 表示,如果w不為0.0,這些齊次坐標(biāo)表示的頂點(diǎn)即為三維空間點(diǎn)(x/w, y/w, z/w)。編程者可以自己指定w值,但很少這樣做。一般來(lái)說(shuō),w缺省為1.0。
7.1.3 線(Line)
在OpenGL中,線代表線段(Line Segment),不是數(shù)學(xué)意義上的那種沿軸兩個(gè)方向無(wú)限延伸的線。這里的線由一系列頂點(diǎn)順次連結(jié)而成,有閉合和不閉合兩種。見圖7-1所示。
圖7-1 線段的兩種連結(jié)方式 |
7.1.4 多邊形(Polygon)
OpenGL中定義的多邊形是由一系列線段依次連結(jié)而成的封閉區(qū)域。這些線段不能交叉,區(qū)域內(nèi)不能有空洞,多邊形必須在凸多邊形,否則不能被OpenGL函數(shù)接受。合法和非法多邊形圖示見圖7-2。
圖7-2 合法和非法多邊形 |
OpenGL多邊形可以是平面多邊形,即所有頂點(diǎn)在一個(gè)平面上,也可以是空間多邊形。更復(fù)雜的多邊形將在提高篇中介紹。
7.2、繪制圖元
7.2.1 定義頂點(diǎn)
在OpenGL中,所有幾何物體最終都由有一定順序的頂點(diǎn)集來(lái)描述。
函數(shù)glVertex{234}{sifd}[v](TYPE coords)可以用二維、三維或齊次坐標(biāo)定義頂點(diǎn)。舉例如下:
glVertex2s(2,3);
glVertex3d(0.0,1.0,3.1414926535);
glVertex4f(2.4,1.0,-2.2,2.0);
GLfloat pp[3]={5.0,2.0,10.2};
glVertex3fv(pp);
第一例子表示一個(gè)空間頂點(diǎn)(2, 3, 0),第二個(gè)例子表示用雙精度浮點(diǎn)數(shù)定義一個(gè)頂點(diǎn),第三個(gè)例子表示用齊次坐標(biāo)定義一個(gè)頂點(diǎn),其真實(shí)坐標(biāo)為(1.2, 0.5, -1.1),最后一個(gè)例子表示用一個(gè)指針(或數(shù)組)定義頂點(diǎn)。
7.2.2 構(gòu)造幾何圖元
在實(shí)際應(yīng)用中,通常用一組相關(guān)的頂點(diǎn)序列以一定的方式組織起來(lái)定義某個(gè)幾何圖元,而不采用單獨(dú)定義多個(gè)頂點(diǎn)來(lái)構(gòu)造幾何圖元。在OpenGL中,所有被定義的頂點(diǎn)必須放在glBegain()和glEnd()兩個(gè)函數(shù)之間才能正確表達(dá)一個(gè)幾何圖元或物體,否則,glVertex*()不完成任何操作。如:
glBegin(GL_POLYGON);
glVertex2f(0.0,0.0);
glVertex2f(0.0,3.0);
glVertex2f(3.0,3.0);
glVertex2f(4.0,1.5);
glVertex2f(3.0,0.0);
glEnd();
以上這段程序定義了一個(gè)多邊形,如果將glBegin()中的參數(shù)GL_POLYGON改為GL_POINTS,則圖形變?yōu)橐唤M頂點(diǎn)(5個(gè)),見圖7-3所示。
圖7-3 繪制多邊形或一組頂點(diǎn) |
點(diǎn)函數(shù)glBegin(GLenum mode)標(biāo)志描述一個(gè)幾何圖元的頂點(diǎn)列表的開始,其參數(shù)mode表示幾何圖元的描述類型。所有類型及說(shuō)明見表7-1所示,相應(yīng)的圖示見圖7-4。
類型 | 說(shuō)明 |
GL_POINTS | 單個(gè)頂點(diǎn)集 |
GL_LINES | 多組雙頂點(diǎn)線段 |
GL_POLYGON | 單個(gè)簡(jiǎn)單填充凸多邊形 |
GL_TRAINGLES | 多組獨(dú)立填充三角形 |
GL_QUADS | 多組獨(dú)立填充四邊形 |
GL_LINE_STRIP | 不閉合折線 |
GL_LINE_LOOP | 閉合折線 |
GL_TRAINGLE_STRIP | 線型連續(xù)填充三角形串 |
GL_TRAINGLE_FAN | 扇形連續(xù)填充三角形串 |
GL_QUAD_STRIP | 連續(xù)填充四邊形串 |
表7-1 幾何圖元類型和說(shuō)明
|
圖7-4 幾何圖元類型 |
函數(shù)glEnd()標(biāo)志頂點(diǎn)列表的結(jié)束。
從圖7-4中可看出,可以采用許多方法構(gòu)造幾何圖元,這些方法僅僅依賴于所給的頂點(diǎn)數(shù)據(jù)。
在glBegin()和glEnd()之間最重要的信息就是由函數(shù)glVertex*()定義的頂點(diǎn),必要時(shí)也可為每個(gè)頂點(diǎn)指定顏色、法向、紋理坐標(biāo)或其他,即調(diào)用相關(guān)的函數(shù),見表7-2所示,具體用法以后會(huì)逐步介紹。
函數(shù) | 函數(shù)意義 |
glVertex*() | 設(shè)置頂點(diǎn)坐標(biāo) |
glColor*() | 設(shè)置當(dāng)前顏色 |
glIndex*() | 設(shè)置當(dāng)前顏色表 |
glNormal*() | 設(shè)置法向坐標(biāo) |
glEvalCoord*() | 產(chǎn)生坐標(biāo) |
glCallList(),glCallLists() | 執(zhí)行顯示列表 |
glTexCoord*() | 設(shè)置紋理坐標(biāo) |
glEdgeFlag*() | 控制邊界繪制 |
glMaterial*() | 設(shè)置材質(zhì) |
表7-2 在glBegin()和glEnd()之間可調(diào)用的函數(shù)
|
看如下幾句:
glBegin(GL_POINTS);
glColor3f(1.0,0.0,0.0); /* red color */
glVertex(...);
glColor3f(0.0,1.0,0.0); /* green color */
glColor3f(0.0,0.0,1.0); /* blue color */
glVertex(...);
glVertex(...);
glEnd();
顏色等的設(shè)置只對(duì)當(dāng)前點(diǎn)或后續(xù)點(diǎn)有效。上一例中第一個(gè)點(diǎn)是紅色,第二個(gè)點(diǎn)和第三個(gè)點(diǎn)都是藍(lán)色。其中設(shè)置綠色時(shí),之后沒有頂點(diǎn)操作,而是設(shè)置藍(lán)色,故只有當(dāng)前藍(lán)色對(duì)緊跟其后的兩個(gè)頂點(diǎn)有效。
為了更好地理解構(gòu)造幾何圖元函數(shù)的用法,下面舉一個(gè)簡(jiǎn)單的例子:
例7-3 幾何圖元構(gòu)造例程(drawgeom.c)
#include "glos.h"
#include<GL/gl.h>
#include<GL/glaux.h>
void myinit(void);
void DrawMyObjects(void);
void CALLBACK myReshape(GLsizei w,GLsizei h);
void CALLBACK display(void);
void myinit(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);
}
void CALLBACK myReshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-20.0,20.0,-20.0*(GLfloat)h/(GLfloat)w, 20.0*(GLfloat)h/(GLfloat)w,-50.0,50.0);
else
glOrtho(-20.0*(GLfloat)h/(GLfloat)w, 20.0*(GLfloat)h/(GLfloat)w,-20.0,20.0,-50.0,50.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void CALLBACK display(void)
{
glColor3f(1.0,1.0,0.0);
DrawMyObjects();
glFlush();
}
void DrawMyObjects(void)
{
/* draw some points */
glBegin(GL_POINTS);
glColor3f(1.0,0.0,0.0);
glVertex2f(-10.0,11.0);
glColor3f(1.0,1.0,0.0);
glVertex2f(-9.0,10.0);
glColor3f(0.0,1.0,1.0);
glVertex2f(-8.0,12.0);
glEnd();
/* draw some line_segments */
glBegin(GL_LINES);
glColor3f(1.0,1.0,0.0);
glVertex2f(-11.0,8.0);
glVertex2f(-7.0,7.0);
glColor3f(1.0,0.0,1.0);
glVertex2f(-11.0,9.0);
glVertex2f(-8.0,6.0);
glEnd();
/* draw one opened_line */
glBegin(GL_LINE_STRIP);
glColor3f(0.0,1.0,0.0);
glVertex2f(-3.0,9.0);
glVertex2f(2.0,6.0);
glVertex2f(3.0,8.0);
glVertex2f(-2.5,6.5);
glEnd();
/* draw one closed_line */
glBegin(GL_LINE_LOOP);
glColor3f(0.0,1.0,1.0);
glVertex2f(7.0,7.0);
glVertex2f(8.0,8.0);
glVertex2f(9.0,6.5);
glVertex2f(10.3,7.5);
glVertex2f(11.5,6.0);
glVertex2f(7.5,6.0);
glEnd();
/* draw one filled_polygon */
glBegin(GL_POLYGON);
glColor3f(0.5,0.3,0.7);
glVertex2f(-7.0,2.0);
glVertex2f(-8.0,3.0);
glVertex2f(-10.3,0.5);
glVertex2f(-7.5,-2.0);
glVertex2f(-6.0,-1.0);
glEnd();
/* draw some filled_quandrangles */
glBegin(GL_QUADS);
glColor3f(0.7,0.5,0.2);
glVertex2f(0.0,2.0);
glVertex2f(-1.0,3.0);
glVertex2f(-3.3,0.5);
glVertex2f(-0.5,-1.0);
glColor3f(0.5,0.7,0.2);
glVertex2f(3.0,2.0);
glVertex2f(2.0,3.0);
glVertex2f(0.0,0.5);
glVertex2f(2.5,-1.0);
glEnd();
/* draw some filled_strip_quandrangles */
glBegin(GL_QUAD_STRIP);
glVertex2f(6.0,-2.0);
glVertex2f(5.5,1.0);
glVertex2f(8.0,-1.0);
glColor3f(0.8,0.0,0.0);
glVertex2f(9.0,2.0);
glVertex2f(11.0,-2.0);
glColor3f(0.0,0.0,0.8);
glVertex2f(11.0,2.0);
glVertex2f(13.0,-1.0);
glColor3f(0.0,0.8,0.0);
glVertex2f(14.0,1.0);
glEnd();
/* draw some filled_triangles */
glBegin(GL_TRIANGLES);
glColor3f(0.2,0.5,0.7);
glVertex2f(-10.0,-5.0);
glVertex2f(-12.3,-7.5);
glVertex2f(-8.5,-6.0);
glColor3f(0.2,0.7,0.5);
glVertex2f(-8.0,-7.0);
glVertex2f(-7.0,-4.5);
glVertex2f(-5.5,-9.0);
glEnd();
/* draw some filled_strip_triangles */
glBegin(GL_TRIANGLE_STRIP);
glVertex2f(-1.0,-8.0);
glVertex2f(-2.5,-5.0);
glColor3f(0.8,0.8,0.0);
glVertex2f(1.0,-7.0);
glColor3f(0.0,0.8,0.8);
glVertex2f(2.0,-4.0);
glColor3f(0.8,0.0,0.8);
glVertex2f(4.0,-6.0);
glEnd();
/* draw some filled_fan_triangles */
glBegin(GL_TRIANGLE_FAN);
glVertex2f(8.0,-6.0);
glVertex2f(10.0,-3.0);
glColor3f(0.8,0.2,0.5);
glVertex2f(12.5,-4.5);
glColor3f(0.2,0.5,0.8);
glVertex2f(13.0,-7.5);
glColor3f(0.8,0.5,0.2);
glVertex2f(10.5,-9.0);
glEnd();
}
void main(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("Geometric Primitive Types");
myinit();
auxReshapeFunc(myReshape);
auxMainLoop(display);
}
以上程序運(yùn)行結(jié)果就是圖7-4所示的內(nèi)容,這個(gè)例子很好地說(shuō)明了幾何圖元的類型及顏色等函數(shù)的用法。希望讀者自己仔細(xì)分析每個(gè)物體的繪制方法,體會(huì)其中的關(guān)鍵之處,達(dá)到舉一反三的效果。當(dāng)然,還可利用上一章輔助庫(kù)中提供的基本三維圖元構(gòu)造比較復(fù)雜的物體,你不妨也試一試。
八、OpenGL變換
OpenGL變換是本篇的重點(diǎn)內(nèi)容,它包括計(jì)算機(jī)圖形學(xué)中最基本的三維變換,即幾何變換、投影變換、裁剪變換、視口變換,以及針對(duì)OpenGL的特殊變換概念理解和用法,如相機(jī)模擬、矩陣堆棧等。學(xué)好了這章,才開始真正走進(jìn)三維世界。
8.1、從三維空間到二維平面
8.1.1 相機(jī)模擬
在真實(shí)世界里,所有的物體都是三維的。但是,這些三維物體在計(jì)算機(jī)世界中卻必須以二維平面物體的形式表現(xiàn)出來(lái)。那么,這些物體是怎樣從三維變換到二維的呢?下面我們采用相機(jī)(Camera)模擬的方式來(lái)講述這個(gè)概念,如圖8-1所示。
圖8-1 相機(jī)模擬 |
實(shí)際上,從三維空間到二維平面,就如同用相機(jī)拍照一樣,通常都要經(jīng)歷以下幾個(gè)步驟 (括號(hào)內(nèi)表示的是相應(yīng)的圖形學(xué)概念):
第一步,將相機(jī)置于三角架上,讓它對(duì)準(zhǔn)三維景物(視點(diǎn)變換,Viewing Transformation)。
第二步,將三維物體放在適當(dāng)?shù)奈恢茫P妥儞Q,Modeling Transformation)。
第三步,選擇相機(jī)鏡頭并調(diào)焦,使三維物體投影在二維膠片上(投影變換,Projection Transformation)。
第四步,決定二維像片的大小(視口變換,Viewport Transformation)。
這樣,一個(gè)三維空間里的物體就可以用相應(yīng)的二維平面物體表示了,也就能在二維的電腦屏幕上正確顯示了。
8.1.2 三維圖形顯示流程
運(yùn)用相機(jī)模擬的方式比較通俗地講解了三維圖形顯示的基本過程,但在具體應(yīng)用OpenGL函數(shù)庫(kù)編程時(shí),還必須了解三維圖形世界中的幾個(gè)特殊坐標(biāo)系的概念,以及用這些概念表達(dá)的三維圖形顯示流程。
計(jì)算機(jī)本身只能處理數(shù)字,圖形在計(jì)算機(jī)內(nèi)也是以數(shù)字的形式進(jìn)行加工和處理的。大家都知道,坐標(biāo)建立了圖形和數(shù)字之間的聯(lián)系。為了使被顯示的物體數(shù)字化,要在被顯示的物體所在的空間中定義一個(gè)坐標(biāo)系。這個(gè)坐標(biāo)系的長(zhǎng)度單位和坐標(biāo)軸的方向要適合對(duì)被顯示物體的描述,這個(gè)坐標(biāo)系稱為世界坐標(biāo)系。
計(jì)算機(jī)對(duì)數(shù)字化的顯示物體作了加工處理后,要在圖形顯示器上顯示,這就要在圖形顯示器屏幕上定義一個(gè)二維直角坐標(biāo)系,這個(gè)坐標(biāo)系稱為屏幕坐標(biāo)系。這個(gè)坐標(biāo)系坐標(biāo)軸的方向通常取成平行于屏幕的邊緣,坐標(biāo)原點(diǎn)取在左下角,長(zhǎng)度單位常取成一個(gè)象素的長(zhǎng)度,大小可以是整型數(shù)。
為了使顯示的物體能以合適的位置、大小和方向顯示出來(lái),必須要通過投影。投影的方法有兩種,即正射投影和透視投影。
有時(shí)為了突出圖形的一部分,只把圖形的某一部分顯示出來(lái),這時(shí)可以定義一個(gè)三維視景體(Viewing Volume)。正射投影時(shí)一般是一個(gè)長(zhǎng)方體的視景體,透視投影時(shí)一般是一個(gè)棱臺(tái)似的視景體。只有視景體內(nèi)的物體能被投影在顯示平面上,其他部分則不能。在屏幕窗口內(nèi)可以定義一個(gè)矩形,稱為視口(Viewport),視景體投影后的圖形就在視口內(nèi)顯示。
為了適應(yīng)物理設(shè)備坐標(biāo)和視口所在坐標(biāo)的差別,還要作一適應(yīng)物理坐標(biāo)的變換。這個(gè)坐標(biāo)系稱為物理設(shè)備坐標(biāo)系。根據(jù)上面所述,三維圖形的顯示流程應(yīng)如圖8-2所示。
圖8-2 三維圖形的顯示流程 |
8.1.3 基本變換簡(jiǎn)單分析
下面舉一個(gè)簡(jiǎn)單的變換例子,cube.c:
例8-4 簡(jiǎn)單變換例程(cube.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void CALLBACK display (void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f (1.0, 1.0, 1.0);
glLoadIdentity (); /* clear the matrix */
glTranslatef (0.0, 0.0, -5.0); /* viewing transformation */
glScalef (1.0, 2.0, 1.0); /* modeling transformation */
auxWireCube(1.0); /* draw the cube */
glFlush();
}
void myinit (void)
{
glShadeModel (GL_FLAT);
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glMatrixMode (GL_PROJECTION); /* prepare for and then */
glLoadIdentity (); /* define the projection */
glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); /* transformation */
glMatrixMode (GL_MODELVIEW); /* back to modelview matrix */
glViewport (0, 0, w, h); /* define the viewport */
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Perspective 3-D Cube");
myinit ();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序運(yùn)行結(jié)果就是繪制一個(gè)三維的正面透視立方體。其中已經(jīng)用到了相機(jī)模擬中提到的四種基本變換,即視點(diǎn)變換、模型變換、投影變換和視口變換。
|
||
圖8-3 三維的正面透視立方體 |
下面簡(jiǎn)單分析一下整個(gè)程序過程:
1)視點(diǎn)變換。視點(diǎn)變換是在視點(diǎn)坐標(biāo)系中進(jìn)行的。視點(diǎn)坐標(biāo)系于一般的物體所在的世界坐標(biāo)系不同,它遵循左手法則,即左手大拇指指向Z正軸,與之垂直的四個(gè)手指指向X正軸,四指彎曲90度的方向是Y正軸。而世界坐標(biāo)系遵循右手法則的。如圖8-4所示。當(dāng)矩陣初始化glLoadIdentity()后,調(diào)用glTranslatef()作視點(diǎn)變換。函數(shù)參數(shù)(x, y, z)表示視點(diǎn)或相機(jī)在視點(diǎn)坐標(biāo)系中移動(dòng)的位置,這里z=-5.0,意思是將相機(jī)沿Z負(fù)軸移動(dòng)5個(gè)單位。
通常相機(jī)位置缺省值同場(chǎng)景中的物體一樣,都在原點(diǎn)處,而且相機(jī)初始方向都指向Z負(fù)軸。
這里相機(jī)移走后,仍然對(duì)準(zhǔn)立方體。如果相機(jī)需要指向另一方向,則調(diào)用glRotatef()可以改變。
圖8-4 視點(diǎn)坐標(biāo)系與世界坐標(biāo)系 |
2)模型變換。模型變換是在世界坐標(biāo)系中進(jìn)行的。在這個(gè)坐標(biāo)系中,可以對(duì)物體實(shí)施平移 glTranslatef()、旋轉(zhuǎn)glRotatef()和放大縮小glScalef()。例子里只對(duì)物體進(jìn)行比例變換,glScalef(sx, sy, sz)的三個(gè)參數(shù)分別是X、Y、Z軸向的比例變換因子。缺省時(shí)都為1.0,即物體沒變化。程序中物體Y軸比例為2.0,其余都為1.0,就是說(shuō)將立方體變成長(zhǎng)方體。
3)投影變換。投影變換類似于選擇相機(jī)的鏡頭。本例中調(diào)用了一個(gè)透視投影函數(shù) glFrustum(),在調(diào)用它之前先要用glMatrixMode()說(shuō)明當(dāng)前矩陣方式是投影GL_PROJECTION。這個(gè)投影函數(shù)一共有六個(gè)參數(shù),由它們可以定義一個(gè)棱臺(tái)似的視景體。即視景體內(nèi)的部分可見,視景體外的部分不可見,這也就包含了三維裁剪變換。
4)視口變換。視口變換就是將視景體內(nèi)投影的物體顯示在二維的視口平面上。通常,都調(diào)用函數(shù)glViewport()來(lái)定義一個(gè)視口,這個(gè)過程類似于將照片放大或縮小。
總而言之,一旦所有必要的變換矩陣被指定后,場(chǎng)景中物體的每一個(gè)頂點(diǎn)都要按照被指定的變換矩陣序列逐一進(jìn)行變換。注意:OpenGL 中的物體坐標(biāo)一律采用齊次坐標(biāo),即(x, y, z, w),故所有變換矩陣都采用4X4矩陣。一般說(shuō)來(lái),每個(gè)頂點(diǎn)先要經(jīng)過視點(diǎn)變換和模型變換,然后進(jìn)行指定的投影,如果它位于視景體外,則被裁剪掉。最后,余下的已經(jīng)變換過的頂點(diǎn)x、y、z坐標(biāo)值都用比例因子w除,即x/w、y/w、z/w,再映射到視口區(qū)域內(nèi),這樣才能顯示在屏幕上。
8.2、幾何變換
實(shí)際上,上述所說(shuō)的視點(diǎn)變換和模型變換本質(zhì)上都是一回事,即圖形學(xué)中的幾何變換。
只是視點(diǎn)變換一般只有平移和旋轉(zhuǎn),沒有比例變換。當(dāng)視點(diǎn)進(jìn)行平移或旋轉(zhuǎn)時(shí),視點(diǎn)坐標(biāo)系中的物體就相當(dāng)于在世界坐標(biāo)系中作反方向的平移或旋轉(zhuǎn)。因此,從某種意義上講,二者可以統(tǒng)一,只是各自出發(fā)點(diǎn)不一樣而已。讀者可以根據(jù)具體情況,選擇其中一個(gè)角度去考慮,這樣便于理解。
8.2.1 兩個(gè)矩陣函數(shù)解釋
這里先解釋兩個(gè)基本OpenGL矩陣操作函數(shù),便于以后章節(jié)的講述。函數(shù)解釋如下:
void glLoadMatrix{fd}(const TYPE *m)
設(shè)置當(dāng)前矩陣中的元素值。函數(shù)參數(shù)*m是一個(gè)指向16個(gè)元素(m0, m1, ..., m15)的指針,這16個(gè)元素就是當(dāng)前矩陣M中的元素,其排列方式如下:
M = | | m0 m4 m8 m12 | | m1 m5 m9 m13 | | m2 m6 m10 m14 | | m3 m7 m11 M15 | |
void glMultMatrix{fd}(const TYPE *m)
用當(dāng)前矩陣去乘*m所指定的矩陣,并將結(jié)果存放于*m中。當(dāng)前矩陣可以是用glLoadMatrix() 指定的矩陣,也可以是其它矩陣變換函數(shù)的綜合結(jié)果。
當(dāng)幾何變換時(shí),調(diào)用OpenGL的三個(gè)變換函數(shù)glTranslate*()、glRotate*()和glScale*(),實(shí)質(zhì)上相當(dāng)于產(chǎn)生了一個(gè)近似的平移、旋轉(zhuǎn)和比例矩陣,然后調(diào)用glMultMatrix()與當(dāng)前矩陣相乘。但是直接調(diào)用這三個(gè)函數(shù)程序運(yùn)行得快一些,因OpenGL自動(dòng)能計(jì)算矩陣。
8.2.2 平移
平移變換函數(shù)如下:
void glTranslate{fd}(TYPE x,TYPE y,TYPE z)
三個(gè)函數(shù)參數(shù)就是目標(biāo)分別沿三個(gè)軸向平移的偏移量。這個(gè)函數(shù)表示用這三個(gè)偏移量生成的矩陣乘以當(dāng)前矩陣。當(dāng)參數(shù)是(0.0,0.0,0.0)時(shí),表示對(duì)函數(shù)glTranslate*()的操作是單位矩陣,也就是對(duì)物體沒有影響。平移示意如圖8-5所示。
圖8-5 平移示意圖 |
8.2.3 旋轉(zhuǎn)
旋轉(zhuǎn)變換函數(shù)如下:
void glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)
函數(shù)中第一個(gè)參數(shù)是表示目標(biāo)沿從點(diǎn)(x, y, z)到原點(diǎn)的方向逆時(shí)針旋轉(zhuǎn)的角度,后三個(gè)參數(shù)是旋轉(zhuǎn)的方向點(diǎn)坐標(biāo)。這個(gè)函數(shù)表示用這四個(gè)參數(shù)生成的矩陣乘以當(dāng)前矩陣。當(dāng)角度參數(shù)是0.0時(shí),表示對(duì)物體沒有影響。旋轉(zhuǎn)示意如圖8-6所示。
圖8-6 旋轉(zhuǎn)示意圖 |
8.2.3 縮放和反射
縮放和反射變換函數(shù)如下:
void glScale{fd}(TYPE x,TYPE y,TYPE z)
三個(gè)函數(shù)參數(shù)值就是目標(biāo)分別沿三個(gè)軸向縮放的比例因子。這個(gè)函數(shù)表示用這三個(gè)比例因子生成的矩陣乘以當(dāng)前矩陣。這個(gè)函數(shù)能完成沿相應(yīng)的軸對(duì)目標(biāo)進(jìn)行拉伸、壓縮和反射三項(xiàng)功能。當(dāng)參數(shù)是(1.0, 1.0, 1.0)時(shí),表示對(duì)函數(shù)glScale*()操作是單位矩陣,也就是對(duì)物體沒有影響。當(dāng)其中某個(gè)參數(shù)為負(fù)值時(shí),表示將對(duì)目標(biāo)進(jìn)行相應(yīng)軸的反射變換,且這個(gè)參數(shù)不為1.0,則還要進(jìn)行相應(yīng)軸的縮放變換。最好不要令三個(gè)參數(shù)值都為零,這將導(dǎo)致目標(biāo)沿三軸都縮為零。縮放和反射示意如圖8-7所示。
圖8-7 縮放和反射示意圖 |
8.2.5 幾何變換舉例
以上介紹了三個(gè)基本幾何變換函數(shù),下面舉一個(gè)簡(jiǎn)單的例子進(jìn)一步說(shuō)明它們的用法。程序如下:
例 8-5 幾何變換例程(geomtrsf.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void draw_triangle(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void draw_triangle(void)
{
glBegin(GL_LINE_LOOP);
glVertex2f(0.0, 25.0);
glVertex2f(25.0, -25.0);
glVertex2f(-25.0, -25.0);
glEnd();
}
void CALLBACK display(void)
{
glClearColor (0.0, 0.0, 0.0, 1.0);
glClear (GL_COLOR_BUFFER_BIT);
/* draw an original triangle */
glLoadIdentity ();
glColor3f (1.0, 1.0, 1.0); /* white */
draw_triangle ();
/* translating a triangle along X_axis */
glLoadIdentity ();
glTranslatef (-20.0, 0.0, 0.0);
glColor3f(1.0,0.0,0.0); /* red */
draw_triangle ();
/* scaling a triangle along X_axis by 1.5 and along Y_axis by 0.5 */
glLoadIdentity();
glScalef (1.5, 0.5, 1.0);
glColor3f(0.0,1.0,0.0); /* green */
draw_triangle ();
/* rotating a triangle in a counterclockwise direction about Z_axis */
glLoadIdentity ();
glRotatef (90.0, 0.0, 0.0, 1.0);
glColor3f(0.0,0.0,1.0); /* blue */
draw_triangle ();
/* scaling a triangle along Y_axis and reflecting it about Y_axis */
glLoadIdentity();
glScalef (1.0, -0.5, 1.0);
glColor3f(1.0,1.0,0.0); /* yellow */
draw_triangle ();
glFlush();
}
void myinit (void)
{
glShadeModel (GL_FLAT);
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w, 50.0*(GLfloat)h/(GLfloat)w,-1.0,1.0);
else
glOrtho(-50.0*(GLfloat)w/(GLfloat)h, 50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0,-1.0,1.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Geometric Transformations");
myinit ();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序運(yùn)行結(jié)果:第一個(gè)白色三角形是原始三角形,第二個(gè)紅色三角形是白三角沿X 負(fù)軸平移后的三角形,第三個(gè)綠色三角形是白三角分別沿X軸和Y軸比例變換后的三角形,第四個(gè)藍(lán)色三角形是白三角繞Z正軸逆時(shí)針轉(zhuǎn)90度后的三角形,第五個(gè)黃色三角形是白三角沿Y軸方向縮小一倍且相對(duì)于X軸作反射后形成的三角形。
圖8-8 三角形的幾何變換 |
8.3、投影變換
投影變換是一種很關(guān)鍵的圖形變換,OpenGL中只提供了兩種投影方式,一種是正射投影,另一種是透視投影。不管是調(diào)用哪種投影函數(shù),為了避免不必要的變換,其前面必須加上以下兩句:
glMAtrixMode(GL_PROJECTION);
glLoadIdentity();
事實(shí)上,投影變換的目的就是定義一個(gè)視景體,使得視景體外多余的部分裁剪掉,最終圖像只是視景體內(nèi)的有關(guān)部分。本指南將詳細(xì)講述投影變換的概念以及用法。
8.3.1 正射投影(Orthographic Projection)
正射投影,又叫平行投影。這種投影的視景體是一個(gè)矩形的平行管道,也就是一個(gè)長(zhǎng)方體,如圖8-9所示。正射投影的最大一個(gè)特點(diǎn)是無(wú)論物體距離相機(jī)多遠(yuǎn),投影后的物體大小尺寸不變。這種投影通常用在建筑藍(lán)圖繪制和計(jì)算機(jī)輔助設(shè)計(jì)等方面,這些行業(yè)要求投影后的物體尺寸及相互間的角度不變,以便施工或制造時(shí)物體比例大小正確。
圖8-9 正射投影視景體 |
OpenGL正射投影函數(shù)共有兩個(gè),這在前面幾個(gè)例子中已用過。一個(gè)函數(shù)是:
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top,
GLdouble near,GLdouble far)
它創(chuàng)建一個(gè)平行視景體。實(shí)際上這個(gè)函數(shù)的操作是創(chuàng)建一個(gè)正射投影矩陣,并且用這個(gè)矩陣乘以當(dāng)前矩陣。其中近裁剪平面是一個(gè)矩形,矩形左下角點(diǎn)三維空間坐標(biāo)是(left,bottom,-near),右上角點(diǎn)是(right,top,-near);遠(yuǎn)裁剪平面也是一個(gè)矩形,左下角點(diǎn)空間坐標(biāo)是(left,bottom,-far),右上角點(diǎn)是(right,top,-far)。所有的near和far值同時(shí)為正或同時(shí)為負(fù)。如果沒有其他變換,正射投影的方向平行于Z軸,且視點(diǎn)朝向Z負(fù)軸。
這意味著物體在視點(diǎn)前面時(shí)far和near都為負(fù)值,物體在視點(diǎn)后面時(shí)far和near都為正值。另一個(gè)函數(shù)是:
void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)
它是一個(gè)特殊的正射投影函數(shù),主要用于二維圖像到二維屏幕上的投影。它的near和far缺省值分別為-1.0和1.0,所有二維物體的Z坐標(biāo)都為0.0。因此它的裁剪面是一個(gè)左下角點(diǎn)為(left,bottom)、右上角點(diǎn)為(right,top)的矩形。
8.3.2 透視投影(Perspective Projection)
透視投影符合人們心理習(xí)慣,即離視點(diǎn)近的物體大,離視點(diǎn)遠(yuǎn)的物體小,遠(yuǎn)到極點(diǎn)即為消失,成為滅點(diǎn)。它的視景體類似于一個(gè)頂部和底部都被切除掉的棱椎,也就是棱臺(tái)。這個(gè)投影通常用于動(dòng)畫、視覺仿真以及其它許多具有真實(shí)性反映的方面。
OpenGL透視投影函數(shù)也有兩個(gè),其中函數(shù)glFrustum()在8.1.3節(jié)中提到過,它所形成的視景體如圖8-10所示。
圖8-10 函數(shù)glFrustum()透視投影視景體 |
這個(gè)函數(shù)原型為:
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,
GLdouble near,GLdouble far);
它創(chuàng)建一個(gè)透視視景體。其操作是創(chuàng)建一個(gè)透視投影矩陣,并且用這個(gè)矩陣乘以當(dāng)前矩陣。這個(gè)函數(shù)的參數(shù)只定義近裁剪平面的左下角點(diǎn)和右上角點(diǎn)的三維空間坐標(biāo),即(left,bottom,-near)和(right,top,-near);最后一個(gè)參數(shù)far是遠(yuǎn)裁剪平面的Z負(fù)值,其左下角點(diǎn)和右上角點(diǎn)空間坐標(biāo)由函數(shù)根據(jù)透視投影原理自動(dòng)生成。near和far表示離視點(diǎn)的遠(yuǎn)近,它們總為正值。
另一個(gè)函數(shù)是:
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
它也創(chuàng)建一個(gè)對(duì)稱透視視景體,但它的參數(shù)定義于前面的不同,如圖8-11所示。其操作是創(chuàng)建一個(gè)對(duì)稱的透視投影矩陣,并且用這個(gè)矩陣乘以當(dāng)前矩陣。參數(shù) fovy定義視野在X-Z平面的角度,范圍是[0.0, 180.0];參數(shù)aspect是投影平面寬度與高度的比率;參數(shù)zNear和Far分別是遠(yuǎn)近裁剪面沿Z負(fù)軸到視點(diǎn)的距離,它們總為正值。
圖8-11 函數(shù)gluPerspective()透視投影視景體 |
以上兩個(gè)函數(shù)缺省時(shí),視點(diǎn)都在原點(diǎn),視線沿Z軸指向負(fù)方向。二者的應(yīng)用實(shí)例將在后續(xù)章節(jié)中介紹。
8.4、裁剪變換
在OpenGL中,空間物體的三維裁剪變換包括兩個(gè)部分:視景體裁剪和附加平面裁剪。視景體裁剪已經(jīng)包含在投影變換里,前面已述,這里不再重復(fù)。下面簡(jiǎn)單講一下平面裁剪函數(shù)的用法。
除了視景體定義的六個(gè)裁剪平面(上、下、左、右、前、后)外,用戶還可自己再定義一個(gè)或多個(gè)附加裁剪平面,以去掉場(chǎng)景中無(wú)關(guān)的目標(biāo),如圖8-12所示。
圖8-12 附加裁剪平面和視景體 |
附加平面裁剪函數(shù)為:
void glClipPlane(GLenum plane,Const GLdouble *equation);
函數(shù)定義一個(gè)附加的裁剪平面。其中參數(shù)equation指向一個(gè)擁有四個(gè)系數(shù)值的數(shù)組,這四個(gè)系數(shù)分別是裁剪平面Ax+By+Cz+D=0的A、B、 C、D值。因此,由這四個(gè)系數(shù)就能確定一個(gè)裁剪平面。參數(shù)plane是GL_CLIP_PLANEi(i=0,1,...),指定裁剪面號(hào)。
在調(diào)用附加裁剪函數(shù)之前,必須先啟動(dòng)glEnable(GL_CLIP_PLANEi),使得當(dāng)前所定義的裁剪平面有效;當(dāng)不再調(diào)用某個(gè)附加裁剪平面時(shí),可用glDisable(GL_CLIP_PLANEi)關(guān)閉相應(yīng)的附加裁剪功能。
下面這個(gè)例子不僅說(shuō)明了附加裁剪函數(shù)的用法,而且調(diào)用了gluPerspective()透視投影函數(shù),讀者可以細(xì)細(xì)體會(huì)其中的用法。例程如下:
例8-6 裁剪變換例程(clipball.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void CALLBACK display(void)
{
GLdouble eqn[4] = {1.0, 0.0, 0.0, 0.0};
glClear(GL_COLOR_BUFFER_BIT);
glColor3f (1.0, 0.0, 1.0);
glPushMatrix();
glTranslatef (0.0, 0.0, -5.0);
/* clip the left part of wire_sphere : x<0 */
glClipPlane (GL_CLIP_PLANE0, eqn);
glEnable (GL_CLIP_PLANE0);
glRotatef (-90.0, 1.0, 0.0, 0.0);
auxWireSphere(1.0);
glPopMatrix();
glFlush();
}
void myinit (void)
{
glShadeModel (GL_FLAT);
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Arbitrary Clipping Planes");
myinit ();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
圖8-13 剪取后的網(wǎng)狀半球體 |
8.5、視口變換
在前面幾節(jié)內(nèi)容中已相繼提到過視口變換,這一節(jié)將針對(duì)OpenGL來(lái)講述視口變換的原理及其相關(guān)函數(shù)的用法。運(yùn)用相機(jī)模擬方式,我們很容易理解視口變換就是類似于照片的放大與縮小。在計(jì)算機(jī)圖形學(xué)中,它的定義是將經(jīng)過幾何變換、投影變換和裁剪變換后的物體顯示于屏幕窗口內(nèi)指定的區(qū)域內(nèi),這個(gè)區(qū)域通常為矩形,稱為視口。OpenGL中相關(guān)函數(shù)是:
glViewport(GLint x,GLint y,GLsizei width, GLsizei height);
這個(gè)函數(shù)定義一個(gè)視口。函數(shù)參數(shù)(x, y)是視口在屏幕窗口坐標(biāo)系中的左下角點(diǎn)坐標(biāo),參數(shù)width和height分別是視口的寬度和高度。缺省時(shí),參數(shù)值即(0, 0, winWidth, winHeight) 指的是屏幕窗口的實(shí)際尺寸大小。所有這些值都是以象素為單位,全為整型數(shù)。
注意:在實(shí)際應(yīng)用中,視口的長(zhǎng)寬比率總是等于視景體裁剪面的長(zhǎng)寬比率。如果兩個(gè)比率不相等,那么投影后的圖像顯示于視口內(nèi)時(shí)會(huì)發(fā)生變形,如圖8-14所示。另外,屏幕窗口的改變一般不明顯影響視口的大小。因此,在調(diào)用這個(gè)函數(shù)時(shí),最好實(shí)時(shí)檢測(cè)窗口尺寸,及時(shí)修正視口的大小,保證視口內(nèi)的圖像能隨窗口的變化而變化,且不變形。
圖8-14 視景體到視口的映射 |
8.6 矩陣堆棧
學(xué)過計(jì)算機(jī)的人也許都知道這個(gè)使用頻率極高的名詞 — “堆棧”。顧名思義,堆棧指的是一個(gè)頂部打開底部封閉的柱狀物體,通常用來(lái)存放常用的東西。這些東西從頂部依次放入,但取出時(shí)也只能從頂部取出,即“先進(jìn)后出,后進(jìn)先出”。在計(jì)算機(jī)中,它常指在內(nèi)存中開辟的一塊存放某些變量的連續(xù)區(qū)域。因此,OpenGL的矩陣堆棧指的就是內(nèi)存中專門用來(lái)存放矩陣數(shù)據(jù)的某塊特殊區(qū)域。
實(shí)際上,在創(chuàng)建、裝入、相乘模型變換和投影變換矩陣時(shí),都已用到堆棧操作。一般說(shuō)來(lái),矩陣堆棧常用于構(gòu)造具有繼承性的模型,即由一些簡(jiǎn)單目標(biāo)構(gòu)成的復(fù)雜模型。例如,一輛自行車就是由兩個(gè)輪子、一個(gè)三角架及其它一些零部件構(gòu)成的。它的繼承性表現(xiàn)在當(dāng)自行車往前走時(shí),首先是前輪旋轉(zhuǎn),然后整個(gè)車身向前平移,接著是后輪旋轉(zhuǎn),然后整個(gè)車身向前平移,如此進(jìn)行下去,這樣自行車就往前走了。
矩陣堆棧對(duì)復(fù)雜模型運(yùn)動(dòng)過程中的多個(gè)變換操作之間的聯(lián)系與獨(dú)立十分有利。因?yàn)樗芯仃嚥僮骱瘮?shù)如glLoadMatrix()、glMultMatrix()、 glLoadIdentity()等只處理當(dāng)前矩陣或堆棧頂部矩陣,這樣堆棧中下面的其它矩陣就不受影響。堆棧操作函數(shù)有以下兩個(gè):
void glPushMatrix(void);
void glPopMatrix(void);
第一個(gè)函數(shù)表示將所有矩陣依次壓入堆棧中,頂部矩陣是第二個(gè)矩陣的備份;壓入的矩陣數(shù)不能太多,否則出錯(cuò)。第二個(gè)函數(shù)表示彈出堆棧頂部的矩陣,令原第二個(gè)矩陣成為頂部矩陣,接受當(dāng)前操作,故原頂部矩陣被破壞;當(dāng)堆棧中僅存一個(gè)矩陣時(shí),不能進(jìn)行彈出操作,否則出錯(cuò)。由此看出,矩陣堆棧操作與壓入矩陣的順序剛好相反,編程時(shí)要特別注意矩陣操作的順序。
為了更好地理解這兩個(gè)函數(shù),我們可以形象地認(rèn)為glPushMatrix()就是“記住自己在哪”,glPopMatrix()就是“返回自己原來(lái)所在地”。請(qǐng)看下面一例:
例8-7 堆棧操作例程(arm.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void drawPlane(void);
void CALLBACK elbowAdd (void);
void CALLBACK elbowSubtract (void);
void CALLBACK shoulderAdd (void);
void CALLBACK shoulderSubtract (void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
static int shoulder = 0, elbow = 0;
void CALLBACK elbowAdd (void)
{
elbow = (elbow + 5) % 360;
}
void CALLBACK elbowSubtract (void)
{
elbow = (elbow - 5) % 360;
}
void CALLBACK shoulderAdd (void)
{
shoulder = (shoulder + 5) % 360;
}
void CALLBACK shoulderSubtract (void)
{
shoulder = (shoulder - 5) % 360;
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0, 1.0, 1.0);
glPushMatrix();
glTranslatef (-0.5, 0.0, 0.0);
glRotatef ((GLfloat)
shoulder, 0.0, 0.0, 1.0);
glTranslatef (1.0, 0.0, 0.0);
auxWireBox(2.0, 0.2, 0.5);
glTranslatef (1.0, 0.0, 0.0);
glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);
glTranslatef (0.8, 0.0, 0.0);
auxWireBox(1.6, 0.2, 0.5);
glPopMatrix();
glFlush();
}
void myinit (void)
{
glShadeModel (GL_FLAT);
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glTranslatef (0.0, 0.0, -5.0); /* viewing transform */
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 400, 400);
auxInitWindow ("Composite Modeling Transformations");
myinit ();
auxKeyFunc (AUX_LEFT, shoulderSubtract);
auxKeyFunc (AUX_RIGHT, shoulderAdd);
auxKeyFunc (AUX_UP, elbowAdd);
auxKeyFunc (AUX_DOWN, elbowSubtract);
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
從以上例程可以看出,復(fù)雜的機(jī)械手臂是由兩個(gè)簡(jiǎn)單的長(zhǎng)方體依據(jù)一定的繼承關(guān)系構(gòu)成的,而這個(gè)繼承關(guān)系是由矩陣堆棧的順序決定的。
圖8-15 簡(jiǎn)單機(jī)械手臂的符合運(yùn)動(dòng) |
九、OpenGL顏色
幾乎所有OpenGL應(yīng)用目的都是在屏幕窗口內(nèi)繪制彩色圖形,所以顏色在OpenGL編程中占有很重要的地位。這里的顏色與繪畫中的顏色概念不一樣,它屬于RGB顏色空間,只在監(jiān)視器屏幕上顯示。另外,屏幕窗口坐標(biāo)是以象素為單位,因此組成圖形的每個(gè)象素都有自己的顏色,而這種顏色值是通過對(duì)一系列OpenGL函數(shù)命令的處理最終計(jì)算出來(lái)的。本章將講述計(jì)算機(jī)顏色的概念以及OpenGL的顏色模式、顏色定義和兩種模式應(yīng)用場(chǎng)合等內(nèi)容,若掌握好顏色的應(yīng)用,你就能走進(jìn)繽紛絢麗的色彩世界,從中享受無(wú)窮的樂趣。
9.1、計(jì)算機(jī)顏色
9.1.1 顏色生成原理
計(jì)算機(jī)顏色不同于繪畫或印刷中的顏色,顯示于計(jì)算機(jī)屏幕上每一個(gè)點(diǎn)的顏色都是由監(jiān)視器內(nèi)部的電子槍激發(fā)的三束不同顏色的光(紅、綠、藍(lán))混合而成,因此,計(jì)算機(jī)顏色通 常用R(Red)、G(Green)、B(Blue)三個(gè)值來(lái)表示,這三個(gè)值又稱為顏色分量。顏色生成原理 示意圖見圖9-1所示。
圖9-1 計(jì)算機(jī)顏色生成原理 |
9.1.2 RGB色立體(RGB Color Cube)
所有監(jiān)視器屏幕的顏色都屬于RGB顏色空間,如果用一個(gè)立方體形象地表示RGB顏色組成關(guān)系,那么就稱這個(gè)立方體為RGB色立體,如圖9-2所示。
圖9-2 RGB色立體 |
在圖中,R、G、B三值的范圍都是從0.0到1.0。如果某顏色分量越大,則表示對(duì)應(yīng)的顏色分量越亮,也就是它在此點(diǎn)所貢獻(xiàn)的顏色成分越多;反之,則越暗或越少。當(dāng)R、G、B三個(gè)值都為0.0時(shí),此點(diǎn)顏色為黑色(Black);當(dāng)三者都為1.0時(shí),此點(diǎn)顏色為白色(White);當(dāng)三個(gè)顏色分量值相等時(shí),表示三者貢獻(xiàn)一樣,因此呈現(xiàn)灰色(Grey),在圖中表現(xiàn)為從黑色頂點(diǎn)到白色頂點(diǎn)的那條對(duì)角線;當(dāng)R=1.0、G=1.0、B=0.0時(shí),此點(diǎn)顏色為黃色(Yellow);同理,R=1.0、G=0.0、B=1.0時(shí)為洋紅色,也叫品色(Magenta);R=0.0、G=1.0、B=1.0時(shí)為青色(Cyan)。
9.2、顏色模式
OpenGL顏色模式一共有兩個(gè):RGB(RGBA)模式和顏色表模式。在RGB模式下,所有的顏色定義全用R、G、B三個(gè)值來(lái)表示,有時(shí)也加上 Alpha值(與透明度有關(guān)),即RGBA模式。在顏色表模式下,每一個(gè)象素的顏色是用顏色表中的某個(gè)顏色索引值表示,而這個(gè)索引值指向了相應(yīng)的R、G、 B值。這樣的一個(gè)表成為顏色映射(Color Map)。
9.2.1 RGBA模式(RGBA Mode)
在RGBA模式下,可以用glColor*()來(lái)定義當(dāng)前顏色。其函數(shù)形式為:
void glColor3{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b);
void glColor4{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b,TYPE a);
void glColor3{b s i f d ub us ui}v(TYPE *v);
void glColor4{b s i f d ub us ui}v(TYPE *v);
設(shè)置當(dāng)前R、G、B和A值。這個(gè)函數(shù)有3和4兩種方式,在前一種方式下,a值缺省為1.0,后一種Alpha值由用戶自己設(shè)定,范圍從0.0到1.0。同樣,它也可用指針傳遞參數(shù)。另外,函數(shù)的第二個(gè)后綴的不同使用,其相應(yīng)的參數(shù)值及范圍不同,見下表9-1所示。雖然這些參數(shù)值不同,但實(shí)際上 OpenGL已自動(dòng)將它們映射在0.0到1.0或-1.0或范圍之內(nèi)。因此,靈活使用這些后綴,會(huì)給你編程帶來(lái)很大的方便。
后綴 | 數(shù)據(jù)類型 | 最小值 | 最小值映射 | 最大值 | 最大值映射 |
b | 1字節(jié)整型數(shù) | -128 | -1.0 | 127 | 1.0 |
s | 2字節(jié)整型數(shù) | -32,768 | -1.0 | 32,767 | 1.0 |
i | 4字節(jié)整型數(shù) | -2,147,483,648 | -1.0 | 2,147,483,647 | 1.0 |
ub | 1字節(jié)無(wú)符號(hào)整型數(shù) | 0 | 0.0 | 255 | 1.0 |
us | 2字節(jié)無(wú)符號(hào)整型數(shù) | 0 | 0.0 | 65,535 | 1.0 |
ui | 4字節(jié)無(wú)符號(hào)整型數(shù) | 0 | 0.0 | 4,294,967,295 | 1.0 |
表9-1 整型顏色值到浮點(diǎn)數(shù)的轉(zhuǎn)換
|
9.2.2 顏色表模式(Color_Index Mode)
在顏色表方式下,可以調(diào)用glIndex*()函數(shù)從顏色表中選取當(dāng)前顏色。其函數(shù)形式為:
void glIndex{sifd}(TYPE c);
void glIndex{sifd}v(TYPE *c);
設(shè)置當(dāng)前顏色索引值,即調(diào)色板號(hào)。若值大于顏色位面數(shù)時(shí)則取模。
9.2.3 兩種模式應(yīng)用場(chǎng)合
在大多情況下,采用RGBA模式比顏色表模式的要多,尤其許多效果處理,如陰影、光照、霧、反走樣、混合等,采用RGBA模式效果會(huì)更好些;另外,紋理映射只能在RGBA模式下進(jìn)行。下面提供幾種運(yùn)用顏色表模式的情況(僅供參考):
1)若原來(lái)應(yīng)用程序采用的是顏色表模式則轉(zhuǎn)到OpenGL上來(lái)時(shí)最好仍保持這種模式,便于移植。
2)若所用顏色不在缺省提供的顏色許可范圍之內(nèi),則采用顏色表模式。
3)在其它許多特殊處理,如顏色動(dòng)畫,采用這種模式會(huì)出現(xiàn)奇異的效果。
9.3、顏色應(yīng)用舉例
顏色是一個(gè)極具吸引力的應(yīng)用,在前面幾章中已經(jīng)逐步介紹了RGBA模式的應(yīng)用方式,這里就不再多述。下面著重說(shuō)一下顏色表模式的應(yīng)用方法,請(qǐng)看例程:
例9-1 顏色表應(yīng)用例程(cindex.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
void myinit(void);
void InitPalette(void);
void DrawColorFans(void);
void CALLBACK myReshape(GLsizei w,GLsizei h);
void CALLBACK display(void);
void myinit(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);
}
void InitPalette(void)
{
GLint j;
static GLfloat rgb[][3]={
{1.0,0.0,0.0},{1.0,0.0,0.5},{1.0,0.0,1.0},{0.0,0.0,1.0},
{0.0,1.0,1.0},{0.0,1.0,0.0},{1.0,1.0,0.0},{1.0,0.5,0.0}};
for(j=0;j<8;j++)
auxSetOneColor(j+1,rgb[j][0],rgb[j][1],rgb[j][2]);
}
void CALLBACK myReshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-12.0,12.0,-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-30.0,30.0);
else
glOrtho(-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-12.0,12.0,-30.0,30.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void CALLBACK display(void)
{
InitPalette();
DrawColorFans();
glFlush();
}
void DrawColorFans(void)
{
GLint n;
GLfloat pp[8][2]={
{7.0,-7.0},{0.0,-10.0},{-7.0,-7.0},{-10.0,0.0},
{-7.0,7.0}, {0.0,10.0},{7.0,7.0},{10.0,0.0}};
/* draw some filled_fan_triangles */
glBegin(GL_TRIANGLE_FAN);
glVertex2f(0.0,0.0);
glVertex2f(10.0,0.0);
for(n=0;n<8;n++)
{
glIndexi(n+1);
glVertex2fv(pp[n]);
}
glEnd();
}
void main(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_INDEX);
auxInitPosition(0,0,500,500);
auxInitWindow("Color Index");
myinit();
auxReshapeFunc(myReshape);
auxMainLoop(display);
}
這個(gè)程序運(yùn)行結(jié)果是在屏幕上顯示八個(gè)連成扇形的不同顏色的三角形,每個(gè)三角形的顏色定義采用顏色表模式。其中,調(diào)用了輔助庫(kù)函數(shù)auxSetOneColor()來(lái)裝載顏色映射表,即調(diào)色板。因?yàn)閷⒛硞€(gè)顏色裝載到顏色查找表(color lookup table)中的過程必須依賴窗口系統(tǒng),而OpenGL函數(shù)與窗口系統(tǒng)無(wú)關(guān),所以這里就調(diào)用輔助庫(kù)的函數(shù)來(lái)完成這個(gè)過程,然后才調(diào)用OpenGL自己的函數(shù)glIndex()設(shè)置當(dāng)前的顏色號(hào)。
圖9-3 自定義調(diào)色板 |
十、OpenGL光照
10.1、真實(shí)感圖形基本概念
真實(shí)感圖形繪制是計(jì)算機(jī)圖形學(xué)的一個(gè)重要組成部分,它綜合利用數(shù)學(xué)、物理學(xué)、計(jì)算機(jī)科學(xué)和其它科學(xué)知識(shí)在計(jì)算機(jī)圖形設(shè)備上生成象彩色照片那樣的具有真實(shí)感的圖形。一般說(shuō)來(lái),用計(jì)算機(jī)在圖形設(shè)備上生成真實(shí)感圖形必須完成以下四個(gè)步驟:一是用建模,即用一定的數(shù)學(xué)方法建立所需三維場(chǎng)景的幾何描述,場(chǎng)景的幾何描述直接影響圖形的復(fù)雜性和圖形繪制的計(jì)算耗費(fèi);二是將三維幾何模型經(jīng)過一定變換轉(zhuǎn)為二維平面透視投影圖;三是確定場(chǎng)景中所有可見面,運(yùn)用隱藏面消隱算法將視域外或被遮擋住的不可見面消去;四是計(jì)算場(chǎng)景中可見面的顏色,即根據(jù)基于光學(xué)物理的光照模型計(jì)算可見面投射到觀察者眼中的光亮度大小和顏色分量,并將它轉(zhuǎn)換成適合圖形設(shè)備的顏色值,從而確定投影畫面上每一象素的顏色,最終生成圖形。
由于真實(shí)感圖形是通過景物表面的顏色和明暗色調(diào)來(lái)表現(xiàn)景物的幾何形狀、空間位置以及表面材料的,而一個(gè)物體表面所呈現(xiàn)的顏色是由表面向視線方向輻射的光能決定的。在計(jì)算機(jī)圖形學(xué)中,常采用一個(gè)既能表示光能大小又能表示其顏色組成的物理量即光亮度(luminance)或光強(qiáng)(intensity of light)來(lái)描述物體表面朝某方向輻射光能的顏色。采用這個(gè)物理量可以正確描述光在物體表面的反射、透射和吸收現(xiàn)象,因而可以正確計(jì)算處物體表面在空間給定方向上的光能顏色。
物體表面向空間給定方向輻射的光強(qiáng)可應(yīng)用光照模型進(jìn)行計(jì)算。簡(jiǎn)單的光照模型通常假定物體表面是光滑的且由理想材料構(gòu)成,因此只考慮光源照射在物體表面產(chǎn)生的反射光,所生成的圖形可以模擬處不透明物體表面的明暗過渡,具有一定的真實(shí)感效果。復(fù)雜的光照模型除了考慮上述因素外,還要考慮周圍環(huán)境的光對(duì)物體表面的影響。如光亮平滑的物體表面會(huì)將環(huán)境中其它物體映像在表面上,而通過透明物體也可看到其后的環(huán)境景象。這類光照模型稱為整體光照模型,它能模擬出鏡面映像、透明等較精致的光照效果。為了更真實(shí)的繪制圖形,還要考慮物體表面的細(xì)節(jié)紋理,這通常使用一種稱為“紋理映射”(texture mapping)的技術(shù)把已有的平面花紋圖案映射到物體表面上,并在應(yīng)用光照模型時(shí)將這些花紋的顏色考慮進(jìn)去,物體表面細(xì)節(jié)的模擬使繪制的圖形更接近自然景物。
以上內(nèi)容中,真實(shí)感圖形繪制的四大步驟前兩步在前面的章節(jié)已經(jīng)詳細(xì)介紹過,這里不再重復(fù),第三步OpenGL將自動(dòng)完成所有消隱過程,第四步下面幾節(jié)詳述。另外,部分復(fù)雜光照模型應(yīng)用將在后續(xù)章節(jié)里介紹。
10.2、光照模型
10.2.1 簡(jiǎn)單光照模型
當(dāng)光照射到一個(gè)物體表面上時(shí),會(huì)出現(xiàn)三種情形。首先,光可以通過物體表面向空間反射,產(chǎn)生反射光。其次,對(duì)于透明體,光可以穿透該物體并從另一端射出,產(chǎn)生透射光。最后,部分光將被物體表面吸收而轉(zhuǎn)換成熱。在上述三部分光中,僅僅是透射光和反射光能夠進(jìn)入人眼產(chǎn)生視覺效果。這里介紹的簡(jiǎn)單光照模型只考慮被照明物體表面的反射光影響,假定物體表面光滑不透明且由理想材料構(gòu)成,環(huán)境假設(shè)為由白光照明。
一般來(lái)說(shuō),反射光可以分成三個(gè)分量,即環(huán)境反射、漫反射和鏡面反射。環(huán)境反射分量假定入射光均勻地從周圍環(huán)境入射至景物表面并等量地向各個(gè)方向反射出去,通常物體表面還會(huì)受到從周圍環(huán)境來(lái)的反射光(如來(lái)自地面、天空、墻壁等的反射光)的照射,這些光常統(tǒng)稱為環(huán)境光(Ambient Light);漫反射分量表示特定光源在景物表面的反射光中那些向空間各方向均勻反射出去的光,這些光常稱為漫射光(Diffuse Light);鏡面反射光為朝一定方向的反射光,如一個(gè)點(diǎn)光源照射一個(gè)金屬球時(shí)會(huì)在球面上形成一塊特別亮的區(qū)域,呈現(xiàn)所謂“高光(Highlight)”,它是光源在金屬球面上產(chǎn)生的鏡面反射光(Specular Light)。對(duì)于較光滑物體,其鏡面反射光的高光區(qū)域小而亮;相反,粗糙表面的鏡面反射光呈發(fā)散狀態(tài),其高光區(qū)域大而不亮。下面先看一個(gè)簡(jiǎn)單的光照例程。
例10-1 簡(jiǎn)單光照例程(light0.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void myinit(void)
{
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auxSolidSphere(1.0);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Simple Lighting");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序運(yùn)行結(jié)果是顯示一個(gè)具有灰色光影的球。其中函數(shù)myinit()中包含了關(guān)鍵的設(shè)定光源位置、啟動(dòng)光照等幾句,而其它程序語(yǔ)言幾乎與以前的沒有多大區(qū)別,但效果卻完全不一樣。下面幾個(gè)小節(jié)將詳細(xì)介紹有關(guān)函數(shù)的用法。
圖10-1 帶光影的灰色球體 |
10.2.2 OpenGL光組成
在OpenGL簡(jiǎn)單光照模型中的幾種光分為:輻射光(Emitted Light)、環(huán)境光(Ambient Light)、漫射光(Diffuse Light)、鏡面光(Specular Light)。
輻射光是最簡(jiǎn)單的一種光,它直接從物體發(fā)出并且不受任何光源影響。
環(huán)境光是由光源發(fā)出經(jīng)環(huán)境多次散射而無(wú)法確定其方向的光,即似乎來(lái)自所有方向。一般說(shuō)來(lái),房間里的環(huán)境光成分要多些,戶外的相反要少得多,因?yàn)榇蟛糠止獍聪嗤较蛘丈洌以趹敉夂苌儆衅渌矬w反射的光。當(dāng)環(huán)境光照到曲面上時(shí),它在各個(gè)方向上均等地發(fā)散(類似于無(wú)影燈光)。
漫射光來(lái)自一個(gè)方向,它垂直于物體時(shí)比傾斜時(shí)更明亮。一旦它照射到物體上,則在各個(gè)方向上均勻地發(fā)散出去。于是,無(wú)論視點(diǎn)在哪里它都一樣亮。來(lái)自特定位置和特定方向的任何光,都可能有散射成分。
鏡面光來(lái)自特定方向并沿另一方向反射出去,一個(gè)平行激光束在高質(zhì)量的鏡面上產(chǎn)生100%的鏡面反射。光亮的金屬和塑料具有很高非反射成分,而象粉筆和地毯等幾乎沒有反射成分。因此,三某種意義上講,物體的反射程度等同于其上的光強(qiáng)(或光亮度)。
10.2.3 創(chuàng)建光源(Light Source)
光源有許多特性,如顏色、位置、方向等。選擇不同的特性值,則對(duì)應(yīng)的光源作用在物體上的效果也不一樣,這在以后的章節(jié)中會(huì)逐步介紹的。下面詳細(xì)講述定義光源特性的函數(shù)glLight*():
void glLight{if}[v](GLenum light , GLenum pname, TYPE param)
創(chuàng)建具有某種特性的光源。其中第一個(gè)參數(shù)light指定所創(chuàng)建的光源號(hào),如GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7。第二個(gè)參數(shù)pname指定光源特性,這個(gè)參數(shù)的輔助信息見表10-1所示。最后一個(gè)參數(shù)設(shè)置相應(yīng)的光源特性值。
pname 參數(shù)名 | 缺省值 | 說(shuō)明 |
GL_AMBIENT | (0.0, 0.0, 0.0, 1.0) | RGBA模式下環(huán)境光 |
GL_DIFFUSE | (1.0, 1.0, 1.0, 1.0) | RGBA模式下漫反射光 |
GL_SPECULAR | (1.0,1.0,1.0,1.0) | RGBA模式下鏡面光 |
GL_POSITION | (0.0,0.0,1.0,0.0) | 光源位置齊次坐標(biāo)(x,y,z,w) |
GL_SPOT_DIRECTION | (0.0,0.0,-1.0) | 點(diǎn)光源聚光方向矢量(x,y,z) |
GL_SPOT_EXPONENT | 0.0 | 點(diǎn)光源聚光指數(shù) |
GL_SPOT_CUTOFF | 180.0 | 點(diǎn)光源聚光截止角 |
GL_CONSTANT_ATTENUATION | 1.0 | 常數(shù)衰減因子 |
GL_LINER_ATTENUATION | 0.0 | 線性衰減因子 |
GL_QUADRATIC_ATTENUATION | 0.0 | 平方衰減因子 |
表10-1 函數(shù)glLight*()參數(shù)pname說(shuō)明
|
注意:以上列出的GL_DIFFUSE和GL_SPECULAR的缺省值只能用于GL_LIGHT0,其他幾個(gè)光源的GL_DIFFUSE和GL_SPECULAR缺省值為(0.0,0.0,0.0,1.0)。另外,表中后六個(gè)參數(shù)的應(yīng)用放在下一篇中介紹。在上面例程中,光源的創(chuàng)建為:
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
其中l(wèi)ight_position是一個(gè)指針,指向定義的光源位置齊次坐標(biāo)數(shù)組。其它幾個(gè)光源特性都為缺省值。同樣,我們也可用類似的方式定義光源的其他幾個(gè)特性值,例如:
GLfloat light_ambient [] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat light_diffuse [] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT , light_ambient );
glLightfv(GL_LIGHT0, GL_DIFFUSE , light_diffuse );
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
10.2.4 啟動(dòng)光照
在OpenGL中,必須明確指出光照是否有效或無(wú)效。如果光照無(wú)效,則只是簡(jiǎn)單地將當(dāng)前顏色映射到當(dāng)前頂點(diǎn)上去,不進(jìn)行法向、光源、材質(zhì)等復(fù)雜計(jì)算,那么顯示的圖形就沒有真實(shí)感,如前幾章例程運(yùn)行結(jié)果顯示。要使光照有效,首先得啟動(dòng)光照,即:
glEnable(GL_LIGHTING);
若使光照無(wú)效,則調(diào)用gDisable(GL_LIGHTING)可關(guān)閉當(dāng)前光照。然后,必須使所定義的每個(gè)光源有效,例light0.c中只用了一個(gè)光源,即:
glEnable(GL_LIGHT0);
其它光源類似,只是光源號(hào)不同而已。
10.3、明暗處理
在計(jì)算機(jī)圖形學(xué)中,光滑的曲面表面常用多邊形予以逼近和表示,而每個(gè)小多邊形輪廓(或內(nèi)部)就用單一的顏色或許多不同的顏色來(lái)勾畫(或填充),這種處理方式就稱為明暗處理。在OpenGL中,用單一顏色處理的稱為平面明暗處理(Flat Shading),用許多不同顏色處理的稱為光滑明暗處理(Smooth Shading),也稱為Gourand明暗處理(Gourand Shading)。設(shè)置明暗處理模式的函數(shù)為:
void glShadeModel(GLenum mode);
函數(shù)參數(shù)為GL_FLAT或GL_SMOOTH,分別表示平面明暗處理和光滑明暗處理。
應(yīng)用平面明暗處理模式時(shí),多邊形內(nèi)每個(gè)點(diǎn)的法向一致,且顏色也一致;應(yīng)用光滑明暗處理模式時(shí),多邊形所有點(diǎn)的法向是由內(nèi)插生成的,具有一定的連續(xù)性,因此每個(gè)點(diǎn)的顏色也相應(yīng)內(nèi)插,故呈現(xiàn)不同色。這種模式下,插值方法采用的是雙線性插值法,如圖10-2所示。
圖10-2 Gouraud明暗處理 |
Gouraud明暗處理通常算法為:先用多邊形頂點(diǎn)的光強(qiáng)線性插值出當(dāng)前掃描線與多邊形邊交點(diǎn)處的光強(qiáng),然后再用交點(diǎn)的光強(qiáng)線插值處掃描線位于多邊形內(nèi)區(qū)段上每一象素處的光強(qiáng)值。圖中顯示出一條掃描線與多邊形相交,交線的端點(diǎn)是A點(diǎn)和B點(diǎn),P點(diǎn)是掃描線上位于多邊形內(nèi)的任一點(diǎn),多邊形三個(gè)頂點(diǎn)的光強(qiáng)分別為I1、I2和I3.取A點(diǎn)的光強(qiáng)Ia為I1和I2的線性插值,B點(diǎn)的光強(qiáng)Ib為I1和I3的線性插值,P點(diǎn)的光強(qiáng)Ip則為Ia和Ib的線性插值。采用Gouraud明暗處理不但可以使用多邊形表示的曲面光強(qiáng)連續(xù),而且計(jì)算量很小。這種算法還可以以增量的形式改進(jìn),且能用硬件直接實(shí)現(xiàn)算法,從而廣泛用于計(jì)算機(jī)實(shí)時(shí)圖形生成。請(qǐng)看下面光滑明暗處理的例程:
例10-2 明暗處理例程(Shading.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void object(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
/* GL_SMOOTH is actually the default shading model. */
void myinit (void)
{
glShadeModel (GL_SMOOTH);
}
void object(void)
{
glBegin (GL_POLYGON);
glColor3f (1.0, 0.0, 0.0);
glVertex2f (4.0, 4.0);
glColor3f(1.0,1.0,1.0);
glVertex2f (12.0, 4.0);
glColor3f(0.0,0.0,1.0);
glVertex2f (12.0, 12.0);
glColor3f(0.0,1.0,0.0);
glVertex2f (4.0, 12.0);
glEnd ();
}
void CALLBACK display(void)
{
glClear (GL_COLOR_BUFFER_BIT);
object ();
glFlush ();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
gluOrtho2D (0.0, 16.0, 0.0, 16.0 * (GLfloat) h/(GLfloat) w);
else
gluOrtho2D (0.0, 16.0 * (GLfloat) w/(GLfloat) h, 0.0, 16.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Smooth Shading");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序運(yùn)行結(jié)果是在屏幕上顯示一個(gè)色彩連續(xù)變化的三角形。這個(gè)程序是用的RGBA顯示模式,若改用顏色表模式,則顏色內(nèi)插實(shí)際上是顏色表的內(nèi)插,因此呈現(xiàn)的顏色可能不連續(xù)。網(wǎng)友不妨自己試試。
另外,若在light0.c程序中加上一句定義GL_FLAT明暗處理模式,則又會(huì)出現(xiàn)怎樣的情形呢?讀者可以仔細(xì)比較一下。
圖10-3 高氏明暗處理的正方形 |
10.4、材質(zhì)
10.4.1 材質(zhì)顏色
OpenGL用材料對(duì)光的紅、綠、藍(lán)三原色的反射率來(lái)近似定義材料的顏色。象光源一樣,材料顏色也分成環(huán)境、漫反射和鏡面反射成分,它們決定了材料對(duì)環(huán)境光、漫反射光和鏡面反射光的反射程度。在進(jìn)行光照計(jì)算時(shí),材料對(duì)環(huán)境光的反射率與每個(gè)進(jìn)入光源的環(huán)境光結(jié)合,對(duì)漫反射光的反射率與每個(gè)進(jìn)入光源的漫反射光結(jié)合,對(duì)鏡面光的反射率與每個(gè)進(jìn)入光源的鏡面反射光結(jié)合。對(duì)環(huán)境光與漫反射光的反射程度決定了材料的顏色,并且它們很相似。對(duì)鏡面反射光的反射率通常是白色或灰色(即對(duì)鏡面反射光中紅、綠、藍(lán)的反射率相同)。鏡面反射高光最亮的地方將變成具有光源鏡面光強(qiáng)度的顏色。例如一個(gè)光亮的紅色塑料球,球的大部分表現(xiàn)為紅色,光亮的高光將是白色的。
10.4.2 材質(zhì)定義
材質(zhì)的定義與光源的定義類似。其函數(shù)為:
void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);
定義光照計(jì)算中用到的當(dāng)前材質(zhì)。face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明當(dāng)前材質(zhì)應(yīng)該應(yīng)用到物體的哪一個(gè)面上;pname說(shuō)明一個(gè)特定的材質(zhì);param是材質(zhì)的具體數(shù)值,若函數(shù)為向量形式,則param是一組值的指針,反之為參數(shù)值本身。非向量形式僅用于設(shè)置GL_SHINESS。pname參數(shù)值具體內(nèi)容見表10-1。另外,參數(shù)GL_AMBIENT_AND_DIFFUSE表示可以用相同的 RGB值設(shè)置環(huán)境光顏色和漫反射光顏色。
參數(shù)名 | 缺省值 | 說(shuō)明 |
GL_AMBIENT | (0.2, 0.2, 0.2, 1.0) | 材料的環(huán)境光顏色 |
GL_DIFFUSE | (0.8, 0.8, 0.8, 1.0) | 材料的漫反射光顏色 |
GL_AMBIENT_AND_DIFFUSE | 材料的環(huán)境光和漫反射光顏色 | |
GL_SPECULAR | (0.0, 0.0, 0.0, 1.0) | 材料的鏡面反射光顏色 |
GL_SHINESS | 0.0 | 鏡面指數(shù)(光亮度) |
GL_EMISSION | (0.0, 0.0, 0.0, 1.0) | 材料的輻射光顏色 |
GL_COLOR_INDEXES | (0, 1, 1) | 材料的環(huán)境光、漫反射光和鏡面光顏色 |
表10-2 函數(shù)glMaterial*()參數(shù)pname的缺省值
|
例10-3 材質(zhì)定義例程(light1.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void); void myinit(void)
{
/* 設(shè)置材質(zhì)的各種光的顏色成分反射比率 */
GLfloat mat_ambient[]={0.8,0.8,0.8,1.0};
GLfloat mat_diffuse[]={0.8,0.0,0.8,1.0}; /* 紫色 */
GLfloat mat_specular[] = { 1.0, 0.0, 1.0, 1.0 }; /* 亮紫色 */
GLfloat mat_shininess[] = { 50.0 };
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auxSolidSphere(1.0);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH16);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Lighting_1 ");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序運(yùn)行結(jié)果是一個(gè)紫色的球。在函數(shù)myinit()中定義了球的材質(zhì)顏色,光源的定義仍延用light0.c中的,而light.c物體的光源定義為缺省形式。從例子中明顯地看出,物體的材質(zhì)顏色定義與光源顏色定義幾乎一樣,物體反射到眼中的顏色與二者都有關(guān)系,具體關(guān)系請(qǐng)看下一小節(jié)。
10.4.3 材質(zhì)RGB值和光源RGB值的關(guān)系
材質(zhì)的顏色與光源的顏色有些不同。對(duì)于光源,R、G、B值等于R、G、B對(duì)其最大強(qiáng)度的百分比。若光源顏色的R、G、B值都是1.0,則是最強(qiáng)的白光;若值變?yōu)?.5,顏色仍為白色,但強(qiáng)度為原來(lái)的一半,于是表現(xiàn)為灰色;若R=G=1.0,B=0.0,則光源為黃色。對(duì)于材質(zhì),R、G、B值為材質(zhì)對(duì)光的 R、G、B成分的反射率。比如,一種材質(zhì)的R=1.0、G=0.5、B=0.0,則材質(zhì)反射全部的紅色成分,一半的綠色成分,不反射藍(lán)色成分。也就是說(shuō),若OpenGL的光源顏色為(LR、LG、LB),材質(zhì)顏色為(MR、MG、MB),那么,在忽略所有其他反射效果的情況下,最終到達(dá)眼睛的光的顏色為(LR*MR、LG*MG、LB*MB)。
同樣,如果有兩束光,相應(yīng)的值分別為(R1、G1、B1)和(R2、G2、B2),則OpenGL 將各個(gè)顏色成分相加,得到(R1+R2、G1+G2、B1+B2),若任一成分的和值大于1(超出了設(shè)備所能顯示的亮度)則約簡(jiǎn)到1.0。下面一例程就說(shuō)明了二者之間的關(guān)系。
例10-4 材質(zhì)與光源的RGB關(guān)系例程(light2.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void myinit(void)
{
GLfloat mat_ambient[]= { 0.8, 0.8, 0.8, 1.0 };
GLfloat mat_diffuse[]= { 0.8, 0.0, 0.8, 1.0 }; /* 紫色 */
GLfloat mat_specular[] = { 1.0, 0.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light_diffuse[]= { 0.0, 0.0, 1.0, 1.0}; /* 藍(lán)色 */
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auxSolidSphere(1.0);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH16);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Lighting_2 ");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序運(yùn)行結(jié)果是一個(gè)藍(lán)色的球,其中高光部分仍為上一例的亮紫色。從上可看出,球漫反射光的結(jié)果是mat_diffuse[]與 light_diffuse[]中的三個(gè)顏色分量值相乘,即 (0.0*1.0,0.0*1.0,0.8*1.0,1.0*1.0)=(0.0,0.0,0.8,1.0),所以球大部分呈現(xiàn)藍(lán)色。
圖10-4 光照藍(lán)色球(高光為紅色) |
10.4.4 材質(zhì)改變
在實(shí)際應(yīng)用的許多情況下,不同的物體或同一物體的不同部分都有可能設(shè)置不同的材質(zhì),OpenGL函數(shù)庫(kù)提供了兩種方式實(shí)現(xiàn)這種要求。下面一例程采用的是設(shè)置矩陣堆棧來(lái)保存不同物體的材質(zhì)信息:
例10-5 矩陣堆棧改變材質(zhì)例程(chgmat1.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
/* 初始化z-buffer、光源和光照模型,在此不具體定義材質(zhì)。*/
void myinit(void)
{
GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat position[] = { 0.0, 3.0, 2.0, 0.0 };
GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glClearColor(0.0, 0.1, 0.1, 0.0);
}
void CALLBACK display(void)
{
GLfloat no_mat[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat mat_ambient[] = { 0.7, 0.7, 0.7, 1.0 };
GLfloat mat_ambient_color[] = { 0.8, 0.8, 0.2, 1.0 };
GLfloat mat_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat no_shininess[] = { 0.0 };
GLfloat low_shininess[] = { 5.0 };
GLfloat high_shininess[] = { 100.0 };
GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* 第一行第一列繪制的球僅有漫反射光而無(wú)環(huán)境光和鏡面光。*/
glPushMatrix();
glTranslatef (-3.75, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第一行第二列繪制的球有漫反射光和鏡面光,并有低高光,而無(wú)環(huán)境光 。*/
glPushMatrix();
glTranslatef (-1.25, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第一行第三列繪制的球有漫反射光和鏡面光,并有很亮的高光,而無(wú)環(huán)境光 。*/
glPushMatrix();
glTranslatef (1.25, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第一行第四列繪制的球有漫反射光和輻射光,而無(wú)環(huán)境和鏡面反射光。*/
glPushMatrix();
glTranslatef (3.75, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
auxSolidSphere(1.0);
glPopMatrix();
/* 第二行第一列繪制的球有漫反射光和環(huán)境光,而鏡面反射光。*/
glPushMatrix();
glTranslatef (-3.75, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第二行第二列繪制的球有漫反射光、環(huán)境光和鏡面光,且有低高光。*/
glPushMatrix();
glTranslatef (-1.25, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第二行第三列繪制的球有漫反射光、環(huán)境光和鏡面光,且有很亮的高光。*/
glPushMatrix();
glTranslatef (1.25, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第二行第四列繪制的球有漫反射光、環(huán)境光和輻射光,而無(wú)鏡面光。*/
glPushMatrix();
glTranslatef (3.75, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
auxSolidSphere(1.0); glPopMatrix();
/* 第三行第一列繪制的球有漫反射光和有顏色的環(huán)境光,而無(wú)鏡面光。*/
glPushMatrix();
glTranslatef (-3.75, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第三行第二列繪制的球有漫反射光和有顏色的環(huán)境光以及鏡面光,且有低高光。*/
glPushMatrix();
glTranslatef (-1.25, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第三行第三列繪制的球有漫反射光和有顏色的環(huán)境光以及鏡面光,且有很亮的高光。*/
glPushMatrix();
glTranslatef (1.25, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere(1.0);
glPopMatrix();
/* 第三行第四列繪制的球有漫反射光和有顏色的環(huán)境光以及輻射光,而無(wú)鏡面光。*/
glPushMatrix();
glTranslatef (3.75, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
auxSolidSphere(1.0);
glPopMatrix();
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= (h * 2))
glOrtho (-6.0, 6.0, -3.0*((GLfloat)h*2)/(GLfloat)w,
3.0*((GLfloat)h*2)/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-6.0*(GLfloat)w/((GLfloat)h*2),
6.0*(GLfloat)w/((GLfloat)h*2), -3.0, 3.0, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 600, 450);
auxInitWindow ("Material");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
圖10-5 多種光和材質(zhì)的變化效果 |
以上程序運(yùn)行結(jié)果是繪制12個(gè)球(3行4列)。第一行的球材質(zhì)都沒有環(huán)境反射光,第二行的都有一定的環(huán)境反射光,第三行的都有某種顏色的環(huán)境光。而第一列的球材質(zhì)僅有藍(lán)色的漫反射光;第二列的不僅有藍(lán)漫反射光,而且還有鏡面反射光,較低的高光;第三列的不僅有藍(lán)漫反射光,而且還有鏡面反射光,很亮的高光;第四列的還包括輻射光,但無(wú)鏡面光。
這個(gè)程序運(yùn)用矩陣堆棧多次調(diào)用glMaterialfv()來(lái)設(shè)置每個(gè)球的材質(zhì),也就是改變同一場(chǎng)景中的不同物體的顏色。但由于這個(gè)函數(shù)的應(yīng)用有個(gè)性能開銷,因此建議最好盡可能少的改變材質(zhì),以減少改變材質(zhì)時(shí)所帶來(lái)的性能開銷,可采用另一種方式即改變材質(zhì)顏色,相應(yīng)函數(shù)為glColorMaterial(),說(shuō)明如下:
void glColorMaterial(GLenum face,GLenum mode);
函數(shù)參數(shù)face指定面,值有GL_FRONT、GL_BACK或GL_FRONT_AND_BACK(缺省值)。mode指定材質(zhì)成分,值有 GL_AMBIENT、GL_DIFFUSE、GL_AMBIENT_AND_DIFFUSE(缺省值)、GL_SPECULAR或 GLEMISSION。
注意:這個(gè)函數(shù)說(shuō)明了兩個(gè)獨(dú)立的值,第一個(gè)參數(shù)說(shuō)明哪一個(gè)面和哪些面被修改,而第二個(gè)參數(shù)說(shuō)明這些面的哪一個(gè)或哪些材質(zhì)成分要被修改。OpenGL并不為每一種face保持獨(dú)立的mode變量。在調(diào)用glColorMterial() 以后,首先需要用GL_COLOR_MATERIAL作為參數(shù)調(diào)用glEnable()來(lái)啟動(dòng)顏色材質(zhì),然后在繪圖時(shí)調(diào)用glColor*()來(lái)改變當(dāng)前顏色,或用glMaterial()來(lái)改變材質(zhì)成分。當(dāng)不用這種方式來(lái)改變材質(zhì)時(shí),可調(diào)用glDisable(GL_COLOR_MATERIAL)來(lái)關(guān)閉取消。如下面一段代碼:
glColorMaterial(GL_FRONT,GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
glColor3f(0.3,0.5,0.7);
/* draw some objects here. */
glcolor3f(0.0,1.0,0.0);
/* draw other objects here.*/
glDisable(GL_COLOR_MATERIAL);
當(dāng)需要改變場(chǎng)景中大部分方面的單個(gè)材質(zhì)時(shí),最好調(diào)用glColorMaterial();當(dāng)需要修改不止一個(gè)材質(zhì)參數(shù)時(shí),最好調(diào)用glMaterial*()。注意,當(dāng)不需要顏色材質(zhì)時(shí)一定要關(guān)閉它,以避免相應(yīng)的開銷。下面來(lái)看一個(gè)顏色材質(zhì)的具體應(yīng)用例子:
例10-6 顏色定義改變材質(zhì)例程(chgmat2.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void myinit(void)
{
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glColorMaterial(GL_FRONT, GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* draw one yellow ball */
glLoadIdentity();
glTranslatef(-0.7,0.0,0.0);
glColor3f(1.0,1.0,0.0);
auxSolidSphere(0.5);
/* draw one red cone */
glLoadIdentity();
glRotatef(-65.0,1.0,0.0,0.0);
glTranslatef(0.7,0.0,0.0);
glColor3f(1.0,0.0,0.0);
auxSolidCone(0.4,0.6);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("ColorMaterial Mode");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序改變的是漫反射顏色。場(chǎng)景中顯示了一個(gè)黃色的球和一個(gè)紅色的錐體。
圖10-6 漫反射材質(zhì)改變 |