opengl學習筆記


摘自www.opengl.org樓主會不定時更新

OpenGL編程指南
OpenGL編程指南



學習OpenGL的官方指南1.1版




 

 

 

 

 

 

 

 

 

 

 

 

 



 

 

 

 

 

 

 

 

OpenGL編程指南
編程指南 >第1章



第1章
OpenGL簡介


章目標

閱讀本章后,您將可以執行以下操作:

  • 一般來說,OpenGL是如何做的
  • 識別不同級別的渲染復雜性
  • 了解OpenGL程序的基本結構
  • 識別OpenGL命令語法
  • 識別OpenGL渲染管道的操作順序
  • 了解如何在OpenGL程序中繪制圖形

本章介紹OpenGL。它有以下主要部分:


什么是OpenGL?

OpenGL是圖形硬件的軟件界面。該界面由約150個不同的命令組成,用於指定生成交互式三維應用程序所需的對象和操作。

OpenGL被設計為一個簡化的,獨立於硬件的接口,可在許多不同的硬件平台上實現。為了達到這些質量,OpenGL中不包括用於執行窗口任務或獲取用戶輸入的命令; 相反,您必須通過任何窗口系統來控制您使用的特定硬件。類似地,OpenGL不提供用於描述三維對象模型的高級命令。這樣的命令可能允許您指定比較復雜的形狀,如汽車,身體部位,飛機或分子。使用OpenGL,您必須從一小組幾何圖元(點,線和多邊形)中構建所需的模型

提供這些功能的復雜庫可以建立在OpenGL之上。OpenGL實用程序庫(GLU)提供了許多建模功能,例如二次曲面和NURBS曲線和曲面。GLU是每個OpenGL實現的標准組成部分。此外,還有一個更高級別的面向對象的工具包Open Inventor,它是OpenGL上構建的,可以單獨使用OpenGL的許多實現。有關Open Inventor的更多信息,請參閱“OpenGL相關庫”。)

現在你知道什么是OpenGL的沒有做,這里就是它不會做。看看色板 - 它們說明了OpenGL的典型用途。他們表明這本書,封面上的場景呈現在在越來越復雜的方式使用OpenGL電腦(這是說,繪制)。以下列表一般性地描述了這些照片是如何制作的。

  • “板1”顯示整個場景顯示為線框模型 - 也就是說,仿佛場景中的所有對象都是用線制成的。每條線對應於原始邊緣(通常為多邊形)。例如,桌子的表面由三角形多邊形構成,它們像餡餅一樣定位。

請注意,如果對象是固體而不是線框,則可以看到將被遮擋的對象部分。例如,您可以看到窗外的山丘的整個模型,即使這個模型大部分通常被房間的牆壁隱藏。地球似乎幾乎是堅實的,因為它由數百個彩色塊組成,您可以看到所有塊的所有邊緣的線框線,甚至是形成地球背面的線框線。構建地球的方式讓您了解如何通過匯編較低級別的對象來創建復雜的對象。

  • “Plate 2”顯示了相同線框場景深度提示版本。請注意,距離眼睛更遠的線條更加暗淡,就像在現實生活中一樣,從而給出了深度的視覺提示。OpenGL使用大氣效應(統稱為霧)來實現深度提示。
  • “Plate 3”顯示了線框場景抗鋸齒版本。抗鋸齒是一種減少鋸齒狀邊緣(也稱為鋸齒狀)的技術,它用近似光滑的邊緣,這些像素用於限制在矩形網格上的圖像元素這些鋸齒通常是最接近水平或近垂直線的可見光。
  • “板4”示出了一個平坦陰影不點亮的場景版本。場景中的對象現在顯示為實體。它們出現“平坦”,意思是僅使用一種顏色來渲染每個多邊形,因此它們看起來並不平滑。沒有任何光源的影響。
  • “板5”顯示了一個點亮,光滑陰影的場景版本。注意當對象被遮蔽以響應房間中的光源時,場景看起來更逼真和立體,就好像物體平滑地圓滑一樣。
  • “Plate 6”陰影紋理添加到以前版本的場景中。陰影不是OpenGL的明確定義的功能(沒有“陰影命令”),但您可以使用第14章中描述的技術自己創建它們紋理映射允許您將二維圖像應用於三維對象。在這個場景中,表面上的頂部是紋理映射最有活力的例子。地板和桌子表面上的木紋都是紋理映射的,以及壁紙和玩具頂部(在桌子上)。
  • “板7”顯示場景中的運動模糊對象。似乎被捕獲的獅身人面像(或狗,取決於你的Rorschach傾向)向前移動,留下其運動路徑的模糊痕跡。
  • “板8”顯示了從不同的角度為這本書的封面繪制的場景。該板表明圖像確實是三維物體模型的快照。
  • “板9”帶來了“板2”中所見的霧的使用以顯示空氣中存在煙霧顆粒。請注意,“板塊2”的相同效果現在對“板塊9”有更大的影響
  • “板10”顯示景深效應,其模擬相機鏡頭無法保持拍攝場景中的所有物體的焦點。相機專注於場景中的特定位置。比這個位置更接近或更遠的物體有些模糊。

色板讓您了解可以使用OpenGL圖形系統的各種事物。以下列表簡要介紹了OpenGL在屏幕上呈現圖像的主要圖形操作。有關此操作順序的詳細信息,請參閱“OpenGL渲染流水線”。)

從幾何圖元構造形狀,從而創建對象的數學描述。(OpenGL將點,線,多邊形,圖像和位圖視為基元)。

在三維空間中排列對象,並選擇要查看組合場景的所需視點。 

計算所有對象的顏色。顏色可能由應用程序明確分配,由指定的照明條件確定,通過將紋理粘貼到對象上獲得,或者這些三個操作的某種組合。

將對象及其相關顏色信息的數學描述轉換為屏幕上的像素。這個過程稱為光柵化

在這些階段,OpenGL可能會執行其他操作,例如消除其他對象隱藏的對象部分。此外,在場景被光柵化之后,但在屏幕繪制之前,您可以根據需要對像素數據執行一些操作。

在某些實現中(例如使用X Window系統),即使顯示您創建的圖形的計算機不是運行圖形程序的計算機,OpenGL也可以工作。如果您在網絡計算機環境中工作,那么許多計算機通過數字網絡彼此連接,可能是這種情況。在這種情況下,您的程序運行並發出OpenGL繪圖命令的計算機稱為客戶端,接收這些命令並執行繪圖的計算機稱為服務器。用於傳輸OpenGL命令的格式(稱為協議)從客戶端到服務器總是相同的,因此即使客戶端和服務器是不同種類的計算機,OpenGL程序也可以跨網絡運行。如果一個OpenGL程序沒有在網絡上運行,那么只有一台計算機,它既是客戶機又是服務器。


OpenGL代碼的Smidgen

因為您可以使用OpenGL圖形系統進行許多操作,因此OpenGL程序可能會變得復雜。然而,一個有用的程序的基本結構可以是簡單的:它的任務是初始化一些控制OpenGL呈現和指定要呈現的對象的狀態。

在看一些OpenGL代碼之前,我們來看幾個術語。您已經看到使用的渲染是計算機從模型創建圖像的過程。這些模型或對象由幾何基元 - 點,線和多邊形構成 - 由頂點指定。

最終渲染的圖像由屏幕上繪制的像素組成; 像素是顯示硬件可以放在屏幕上的最小可見元素。有關像素的信息(例如,它們應該是什么顏色)在內存中被組織成位平面。位平面是存儲器的一個區域,其中存儲屏幕上每個像素的一位信息; 該位可能指示特定像素應該是紅色的,例如。位板本身被組織成一個幀緩沖區,它保存圖形顯示所需的所有信息,以控制屏幕上所有像素的顏色和強度。

現在看看OpenGL程序可能是什么樣的。示例1-1在黑色背景上呈現白色矩形,如圖1-1所示。

圖1-1:黑色背景上的白色矩形

示例1-1: OpenGL代碼塊

#include <whateverYouNeed.h>
 main(){
 InitializeAWindowPlease();
 glClearColor(0.0,0.0,0.0,0.0); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,1.0,1.0); glOrtho(0.0,1.0,0.0,1.0,1.0,1.0); 在glBegin(GL_POLYGON); glVertex3f(0.25,0.25,0.0); glVertex3f(0.75,0.25,0.0); glVertex3f(0.75,0.75,0.0); glVertex3f(0.25,0.75,0.0); glEnd(); glFlush();
 UpdateTheWindowAndCheckForEvents(); }

main()例程的第一行在屏幕上初始化一個窗口InitializeAWindowPlease()例程用於窗口系統特定例程的占位符,通常不是OpenGL調用。接下來的兩行是將窗口清除為黑色的OpenGL命令:glClearColor()確定窗口將被清除的顏色,glClear()實際上清除窗口。一旦清除色彩設置,只要調用glClear(),窗口就會被清除為該顏色此清除顏色可以通過另一次調用glClearColor()來更改同樣,glColor3f()命令建立用於繪制對象的顏色 - 在這種情況下,顏色為白色。在此點之后繪制的所有對象都使用此顏色,直到用另一個調用更改顏色為止。

程序中使用的下一個OpenGL命令glOrtho()指定OpenGL在繪制最終圖像時以及如何映射到屏幕的假定坐標系。glBegin()glEnd()括起來的下一個調用定義了要繪制的對象 - 在本示例中,它是一個具有四個頂點的多邊形。多邊形的“角”由glVertex3f()命令定義您可能可以從(x,y,z)坐標的參數中猜出,多邊形是z = 0平面上的一個矩形。

最后,glFlush()確保繪圖命令實際上被執行而不是存儲在等待其他OpenGL命令緩沖區中所述UpdateTheWindowAndCheckForEvents()占位符例程管理窗口的內容,並開始事件處理。

實際上,這塊OpenGL代碼結構不是很好。你可能會問:“如果我嘗試移動或調整窗口大小,會發生什么?” 或者,“每當繪制矩形時,是否需要重置坐標系?” 在本章的后面,您將看到實際工作的InitializeAWindowPlease()UpdateTheWindowAndCheckForEvents()的替換,但將需要重組代碼以使其高效。

OpenGL命令語法

正如您可能從上一節中的簡單程序中觀察到的,OpenGL命令使用前綴gl和初始大寫字母來構成命名名稱的每個單詞例如,記住glClearColor()))。類似地,OpenGL定義的常量以GL_開頭,使用所有大寫字母,並使用下划線分隔單詞(如GL_COLOR_BUFFER_BIT)。

您可能還注意到附加到某些命令名稱一些看似無關的字母(例如,3FglColor3f()和glVertex3f() )。這是真的,命令名稱顏色部分glColor3f()足以將命令定義為設置當前顏色的命令。但是,已經定義了多個這樣的命令,以便您可以使用不同類型的參數。特別地,后綴3部分表示給出三個參數; Color命令的另一個版本需要四個參數。˚F后綴的一部分表示參數是浮點數。具有不同的格式允許OpenGL以他或她自己的數據格式接受用戶的數據。

一些OpenGL命令為其參數接受多達8種不同的數據類型。用於指定OpenGL的ISO C實現的這些數據類型的后綴的字母與相應的OpenGL類型定義一起顯示在表1-1中。您正在使用的OpenGL的特定實現可能不會完全遵循該方案; 例如,C ++或Ada中的一個實現不需要。

表1-1:命令后綴和參數數據類型

后綴

數據類型

典型相應的C語言類型

OpenGL類型定義

b

8位整數

簽字字

GLbyte

小號

16位整數

表示GLshort

一世

32位整數

int或long

GLint,GLsizei

F

32位浮點數

浮動

GLfloat,GLclampf

ð

64位浮點數

GLdouble,GLclampd

UB

8位無符號整數

無符號字符

GLubyte,GLboolean

我們

16位無符號整數

無符號短

GLushort

UI

32位無符號整數

unsigned int或unsigned long

GLuint,GLenum,GLbitfield

因此,這兩個命令

glVertex2i(1,3); glVertex2f(1.0,3.0);

是等效的,除了第一個將頂點的坐標指定為32位整數,第二個將它們指定為單精度浮點數。

注意: OpenGL的實現在選擇用於表示OpenGL數據類型的C數據類型方面有一定的余地。如果您在整個應用程序中堅決使用OpenGL定義的數據類型,則在不同實現之間移植代碼時,將避免不匹配的類型。

一些OpenGL命令可以取最后一個字母v,這表示該命令使用指向一個值(或數組)的指針而不是一系列單個參數。許多命令都具有向量和非向量版本,但是一些命令僅接受各自的參數,而其他命令則要求將至少一些參數指定為向量。以下幾行顯示如何使用設置當前顏色的命令的向量和非向量版本:

glColor3f(1.0,0.0,0.0); GLfloat color_array [] = {1.0,0.0,0.0}; glColor3fv(color_array);

最后,OpenGL定義了typedef GLvoid。這最常用於接受指向數組數組的指針的OpenGL命令。

在本指南的其余部分(實際代碼示例除外)中,OpenGL命令僅由其基本名稱引用,並包含星號以指示命令名稱可能更多。例如,glColor *()代表用於設置當前顏色的命令的所有變體。如果我們想要指出特定命令的一個版本的具體問題,我們將包含定義該版本所需的后綴。例如,glVertex * v()是指用於指定頂點的所有命令的矢量版本。

OpenGL作為狀態機

OpenGL是一個狀態機。你把它放入各種狀態(或模式),然后才能保持有效,直到你改變它們。正如你已經看到的,當前的顏色是一個狀態變量。您可以將當前顏色設置為白色,紅色或任何其他顏色,然后每個對象都用該顏色繪制,直到將當前顏色設置為其他顏色為止。當前的顏色只是OpenGL維護的許多狀態變量之一。其他控制諸如當前的觀看和投影變換,線和多邊形點狀圖案,多邊形繪圖模式,像素包裝約定,燈的位置和特征以及正在繪制的對象的材料屬性。許多狀態變量指使用glEnable()glDisable()命令啟用或禁用的模式,

每個狀態變量或模式都有一個默認值,並且您可以隨時查詢系統中每個變量的當前值。通常,您使用以下六個命令之一執行此操作:glGetBooleanv()glGetDoublev()glGetFloatv()glGetIntegerv()glGetPointerv()glIsEnabled()您選擇哪些命令取決於要在其中給出答案的數據類型。某些狀態變量具有更具體的查詢命令(如glGetLight *()glGetError()glGetPolygonStipple())。另外,您可以在屬性堆棧上保存狀態變量的集合glPushAttrib()glPushClientAttrib(),臨時修改它們,然后使用glPopAttrib()glPopClientAttrib()還原值對於臨時狀態更改,您應該使用這些命令而不是任何查詢命令,因為它們可能會更有效率。

有關可以查詢的狀態變量的完整列表,請參閱附錄B. 對於每個變量,附錄還列出了一個建議的glGet *()命令,它返回變量的值,它所屬的屬性類和變量的默認值。


OpenGL渲染管道

OpenGL的大多數實現具有類似的操作順序,稱為OpenGL渲染管道的一系列處理階段。如圖1-2所示,這種排序不是OpenGL如何實現的嚴格規則,而是為預測OpenGL將要做的事情提供可靠的指導。

如果您是三維圖形的新手,即將到來的描述可能看起來像消防水帶上的飲用水。你現在可以看看,但是在閱讀本書的每一章時,請回到圖1-2。

下圖顯示了OpenGL處理​​數據所需的Henry Ford裝配線方法。幾何數據(頂點,線和多邊形)沿着包括評估者和每頂點操作在內的一行框的路徑,而像素數據(像素,圖像和位圖)在一部分過程中被不同地對待。在將最終像素數據寫入幀緩沖區之前,兩種類型的數據都經歷相同的最終步驟(光柵化和每個片段操作)。

圖1-2:操作順序

現在,您將看到有關OpenGL渲染管道關鍵階段的更多詳細信息。

顯示列表

所有數據,無論是描述幾何或像素,都可以保存在顯示列表中,以供當前或以后使用。(在顯示列表中保留數據的替代方法是立即處理數據 - 也稱為即時模式)。當執行顯示列表時,保留的數據將從顯示列表發送,就像應用程序立即發送的一樣模式。有關顯示列表的更多信息,請參閱第7章。)

評價者

所有幾何圖元最終由頂點描述。參數曲線和曲面可以最初由控制點和稱為基函數的多項式函數描述。評估者提供了一種從控制點導出用於表示曲面的頂點的方法。該方法是多項式映射,可以從控制點產生表面法線,紋理坐標,顏色和空間坐標值。有關評估者的更多信息,請參閱第12章。)

每頂點操作

對於頂點數據,接下來是“每頂點操作”階段,它將頂點轉換為基元。一些頂點數據(例如,空間坐標)被4×4個浮點矩陣變換。空間坐標從3D世界中的位置投影到屏幕上的位置。(有關轉換矩陣的詳細信息,請參見第3章。)

如果啟用高級功能,這個階段甚至更繁忙。如果使用紋理,則可以在此處生成和轉換紋理坐標。如果啟用了照明,則使用變換的頂點,表面法線,光源位置,材料屬性和其他照明信息執行照明計算,以產生顏色值。

原始大會

剪切是原始裝配的主要部分,是消除由平面限定的半空間外的幾何形狀的部分。點裁剪簡單地傳遞或拒絕頂點; 線或多邊形裁剪可以根據線或多邊形的剪裁方式添加額外的頂點。

在某些情況下,這之后是透視分割,這使得遙遠的幾何對象看起來比較近的物體小。然后應用視口和深度(z坐標)操作。如果啟用了剔除,並且原語是多邊形,則可能會被剔除測試所拒絕。根據多邊形模式,可以將多邊形繪制為點或線。(請參閱第2章“多邊形細節”。)

該階段的結果是完整的幾何圖元,它們是具有相關顏色,深度和有時紋理坐標值的轉換和剪切頂點以及光柵化步驟的准則。

像素操作

雖然幾何數據通過OpenGL渲染管道有一條路徑,但像素數據采用不同的路線。系統內存中的陣列中的像素首先從多種格式之一解壓縮到適當數量的組件中。接下來,數據被縮放,偏置,並由像素圖處理。結果被鉗位,然后被寫入紋理存儲器或發送到光柵化步驟。(見第8章“成像管道”)。

如果從幀緩沖器讀取像素數據,則執行像素傳送操作(比例,偏移,映射和鉗位)。然后將這些結果打包成適當的格式並返回到系統內存中的數組。

有特殊的像素復制操作將幀緩沖區中的數據復制到幀緩沖區的其他部分或紋理內存。在將數據寫入紋理存儲器或返回到幀緩沖器之前,通過像素傳輸操作進行單次通過。

紋理裝配

OpenGL應用程序可能希望將紋理圖像應用到幾何對象上,使其看起來更逼真。如果使用幾個紋理圖像,將它們放入紋理對象中是明智的,以便您可以輕松地在其中切換。

一些OpenGL實現可能有特殊的資源來加速紋理性能。可能會有專門的高性能紋理記憶。如果該內存可用,則可以優先考慮紋理對象來控制這種有限和有價值的資源的使用。(見第9章

光柵化

柵格化是將幾何和像素數據轉換成片段每個片段正方形對應於幀緩沖區中的像素。在頂點連接成線條時,考慮到線和多邊形點,線寬,點大小,陰影模型和支持抗鋸齒的覆蓋計算,或者為填充的多邊形計算內部像素。為每個片段平方分配顏色和深度值。

片段操作

在值實際存儲到幀緩沖區之前,執行一系列可能會改變甚至丟棄片段的操作。所有這些操作都可以啟用或禁用。

可能遇到的第一個操作是紋理化,其中從紋理存儲器為每個片段生成紋理(紋理元素)並應用於片段。然后可以應用霧計算,然后進行剪刀測試,alpha測試,模板測試和深度緩沖測試(深度緩沖區用於隱藏表面去除)。失敗的測試可能會導致片段平方的繼續處理。然后,可以執行混合,抖動,邏輯操作和掩碼的掩碼。(見第6章以及第10章)最后,將徹底processedfragment被吸入到相應的緩沖器,在那里它終於前進到是像素,取得其最終的休息處。


OpenGL相關庫

OpenGL提供了強大但原始的渲染命令集,所有更高級別的繪圖都必須按照這些命令進行。此外,OpenGL程序必須使用窗口系統的底層機制。有許多庫可以讓您簡化編程任務,其中包括:

  • OpenGL實用程序庫(GLU)包含幾個使用較低級別的OpenGL命令執行諸如為特定的視圖方向和投影設置矩陣,執行多邊形細分和渲染曲面等任務的例程。該庫是每個OpenGL實現的一部分。GLU的部分在OpenGL參考手冊中有描述本指南中描述了更有用的GLU例程,它們與正在討論的主題相關,如第11 章和第12章“GLU NURBS界面”中的所有內容GLU例程使用前綴glu
  • 對於每個窗口系統,都有一個庫來擴展該窗口系統的功能,以支持OpenGL渲染。對於使用X Window系統的機器,OpenGL擴展到X Window系統(GLX)作為OpenGL的附件提供。GLX例程使用前綴glX對於Microsoft Windows,WGL例程提供Windows到OpenGL界面。所有WGL例程都使用前綴wgl對於IBM OS / 2,PGL是OpenGL接口的Presentation Manager,其例程使用前綴pgl

所有這些窗口系統擴展庫在附錄C中有更詳細的描述此外,GLX程序也在OpenGL參考手冊中進行了說明

  • OpenGL實用工具包(GLUT)是由Mark Kilgard編寫的一個與窗口系統無關的工具包,用於隱藏不同窗口系統API的復雜性。GLUT是下一節的主題,它在Mark Kilgard的“ Open Window Programming for the X Window System”(ISBN 0-201-48359-9)中有更詳細的描述GLUT例程使用前綴過濾。前言中的“如何獲取示例代碼”介紹了如何使用ftp獲取GLUT的源代碼。
  • Open Inventor是基於OpenGL的面向對象工具包,它提供了創建交互式三維圖形應用程序的對象和方法。Open Inventor是用C ++編寫的,它為用戶交互提供了預置對象和內置事件模型,用於創建和編輯三維場景的高級應用程序組件,以及以其他圖形格式打印對象和交換數據的能力。Open Inventor與OpenGL分開。

包含文件

對於所有OpenGL應用程序,您需要在每個文件中包含gl.h頭文件。幾乎所有OpenGL應用程序都使用GLU,前面提到的OpenGL實用程序庫,它需要包含glu.h頭文件。所以幾乎每個OpenGL源文件都以

#include <GL / gl.h>
#include <GL / glu.h>

如果您直接訪問窗口界面庫以支持OpenGL,例如GLX,AGL,PGL或WGL,則必須包含其他頭文件。例如,如果您正在調用GLX,則可能需要將這些行添加到代碼中

#include <X11 / Xlib.h>
#include <GL / glx.h>

如果您正在使用GLUT來管理窗口管理器任務,那么您應該包括

#include <GL / glut.h>

請注意,glut.h自動包括gl.h,glu.h和glx.h,因此包括所有三個文件都是多余的。用於Microsoft Windows的GLUT包括訪問WGL的相應頭文件。

GLUT,OpenGL實用工具包

如你所知,OpenGL包含渲染命令,但是被設計為獨立於任何窗口系統或操作系統。因此,它不包含打開窗口或從鍵盤或鼠標讀取事件的命令。不幸的是,編寫一個完整的圖形程序是不可能的,至少打開一個窗口,最有趣的程序需要一些用戶輸入或來自操作系統或窗口系統的其他服務。在許多情況下,完整的程序是最有趣的例子,所以本書使用GLUT來簡化打開窗口,檢測輸入等等。如果您的系統上已經實現了OpenGL和GLUT,那么這本書中的示例應該與它們鏈接時不會改變。

另外,由於OpenGL繪圖命令僅限於生成簡單幾何圖元(點,線和多邊形)的命令,所以GLUT包含幾個創建更復雜的三維對象(例如球體,圓環和茶壺)的例程。這樣,程序輸出的快照就可以很有趣了。(請注意,OpenGL實用程序庫GLU還具有創建與GLUT相同的三維對象(例如球體,圓柱體或錐體)的四維例程。)

GLUT可能對於全功能的OpenGL應用程序可能不滿意,但您可能會發現它是學習OpenGL的有用起點。本節的其余部分簡要描述了GLUT例程的一小部分,以便您可以按照本書其余部分中的編程示例進行操作。(有關GLUT子集的更多詳細信息,請參閱附錄D,或者有關GLUT其余部分的信息,請參閱X Window系統OpenGL編程章節4和5 )。

窗口管理

五個例程執行必要的任務來初始化窗口。

  • glutInit(int * argc,char ** argv)初始化GLUT並處理任何命令行參數(對於X,這將是像-display和-geometry這樣的選項)。在任何其他GLUT例程之前應該調用glutInit()
  • glutInitDisplayMode(unsigned int mode)指定是使用RGBA還是顏色索引顏色模型。您還可以指定是要使用單緩沖或雙緩沖窗口。(如果您正在使用顏色索引模式,則需要將某些顏色加載到顏色映射中;使用glutSetColor()來執行此操作。)最后,您可以使用此例程來指示您希望窗口具有相關聯的深度,模板和/或累積緩沖器。例如,如果要使用雙緩沖窗口,RGBA顏色模型和深度緩沖區,則可以調用glutInitDisplayModeGLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)。
  • glutInitWindowPosition(int x,int y)指定窗口左上角的屏幕位置。
  • glutInitWindowSize(int width,int size)指定窗口的大小(以像素為單位)。
  • int glutCreateWindow(char * string)創建一個帶有OpenGL上下文的窗口。它返回新窗口的唯一標識符。警告:直到glutMainLoop()被調用(見下一節),窗口尚未顯示。

顯示回調

glutDisplayFunc(void(* func)(void))是您將看到的第一個也是最重要的事件回調函數。每當GLUT確定窗口的內容需要重新顯示時,將執行glutDisplayFunc()注冊的回調函數因此,您應該放置顯示回調函數中需要重新繪制場景的所有例程。

如果您的程序更改了窗口的內容,有時您將不得不調用glutPostRedisplay(void),這會使glutMainLoop()在其下一個機會中調用注冊的顯示回調。

運行程序

最后一件事就是調用glutMainLoop(void)。現在已經創建了所有已創建的窗口,並且渲染到這些窗口現在已經有效了。事件處理開始,並且注冊的顯示回調被觸發。一旦這個循環進入,它永遠不會退出!

示例1-2顯示了如何使用GLUT創建示例1-1中所示的簡單程序注意代碼重組。為了最大限度地提高效率,僅需調用一次的操作(設置背景顏色和坐標系)現在處於一個稱為init()的過程中渲染(可能重新呈現)場景的操作位於display()過程中,這是注冊的GLUT顯示回調。

示例1-2:使用GLUT的簡單OpenGL程序:hello.c

#include <GL/gl.h>
#include <GL/glut.h>

void display(void)
{
/*  clear all pixels  */
    glClear (GL_COLOR_BUFFER_BIT);

/*  draw white polygon (rectangle) with corners at
 *  (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0)  
 */
    glColor3f (1.0, 1.0, 1.0);
    glBegin(GL_POLYGON);
        glVertex3f (0.25, 0.25, 0.0);
        glVertex3f (0.75, 0.25, 0.0);
        glVertex3f (0.75, 0.75, 0.0);
        glVertex3f (0.25, 0.75, 0.0);
    glEnd();

/*  don't wait!  
 *  start processing buffered OpenGL routines 
 */
    glFlush ();
}

void init (void) 
{
/*  select clearing (background) color       */
    glClearColor (0.0, 0.0, 0.0, 0.0);

/*  initialize viewing values  */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}

/* 
 *  Declare initial window size, position, and display mode
 *  (single buffer and RGBA).  Open window with "hello"
 *  in its title bar.  Call initialization routines.
 *  Register callback function to display graphics.
 *  Enter main loop and process events.
 */
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize (250, 250); 
    glutInitWindowPosition (100, 100);
    glutCreateWindow ("hello");
    init ();
    glutDisplayFunc(display); 
    glutMainLoop();
    return 0;   /* ISO C requires main to return int. */
}

處理輸入事件

您可以使用這些例程來注冊在發生指定事件時調用的回調命令。

  • glutReshapeFunc(void(* func)(int w,int h))指示在調整窗口大小時應采取的操作。
  • glutKeyboardFunc(void(* func)(unsigned char key,int x,int y))和glutMouseFunc(void(* func)(int button,int state,int x,int y))允許您鏈接鍵盤鍵或按下或釋放鍵或鼠標按鈕時調用的例程的鼠標按鈕。
  • 當鼠標按鍵被移動時,glutMotionFunc(void(* func)(int x,int y))會注冊一個例程來回調。

管理背景過程

如果沒有其他事件處於掛起狀態,則可以指定要執行的函數 - 例如,當事件循環將以空閑狀態時,將使用glutIdleFunc(void(* func)(void))。此例程將指向該函數的指針作為其唯一參數。傳遞NULL(零)以禁用該函數的執行。

繪制三維對象

GLUT包括用於繪制這些三維對象的幾個例程:

錐體

二十面體

茶壺

立方體

八面體

四面體

十二面體

領域

花托

您可以將這些對象繪制為線框或固定陰影對象,並定義表面法線。例如,立方體和球體的例程如下所示:

void glutWireCube(GLdouble size);

void glutSolidCube(GLdouble size);

void glutWireSphere(GLdouble radius,GLint slice, GLint stacks);

void glutSolidSphere(GLdouble radius,GLint 片, GLint 堆棧);

所有這些模型都以世界坐標系的起源為中心。(有關所有這些繪圖程序的原型的信息,請參閱。)


動畫

在圖形計算機上可以做的最令人興奮的事情之一就是繪制移動圖片。無論您是工程師,試圖看到您正在設計的機械部件的各個方面,飛行員學習如何使用仿真飛行飛機,或僅僅是電腦游戲愛好者,很明顯,動畫是計算機圖形學的重要組成部分。

在電影院里,通過拍攝一系列照片並在屏幕上以每秒24個的速度投影,實現動作。每個框架移動到透鏡后面的位置,快門被打開,並且框架被顯示。快門瞬間關閉,同時膠片前進到下一幀,然后顯示該幀,依此類推。雖然你每秒鍾看24個不同的幀,但你的大腦將它們全部融入到一個平滑的動畫中。(老查理·卓別林的電影每秒16幀拍攝,顯得很厲害)事實上,大多數現代投影機以每秒48張的速度顯示兩張照片,以減少閃爍。計算機圖形屏幕通常刷新(重畫圖片)每秒大約60到76次,有些甚至每秒刷新約120次刷新。顯然,

電影投影的主要原因是顯示時每幀都完成。假設你嘗試用這樣的程序來做你的百萬幀電影的電腦動畫:

open_window();  for(i = 0; i <1000000; i ++){  clear_the_window();  draw_frame(ⅰ);  wait_until_a_24th_of_a_second_is_over();  }

如果您添加系統清除屏幕並繪制典型框架所需的時間,則根據接近1/24秒清除和繪制的程序,此程序會產生越來越多的令人不安的結果。假設繪圖幾乎要完整的1/24秒。首先繪制的物品在整個1/24秒內可見,並在屏幕上呈現一個實心圖像; 當程序從下一幀開始時,朝向最終繪制的項目立即被清除。他們最多呈現幽靈般的形象,因為在1/24秒鍾的大部分時間,你的眼睛正在觀看清晰的背景,而不是不幸的物品,最后被畫出來。問題是這個程序不顯示完全繪制的框架; 相反,您會看到繪圖發生。

大多數OpenGL實現提供雙緩沖 - 提供兩個完整顏色緩沖區的硬件或軟件。一個被顯示,而另一個被繪制。當框架的繪制完成時,兩個緩沖區被交換,所以正在查看的緩沖區現在用於繪制,反之亦然。這就像一個電影放映機,只有兩幀在一個循環中; 當一個人被投射在屏幕上時,藝術家正在拼命地擦除並重新繪制不可見的框架。只要藝術家足夠快,觀眾注意到該設置與已經繪制了所有框架的設置之間沒有區別,並且投影機只是簡單地一個接一個地顯示它們。雙緩沖,每幅畫面僅在繪圖完成時顯示; 觀看者從未看到部分畫框。

上述程序的修改版本可以顯示平滑的動畫圖形,如下所示:

open_window_in_double_buffer_mode();  for(i = 0; i <1000000; i ++){  clear_the_window();  draw_frame(ⅰ);  swap_the_buffers();  }

刷新暫停

對於某些OpenGL實現,除了簡單地交換可見和可繪制的緩沖區之外,swap_the_buffers()例程等待直到當前屏幕刷新周期結束,以便前一個緩沖區被完全顯示。該例程也允許從一開始就完全顯示新的緩沖區。假設您的系統每秒刷新顯示60次,這意味着您可以實現的最快幀速率為每秒60幀(fps),如果所有幀都可以在1/60秒內清除並繪制,您的動畫將按照這個速度順利運行。

在這種系統上經常發生的情況是,框架太復雜,無法畫出1/60秒,因此每幀都會顯示不止一次。例如,如果繪制幀需要1/45秒,您將獲得30 fps,並且圖形空閑1 / 30-1 / 45 = 1/90秒/幀或三分之一的時間。

此外,視頻刷新率是恆定的,這可能會產生一些意想不到的性能后果。例如,每刷新監視器為1/60秒,幀頻恆定,您可以以60 fps,30 fps,20 fps,15 fps,12 fps等運行(60/1,60/2,60 / 3,60 / 4,60 / 5,...)。這意味着,如果您正在編寫應用程序並逐漸添加功能(例如,它是一個飛行模擬器,並且您正在添加地面風景),起初您添加的每個功能對整體性能沒有影響 - 您仍然可以獲得60 fps。那么突然之間,你會添加一個新的功能,並且系統不能在1/60秒內完全畫出整個事情,所以動畫從60 fps減慢到30 fps,因為它錯過了第一個可能的緩沖區 - 交換時間

如果現場的復雜性接近任何魔法時刻(1/60秒,2/60秒,3/60秒等等),那么由於隨機變化,一些幀在時間上略微下降,有些稍微下。那么幀速率是不規則的,這可以在視覺上令人不安。在這種情況下,如果您無法簡化場景,以便所有的框架都足夠快,添加一個有意的,微小的延遲可能會更好,以確保它們都錯過,給出一個恆定的,較慢的幀速率。如果您的框架具有非常不同的復雜性,則可能需要更復雜的方法。

Motion = Redraw + Swap

真實動畫程序的結構與此描述沒有太大差異。通常,對於每個幀,從頭開始重繪整個緩沖區比找出哪些部分需要重繪更容易。對於諸如三維飛行模擬器之類的應用來說,這尤其如此,其中飛機方向的微小變化改變了窗外的所有位置。

在大多數動畫中,場景中的對象只是用不同的變換重新繪制 - 觀察者的觀點移動,或者汽車向下移動一點,或者物體稍微旋轉。如果非繪圖操作需要重新計算,則可達到的幀速率通常會降低。但是請記住,swap_the_buffers()例程中的空閑時間通常可以用於這種計算。

OpenGL沒有swap_the_buffers()命令,因為該功能可能在所有硬件上都不可用,在任何情況下,它都非常依賴於窗口系統。例如,如果您使用X Window系統並直接訪問,則可以使用以下GLX例程:

void glXSwapBuffers(Display * dpy,Window window);

(有關其他窗口系統的等效例程,請參閱附錄C.

如果您正在使用GLUT庫,則需要調用此例程:

void glutSwapBuffers(void);

示例1-3說明了在繪制旋轉正方形的示例中使用glutSwapBuffers(),如圖1-3所示。以下示例還顯示了如何使用GLUT來控制輸入設備並打開和關閉空閑功能。在這個例子中,鼠標按鈕可以開啟和關閉旋轉。

 

圖1-3:雙緩沖旋轉方形

示例1-3:雙緩沖程序:double.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

static GLfloat spin = 0.0;

void init(void) 
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glPushMatrix();
   glRotatef(spin, 0.0, 0.0, 1.0);
   glColor3f(1.0, 1.0, 1.0);
   glRectf(-25.0, -25.0, 25.0, 25.0);
   glPopMatrix();
   glutSwapBuffers();
}

void spinDisplay(void)
{
   spin = spin + 2.0;
   if (spin > 360.0)
      spin = spin - 360.0;
   glutPostRedisplay();
}

void reshape(int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void mouse(int button, int state, int x, int y) 
{
   switch (button) {
      case GLUT_LEFT_BUTTON:
         if (state == GLUT_DOWN)
            glutIdleFunc(spinDisplay);
         break;
      case GLUT_MIDDLE_BUTTON:
         if (state == GLUT_DOWN)
            glutIdleFunc(NULL);
         break;
      default:
         break;
   }
}

/* 
 *  Request double buffer display mode.
 *  Register mouse input callback functions
 */
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (250, 250); 
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display); 
   glutReshapeFunc(reshape); 
   glutMouseFunc(mouse);
   glutMainLoop();
   return 0;
}

 
OpenGL編程指南
編程指南 >第2章



第二章
國家管理和繪制幾何對象


章目標

閱讀本章后,您將可以執行以下操作:

  • 將窗口清除為任意顏色
  • 強制任何待處理的繪圖完成
  • 繪制任何幾何圖元 - 點,線和多邊形 - 在兩個或三個維度
  • 打開和關閉狀態並查詢狀態變量
  • 控制這些圖元的顯示 - 例如,繪制虛線或者勾勒出的多邊形
  • 在固體物體表面上的適當點指定法向量
  • 使用頂點數組來存儲和訪問很多幾何數據,只需要幾個函數調用
  • 200新新新新旗新新200新新200新200新新200新200新新200新200新新200新200新新200新新200新新200新新

雖然您可以使用OpenGL繪制復雜而有趣的圖片,但它們都是由少量的原始圖形項目構成的。這不應該太令人驚訝 - 看看達芬奇是用鉛筆和油漆刷完成的。

在最高抽象層次上,有三種基本繪圖操作:清除窗口,繪制幾何對象和繪制柵格對象。第8章將介紹柵格對象,其中包括二維圖像,位圖和字符字體等在本章中,您將學習如何清除屏幕並繪制幾何對象,包括點,直線和平面多邊形。

你可能會想到自己,“等一下,我看過很多電腦和電視上的電腦圖形,有很多漂亮的陰影曲線和表面,如果所有的OpenGL都可以繪制,那么是直線的和平面多邊形?即使這本書的封面上的圖像也包括圓桌和桌子上有曲面的物體。事實證明,你看到的所有曲線和曲面都是由大量的小平面多邊形或直線近似的,這與蓋上的球體是由一大組矩形塊組成的方式大致相同。地球儀似乎沒有光滑的表面,因為與全球相比,這些地塊相對較大。在本章的后面,

本章主要內容如下:

  • “繪圖生存套件”解釋了如何清除窗口並強制繪制完成。它還提供了有關控制幾何對象顏色和描述坐標系的基本信息。
  • “描述點,線和多邊形”顯示了原始幾何對象的集合以及如何繪制它們。
  • “基本狀態管理”描述了如何打開和關閉一些狀態(模式)和查詢狀態變量。
  • “顯示點,線和多邊形”解釋了對於如何繪制圖元的細節,您可以使用哪些控件,例如直徑點是否為實線或虛線,以及是否勾畫或填充多邊形。
  • 常規向量討論了如何為幾何對象指定法向量,並簡要地說明這些向量是什么。
  • “頂點數組”顯示了如何將很多幾何數據放入幾個數組中,以及如何僅使用幾個函數調用來渲染其描述的幾何。減少功能調用可能會提高渲染的效率和性能。
  • “屬性組”顯示如何查詢狀態變量的當前值,以及如何一次性保存和還原幾個相關的狀態值。
  • “構建表面多邊形模型的一些提示”探討了構造多邊形近似表面的問題和技術。

在閱讀本章的其余部分時,要注意的一點是,使用OpenGL,除非另有指定,否則每次發出繪圖命令時,都會繪制指定的對象。這可能看起來很明顯,但在某些系統中,您首先列出要繪制的東西。列表完成后,您可以通過圖形硬件來繪制列表中的項目。第一種風格稱為立即模式圖形,是默認的OpenGL風格。除了使用立即模式,您還可以選擇將一些命令保存在列表中(稱為顯示列表)以供以后繪制。立體模式的圖形通常更容易編程,但顯示列表通常更有效率。第7章告訴你如何使用顯示列表,以及為什么要使用它們。


繪圖生存套件

本節介紹如何清除窗口,准備繪制,設置要繪制的對象的顏色,並強制繪制完成。這些主題中沒有一個與幾何對象直接相關,但任何繪制幾何對象的程序都必須處理這些問題。

清除窗口

在電腦屏幕上繪制的圖紙與紙上的圖紙不同,因為紙張開始白色,所有您需要做的就是繪制圖片。在電腦上,握住圖片的內存通常會填充您畫出的最后一張照片,因此,在開始繪制新場景之前,通常需要將其清除為一些背景顏色。您用於背景的顏色取決於應用程序。對於文字處理器,您可以在開始繪制文本之前清除為白色(紙張的顏色)。如果你從宇宙飛船上畫出一個觀點,那么在開始繪制星星,行星和外星人的宇宙飛船之前,你應該清楚空白的黑色。有時您可能不需要清除屏幕; 例如,如果圖像是房間的內部,則繪制所有牆壁時,整個圖形窗口將被覆蓋。

在這一點上,您可能會想知道為什么我們一直在談論清除窗口 - 為什么不繪制一個足夠大的覆蓋整個窗口的適當顏色的矩形?首先,清除窗口的特殊命令比通用繪圖命令更有效率。另外,如第3章所示,OpenGL允許您任意設置坐標系,查看位置和查看方向,因此可能難以為窗口清除矩形找出適當的大小和位置。最后,在許多機器上,除了包含顯示的像素的顏色的緩沖器之外,圖形硬件還包括多個緩沖器。這些其他緩沖區必須不時清除,並且使用一個可以清除任何組合的單個命令很方便。(有關所有可能的緩沖區的討論,請參見第10章。)

您還必須知道像素的顏色如何存儲在稱為位平面的圖形硬件中有兩種存儲方式。可以將像素的紅色,綠色,藍色和阿拉伯(RGBA)值直接存儲在位平面中,或者存儲引用顏色查找表的單個索引值。RGBA彩色顯示模式是更常用的,所以本書大部分的例子都使用它。有關兩種顯示模式的更多信息,請參見第4章。)您可以安全地忽略所有對alpha值的引用,直到第6章

例如,這些代碼行將RGBA模式窗口清除為黑色:

glClearColor(0.0,0.0,0.0,0.0); 
glClear(GL_COLOR_BUFFER_BIT);

第一行將清除顏色設置為黑色,下一個命令將整個窗口清除為當前清除顏色。glClear()的單個參數指示要清除的緩沖區。在這種情況下,程序僅清除保存屏幕上顯示的圖像的顏色緩沖區。通常,您在應用程序的早期設置一次清除顏色,然后根據需要經常清除緩沖區。OpenGL會將當前的清除顏色跟蹤為狀態變量,而不需要在每次清除緩沖區時指定它。

第4第10章再談其他緩沖區的使用方式。現在,你需要知道的是清理它們很簡單。例如,要清除顏色緩沖區和深度緩沖區,您將使用以下命令序列:

glClearColor(0.0,0.0,0.0,0.0); 
glClearDepth(1.0); 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

在這種情況下,呼叫到glClearColor()是相同的前,glClearDepth()命令指定了深度緩沖器的每個像素是要設置的值,並且該參數的glClear()命令現在包括的所有要清除的緩沖區按位或。glClear()的以下摘要包括一個列表可以清除的緩沖區,它們的名稱以及討論每種類型的緩沖區的章節。

void glClearColor(GLclampf  red  ,GLclampf  green  ,GLclampf  blue 
GLclampf 
alpha  );
設置當前清除顏色,用於清除RGBA模式下的色彩緩沖區。(有關 RGBA模式的更多信息,請參閱 第4章 。) 如果需要 紅色 綠色 藍色 Alpha  值將被鉗位到范圍[0,1]。默認清除顏色為(0,0,0,0),為黑色。
void glClear(GLbitfield  mask  );
將指定的緩沖區清除為其當前清除值。 掩模 參數是在列出的值的按位或組合 表2-1 
表2-1: 清除緩沖區

緩沖

名稱

參考

彩色緩沖

GL_COLOR_BUFFER_BIT

第四章

深度緩沖區

GL_DEPTH_BUFFER_BIT

第十章

累積緩沖液

GL_ACCUM_BUFFER_BIT

第十章

模板緩沖區

GL_STENCIL_BUFFER_BIT

第十章

在發出清除多個緩沖區的命令之前,如果要使用默認RGBA顏色,深度值,累積顏色和模板索引以外的其他值,則必須設置要清除每個緩沖區的值。除了設置清除顏色和深度緩沖區的當前值glClearColor()glClearDepth()命令之外,glClearIndex()glClearAccum()glClearStencil()指定用於將顏色索引,累積顏色和模板索引用於清除相應的緩沖區。(參見第4章以及第10章為這些緩沖液和它們的用途的描述。)

OpenGL允許您指定多個緩沖區,因為清除通常是一個緩慢的操作,因為窗口中每個像素(可能數百萬)都被觸摸,一些圖形硬件允許同時清除緩沖區集。不支持同時清除的硬件依次執行。和...之間的不同

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);

盡管兩者都具有相同的最終效果,但第一個例子可能會在許多機器上運行得更快。肯定不會跑得更慢。

指定顏色

使用OpenGL,被繪制對象的形狀描述與其顏色的描述無關。每當繪制特定的幾何對象時,使用當前指定的着色方案進行繪制。着色方案可能很簡單,“把所有的東西都放在消防車紅色”,或者可能像“假設物體是由藍色塑料制成的那樣復雜,有一個黃色的聚光燈指向這樣一個方向,在其他地方都有一般的低級紅棕色燈。“ 一般來說,OpenGL程序員首先設置顏色或着色方案,然后繪制對象。在顏色或着色方案改變之前,所有對象都以該顏色繪制或使用該着色方案。

例如,偽代碼

set_current_color(紅色); 
draw_object(A); 
draw_object(B); 
set_current_color(綠色); 
set_current_color(藍色); 
draw_object(C);

繪制對象A和B為紅色,對象C為藍色。第四行將當前顏色設置為綠色的命令將被浪費掉。

着色,照明和陰影都是整個章節或大部分專題的主題。然而,要繪制可以看到的幾何圖元,您需要一些基本知識來了解如何設置當前顏色; 這些信息在下面的段落中提供。(見第4第5章有關這些主題的詳細信息。)

要設置顏色,請使用命令glColor3f()它需要三個參數,所有這些參數都是0.0到1.0之間的浮點數。該參數是為了,紅色,綠色和藍色組件的顏色。您可以將這三個值視為指定顏色的“混合”:0.0表示不使用任何組件,1.0表示使用該組件的所有內容。因此,代碼

glColor3f(1.0,0.0,0.0);

使系統可以畫出最亮的紅色,沒有綠色或藍色組件。全零都變黑 相反,所有的都是白色的。將所有三個組件設置為0.5會產生灰色(黑色和白色之間的中間)。這里有八個命令及其設置的顏色。

glColor3f(0.0,0.0,0.0); 黑色 glColor3f(1.0,0.0,0.0);  glColor3f(0.0,1.0,0.0); 綠色 glColor3f(1.0,1.0,0.0); 黃色 glColor3f(0.0,0.0,1.0); 藍色 glColor3f(1.0,0.0,1.0); 品紅 glColor3f(0.0,1.0,1.0); 青色 glColor3f(1.0,1.0,1.0); 白色

您可能已經注意到,設置清除顏色的例程glClearColor()需要四個參數,前三個參數與glColor3f()的參數匹配第四個參數是alpha值; 在第6章的“混合”中有詳細的介紹現在,將glClearColor()的第四個參數設置為0.0,這是其默認值。

強制完成繪圖

如您在第1章中的“OpenGL渲染流水線”中所見大多數現代圖形系統可以被認為是裝配線。主要中央處理單元(CPU)發出繪圖命令。也許其他硬件做幾何變換。執行剪切,然后進行陰影和/或紋理化。最后,將這些值寫入位平面進行顯示。在高端架構中,這些操作中的每一個都由不同的硬件執行,這些硬件被設計為快速執行其特定任務。在這樣的架構中,CPU不需要等待每個繪圖命令完成,然后再發出下一個繪圖命令。當CPU正在向管道發送一個頂點時,轉換硬件正在轉換發送的最后一個,正在被裁剪之前的一個等等。在這樣一個系統中,

此外,應用程序可能在多台機器上運行。例如,假設主程序在其他地方(在稱為客戶機的機器上)運行,並且正在通過網絡連接到客戶端的工作站或終端(服務器)上查看繪圖結果。在這種情況下,由於相當多的開銷通常與每個網絡傳輸相關聯,所以每次都通過網絡一次發送每個命令可能是非常低效的。通常,在發送之前,客戶端將一組命令收集到單個網絡數據包中。不幸的是,客戶端上的網絡代碼通常無法知道圖形程序完成了繪制幀或場景。在最壞的情況下,它會永遠等待足夠的附加繪圖命令來填充數據包,

因此,OpenGL提供了glFlush()命令,即使它可能不滿,也迫使客戶端發送網絡數據包。哪里沒有網絡,所有的命令都是在服務器上立即執行,glFlush()可能沒有任何效果。但是,如果您正在編寫一個要使用和不使用網絡正常工作的程序,請在每個框架或場景的末尾包括調用glFlush()注意,glFlush()不等待繪圖完成 - 它只是強制繪圖開始執行,從而保證所有以前的命令在有限的時間內執行,即使沒有進一步的渲染命令被執行。

還有其他glFlush()有用的情況。

  • 在系統內存中構建映像並且不想不斷更新屏幕的軟件渲染器。
  • 收集渲染命令集以攤銷啟動成本的實施。上述網絡傳輸示例是其中的一個實例。
void 

一些命令 - 例如,以雙緩沖模式交換緩沖區的命令 - 在掛起的命令發生之前自動將其掛起。

如果glFlush()不足夠,請嘗試使用glFinish()此命令以glFlush()方式刷新網絡,然后等待來自圖形硬件或網絡的通知,指示圖形在幀緩沖區中已完成。如果要同步任務,您可能需要使用glFinish(),例如,在使用Display PostScript繪制標簽之前,確保您的三維渲染在屏幕上。另一個例子是確保繪圖在開始接受用戶輸入之前已經完成。發布glFinish()后命令,您的圖形進程被阻止,直到它從圖形硬件接收到繪圖完成的通知。請記住,過度使用glFinish()可以降低應用程序的性能,特別是如果您通過網絡運行,因為它需要往返通信。如果glFlush()足以滿足您的需要,請使用它而不是glFinish()

void  glFinish (void);
強制所有以前發布的OpenGL命令完成。直到前面命令的所有效果都被完全實現為止,此命令才會返回。

坐標系生存套件

每當您最初打開窗口或稍后移動或調整窗口大小時,窗口系統將發送一個事件通知您。如果您使用GLUT,通知將自動化; 任何已經注冊到glutReshapeFunc()的例程都將被調用。你必須注冊一個回調函數

  • 重新建立將成為新的渲染畫布的矩形區域
  • 定義要繪制對象的坐標系

第3章中,您將看到如何定義三維坐標系,但是現在,只需創建一個簡單的基本二維坐標系即可繪制幾個對象。調用glutReshapeFuncreshape),其中reshape()是示例2-1所示的以下函數。

示例2-1:重新調用回調函數

void reshape(int w,int h) { glViewport(0,0,(GLsizei)w,(GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0,(GLdouble)w,0.0,(GLdouble)h); }

GLUT的內部函數將傳遞這個函數兩個參數:新的,移動的或已調整大小的窗口的寬度和高度(以像素為單位)。glViewport()將繪制的像素矩形調整為整個新窗口。接下來的三個例程調整繪圖坐標系,使左下角為(0,0),右上角為(wh)(見圖2-1)。

再說一下,想一下一張圖紙。reshape()中wh表示圖形紙上有多少列和正方形。那么你必須把軸放在圖紙上。所述gluOrtho2D()例行程序把原點,(0,0),在最低,最左邊的方一路,並使得每個正方形表示一個單元。現在,當您在本章的其余部分渲染點,線和多邊形時,它們將以容易預測的方塊出現在本文中。(現在,保持所有的對象二維。)

 

圖2-1:坐標系由w = 50定義,h = 50


描述點,線和多邊形

本節介紹如何描述OpenGL幾何圖元。最終根據它們的頂點描述所有幾何圖元- 定義點本身的坐標,線段的端點或多邊形的角。下一節將討論這些圖元是如何顯示的,以及它們在顯示器上的控制。

什么是點,線和多邊形?

你可能有一個很好的想法,數學家意味着什么是術語,線和多邊形。OpenGL的含義是相似的,但不完全相同。

一個區別來自於基於計算機的計算的局限性。在任何OpenGL實現中,浮點計算的精度都是有限的,它們有四舍五入的錯誤。因此,OpenGL點,線和多邊形的坐標也有相同的問題。

另一個更重要的區別來自於光柵圖形顯示的局限性。在這樣的顯示器上,最小的可顯示單元是像素,雖然像素可能小於1/100英寸寬,但它們仍然遠遠大於數學家的無限小(點)或無限薄的概念)。當OpenGL執行計算時,它假定點被表示為浮點數的向量。然而,一個點通常(但不總是)繪制為單個像素,並且具有稍微不同坐標的許多不同點可以由相同像素上的OpenGL繪制。

一個點由一組稱為頂點的浮點數表示。所有內部計算完成,就好像頂點是三維的。由用戶指定為二維(即只有xy坐標)的頂點由OpenGL 分配給等於零z坐標。

高級

OpenGL在三維投影幾何的均勻坐標中工作,因此對於內部計算,所有頂點都用四個浮點坐標(xyzw)表示。如果w與零不同,這些坐標對應於歐幾里德三維點(x / w y / w z / w)。您可以在OpenGL命令中指定w坐標,但很少這樣做。如果沒有指定w坐標,那么它被理解為1.0。有關均勻坐標系的更多信息,請參閱附錄F.

在OpenGL中,術語是指一個線段,而不是數學家的版本在兩個方向上延伸到無窮大。有一些簡單的方法來指定連接的一系列線段,甚至是一個閉合的連接的一系列段(見圖2-2)。然而,在所有情況下,構成連接系列的線根據其端點處的頂點來指定。

圖2-2:兩個連接的線段系列

多邊形

多邊形是由線段的單個閉環包圍的區域,其中線段由端點處的頂點指定。多邊形通常用內部填充的像素繪制,但也可以將其繪制為輪廓或一組點。(參見“多邊形細節”。

一般來說,多邊形可能是復雜的,因此OpenGL對構成原始多邊形的一些強大的限制。首先,OpenGL多邊形的邊緣不能相交(數學家會將滿足這個條件的多邊形稱為簡單多邊形)。第二,OpenGL多邊形必須是凸的,意味着他們不能有壓痕。正確地說,如果在內部的任何兩個點處連接它們的線段也在內部,則區域是凸的。有關無效多邊形的一些示例,請參見圖2-3。然而,OpenGL並不限制構成凸多邊形邊界的線段的數量。注意,不能描述具有孔的多邊形。它們是非凸的,它們不能用由單個閉環組成的邊界繪制。請注意,如果您使用非凸面填充的多邊形來呈現OpenGL,則可能會按照您的期望繪制它。例如,在大多數系統上,不會多於多邊形的凸包被填充。在某些系統上,小於凸包可能會被填滿。

圖2-3:有效和無效的多邊形

OpenGL對有效多邊形類型的限制的原因在於,為限制類多邊形提供快速的多邊形渲染硬件要簡單得多。簡單的多邊形可以快速渲染。困難的情況很難迅速發現。所以為了獲得最大的性能,OpenGL會越過它的手指,並假定多邊形是簡單的。

許多現實世界的表面由非常多邊形,非凸多邊形或具有孔的多邊形組成。由於所有這樣的多邊形可以由簡單的凸多邊形的聯合形成,所以在GLU庫中提供了用於構建更復雜對象的一些例程。這些例程將復雜的描述和細分,或將它們分解成可以渲染的較簡單OpenGL多邊形的組。(有關細分例程的更多信息,請參見第11章中的“多邊形細分”)。

由於OpenGL頂點總是三維的,所以形成特定多邊形邊界的點不一定位於空間中的同一平面上。(當然,在許多情況下,如果所有的z坐標都為零,或者如果多邊形是三角形)。如果多邊形的頂點不在同一平面上,則在空間中進行各種旋轉之后,視點的變化和投影到顯示屏上,點可能不再形成簡單的凸多邊形。例如,想象一個四點四邊形這些點稍微偏離平面,並且看起來幾乎是邊緣的。您可以獲得一個非常簡單的多邊形,類似於領結,如圖2-4所示,不能保證正確呈現。如果您通過由位於真實表面上的點形成的四邊形近似曲面,則這種情況並不常見。您可以隨時通過使用三角形來避免問題,因為任何三個點總是位於一個平面上。

圖2-4:非平面多邊形轉換為非常多邊形

矩形

由於矩形在圖形應用程序中很常見,所以OpenGL提供了一個填充矩形的繪圖原語glRect *()您可以按照“OpenGL幾何繪圖基元”中所述繪制一個矩形作為多邊形,但是您的OpenGL的特定實現可能會為矩形優化了glRect *()

void glRect {sifd}( TYPEx1  TYPEy1  TYPEx2  TYPEy2  ); 
void glRect {sifd} v
TYPE * v1  TYPE * v2  );
Draws the rectangle defined by the corner points ( x1, y1 ) and ( x2, y2 ). The rectangle lies in the plane  z =0 and has sides parallel to the  x - and  y -axes. If the vector form of the function is used, the corners are given by two pointers to arrays, each of which contains an ( x, y ) pair.

Note that although the rectangle begins with a particular orientation in three-dimensional space (in the x-y plane and parallel to the axes), you can change this by applying rotations or other transformations. (See Chapter 3 for information about how to do this.)

Curves and Curved Surfaces

通過短線段或小多邊形區域,任何平滑的曲線或曲面可以近似為任意任意的精度。因此,將曲線和曲面充分地細分,然后用直線段或平面多邊形逼近它們,使它們看起來彎曲(見圖2-5)。如果你懷疑這真的有效,想象細分,直到每個線段或多邊形如此微小,它比屏幕上的像素小。

 

圖2-5:近似曲線

即使曲線不是幾何圖元,OpenGL確實為細分和繪制提供了一些直接的支持。有關如何繪制曲線和曲面的信息,請參閱第12章。)

指定頂點

使用OpenGL,所有幾何對象最終被描述為一組有序的頂點。您可以使用glVertex *()命令來指定一個頂點。

void glVertex {234} {sifd} [v]( TYPEcoords  );
指定用於描述幾何對象的頂點。通過選擇適當的命令版本,您可以為特定頂點提供多達四個坐標(x,y,z,w)或至少兩個(x,y)。如果您使用的版本未明確指定z或w,則z被理解為0,w被理解為1.調用glVertex *()僅在glBegin()glEnd()之間有效

示例2-2提供了使用glVertex *()的一些示例

示例2-2: glVertex *()的合法用途

glVertex2s(2,3);  glVertex3d(0.0,0.0,3.1415926535898);  glVertex4f(2.3,1.0,2.2,2.0);  GLdouble dvect [3] = {5.0,9.0,1992.0}; glVertex3dv(dvect);

第一個例子表示具有三維坐標(2,3,0)的頂點。(記住,如果沒有指定,z坐標被理解為0.)第二個例子中的坐標是(0.0,0.0,3.1415926535898)(雙精度浮點數)。第三個例子表示具有三維坐標(1.15,0.5,1.1)的頂點。(請記住x,yz坐標最終被w坐標除。)在最后一個例子中,dvect是一個指向三個雙精度浮點數的數組的指針。

在某些機器上,glVertex *()的矢量格式更有效率,因為只需要將一個參數傳遞給圖形子系統。特殊硬件可能能夠在一個批次中發送一整套坐標系。如果您的機器是這樣的,那么安排數據是有利的,以便頂點坐標在內存中順序打包。在這種情況下,通過使用OpenGL的頂點數組操作可能會增加性能。(參見“頂點數組”

OpenGL幾何繪圖基元

現在您已經看到如何指定頂點,您仍然需要知道如何告訴OpenGL從這些頂點創建一組點,一條線或多邊形。要做到這一點,你括號每組頂點的調用之間在glBegin()和調用glEnd() 傳遞給glBegin()的參數決定了哪些幾何圖元從頂點構造。例如,示例2-3>指定了圖2-6所示的多邊形頂點。

示例2-3:填充多邊形

在glBegin(GL_POLYGON); glVertex2f(0.0,0.0); glVertex2f(0.0,3.0); glVertex2f(4.0,3.0); glVertex2f(6.0,1.5); glVertex2f(4.0,0.0); glEnd();

 

圖2-6:繪制多邊形或一組點

如果您使用GL_POINTS而不是GL_POLYGON,則原始圖將只是圖2-6所示的五個點。glBegin()的以下功能摘要中的表2-2 列出了十個可能的參數和相應的基元類型。

void glBegin(GLenum  mode  );
標記描述幾何圖元的頂點數據列表的開頭。原型的類型由 模式 指示 ,可以是 表2-2  所示的任何值
表2-2: 幾何原始名稱和含義

含義

GL_POINTS

個別點

GL_LINES

頂點對被解釋為單獨的線段

GL_LINE_STRIP

系列連接線段

GL_LINE_LOOP

same as above, with a segment added between last and first vertices

GL_TRIANGLES

triples of vertices interpreted as triangles

GL_TRIANGLE_STRIP

linked strip of triangles

GL_TRIANGLE_FAN

linked fan of triangles

GL_QUADS

quadruples of vertices interpreted as four-sided polygons

GL_QUAD_STRIP

linked strip of quadrilaterals

GL_POLYGON

boundary of a simple, convex polygon


void glEnd(void);

Marks the end of a vertex-data list.

Figure 2-7 shows examples of all the geometric primitives listed in Table 2-2. The paragraphs that follow the figure describe the pixels that are drawn for each of the objects. Note that in addition to points, several types of lines and polygons are defined. Obviously, you can find many ways to draw the same primitive. The method you choose depends on your vertex data.

 

Figure 2-7 : Geometric Primitive Types

As you read the following descriptions, assume that n vertices (v0, v1, v2, ... , vn-1) are described between a glBegin() and glEnd() pair.

GL_POINTS

Draws a point at each of the n vertices.

GL_LINES

Draws a series of unconnected line segments. Segments are drawn between v0 and v1, between v2 and v3, and so on. If n is odd, the last segment is drawn between vn-3 and vn-2, and vn-1 is ignored.

GL_LINE_STRIP

Draws a line segment from v0 to v1, then from v1 to v2, and so on, finally drawing the segment from vn-2 to vn-1. Thus, a total of n-1 line segments are drawn. Nothing is drawn unless n is larger than 1. There are no restrictions on the vertices describing a line strip (or a line loop); the lines can intersect arbitrarily.

GL_LINE_LOOP

Same as GL_LINE_STRIP, except that a final line segment is drawn from vn-1 to v0, completing a loop.

GL_TRIANGLES

Draws a series of triangles (three-sided polygons) using vertices v0, v1, v2, then v3, v4, v5, and so on. If n isn't an exact multiple of 3, the final one or two vertices are ignored.

GL_TRIANGLE_STRIP

使用頂點v0,v1,v2,然后v2,v1,v3(注意順序),然​​后是v2,v3,v4等繪制一系列三角形(三面多邊形)。排序是為了確保三角形都以相同的方向繪制,以便條帶可以正確地形成表面的一部分。保持方向對於某些操作很重要,例如剔除。(參見“反轉和剔除多邊形面”對於任何繪制,n必須至少為3。

GL_TRIANGLE_FAN

與GL_TRIANGLE_STRIP相同,除了頂點是v0,v1,v2,然后是v0,v2,v3,然后是v0,v3,v4等等(見圖2-7)。

GL_QUADS

使用頂點v0,v1,v2,v3,然后v4,v5,v6,v7等繪制一系列四邊形(四邊形多邊形)。如果n不是4的倍數,則忽略最后的一個,兩個或三個頂點。

GL_QUAD_STRIP

繪制一系列從v0,v1,v3,v2,v2,v3,v5,v4,v4,v5,v7,v6等開始的四邊形(四邊形多邊形)(見圖2-7)。在繪制任何東西之前,n必須至少為4。如果n為奇數,則忽略最終頂點。

GL_POLYGON

使用點v0,...,vn-1作為頂點繪制多邊形。n必須至少為3,否則不繪制。另外,指定的多邊形本身不能相交,必須是凸的。如果頂點不滿足這些條件,結果是不可預測的。

使用glBegin()和glEnd()的限制

關於頂點的最重要的信息是它們的坐標,它們由glVertex *()命令指定您還可以為每個頂點提供額外的頂點特定數據 - 顏色,法向量,紋理坐標或這些特殊組合 - 使用特殊命令。另外還有一些命令在glBegin()glEnd()之間有效表2-3包含這些有效命令的完整列表。

表2-3: glBegin()和glEnd()之間的有效命令

命令

命令目的

參考

glVertex *()

設置頂點坐標

第2章

glColor *()

設置當前顏色

第四章

glIndex *()

設置當前顏色索引

第四章

glNormal *()

設定法線矢量坐標

第2章

glTexCoord *()

設置紋理坐標

第九章

glEdgeFlag *()

控制邊緣的繪制

第2章

glMaterial *()

設置材料屬性

第五章

glArrayElement()

提取頂點數組數據

第2章

glEvalCoord *(),glEvalPoint *()

生成坐標

第十二章

glCallList(),glCallLists()

執行顯示列表

第7章

glBegin()glEnd()之間沒有其他OpenGL命令有效,並使大多數其他OpenGL調用生成錯誤。glBegin()glEnd()之間調用時,一些頂點數組命令,如glEnableClientState()glVertexPointer()都有未定義的行為,但不一定會產生錯誤。(此外,與OpenGL相關的例程,如glX *()例程在glBegin()glEnd()之間有未定義的行為。這些情況應該避免,調試可能會更加困難。

Note, however, that only OpenGL commands are restricted; you can certainly include other programming-language constructs (except for calls, such as the aforementioned glX*() routines). For example, Example 2-4 draws an outlined circle.

Example 2-4 : Other Constructs between glBegin() and glEnd()

#define PI 3.1415926535898 
GLint circle_points = 100; 
glBegin(GL_LINE_LOOP); 
for (i = 0; i < circle_points; i++) {    
   angle = 2*PI*i/circle_points; 
   glVertex2f(cos(angle), sin(angle)); 
} 
glEnd();

Note: This example isn't the most efficient way to draw a circle, especially if you intend to do it repeatedly. The graphics commands used are typically very fast, but this code calculates an angle and calls the sin() and cos() routines for each vertex; in addition, there's the loop overhead. (Another way to calculate the vertices of a circle is to use a GLU routine; see "Quadrics: Rendering Spheres, Cylinders, and Disks" in Chapter 11.) If you need to draw lots of circles, calculate the coordinates of the vertices once and save them in an array and create a display list (see Chapter 7), or use vertex arrays to render them.

Unless they are being compiled into a display list, all glVertex*() commands should appear between some glBegin() and glEnd() combination. (If they appear elsewhere, they don't accomplish anything.) If they appear in a display list, they are executed only if they appear between a glBegin() and a glEnd(). (See Chapter 7 for more information about display lists.)

Although many commands are allowed between glBegin() and glEnd(), vertices are generated only when a glVertex*() command is issued. At the moment glVertex*() is called, OpenGL assigns the resulting vertex the current color, texture coordinates, normal vector information, and so on. To see this, look at the following code sequence. The first point is drawn in red, and the second and third ones in blue, despite the extra color commands.

glBegin(GL_POINTS); 
   glColor3f(0.0, 1.0, 0.0);                  /* green */ 
   glColor3f(1.0, 0.0, 0.0);                  /* red */ 
   glVertex(...); 
   glColor3f(1.0, 1.0, 0.0);                  /* yellow */ 
   glColor3f(0.0, 0.0, 1.0);                  /* blue */ 
   glVertex(...); 
   glVertex(...); 
glEnd();

You can use any combination of the 24 versions of the glVertex*() command between glBegin() and glEnd(), although in real applications all the calls in any particular instance tend to be of the same form. If your vertex-data specification is consistent and repetitive (for example, glColor*glVertex*glColor*glVertex*,...), you may enhance your program's performance by using vertex arrays. (See "Vertex Arrays.")


Basic State Management

在上一節中,您看到了一個狀態變量的例子,當前的RGBA顏色以及它如何與一個原語相關聯。OpenGL維護許多狀態和狀態變量。物體可以通過照明,紋理化,隱藏的表面去除,霧化或影響其外觀的一些其它狀態而被渲染。

默認情況下,大多數這些狀態最初是無效的。這些狀態可能是昂貴的激活; 例如,打開紋理映射幾乎肯定會降低渲染原始圖像的速度。然而,由於增強的圖形功能,圖像的質量將會提高,看起來更加逼真。

要打開和關閉許多這些狀態,請使用這兩個簡單的命令:

void glEnable(GLenum  cap  ); 
void glDisable(GLenum 
cap  );
glEnable()打開一個功能,glDisable()將其關閉。有40個枚舉值可以作為參數傳遞給glEnable()glDisable()一些例子是GL_BLEND(它控制混合RGBA值),GL_DEPTH_TEST(控制深度比較和深度緩沖區更新),GL_FOG(控制霧),GL_LINE_STIPPLE(圖案線),GL_LIGHTING(你得到想法),以及所以。

您還可以檢查當前狀態是否已啟用或禁用。

GLboolean glIsEnabled(GLenum  功能
根據所查詢的能力是否被激活,R運行GL_TRUE或GL_FALSE。

你剛剛看到的狀態有兩個設置:開和關。然而,大多數OpenGL例程為更復雜的狀態變量設置值。例如,例程glColor3f()設置三個值,它們是GL_CURRENT_COLOR狀態的一部分。有五個查詢例程用於查找為多個狀態設置的值:

void glGetBooleanv(GLenum pname ,GLboolean * params ); 
void glGetIntegerv(GLenum 
pname ,GLint * params ); 
void glGetFloatv(GLenum 
pname ,GLfloat * params ); 
void glGetDoublev(GLenum 
pname ,GLdouble * params ); 
void glGetPointerv(GLenum 
pname ,GLvoid ** params );
獲取布爾,整數,浮點,雙精度或指針狀態變量。所述 PNAME  參數是指示所述狀態變量返回一個符號常數, PARAMS  是一個指針所指示的類型,在其中放置返回的數據的數組。有關  pname 的可能值,請參見 附錄B中 的表例如,要獲取當前的RGBA顏色,附錄B中的表格建議您使用glGetIntegerv(GL_CURRENT_COLOR,params )或glGetFloatv(GL_CURRENT_COLOR,params )。如果需要返回所需變量作為請求的數據類型,則執行類型轉換。

這些查詢例程處理獲取狀態信息的大多數但不是全部請求。(有關另外16個查詢例程,請參見附錄B中的“查詢命令”。)


顯示點,線和多邊形

默認情況下,一個點被畫成屏幕上的單個像素,一條線被畫成固體,一個像素寬,並且多邊形被固定地填充。以下段落討論如何更改這些默認顯示模式的細節。

點細節

要控制渲染點的大小,請使用glPointSize()並提供所需的大小(以像素為單位)作為參數。

void glPointSize(GLfloat  size  );
設置渲染點的寬度(以像素為單位) 大小 必須大於0.0,默認值為1.0。

屏幕上為各種點寬度繪制的像素的實際收集取決於是否啟用抗鋸齒。(抗鋸齒是渲染點和線的平滑技術;更多詳細信息,請參見第6章中的“抗鋸齒”)。如果禁用抗鋸齒(默認值),則小數寬度將舍入為整數寬度,屏幕對齊繪制像素的正方形區域。因此,如果寬度為1.0,則平方為1像素乘1像素; 如果寬度為2.0,則平方為2像素×2像素,依此類推。

通過啟用抗鋸齒,繪制圓形像素,並且邊界上的像素通常以小於全強度繪制,以使邊緣更平滑。在此模式下,非整數寬度不舍入。

大多數OpenGL實現支持非常大的點大小。抗鋸齒點的最大尺寸是可查詢的,但是相同的信息不可用於標准的別名點。然而,特定的實現可能將標准的別名點的大小限制為不小於其最大抗鋸齒點大小,四舍五入到最接近的整數值。您可以通過glGetFloatv()使用GL_POINT_SIZE_RANGE來獲取此浮點值

行細節

在OpenGL中,你可以指定不同的寬度和線被線帶點以各種方式-點線,虛線,交替點划線,等繪制。

寬線

void glLineWidth(GLfloat  width  );
設置渲染行的寬度(以像素為單位) 寬度 必須大於0.0,默認值為1.0。

The actual rendering of lines is affected by the antialiasing mode, in the same way as for points. (See "Antialiasing" in Chapter 6.) Without antialiasing, widths of 1, 2, and 3 draw lines 1, 2, and 3 pixels wide. With antialiasing enabled, non-integer line widths are possible, and pixels on the boundaries are typically drawn at less than full intensity. As with point sizes, a particular OpenGL implementation might limit the width of nonantialiased lines to its maximum antialiased line width, rounded to the nearest integer value. You can obtain this floating-point value by using GL_LINE_WIDTH_RANGE with glGetFloatv().

Note: Keep in mind that by default lines are 1 pixel wide, so they appear wider on lower-resolution screens. For computer displays, this isn't typically an issue, but if you're using OpenGL to render to a high-resolution plotter, 1-pixel lines might be nearly invisible. To obtain resolution-independent line widths, you need to take into account the physical dimensions of pixels.

Advanced

With nonantialiased wide lines, the line width isn't measured perpendicular to the line. Instead, it's measured in the y direction if the absolute value of the slope is less than 1.0; otherwise, it's measured in the x direction. The rendering of an antialiased line is exactly equivalent to the rendering of a filled rectangle of the given width, centered on the exact line.

Stippled Lines

To make stippled (dotted or dashed) lines, you use the command glLineStipple() to define the stipple pattern, and then you enable line stippling with glEnable().

glLineStipple(1, 0x3F07);
glEnable(GL_LINE_STIPPLE);
void glLineStipple(GLint  factor , GLushort  pattern );
Sets the current stippling pattern for lines. The  pattern  argument is a 16-bit series of 0s and 1s, and it's repeated as necessary to stipple a given line. A 1 indicates that drawing occurs, and 0 that it does not, on a pixel-by-pixel basis, beginning with the low-order bit of the pattern. The pattern can be stretched out by using  factor , which multiplies each subseries of consecutive 1s and 0s. Thus, if three consecutive 1s appear in the pattern, they're stretched to six if  factor  is 2.  factor  is clamped to lie between 1 and 255. Line stippling must be enabled by passing GL_LINE_STIPPLE to glEnable(); it's disabled by passing the same argument to glDisable().

With the preceding example and the pattern 0x3F07 (which translates to 0011111100000111 in binary), a line would be drawn with 3 pixels on, then 5 off, 6 on, and 2 off. (If this seems backward, remember that the low-order bit is used first.) If factor had been 2, the pattern would have been elongated: 6 pixels on, 10 off, 12 on, and 4 off. Figure 2-8 shows lines drawn with different patterns and repeat factors. If you don't enable line stippling, drawing proceeds as if pattern were 0xFFFF and factor 1. (Use glDisable() with GL_LINE_STIPPLE to disable stippling.) Note that stippling can be used in combination with wide lines to produce wide stippled lines.

 

Figure 2-8 : Stippled Lines

One way to think of the stippling is that as the line is being drawn, the pattern is shifted by 1 bit each time a pixel is drawn (or factor pixels are drawn, if factor isn't 1). When a series of connected line segments is drawn between a single glBegin() and glEnd(), the pattern continues to shift as one segment turns into the next. This way, a stippling pattern continues across a series of connected line segments. When glEnd() is executed, the pattern is reset, and - if more lines are drawn before stippling is disabled - the stippling restarts at the beginning of the pattern. If you're drawing lines with GL_LINES, the pattern resets for each independent line.

實施例2-5示出了用幾個不同的點狀圖案和線寬繪制的結果。它還說明如果將線條繪制為一系列單個段而不是單個連接的線條,將會發生什么。運行程序的結果如圖2-9所示。

 

圖2-9:寬引線

示例2-5:線條紋圖案:lines.c

#include <GL / gl.h> #include <GL / glut.h>
 #define drawOneLine(x1,y1,x2,y2)glBegin(GL_LINES); \ glVertex2f((x1),(y1)); glVertex2f((x2),(y2)); glEnd();
 void init(void)  { glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT); }
 void display(void) { 我的
 glClear(GL_COLOR_BUFFER_BIT); / *為所有行選擇白色* / glColor3f(1.0,1.0,1.0);
 / *在第一排,3行,每個都有不同的點數* / glEnable(GL_LINE_STIPPLE);
    glLineStipple(1,0x0101); / * dotted * / drawOneLine(50.0,125.0,150.0,125.0); glLineStipple(1,0x00FF); / * dotted * / drawOneLine(150.0,125.0,250.0,125.0); glLineStipple(1,0x1C47); / * dash / dot / dash * / drawOneLine(250.0,125.0,350.0,125.0); / *在第二排,3條寬線,每個具有不同的點數* / glLineWidth(5.0); glLineStipple(1,0x0101); / * dotted * / drawOneLine(50.0,100.0,150.0,100.0); glLineStipple(1,0x00FF); / * dotted * / drawOneLine(150.0,100.0,250.0,100.0); glLineStipple(1,0x1C47); / * dash / dot / dash * / drawOneLine(250.0,100.0,350.0,100.0); glLineWidth(1.0);
 / *在第3排,6行,帶短划線/點/短划線* / / *作為單條連接線條的一部分* / glLineStipple(1,0x1C47); / * dash / dot / dash * / glBegin(GL_LINE_STRIP); for(i = 0; i <7; i ++) glVertex2f(50.0 +((GLfloat)i * 50.0),75.0); glEnd();
 / *在第4排,6個獨立行與同一點* / for(i = 0; i <6; i ++){ drawOneLine(50.0 +((GLfloat)i * 50.0),50.0, 50.0 +((GLfloat)(i + 1)* 50.0),50.0); }
 / *在第5行,1行,用破折號/點/破折號* / / *和點重復系數為5 * / glLineStipple(5,0x1C47); / * dash / dot / dash * / drawOneLine(50.0,25.0,350.0,25.0);
 glDisable(GL_LINE_STIPPLE); glFlush(); }
 void reshape(int w,int h) { glViewport(0,0,(GLsizei)w,(GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0,(GLdouble)w,0.0,(GLdouble)h); } int main(int argc,char ** argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(400,150);  glutInitWindowPosition(100,100); glutCreateWindow(argv [0]); 在里面 (); glutDisplayFunc(顯示器);  glutReshapeFunc(重塑); glutMainLoop(); 返回0; }

多邊形細節

多邊形通常是通過填充封閉在邊界內的所有像素來繪制的,但您也可以將其作為多邊形繪制,或者簡單地繪制為頂點上的點。一個填充的多邊形可能會被固定地填充或刻成一定的圖案。雖然這里省略了精確的細節,但是填充的多邊形以如下方式繪制:如果相鄰的多邊形共享邊或頂點,則構成邊或頂點的像素被精確地繪制一次 - 它們僅包含在多邊形之一中。這樣做是為了使部分透明的多邊形沒有繪制兩邊,這將使這些邊緣看起來更暗(或更亮,取決於您所繪制的顏色)。請注意,它可能會導致一個或多個像素列或列中沒有填充像素的窄多邊形。抗鋸齒多邊形比點和線更復雜。(看到詳見第六章“抗鋸齒”。)

多邊形作為點,輪廓或固體

一個多邊形有兩面 - 前面和后面 - 根據面向觀看者的一面可能會有不同的渲染。這允許您對固體物體進行剖面視圖,其中內部的部件和外部的部件之間有明顯的區別。默認情況下,前面和后面都以相同的方式繪制。要改變這一點,或僅繪制輪廓或頂點,請使用glPolygonMode()

void glPolygonMode(GLenum  face  ,GLenum  mode  );
控制多面體前后面的繪圖模式。參數 可以是GL_FRONT_AND_BACK,GL_FRONT或GL_BACK;  模式 可以是GL_POINT,GL_LINE或GL_FILL,以指示多邊形是否應繪制為點,概述或填充。默認情況下,前面和后面都被繪制。

例如,您可以使用這個例程的兩個調用來填充正面和后面的輪廓:

glPolygonMode(GL_FRONT,GL_FILL); glPolygonMode(GL_BACK,GL_LINE);

反轉和剔除多邊形面

按照慣例,頂點在屏幕上以逆時針順序顯示的多邊形稱為正面。您可以構建任何“合理”固體的表面 - 數學家將這種表面稱為可定向歧管(球體,甜甜圈和茶壺是可取向的;克萊恩瓶和莫比烏斯條不是) - 從一致方向的多邊形。換句話說,您可以使用所有順時針多邊形或所有逆時針多邊形。(這本質上是可定向的數學定義。)

假設你一直描述一個可定向表面的模型,但是你正好在外面有順時針方向。您可以通過使用glFrontFace()函數交換OpenGL考慮到背面的內容,為前面的多邊形提供所需的方向。

void glFrontFace(GLenum mode);
控制如何確定面向前的多邊形。默認情況下, 模式 是GL_CCW,它對應於窗口坐標中投影多邊形的有序頂點的逆時針方向。如果 模式 為GL_CW,則順時針方向的面朝下。

在由具有一致方向的不透明多邊形構成的完全封閉的表面中,任何面向后的多邊形都不可見 - 它們總是被前面的多邊形遮蔽。如果你在這個表面之外,你可以啟用剔除OpenGL確定的面向后面的多邊形。類似地,如果您在對象內,則只有后向面的多邊形是可見的。要指示OpenGL丟棄前面或后面的多邊形,請使用glCullFace()命令,並使用glEnable()進行剔除

void glCullFace(GLenum  mode  );
表示哪些多邊形在被轉換為屏幕坐標之前應該被丟棄(剔除)。模式是GL_FRONT,GL_BACK或GL_FRONT_AND_BACK,以指示正面,背面或所有多邊形。要生效,必須使用GL_CULL_FACE的glEnable()來啟用剔除它可以用glDisable()和相同的參數禁用

高級

在更技術上,多面體的面是面向前還是背面的決定取決於以窗口坐標計算的多邊形面積的符號。計算這個區域的一種方法是

其中x i和y i是n -vertex多邊形的第i個頂點xy窗口坐標

假設GL_CCW被指定,如果a > 0,那么與該頂點對應的多邊形被認為是正面的; 否則,它是面對的。如果GL_CW被指定,並且如果一個 <0,則對應的多邊形是面向前方; 否則,它是面對的。

嘗試這個

通過添加一些填充的多邊形來修改示例2-5。嘗試不同的顏色。嘗試不同的多邊形模式 還可以選擇查看其效果。

起刺多邊形

默認情況下,填充的多邊形以固體圖案繪制。它們也可以用32位32位窗口對齊的點模式填充,您可以使用glPolygonStipple()指定

void glPolygonStipple(const GLubyte * mask );
定義填充多邊形的當前條件模式。的參數 掩碼 是一個指向32 ' 的是真實解釋為0和1的一個掩模32位圖。在出現1的情況下,繪制多邊形中的相應像素,並且在出現0時,不繪制任何內容。 圖2-10  顯示了如何通過 掩碼中 的字符構造點畫圖案 使用glEnable()glDisable()以GL_POLYGON_STIPPLE作為參數,啟用和禁用多邊形點畫掩碼數據的解釋受glPixelStore *() GL_UNPACK *模式的影響。(請參見 第8章中的“控制像素存儲模式” 。)

除了定義當前的多邊形點畫圖案,您必須啟用點畫:

glEnable(GL_POLYGON_STIPPLE);

使用具有相同參數的glDisable()來禁用多邊形點畫。

圖2-11顯示了不間斷繪制的多邊形的結果,然后用兩種不同的點畫圖案進行繪制。程序如例2-6所示。由於程序在黑色背景上繪制為白色,因此使用圖2-10中的圖案作為模板,會發生白色到黑色的反轉(從圖2-10到圖2-11)。

 

圖2-10:構造多邊形條紋圖案

 

圖2-11:有條紋的多邊形

示例2-6:多邊形條紋圖案:polys.c

#include <GL / gl.h> #include <GL / glut.h> void display(void) { GLubyte fly [] = { 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,0x33,0xcc,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}; GLubyte半色調[] = { 0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55, 0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55, 0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55, 0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55, 0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55,  0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55, 0xAA,0xAA,0xAA,0xAA,0x55​​,0x55,0x55,0x55};
 glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,1.0,1.0);
 / *繪制一個固體,無張開的矩形,* /  / *然后兩個點畫矩形* / glRectf(25.0,25.0,125.0,125.0); glEnable(GL_POLYGON_STIPPLE); glPolygonStipple(fly); glRectf(125.0,25.0,225.0,125.0); glPolygonStipple(半色調); glRectf(225.0,25.0,325.0,125.0); glDisable(GL_POLYGON_STIPPLE);
 glFlush(); }
 void init(void)  { glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT);  }
 void reshape(int w,int h) { glViewport(0,0,(GLsizei)w,(GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0,(GLdouble)w,0.0,(GLdouble)h); }
 int main(int argc,char ** argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(350,150); glutCreateWindow(argv [0]); 在里面 (); glutDisplayFunc(顯示器); glutReshapeFunc(重塑); glutMainLoop(); 返回0;  }

您可能希望使用顯示列表來存儲多邊形點數模式以最大限度地提高效率。(見第7章“顯示清單設計哲學”)。

標記多邊形邊界

高級

OpenGL只能渲染凸多邊形,但實際上會出現許多非凸多邊形。要繪制這些非凸多邊形,您通常將它們細分為凸多邊形 - 通常是三角形,如圖2-12所示,然后繪制三角形。不幸的是,如果您將一般多邊形分解成三角形並繪制三角形,那么您無法真正使用glPolygonMode()畫多邊形的輪廓,因為你得到所有的三角形輪廓。要解決這個問題,你可以告訴OpenGL一個特定的頂點是否在邊界邊緣之前; OpenGL通過與每個頂點一起傳遞一個位來指示該頂點是否跟隨邊界邊緣來跟蹤該信息。然后,當在GL_LINE模式中繪制多邊形時,不繪制非邊界邊。在圖2-12中,虛線表示添加的邊。

圖2-12:細分非凸多邊形

默認情況下,所有頂點都標記在邊界邊界之前,但您可以使用命令glEdgeFlag *()手動控制邊緣標志的設置此命令在glBegin()glEnd()對之間使用,並且會影響它之后指定的所有頂點,直到下一個glEdgeFlag()調用為止它僅適用於為多邊形,三角形和四邊形指定的頂點,而不適用於為三角形或四邊形條指定的頂點。

void glEdgeFlag(GLboolean  flag  ); 
void glEdgeFlagv(const GLboolean * 
flag  );
指示頂點是否應被視為初始化多邊形的邊界邊緣。如果 標志 為GL_TRUE,則將邊緣標志設置為TRUE(默認值),並將任何創建的頂點視為在邊界邊緣之前,直到此函數再次被調用, 標志 為GL_FALSE。

舉例2-7繪制了如圖2-13所示的輪廓。

 

圖2-13:使用邊緣標記繪制的輪廓多邊形

示例2-7:標記多邊形邊界

glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); 
在glBegin(GL_POLYGON); 
    glEdgeFlag(GL_TRUE); 
    glVertex3fv(V0); 
    glEdgeFlag(GL_FALSE); 
    glVertex3fv(V1); 
    glEdgeFlag(GL_TRUE); 
    glVertex3fv(V2); 
glEnd();

正常向量

法向量(或正常的簡稱)是在這是垂直於表面的方向上指向的矢量。對於平坦表面,表面上的每個點的一個垂直方向是相同的,但是對於一般的曲面,法線方向在表面的每個點可能不同。使用OpenGL,您可以為每個多邊形或每個頂點指定一個法線。相同多邊形的頂點可能共享相同的法線(對於平坦的表面)或具有不同的法線(對於曲面)。但是不能在頂點以外的任何地方分配法線。

物體的法向矢量定義其表面在空間中的取向 - 特別是其相對於光源的取向。OpenGL使用這些向量來確定對象在其頂點接收多少光。照明 - 一個很大的話題本身就是第5章的主題,您可能希望在閱讀該章后查看以下信息。在這里簡要討論法向矢量,因為您在定義對象的幾何體的同時為對象定義法向量。

您使用glNormal *()將當前法線設置為傳入的參數的值。對glVertex *()的后續調用會使指定的頂點分配當前法線。通常,每個頂點具有不同的法線,這需要一系列交替調用,如例2-8所示。

示例2-8:頂點的曲面法線

glBegin(GL_POLYGON); 
   glNormal3fv(N0); 
   glVertex3fv(V0); 
   glNormal3fv(N1); 
   glVertex3fv(V1); 
   glNormal3fv(N2); 
   glVertex3fv(V2); 
   glNormal3fv(N3); 
   glVertex3fv(V3); 
glEnd();
void glNormal3 {bsidf}(TYPEnx,TYPEny,TYPEnz); 
void glNormal3 {bsidf} v(const 
TYPE  * v);
設置由參數指定的當前法向量。非向量版本(沒有v)需要三個參數,它們指定一個 被認為是正常的( nx,ny,nz  )向量。或者,您可以使用此函數的向量版本(使用v)並提供三個元素的單個數組來指定所需的法線。bs ^,以及的版本線性擴展他們的參數值在范圍[-1.0,1.0]。

找到一個對象的法線沒有任何魔法 - 最有可能的是,你必須執行一些可能包括衍生物的計算,但是有幾種可以用來實現某些效果的技巧和技巧。附錄E解釋了如何找到表面的法向量。如果您已經知道如何執行此操作,如果您可以依靠始終提供正常向量,或者您不想使用OpenGL照明設施提供的照明設備,則無需閱讀本附錄。

注意,在表面的給定點,兩個向量垂直於表面,並且它們指向相反的方向。按照慣例,正常是指向正在建模的表面外部的法線。(如果你的模型內部和外部相反,只需將每個法向量從(x,y,z)更改為( - &xgr;, - y, - z))。

另外,請注意,由於正常矢量僅指示方向,它們的長度通常是無關緊要的。您可以指定任何長度的法線,但最終在執行照明計算之前必須將其轉換為長度為1。(長度為1的向量稱為單位長度,或歸一化)。一般來說,應提供標准化的法向量。為了制作單位長度的法向量,將其xyz分量的每一個除以法線的長度:

只要您的模型轉換僅包括旋轉和平移,則正態矢量保持歸一化。(有關變換的討論,請參閱第3章。)如果執行不規則變換(例如縮放或乘以剪切矩陣),或者如果指定了非單位長度法線,則應該在轉換后將OpenGL自動歸一化法向量。為此,請使用GL_NORMALIZE作為參數調用glEnable()默認情況下,禁用自動歸一化。請注意,自動歸一化通常需要額外的計算,這可能會降低應用程序的性能。

 

頂點數組

您可能已經注意到,OpenGL需要許多函數調用來渲染幾何圖元。繪制一個20邊的多邊形需要22個函數調用:一次調用glBegin(),一個調用每個頂點,最后調用glEnd()在前面的兩個代碼示例中,附加信息(多邊形邊界邊緣標記或表面法線)為每個頂點添加了函數調用。這可以快速將一個幾何對象所需的函數調用次數增加一倍或三倍。對於某些系統,函數調用有很大的開銷,可能會阻礙性能。

另外一個問題是在相鄰多邊形之間共享的頂點的冗余處理。例如,圖2-14中的立方體具有六個面和八個共享頂點。不幸的是,使用描述此對象的標准方法,每個頂點必須指定三次:對於使用它的每個面都是一次。因此,24個頂點將被處理,即使八個足夠了。

圖2-14:六面 八個共享頂點

OpenGL具有頂點數組例程,允許您使用幾個數組指定大量與頂點相關的數據,並使用同樣少的函數調用訪問該數據。使用頂點數組例程,20面多邊形中的所有20個頂點可以放在一個數組中,並用一個函數調用。如果每個頂點也有一個表面正常,那么所有20個表面法線都可以放在另一個數組中,也可以用一個函數調用。

在頂點數組中排列數據可能會增加應用程序的性能。使用頂點數組可減少函數調用次數,從而提高性能。此外,使用頂點數組可以允許共享頂點的非冗余處理。(OpenGL的所有實現不支持頂點共享。)

注意:頂點數組是OpenGL 1.1版的標准配置,但不是OpenGL 1.0規范的一部分。使用OpenGL 1.0,一些供應商已經實現了頂點數組作為擴展。

使用頂點數組渲染幾何有三個步驟。

激活(啟用)多達六個陣列,每個陣列存儲不同類型的數據:頂點坐標,RGBA顏色,顏色索引,曲面法線,紋理坐標或多邊形邊緣標志。

將數據放入數組或數組。數組由它們的內存位置的地址(即指針)訪問。在客戶端 - 服務器模型中,該數據存儲在客戶端的地址空間中。

用數據繪制幾何。OpenGL通過取消引用指針從所有激活的數組中獲取數據。在客戶端 - 服務器模型中,數據被傳輸到服務器的地址空間。有三種方法可以做到這一點:

訪問各個數組元素(隨機跳躍)

創建單個數組元素的列表(有條不紊地跳躍)

處理順序數組元素

您選擇的取消引用方法可能取決於您遇到的問題類型。

交織的頂點數組數據是另一種常見的組織方法。而不是擁有多達六個不同的數組,每個數組保持不同類型的數據(顏色,表面法線,坐標等),您可能會將不同類型的數據混合到單個數組中。(見“交錯數組”兩種解決方法)

步驟1:啟用數組

第一步是使用枚舉參數調用glEnableClientState(),這會激活所選擇的數組。理論上,您可能需要調用六次以激活六個可用陣列。實際上,您可能只能在一到四個數組之間激活。例如,您不可能同時激活GL_COLOR_ARRAY和GL_INDEX_ARRAY,因為程序的顯示模式支持RGBA模式或顏色索引模式,但可能並不同時支持。

void glEnableClientState(GLenum  array 

指定要啟用的數組。符號常量GL_VERTEX_ARRAY,GL_COLOR_ARRAY,GL_INDEX_ARRAY,GL_NORMAL_ARRAY,GL_TEXTURE_COORD_ARRAY和GL_EDGE_FLAG_ARRAY是可接受的參數。

如果您使用照明,則可能需要為每個頂點定義曲面法線。(請參閱常規向量”。)為了使用頂點數組,您可激活曲面法線和頂點坐標數組:

glEnableClientState(GL_NORMAL_ARRAY); 
glEnableClientState(GL_VERTEX_ARRAY);

假設您要在某點關閉照明,並使用單一顏色繪制幾何。您想要調用glDisable()關閉照明狀態(參見第5章)。現在照明已停用,您也想停止更改表面正常狀態的值,這是浪費的努力。要這樣做,你打電話

glDisableClientState(GL_NORMAL_ARRAY);
void glDisableClientState(GLenum  array  );

指定要禁用的數組。接受與glEnableClientState()相同的符號常量

你可能會問自己為什么OpenGL的架構師創建了這些新的(和long!)命令名稱,gl * ClientState()為什么不能調用glEnable()glDisable()一個原因是glEnable()glDisable()可以存儲在顯示列表中,但頂點數組的規范不能,因為數據保留在客戶端。

步驟2:指定數組的數據

一個簡單的方法是單個命令在客戶機空間中指定單個數組。有六種不同的例程來指定數組 - 每個數組的一個例程。還有一個命令可以一次指定幾個客戶機空間數組,全部來自一個交錯數組。

void glVertexPointer(GLint  size  ,GLenum  type  ,GLsizei  stride 
const GLvoid 
* pointer  );
指定可以訪問空間坐標數據的位置。 指針 是數組中第一個頂點的第一個坐標的內存地址。 type  指定數組中每個坐標的數據類型(GL_SHORT,GL_INT,GL_FLOAT或GL_DOUBLE)。 size  是每個頂點的坐標數,它必須是2,3或4.  stride  是連續頂點之間的字節偏移量。如果 步幅 為0,則頂點被理解為緊緊包裝在陣列中。

要訪問其他五個數組,有五個類似的例程:
void glColorPointer(GLint size, GLenum type, GLsizei stride
const GLvoid *
pointer);
void glIndexPointer(GLenum 
type, GLsizei stride, const GLvoid *pointer);
void glNormalPointer(GLenum 
type, GLsizei stride
const GLvoid *
pointer);
void glTexCoordPointer(GLint 
size, GLenum type, GLsizei stride
const GLvoid *
pointer);
void glEdgeFlagPointer(GLsizei 
stride, const GLvoid *pointer);

例程中的主要區別是大小和類型是唯一的還是必須指定的。例如,表面法線始終具有三個部件,因此指定其大小是多余的。邊緣標志始終是單個布爾值,因此不需要提及大小和類型。表2-4顯示了大小和數據類型的合法值。

表2-4:頂點數組大小(每頂點數值)和數據類型(續)

命令

尺寸

類型參數的

glVertexPointer

2,3,4

GL_SHORT,GL_INT,GL_FLOAT,GL_DOUBLE

glNormalPointer

3

GL_BYTE,GL_SHORT,GL_INT,GL_FLOAT,GL_DOUBLE

glColorPointer

3,4

GL_BYTE,GL_UNSIGNED_BYTE,GL_SHORT,GL_UNSIGNED_SHORT,GL_INT,GL_UNSIGNED_INT,GL_FLOAT,GL_DOUBLE

glIndexPointer

1

GL_UNSIGNED_BYTE,GL_SHORT,GL_INT,GL_FLOAT,GL_DOUBLE

glTexCoordPointer

1,2,3,4

GL_SHORT,GL_INT,GL_FLOAT,GL_DOUBLE

glEdgeFlagPointer

1

無類型參數(數據類型必須為GLboolean)

示例2-9為RGBA顏色和頂點坐標使用頂點數組。RGB浮點值及其相應的(x,y)整數坐標將加載到GL_COLOR_ARRAY和GL_VERTEX_ARRAY中。

示例2-9:啟用和加載頂點數組:varray.c

static GLint vertices[] = {25, 25,
                          100, 325,
                          175, 25,
                          175, 325,
                          250, 25,
                          325, 325};
static GLfloat colors[] = {1.0, 0.2, 0.2,
                          0.2, 0.2, 1.0,
                          0.8, 1.0, 0.2,
                          0.75, 0.75, 0.75,
                          0.35, 0.35, 0.35,
                          0.5, 0.5, 0.5};
 glEnableClientState(GL_COLOR_ARRAY);  glEnableClientState(GL_VERTEX_ARRAY);   glColorPointer(3,GL_FLOAT,0,顏色);  glVertexPointer(2,GL_INT,0,vertices);

在步長為零的情況下,每種類型的頂點數組(RGB顏色,顏色索引,頂點坐標等)都必須緊密包裝。數組中的數據必須是均勻的; 也就是說,數據必須全部為RGB顏色值,所有頂點坐標,或所有其他某些數據類似的數據。

使用除零之外的步幅可能是有用的,特別是在處理交錯數組時。在以下數組GLfloats中,有六個頂點。對於每個頂點,有三個RGB顏色值,它們與(x,y,z)頂點坐標交替。

static GLfloat intertwined[] =
 {1.0,0.2,1.0,100.0,100.0,0.0, 1.0,0.2,0.2,0.0,200.0,0.0, 1.0,1.0,0.2,100.0,300.0,0.0, 0.2,1.0,0.2,200.0 ,300.0,0.0, 0.2,1.0,1.0,300.0,200.0,0.0, 0.2,0.2,1.0,200.0,100.0,0.0};

Stride允許頂點數組在數組中以規則的間隔訪問其所需的數據。例如,為了只引用中的顏色值交織陣列,下面的呼叫從所述數組的開始(其也可以作為傳遞開始&交織[0] 和向前跳到6 * 的sizeof(GLfloat)個字節,這是顏色和頂點坐標值的大小。該跳轉足以達到下一個頂點數據的開頭。

glColorPointer(3,GL_FLOAT,6 * sizeof(GLfloat),交織在一起);

對於頂點坐標指針,您需要從數組中進一步開始,在交織的第四個元素(記住C程序員開始計數為零)。

glVertexPointer(3,GL_FLOAT,6 * sizeof(GLfloat),&interwwined [3]);

步驟3:取消引用和渲染

直到頂點數組的內容被解引用,數組保留在客戶端,並且它們的內容容易改變。在步驟3中,獲取數組的內容,發送到服務器,然后向下發送圖形處理流水線進行渲染。

有三種獲取數據的方法:從單個數組元素(索引位置),數組元素序列和數組元素的有序列表中獲取。

取消單個數組元素

無效glArrayElement(閃爍 第i個
獲取 所有當前啟用的數組的一(第 )個頂點 的數據對於頂點坐標數組,相應的命令將是glVertex [  size  ] [  type  v(),其中 size  是[2,3,4]之一, 類型 是[s,i,f,d]之一,用於GLshort,GLint,GLfloat和GLdouble。大小和類型都由glVertexPointer()定義對於其他啟用的數組,glArrayElement()調用glEdgeFlagv()glTexCoord [  size  ] [  type  v()glColor [  size ] [  type  v()glIndex [  type  v()glNormal [  type  v()如果啟用頂點坐標數組,則在執行(如果啟用)最多五個相應數組值之后,最后執行glVertex * v()例程。

glArrayElement()通常在glBegin()glEnd()之間調用(如果調用外部,glArrayElement()設置所有啟用的數組的當前狀態,除了沒有當前狀態的頂點)。在示例2-10中,使用從啟用頂點的第三個,第四個和第六個頂點繪制三角形數組(再次記住,C程序員開始用零計數數組位置)。

示例2-10:使用glArrayElement()定義顏色和頂點

glEnableClientState (GL_COLOR_ARRAY);
glEnableClientState (GL_VERTEX_ARRAY);
glColorPointer (3, GL_FLOAT, 0, colors);
glVertexPointer (2, GL_INT, 0, vertices);

glBegin(GL_TRIANGLES);
glArrayElement (2);
glArrayElement (3);
glArrayElement (5);
glEnd();

執行時,后五行代碼具有相同的效果

glBegin(GL_TRIANGLES);
glColor3fv(colors+(2*3*sizeof(GLfloat));
glVertex3fv(vertices+(2*2*sizeof(GLint));
glColor3fv(colors+(3*3*sizeof(GLfloat));
glVertex3fv(vertices+(3*2*sizeof(GLint));
glColor3fv(colors+(5*3*sizeof(GLfloat));
glVertex3fv(vertices+(5*2*sizeof(GLint));
glEnd();

Since glArrayElement() is only a single function call per vertex, it may reduce the number of function calls, which increases overall performance.

Be warned that if the contents of the array are changed between glBegin() and glEnd(), there is no guarantee that you will receive original data or changed data for your requested element. To be safe, don't change the contents of any array element which might be accessed until the primitive is completed.

Dereference a List of Array Elements

glArrayElement() is good for randomly "hopping around" your data arrays. A similar routine, glDrawElements(), is good for hopping around your data arrays in a more orderly manner.

void glDrawElements(GLenum  mode , GLsizei  count , GLenum  type
void *
indices );
使用 計數 元素 定義幾何圖元序列,其索引存儲在數組 索引中 類型 必須是GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT或GL_UNSIGNED_INT之一,表示 索引 數組 的數據類型 模式 指定什么樣的基元被構造,並且是glBegin()接受的相同值之一例如,GL_POLYGON,GL_LINE_LOOP,GL_LINES,GL_POINTS等。

glDrawElements()的效果與此命令序列幾乎相同:

int i;
glBegin (mode);
for (i = 0; i < count; i++)
   glArrayElement(indices[i]);
glEnd();

glDrawElements()另外檢查以確保模式計數類型有效。此外,與上述順序不同,執行glDrawElements()導致多個狀態不確定。執行glDrawElements()后,如果相應的數組已被使能,當前RGB顏色,顏色索引,正常坐標,紋理坐標和邊緣標志是不確定的。

使用glDrawElements(),可以將多維數據集的每個面的頂點放置在索引數組中。示例2-11顯示了使用glDrawElements()渲染多維數據集的兩種方法圖2-15顯示了實例2-11中使用的頂點編號。

 

圖2-15:具有編號頂點的立方體

示例2-11:使用glDrawElements()的兩種方法

static GLubyte frontIndices = {4, 5, 6, 7};
static GLubyte rightIndices = {1, 2, 6, 5};
static GLubyte bottomIndices = {0, 1, 5, 4};
static GLubyte backIndices = {0, 3, 2, 1};
static GLubyte leftIndices = {0, 4, 7, 3};
static GLubyte topIndices = {2, 3, 7, 6};
 glDrawElements(GL_QUADS,4,GL_UNSIGNED_BYTE,frontIndices); glDrawElements(GL_QUADS,4,GL_UNSIGNED_BYTE,rightIndices); glDrawElements(GL_QUADS,4,GL_UNSIGNED_BYTE,bottomIndices); glDrawElements(GL_QUADS,4,GL_UNSIGNED_BYTE,backIndices); glDrawElements(GL_QUADS,4,GL_UNSIGNED_BYTE,leftIndices); glDrawElements(GL_QUADS,4,GL_UNSIGNED_BYTE,topIndices);

或者還是更好,把所有的指標都壓在一起:

static GLubyte allIndices = {4,5,6,7,1,2,6,5,  0,1,5,4,0,3,2,1,  0,4,7,3,2,3,7,6};
 glDrawElements(GL_QUADS,24,GL_UNSIGNED_BYTE,allIndices);

注意:glBegin() / glEnd()之間封裝glDrawElements()是一個錯誤

使用glArrayElement()glDrawElements(),您的OpenGL實現也可能會緩存最近處理的頂點,從而允許您的應用程序“共享”或“重用”頂點。拿起上述的立方體,例如,它有六個面(多邊形),但只有八個頂點。每個頂點正好使用三個面。沒有glArrayElement()glDrawElements(),渲染所有六個面都需要處理二十四個頂點,即使十六個頂點將是冗余的。您的OpenGL實現可能能夠最大限度地減少冗余,並且可以處理少至八個頂點。(重用頂點可能會限制在單個glDrawElements()調用中的所有頂點一個glBegin() / glEnd()對中的glArrayElement()

取代數組元素的序列

glArrayElement()glDrawElements() “繞過”你的數據數組時,glDrawArrays()直接通過它們。

void glDrawArrays(GLenum  mode  ,GLint  first  ,GLsizei  count  );
構造方法使用數組元素開始在幾何圖元序列 第一 和在結束 第一 計數 每個已啟用的陣列的-1。 mode  指定什么類型的基元被構造,並且是glBegin()接受的相同值之一例如,GL_POLYGON,GL_LINE_LOOP,GL_LINES,GL_POINTS等。

glDrawArrays()的效果與此命令序列幾乎相同:

int i;
glBegin (mode);
for (i = 0; i < count; i++)
   glArrayElement(first + i);
glEnd();

glDrawElements()的情況一樣glDrawArrays()也對其參數值執行錯誤檢查,如果相應的數組已啟用則會保留當前RGB顏色,顏色索引,正常坐標,紋理坐標和帶有不確定值的邊緣標志。

嘗試這個

  • 更改示例2-13中的二十面體繪圖程序以使用頂點數組。

交錯數組

高級

這一章(在早期的“跨越”),檢查交錯陣列的特殊情況。在該部分中,通過調用glColorPointer()glVertexPointer()來訪問交織在RGB顏色和3D頂點坐標上的數組交織在一起仔細使用步幅有助於正確指定陣列。

static GLfloat intertwined[] =
{1.0,0.2,1.0,100.0,100.0,0.0, 1.0,0.2,0.2,0.0,2.0.0,0.0, 1.0,1.0,0.2,100.0,300.0,0.0, 0.2,1.0,0.2,200.0,300.0,0.0, 0.2,1.0,1.0,300.0,200.0,0.0, 0.2,0.2,1.0,200.0,100.0,0.0};

還有一個巨型例程,glInterleavedArrays(),可以一次指定多個頂點數組。glInterleavedArrays()還啟用和禁用相應的數組(因此它結合了步驟1和2)。該陣列交織在一起,完全適合glInterleavedArrays()支持的十四個數據交錯配置之一所以要指定數組的內容與RGB顏色和頂點數組交織在一起,並啟用這兩個數組,調用

glInterleavedArrays(GL_C3F_V3F,0,intertwined);

glInterleavedArrays()的調用啟用GL_COLOR_ARRAY和GL_VERTEX_ARRAY數組。它會禁用GL_INDEX_ARRAY,GL_TEXTURE_COORD_ARRAY,GL_NORMAL_ARRAY和GL_EDGE_FLAG_ARRAY。

此調用也具有與調用glColorPointer()glVertexPointer()相同的效果,以將六個頂點的值指定到每個數組中。現在,您已准備好進行步驟3:調用glArrayElement()glDrawElements()glDrawArrays()來取消引用數組元素。

void glInterleavedArrays(GLenum  format  ,GLsizei  stride  ,void *  pointer 
初始化所有六個數組,禁用未以 格式 指定 的數組,並啟用指定的數組。 格式 是14個符號常量之一,代表14個數據配置;  表2-5  顯示 格式 值。 stride  指定連續頂點之間的字節偏移量。如果 步幅 為0,則頂點被理解為緊緊包裝在陣列中。 指針 是數組中第一個頂點的第一個坐標的內存地址。

請注意,glInterleavedArrays()不支持邊緣標志。

的力學glInterleavedArrays()是復雜的,並且需要參考實施例2-12和表2-5。在該示例和表中,您將看到et,ec和en,它們是啟用或禁用的紋理坐標,顏色和正常數組的布爾值,您將看到st,sc和sv,它們是紋理坐標,顏色和頂點數組的大小(分量數)。tc是RGBA顏色的數據類型,它是唯一可以具有非浮點交錯值的數組。pc,pn和pv是用於跳過單個顏色,正常和頂點值的計算步驟,s是步數(如果用戶未指定)從一個數組元素跳轉到下一個數組元素。

glInterleavedArrays()的效果與在示例2-12中調用命令序列相同,具有表2-5中定義的許多值。所有指針算術以sizeof(GL_UNSIGNED_BYTE)為單位執行

示例2-12: glInterleavedArrays(format,stride,pointer)的效果

int str;
/*  set et, ec, en, st, sc, sv, tc, pc, pn, pv, and s
 *  as a function of Table 2-5 and the value of format
 */
str = stride;
if (str == 0)
   str = s;
glDisableClientState(GL_EDGE_FLAG_ARRAY);
glDisableClientState(GL_INDEX_ARRAY);
if (et) {
   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   glTexCoordPointer(st, GL_FLOAT, str, pointer);
}
else
   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
if (ec) {
   glEnableClientState(GL_COLOR_ARRAY);
   glColorPointer(sc, tc, str, pointer+pc);
}
else
   glDisableClientState(GL_COLOR_ARRAY);
if (en) {
   glEnableClientState(GL_NORMAL_ARRAY);
   glNormalPointer(GL_FLOAT, str, pointer+pn);
}
else
   glDisableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(sv, GL_FLOAT, str, pointer+pv);

在表2-5中,T和F為True和False。f是sizeof(GL_FLOAT)。c是sizeof(GL_UNSIGNED_BYTE)的4倍,向上舍入為f的最接近的倍數。

表2-5:(續)直接glInterleavedArrays()的變量

格式

EC

ST

SC

SV

TC

個人計算機

PN

光伏

小號

GL_V2F

F

F

F

   

2

     

0

2F

GL_V3F

F

F

F

   

3

     

0

3F

GL_C4UB_V2F

F

Ť

F

 

4

2

GL_UNSIGNED_BYTE

0

 

C

C + 2F

GL_C4UB_V3F

F

Ť

F

 

4

3

GL_UNSIGNED_BYTE

0

 

C

C + 3F

GL_C3F_V3F

F

Ť

F

 

3

3

GL_FLOAT

0

 

3F

1207米

GL_N3F_V3F

F

F

Ť

   

3

   

0

3F

1207米

GL_C4F_N3F_V3F

F

Ť

Ť

 

4

3

GL_FLOAT

0

4F

7F

10F

GL_T2F_V3F

Ť

F

F

2

 

3

     

2F

5F

GL_T4F_V4F

Ť

F

F

4

 

4

     

4F

8F

GL_T2F_C4UB_V3F

Ť

Ť

F

2

4

3

GL_UNSIGNED_BYTE

2F

 

C + 2F

C + 5F

GL_T2F_C3F_V3F

Ť

Ť

F

2

3

3

GL_FLOAT

2F

 

5F

8F

GL_T2F_N3F_V3F

Ť

F

Ť

2

 

3

   

2F

5F

8F

GL_T2F_C4F_N3F_V3F

Ť

Ť

Ť

2

4

3

GL_FLOAT

2F

1207米

9F

12F

GL_T4F_C4F_N3F_V4F

Ť

Ť

Ť

4

4

4

GL_FLOAT

4F

8F

11F

15F

首先學習更簡單的格式GL_V2F,GL_V3F和GL_C3F_V3F。如果您使用C4UB中的任何格式,您可能必須使用一個結構數據類型或做一些精細的類型轉換和指針數學來將四個無符號字節打包成一個32位字。

對於某些OpenGL實現,使用交錯數組可能會增加應用程序性能。使用交錯陣列,您的數據的確切布局是已知的。你知道你的數據是緊密包裝的,可以在一個塊中訪問。如果不使用交錯陣列,則必須檢查步幅和大小信息,以檢測數據是否緊密包裝。

注意:glInterleavedArrays()僅啟用和禁用頂點數組並指定頂點數組數據的值。它不渲染任何東西。您仍然必須完成步驟3,並調用glArrayElement()glDrawElements()glDrawArrays()來取消引用指針和渲染圖形。


屬性組

“基本狀態管理”中,您了解了如何設置或查詢單個狀態或狀態變量。那么你也可以使用一個命令來保存和恢復相關狀態變量的集合的值。

OpenGL將相關的狀態變量組合成屬性組。例如,GL_LINE_BIT屬性由五個狀態變量組成:行寬度,GL_LINE_STIPPLE使能狀態,行規則模式,行規重復計數器和GL_LINE_SMOOTH使能狀態。(參見第6章中的“抗鋸齒”)。使用命令glPushAttrib()glPopAttrib(),您可以一次保存並還原所有五個狀態變量。

一些狀態變量在多個屬性組中。例如,狀態變量GL_CULL_FACE是多邊形和啟用屬性組的一部分。

在OpenGL 1.1版中,現在有兩個不同的屬性堆棧。除了原始屬性堆棧(保存服務器狀態變量的值)之外,還有一個客戶端屬性堆棧,可以通過命令glPushClientAttrib()glPopClientAttrib()訪問

一般來說,使用這些命令比獲取,保存和恢復值更快。硬件中可能會維護一些值,並且獲取它們可能是昂貴的。此外,如果您在遠程客戶端上運行,則所有屬性數據必須通過網絡連接進行傳輸,並在獲取,保存和還原時返回。然而,您的OpenGL實現將屬性堆棧保留在服務器上,從而避免不必要的網絡延遲。

有大約二十個不同的屬性組,可以通過glPushAttrib()glPopAttrib()保存和恢復有兩個客戶端屬性組,可以通過glPushClientAttrib()glPopClientAttrib()保存和還原對於服務器和客戶端,屬性都存儲在堆棧中,該堆棧的深度至少為16個已保存的屬性組。(實現的實際堆棧深度可以使用GL_MAX_ATTRIB_STACK_DEPTH和GL_MAX_CLIENT_ATTRIB_STACK_DEPTH與glGetIntegerv()獲取。)推送完整的堆棧或彈出一個空的生成錯誤。

(請參閱附錄B中的表格,以確定哪些屬性被保存用於特定的掩碼值;也就是說,哪些屬性在特定的屬性組中。)

void glPushAttrib(GLbitfield  mask  ); 
void glPopAttrib(void);
glPushAttrib() 通過將它們放在屬性堆棧上,將所有由位指示的屬性保存在 掩碼中 glPopAttrib()還原與最后一個glPushAttrib()一起保存的那些狀態變量的值 表2-7  列出了可以在邏輯上進行邏輯運算以保存任何屬性組合的可能的掩碼位。每個位對應於各個狀態變量的集合。例如,GL_LIGHTING_BIT是指與照明相關的所有狀態變量,包括當前材料顏色,環境,漫反射,鏡面反射和發射光,啟用的光的列表以及聚光燈的方向。glPopAttrib() 被調用,所有這些變量都被恢復。

特殊掩碼GL_ALL_ATTRIB_BITS用於保存並還原所有屬性組中的所有狀態變量。

表2-6:(續)屬性組

掩碼位

屬性組

GL_ACCUM_BUFFER_BIT

ACCUM緩沖

GL_ALL_ATTRIB_BITS

-

GL_COLOR_BUFFER_BIT

顏色緩沖區

GL_CURRENT_BIT

當前

GL_DEPTH_BUFFER_BIT

深度緩沖

GL_ENABLE_BIT

啟用

GL_EVAL_BIT

EVAL

GL_FOG_BIT

多霧路段

GL_HINT_BIT

暗示

GL_LIGHTING_BIT

燈光

GL_LINE_BIT

GL_LIST_BIT

名單

GL_PIXEL_MODE_BIT

像素

GL_POINT_BIT

GL_POLYGON_BIT

多邊形

GL_POLYGON_STIPPLE_BIT

多邊形點畫

GL_SCISSOR_BIT

剪刀

GL_STENCIL_BUFFER_BIT

模板緩沖區

GL_TEXTURE_BIT

質地

GL_TRANSFORM_BIT

轉變

GL_VIEWPORT_BIT

 

void glPushClientAttrib(GLbitfield  mask  ); 
void glPopClientAttrib(void);
glPushClientAttrib() 通過將它們推送到客戶端屬性堆棧來保存由 掩碼中 位指示的所有屬性。glPopClientAttrib()還原與最后一個glPushClientAttrib()一起保存的那些狀態變量的值 表2-7  列出了可以在邏輯上進行邏輯或運算以保存客戶端屬性的任何組合的可能的掩碼位。
有兩個客戶端屬性組,反饋和選擇,不能使用堆棧機制保存或恢復。
表2-7: 客戶端屬性組

掩碼位

屬性組

GL_CLIENT_PIXEL_STORE_BIT

像素店

GL_CLIENT_VERTEX_ARRAY_BIT

頂點數組

GL_ALL_CLIENT_ATTRIB_BITS

-

不能被推或彈出

反饋

不能被推或彈出

選擇

 


一些提示表面多邊形模型的提示

以下是您在構建曲面的多邊形近似時可能需要使用的一些技術。在閱讀第5 照明和第7章顯示列表之后,您可能需要查看本節照明條件影響模型在繪制后的外觀,並且與顯示列表結合使用時,以下某些技術效率更高。當您閱讀這些技術時,請記住,啟用照明計算時,必須指定法向量以獲得正確的結果。

構建表面的多邊形近似是一種藝術,並沒有經驗的替代。但是,這一節列出了一些可能會更容易入門的指針。

  • 保持多邊形方向一致。確保從外部觀察時,表面上的所有多邊形都朝向相同的方向(均為順時針或全部逆時針)。一致的方向對於多邊形揀選和雙面照明很重要。嘗試第一次獲得這個權利,因為以后解決問題是非常痛苦的。(如果你使用glScale *() 以反映周圍的對稱軸線的一些幾何圖形,你可能會改變與定向glFrontFace()保持一致的方向。)
  • 細分表面時,請注意任何非三角形多邊形。三角形的三個頂點保證位於一個平面上; 任何具有四個或更多頂點的多邊形可能不是。可以從某些方向觀看非平面多邊形,使得邊緣彼此交叉,並且OpenGL可能不會正確地渲染這樣的多邊形。
  • 顯示速度和圖像質量之間始終存在折衷。如果將表面細分為少量多邊形,則會很快呈現,但可能會出現鋸齒狀的外觀; 如果您將其細分為數百萬個小的多邊形,則可能看起來不錯,但可能需要很長時間才能呈現。理想情況下,您可以為細分例程提供一個參數,指示您想要的細分,如果對象距離眼睛更遠,則可以使用較細的細分。此外,當您細分時,使用表面相對平坦的大多邊形,以及高曲率區域中的小多邊形。
  • 對於高質量的圖像,最好在輪廓邊緣細分多於內部。如果表面要相對於眼睛旋轉,這是更堅韌的,因為輪廓邊緣保持移動。在法向量垂直於從表面到視點的矢量 - 即當矢量點積為零時,出現輪廓邊緣。如果該點積接近零,您的細分算法可能會選擇更細分。
  • 盡量避免模型中的T形交叉點(見圖2-16)。如圖所示,不能保證線段AB和BC位於與段AC完全相同的像素上。有時他們會做,有時候他們根本不會改變和取向。這可能導致表面間斷地出現裂紋。

 

圖2-16:修改不合需要的T形交叉點

  • 如果您正在構造封閉的表面,請確保在閉環開始和結束處使用與坐標完全相同的數字,否則由於數值四舍五入,您可以獲得間隙和裂紋。以下是錯誤代碼的二維示例:
/ *不要使用這個代碼* / #define PI 3.14159265  #define EDGES 30 
 / *畫一個圓* / glBegin(GL_LINE_STRIP);  for(i = 0; i <= EDGES; i ++) glVertex2f(cos((2 * PI * i)/ EDGES),sin((2 * PI * i)/ EDGES));  glEnd(); 

只有當您的機器設法計算0和(2 * PI * EDGES / EDGES)的正弦和余弦值並且獲得完全相同的值時,邊緣才能完全相遇。如果你信任你的機器上的浮點單元來做這件事,那么作者有一個橋梁,他們想賣你....要糾正代碼,請確保當 == EDGES,你使用0為正弦和余弦,不是2 * PI * EDGES / EDGES。(或者更簡單的是,使用GL_LINE_LOOP而不是GL_LINE_STRIP,並將循環終止條件更改為i <EDGES。)

一個例子:建立二十面體

為了說明近似表面中出現的一些注意事項,我們來看一些示例代碼序列。該代碼涉及常規二十面體的頂點(這是由十二個面組成的柏拉圖式實體,跨越十二個頂點,每個面都是等邊三角形)。二十面體可以被認為是球體的粗略近似。示例2-13定義構成二十面體的頂點和三角形,然后繪制二十面體。

示例2-13:繪制二十面體

#define X .525731112119133606  #define Z .850650808352039932
 static GLfloat vdata [12] [3] = {  {-X,0.0,Z},{X,0.0,Z},{-X,0.0,-Z},{X,0.0,-Z}  {0.0,Z,X},{0.0,Z,-X},{0.0,-Z,X},{0.0,-Z,-X}  {Z,X,0.0},{-Z,X,0.0},{Z,-X,0.0},{-Z,-X,0.0}  }; static GLuint tindices [20] [3] = {  {0,4,1},{0,9,4},{9,5,4},{4,5,8},{4,8,1}  {8,10,1},{8,3,10},{5,3,8},{5,2,3},{2,7,3}  {7,10,3},{7,6,10},{7,11,6},{11,0,6},{0,1,6},  {6,1,10},{9,0,11},{9,11,2},{9,2,5},{7,2,11}}; int i;
 在glBegin(GL_TRIANGLES);  for(i = 0; i <20; i ++){  / *此處的顏色信息* /  glVertex3fv(VDATA [tindices [I] [0] [0]);  glVertex3fv(VDATA [tindices [I] [1] [0]);  glVertex3fv(VDATA [tindices [I] [2] [0]);  } glEnd();

選擇奇數XZ,使得從二值體的原點到任何頂點的距離為1.0。在數組vdata [] []中給出了十二個頂點的坐標,其中第零個頂點是{ - &Xgr; ,0.0,&Zgr; },第一個是{ X,0.0,Z }等等。數組tindices [] []告訴如何將頂點鏈接成三角形。例如,第一個三角形由第零個,第四個和第一個頂點構成。如果以給定的順序取三角形的頂點,則所有三角形都具有相同的方向。

提及顏色信息的行應由用於設置第i個面的顏色的命令替代如果這里沒有代碼出現,所有的面都以相同的顏色繪制,並且不可能辨別出物體的三維質量。明確指定顏色的另一種方法是定義表面法線並使用照明,如下一節所述。

注意:在本節中描述的所有示例中,除非表面僅繪制一次,否則應該保存計算的頂點和正常坐標,以便每次繪制表面時不需要重復計算。這可以使用您自己的數據結構或通過構建顯示列表來完成。(見第七章

計算表面的正態向量

如果表面要點亮,則需要向表面提供正常的向量。計算該表面上兩個向量的歸​​一化交叉乘積提供法向量。對於二十面體的平坦表面,限定表面的所有三個頂點具有相同的法向量。在這種情況下,需要為每組三個頂點指定一次正常。示例2-14中的代碼可以替換實施例2-13中的“顏色信息”行來繪制二十面體。

示例2-14:為表面生成正常向量

GLfloat d1 [3],d2 [3],norm [3];  for(j = 0; j <3; j ++){  d1 [j] = vdata [tindices [i] [0]] [j] - vdata [tindices [i] [1]] [j]  d2 [j] = vdata [tindices [i] [1]] [j] - vdata [tindices [i] [2]] [j]  } normcrossprod(d1,d2,norm);  glNormal3fv(norm);

函數normcrossprod()產生兩個向量的歸​​一化交叉乘積,如示例2-15所示。

示例2-15:計算兩個向量的規范化交叉積

void normalize(float v[3]) {    
   GLfloat d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); 
   if (d == 0.0) {
      error("zero length vector");    
      return;
   }
   v[0] /= d; v[1] /= d; v[2] /= d; 
}

void normcrossprod(float v1[3], float v2[3], float out[3]) 
{ 
   GLint i, j; 
   GLfloat length;

   out[0] = v1[1]*v2[2] - v1[2]*v2[1]; 
   out[1] = v1[2]*v2[0] - v1[0]*v2[2]; 
   out[2] = v1[0]*v2[1] - v1[1]*v2[0]; 
   normalize(out); 
}

如果您使用二十面體作為陰影球體的近似值,則需要使用垂直於球體真實表面的法向矢量,而不是垂直於面部。對於球體,法向量是簡單的; 每個點與從原點到相應頂點的向量的方向相同。由於二十面體頂點數據是半徑為1的二十面體,所以正常和頂點數據是相同的。下面是代碼繪制平滑陰影球體的二十面體逼近(假設照明被啟用,如第5章所述):

glBegin(GL_TRIANGLES);  for(i = 0; i <20; i ++){  glNormal3fv(VDATA [tindices [I] [0] [0]);  glVertex3fv(VDATA [tindices [I] [0] [0]);  glNormal3fv(VDATA [tindices [I] [1] [0]);  glVertex3fv(VDATA [tindices [I] [1] [0]);  glNormal3fv(VDATA [tindices [I] [2] [0]);  glVertex3fv(VDATA [tindices [I] [2] [0]);  } glEnd();

改進模型

對球體的二十面近似看起來並不好,除非屏幕上的球體圖像相當小,但是有一個簡單的方法可以提高逼近的准確性。想象一下二十面體刻在一個球體上,並將三角形細分,如圖2-17所示。新引入的頂點略微在球體內部,因此通過對它們進行歸一化(將它們除以因子使其具有長度1)將其推到表面。該細分過程可以重復任意精度。圖2-17所示的三個對象分別使用20,80和320個近似三角形。

 

圖2-17:細分以改善表面的多邊形近似

示例2-16執行單個細分,創建80邊球面近似。

示例2-16:單細分

void drawtriangle(float *v1, float *v2, float *v3) 
{ 
   glBegin(GL_TRIANGLES); 
      glNormal3fv(v1); vlVertex3fv(v1);    
      glNormal3fv(v2); vlVertex3fv(v2);    
      glNormal3fv(v3); vlVertex3fv(v3);    
   glEnd(); 
}

void subdivide(float *v1, float *v2, float *v3) 
{ 
   GLfloat v12[3], v23[3], v31[3];    
   GLint i;

   for (i = 0; i < 3; i++) { 
      v12[i] = v1[i]+v2[i]; 
      v23[i] = v2[i]+v3[i];     
      v31[i] = v3[i]+v1[i];    
   } 
   normalize(v12);    
   normalize(v23); 
   normalize(v31); 
   drawtriangle(v1, v12, v31);    
   drawtriangle(v2, v23, v12);    
   drawtriangle(v3, v31, v23);    
   drawtriangle(v12, v23, v31); 
}

for (i = 0; i < 20; i++) { 
   subdivide(&vdata[tindices[i][0]][0],       
             &vdata[tindices[i][1]][0],       
             &vdata[tindices[i][2]][0]); 
}

實施例2-17是實施例2-16的輕微修改,其將三角形遞歸地細分到適當的深度。如果深度值為0,則不執行任何細分,並按原樣繪制三角形。如果深度為1,則執行單個細分,依此類推。

 

例2-17:遞歸細分

void subdivide(float *v1, float *v2, float *v3, long depth)
{
   GLfloat v12[3], v23[3], v31[3];
   GLint i;

   if (depth == 0) {
      drawtriangle(v1, v2, v3);
      return;
   }
   for (i = 0; i < 3; i++) {
      v12[i] = v1[i]+v2[i];
      v23[i] = v2[i]+v3[i];
      v31[i] = v3[i]+v1[i];
   }
   normalize(v12);
   normalize(v23);
   normalize(v31);
   subdivide(v1, v12, v31, depth-1);
   subdivide(v2, v23, v12, depth-1);
   subdivide(v3, v31, v23, depth-1);
   subdivide(v12, v23, v31, depth-1);
}

廣義細分

可以使用例如實施例2-17中所述的遞歸細分技術用於其它類型的表面。通常,如果達到一定深度或者曲率上的某些條件得到滿足,則遞歸結束(表面的高度彎曲部分看起來更好,更細分)。

要看一個更通用的解決問題的細分問題,考慮由兩個變量u [0]u [1]參數化的任意表面假設提供了兩個例程:

void surf(GLfloat u [2],GLfloat vertex [3],GLfloat normal [3]);  浮動曲線(GLfloat u [2]);

如果surf()被傳遞u [],則返回相應的三維頂點和法線向量(長度為1)。如果u []被傳遞給curv(),則計算並返回該點處曲面的曲率。(有關測量曲面曲率的更多信息,請參閱差分幾何的入門教科書。)

示例2-18顯示了在達到最大深度或直到三個頂點的最大曲率小於某個截止值之前,將三角形細分的遞歸例程。

例2-18:廣義細分

void subdivide(float u1[2], float u2[2], float u3[2],
                float cutoff, long depth)
{
   GLfloat v1[3], v2[3], v3[3], n1[3], n2[3], n3[3];
   GLfloat u12[2], u23[2], u32[2];
   GLint i;

   if (depth == maxdepth || (curv(u1) < cutoff &&
       curv(u2) < cutoff && curv(u3) < cutoff)) {
      surf(u1, v1, n1); surf(u2, v2, n2); surf(u3, v3, n3);
      glBegin(GL_POLYGON);
         glNormal3fv(n1); glVertex3fv(v1);
         glNormal3fv(n2); glVertex3fv(v2);
         glNormal3fv(n3); glVertex3fv(v3);
      glEnd();
      return;
   }
   for (i = 0; i < 2; i++) {
      u12[i] = (u1[i] + u2[i])/2.0;
      u23[i] = (u2[i] + u3[i])/2.0;
      u31[i] = (u3[i] + u1[i])/2.0;
   }
   subdivide(u1, u12, u31, cutoff, depth+1);
   subdivide(u2, u23, u12, cutoff, depth+1);
   subdivide(u3, u31, u23, cutoff, depth+1);
   subdivide(u12, u23, u31, cutoff, depth+1);
}

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM