計算機圖形學筆記——第4章 輸出圖元 Python實現

第4章 輸出圖元

圖形軟件包中用來描述各種圖形元素的函數稱爲圖形輸出原語(graphics output primitive)或簡稱爲圖元(primitive)。描述對象幾何要素的輸出圖元一般稱爲幾何圖元(geometric primitive)

座標系統

爲了描述圖形,必須首先確定一個稱爲世界座標系的合適的三維或二維的笛卡爾座標系,接着通過給出世界座標系中的位置等幾何描述定義圖形中的對象。座標範圍(coordinate extent)即對象座標x,y,zx,y,z的最小值和最大值等其他信息一起存儲在場景描述中。座標範圍也稱爲對象的包圍矩形(bounding rectangle)

屏幕座標

視頻監視器上的位置使用與幀緩存中的像素位置相對應的整數屏幕座標(screen coordinate)。像素座標值給出掃描行號(y值)和列號(x值)。屏幕刷新等硬件處理一般從屏幕的左上角開始對像素進行編址。從屏幕最上面的0行到屏幕最下面的某整數數值ymaxy_{max}行對掃描行進行編號,每一行中像素從左到右,從0到xmaxx_{max}進行編號。

一旦確定了一個對象的像素位置,必須將覈實的顏色值存入幀緩存。使用底層函數

setPixel(x,y)

該函數將當前顏色設定值存入幀緩存的整數座標位置(x,y)(x,y)處。使用下列底層函數可以獲得幀緩存的顏色值

getPixel(x,y, color)

絕對和相對座標描述

有些圖形軟件包還允許使用**相對座標(relative coordinate)**來描述位置。

OpenGL中指定二維世界座標系統

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
 gluOrtho2D(xmin, xmax, ymin, ymax)

gluOrtho2D語句描述的座標系統來指定一個或多個要顯式的圖元。

在這裏插入圖片描述

OpenGL畫點函數

要描述一個點的幾何要素,我們只需要在世界座標系中指定一個位置。然後該座標位置和場景中已有的其他幾何描述一起被傳遞給觀察子程序。默認的圖元顏色是白色,而默認的點的大小等於單一屏幕像素大小。

使用下面的OpenGL函數可指定一個點位置的座標值:

glVertex*()

其中*表示該函數要有後綴碼,用來指明空間維數、座標值變量的數據類型和可能形式座標描述。

glBegin()函數的變量用來指定用來要顯式的輸出圖元的類型,而glEnd()函數沒有變量。對於點的繪製,glBegin()函數的變量是符號常量GL_POINTS

儘管術語頂點(vertex)嚴格地代表一個多邊形的角點、一個角兩邊的交點、橢圓和其主軸的交點或幾何結構中其他類似的座標位置,但是OpenGL中的glVertex函數可用於描述任意一點的位置。

OpenGL中的座標位置可以有二維、三維或者四維,glVertex的後綴2,3或4表示其座標的維數。四維描述意味着齊次座標(homogeneous coordinate)表示,其中的齊次參數h(第4維)是笛卡兒座標值的比例因子,因此二維作爲三維的特殊情況來處理,而(x,y)(x,y)等同於(x,y,0)(x,y,0)。此外,OpenGL在內部用四維座標來表示頂點(x,y,0,1)(x,y,0,1)

glVertex函數的第二個後綴來完成,用於指定數值數據類型的後綴是:i(整數)、s(短整數)、f(浮點數)和d(雙精度浮點數)。

OpenGL畫線函數

使用圖元線常量GL_LINES可連接每一對相鄰端點得到一組直線段。由於OpenGL僅在線段共享一個頂點時承認其相連,交叉但不共享頂點的線段則不被承認其相連。如果列出的端點數爲奇數則最後一個端點不被處理。

在這裏插入圖片描述
使用OpenGL的圖元常量GL_LINE_STRIP可以獲得折線(polyline)。而圖元常量是生成封閉折線(closed polyline)的GL_LINE_LOOP

OpenGL曲線函數

生成圓和橢圓等基本曲線的函數並沒有作爲圖元功能包含在OpenGL核心庫中,但是該庫包含了顯示Bezier樣條的功能。OpenGL實用庫(GLU)中包含有球面和柱面等三維面函數及生成有理B樣條的函數。OpenGL實用工具(GLUT)中還有可以用來顯示某些三維曲面的函數。

在這裏插入圖片描述

填充區圖元

使用某種顏色或圖案進行填充的區域,這種類型的圖形部分一般稱爲**填充區(fill area)**或填充的區域。通常,填充區域用於描述實體的表面,但是在其他的應用也是很有用的。填充區域常常是一個平面表面,主要是多邊形。
在這裏插入圖片描述

儘管有可能使用各種形狀,但是圖形庫一般不支持填充任意形狀。多數庫要求填充區指定爲多邊形,由於多邊形有線性邊界,因而比其他的填充形狀更容易處理。多數曲面可用一組適當的多邊形面來逼近。
在這裏插入圖片描述

利用多邊形面片對一曲面進行逼近有時稱爲表面細分(surface tessellation)或者可以使用多邊形網格(polygon mesh)來擬合曲面。線框模型經繪製處理生成具有自然材料表面的顯示,使用一組多邊形面片描述的對象稱爲標準圖形對象(standard graphic object)

多邊形填充區

一個多邊形(polygon) 在數學上的定義由三個或更多頂點的座標位置描述的平面圖形,這些頂點由稱爲多邊形的邊(edge或side)順序連接。因此一個多邊形的所有頂點必須在同一個平面上且所有的邊之間沒有交叉。例如三角形、矩形、八邊形和十六邊形等。有時,任意有封閉折線邊界的平面圖形暗指一個多邊形,而若沒有交叉邊則爲標準多邊形或簡單多邊形。

多邊形分類

多邊形的一個內角(interior angle)是由兩條相鄰邊形成的多邊形之內的角。如果多邊形的所有內角均小於180180^\circ。則該多邊形爲凸(convex)多邊形。凸多邊形的一個等價定義就是它的內部完全在它的任意一邊機及其延長線的一側。不是凸多邊形就是凹(concave)多邊形

退化多邊形(degenerate polygon) 常用來描述共線或重疊座標位置的頂點集,共線頂點生成一個線段。重疊頂點位置可以生成有多餘線段、重疊或長度爲0的邊的多邊形。

在這裏插入圖片描述
OpenGL等圖形軟件要求所有的填充多邊形爲凸多邊形。有些系統僅僅接受三角形填充區。

識別凹多邊形

凹多邊形某些邊的延長線會與其他邊相交且有時一對內點之間的連線會與多邊形邊界相交。如果爲每一條邊建立一個向量,則可使用相鄰邊的叉積倆測試凹凸性。凸多邊形的所有向量的叉積爲同號。如果某些叉積取正值而另一些爲負值,可確定爲凹多邊形。

分割凹多邊形

對於分割凹多邊形的向量方法(vector method),我們首先要形成邊向量,給定相繼的向量位置VkV_kVk+1V_{k+1},定義邊向量:
Ek=Vk+1Vk E_k = V_{k+1}-V_k
接着按多邊形邊界順序計算連續的邊向量的叉積,如果叉積的z分量爲正而另一些爲負,則多邊形爲凹多邊形。如果所有的頂點共線,則得到一個退化多邊形(一條線段)。我們可以通過逆時針方向處理向量來應用向量方法,如果有一個叉積zz的分量爲負值。那多多邊形爲凹且可沿叉積中第一邊向量的直線進行切割。

在這裏插入圖片描述
定義一個具體座標的圖形
在這裏插入圖片描述

多邊形向量表示爲

在這裏插入圖片描述
叉積表爲

在這裏插入圖片描述
因爲E2×E3E_2\times E_3的z分量爲負,我們沿着E2E_2所在直線分割多邊形。該邊的直線方程中斜率爲1且y軸截距爲-1。我們可以確定該直線和其他邊的交點將多邊形分割成兩片。

我們可以使用旋轉法(rotational method)來分割凹多邊形。沿多邊形的邊的逆時針方向,逐一將頂點VkV_k移動座標系原點,然後順時針旋轉多邊形,使下一頂點Vk+1V_{k+1}落在xx軸上,如果再下一個頂點Vk+1V_{k+1}位於xx軸下面,則多邊形爲凹。利用xx軸將多邊形分割成兩個新的多邊形。
在這裏插入圖片描述

叉積

a=(x1,y1,z1)b=(x2,y2,z2)a×b=ijkx1y1z1x2y2z2=(y1z2y2z1)i(x1z2x2z1)j+(x1y2x2y1)k a = (x_1,y_1,z_1) \\ b = (x_2,y_2,z_2) \\ a\times b = \begin{vmatrix}i & j & k \\ x_1 & y_1 & z_1\\ x_2 & y_2 & z_2\end{vmatrix} = (y_1z_2-y_2z_1)i - (x_1z_2-x_2z_1)j + (x_1y_2-x_2y_1)k

點積

a=[a1,a2,,an]b=[b1,b2,,bn]ab=a1b1+a2+b2++anbn=i=1naibi a = [a_1,a_2,\cdots , a_n] \quad b = [b_1,b_2,\cdots,b_n] \\ a \cdot b = a_1b_1 + a_2+b_2 + \cdots + a_nb_n = \sum_{i=1}^n a_ib_i

將凸多邊形分割成三角形集

一旦有了一個凸多邊形的頂點集,我們可以將其變成一組三角形,這通過將任意順序的三個連續頂點定義一個新多邊形(三角形)來實現。然後將三角形的中間頂點從多邊形原頂點隊列中刪除。然後使用相同的過程處理修改後的頂點隊列來分出另一個三角形。

內-外測試

各種圖形處理常常需要鑑別對象的內部區域

奇偶規則(odd-even rule)

也叫做奇偶性規則。該規則從任意位置P到位置座標範圍以外的遠點畫一條概念上的直線(射線),統計沿該射線與各邊的交點數目。如果相交的數目爲奇數,則P是內部(interior)點,否則是外部(exterior)點。

爲了得到精確的相交邊數,必須確認所畫的直線不與任何的多邊形頂點相交。

在這裏插入圖片描述

非零環繞數(nonzero winding-number)

該方法統計多邊形邊以逆時針方向環繞某一個特定點的次數。這個數稱爲環繞數(winding-number)。設想從任意位置P到對象座標範圍外的遠處一點化一條射線。所選擇的射線不能與多邊形的任何頂點相交。當從P點沿射線方向移動時,統計穿過該射線的邊的方向,每當從右到左穿過射線時,邊數家1,從左到右,邊數減1。環繞數的最終位置決定了P的相對位置,假如環繞數爲非零,則P定義爲內部點,否則P是外部點。

可以看出,對於簡單的圖像判斷的還是很簡單的,但是對於比較複雜的形狀,可能會產生不一樣的結果。

多邊形表

場景中的對象一般用一組多邊形面片來描述,實際上,圖形軟件包經常提供以多邊形網格形式描述表面形狀的函數。對每一個對象的描述包括多邊形面片的幾何信息和其他表面參數(如顏色、透明性以及光反射特性)。在輸入每個多邊形的信息時,數據存進一些表格中等待後續的處理、顯示和場景的對象管理。這些多邊形數據表分爲兩組:幾何數據表(頂點座標和標識多邊形面片空間方向的參數),屬性數據表(對象透明程度以及表面的反射性能和紋理特徵)。

幾何數據簡單組織爲三張表:頂點表、邊表和麪片表。
在這裏插入圖片描述

數據表中包含的信息越多,錯誤的檢查越容易。主要的測試包括

  1. 每一個頂點至少有兩條邊作爲端點
  2. 每個邊至少是一個多邊形的部分
  3. 每一個多邊形都是封閉的
  4. 每個多邊形至少有一條共享邊
  5. 如果邊表包含指向多邊形的指針,那麼由多邊形指針應用的每條邊都有一個反向指針指回該多邊形。

平面方程

場景中的每一個多邊形包含在一個無限平面中,平面一般方程爲
Ax+By+Cz+D=0 Ax+By+Cz+D =0
係數A、B、C、D(稱爲平面參數,plane parameter)是描述平面空間特徵的常數。可用平面三個非共線點的座標代入,求出A,B,C,D的值。

可以選擇逆時針的凸多邊形的三個連續頂點(x1,y1,z1)(x2,y2,z2)(x3,y3,z3)(x_1,y_1,z_1)、(x_2,y_2,z_2)、(x_3,y_3,z_3)並解下列聯立方程組來求A/DB/DC/DA/D、B/D、C/D
(A/D)xk+(B/D)yk+(C/D)zk=1k=1,2,3 (A/D)x_k+(B/D)y_k+(C/D)z_k=-1 \quad k=1,2,3
這組方程的解可以使用Cramer規則以行列式形式求出:
A=1y1z11y2z21y3z3B=x11z1x21z2x31z3C=x1y11x2y21x3y31D=x1y1z1x2y2z2x3y3z3 A=\begin{vmatrix} 1 & y_1 & z_1 \\ 1 & y_2 & z_2 \\ 1 & y_3 & z_3 \end{vmatrix} \\ B= \begin{vmatrix} x_1 & 1 &z_1 \\ x_2 & 1 & z_2 \\ x_3 & 1 & z_3 \end{vmatrix} \\ C = \begin{vmatrix} x_1 & y_1 & 1 \\ x_2 & y_2 & 1 \\ x_3 & y_3 & 1 \end{vmatrix} \\ D = - \begin{vmatrix} x_1 & y_1 & z_1 \\ x_2 & y_2 & z_2 \\ x_3 & y_3 & z_3 \end{vmatrix}
展開行列式,可得計算平面係數的表達式

前向面和後向面

由於我們通常處理包圍對象內部的多邊形表面,因此需要區分每個面的兩側,向着對象內部的一側稱爲後向面(back face),可見或朝外的一側稱爲前向面(front face)。判斷一個點相對於多邊形前向和後向面的空間位置是很多圖形算的基本任務。位於所有多邊形所在平面後方(內部)的點是對象的內點。必須注意的是,這種內外分類是與包含多邊形的平面聯繫在一起的,但是使用環繞數或者是奇偶規則的內外測試是針對某些二維邊界的內部。

如果任意點(x,y,z)(x,y,z)不在參數爲A,B,C,D的平面上時,則
Ax+By+Cz+D0 Ax+By+Cz+D\ne 0
因此我們可以利用平面方程來判斷一個點位於該面中的位置

  • 如果Ax+By+Cz+D<0Ax+By+Cz+D<0,則點(x,y,z)(x,y,z)在平面的後方
  • 如果Ax+By+Cz+D>0Ax+By+Cz+D>0,則點(x,y,z)(x,y,z)在平面的前方

在這裏插入圖片描述
多邊形表面的空間方向可用其所在平面的**法向量(normal vector)**來描述,法向量是從平面的內部指向外部,即從多邊形的後方指向前方。

法向量的分量可以通過向量叉積計算獲得,假設我們有一個凸多邊形面片和一個右手座標系,再選擇任意三個頂點V1V2V3V_1、V_2和V_3,滿足從對象外部向內觀察的時候逆時針排序。形成兩個向量,一個從V1V_1V2V_2而第二個從V1V_1V3V_3,按照向量叉積計算N:
N=(V2V1)×(V3V1) N = (V_2-V_1)\times (V_3-V_1)
這樣生成了平面參數A、B和C,接下來將這些值和一個多邊形頂點帶入方程。可以解出DD
NP=D N \cdot P = -D

OpenGL多邊形填充區函數

默認時多邊形內部顯示爲單色,由當前的顏色設定來確定其顏色,作爲選項,可以用圖案來填充多邊形且顯示多邊形的邊作爲內部填充的邊界。函數glBegin中指定多邊形填充區的變量可使用6個不同的符號常量。這6個基本常量可用來顯示單一填充多邊形、一組不相連的填充多邊形或一組相連的填充多邊形。

OpenGL中的填充區必須指定爲凸多邊形。

我們描述的每一個多邊形有兩個面,後面和前面。在OpenGL中,可以爲每個面分別設定填充顏色和其他屬性,並且在三維和二維觀察子程序中要求有前向/後向的標誌。
在這裏插入圖片描述

glRect*(x1, y1, x2, y2)

該矩形的一個角位於座標位置(x1,y1)(x_1,y_1)處,而與其相對的一個角位於座標位置(x2,y2)(x_2,y_2)處。

一個正方形

glRecti(200,100, 50, 250)

在這裏插入圖片描述
另外六個OpenGL多邊形填充圖元的每一個都有glBegin函數中的符號常量及一組glVertex命令描述。使用OpenGL圖元常量GL_POLYGON用來顯示。

我們假設有六個頂點,標號p1p_1p6p_6,描述了一個逆時針次序的二維多邊形頂點位置。

在這裏插入圖片描述
如果一個多邊形頂點少於3個,則什麼都不顯示。

如果將圖元常量變成GL_TRIANGLES,則分別用三角形填充區顯示。

那麼此時前三個座標點定義一個三角形的頂點,後面三個定義下一個三角形。

對於每一個三角形填充區,我們指定逆時針次序的頂點位置。

如果將圖元變量變成GL_TRIANGLES_STRIP,那麼此時的6個頂點將獲得N2N-2個三角形的帶。每一個後續三角形共享前面定義的三角形的一條邊,因此頂點次序的設定必須保證顯示的一致性。

圖元變量GL_TRIANGLES_FAN可以通過扇形的方式獲得三角填充區。定義三角形的順序爲1,n+1,n+21,n+1,n+2

在這裏插入圖片描述
上面都是介紹三角形和一般多邊形的圖元函數,而OpenGL還可以描述 兩類四邊形。用GL_QUADS圖元常量和定義的8個頂點。
在這裏插入圖片描述

前面四個點定義一個四邊形的頂點,下面四個頂點定義下一個四邊形。以此類推,每個四邊形填充區指定逆時針次序的頂點位置。

如果將圖元變量爲GL_QUADS_STRIP,則先指定兩個頂點後,每個四邊形再用兩個頂點指定,我們必須列出每一個四邊形獲得正確的逆時針頂點次序的頂點集。N個頂點可以生成N21\frac{N}{2}-1個四邊形。

在多邊形表中第n個四邊形的頂點依次爲2n1,2n,2n+2,2n+12n-1,2n,2n+2,2n+1

多數圖形軟件包使用逼近平面片來顯示曲面。這是因爲平面方程是線性的。而處理線性方程比二次或其他曲線方程快的多。因此OpenGL和其他圖形軟件包提供多邊形圖元來實施曲面的逼近。在OpenGL中,可用於此目的的圖元有三角形帶(triangles strip)、三角形扇形(triangles fan)和四邊形帶(quad strip)。

儘管OpenGL的基本函數只允許凸多邊形,但實用函數庫(GLU)提供了相關函數來處理凹多邊形和其他線性邊界的非凸對象,可使用一組GLU多邊形細分子程序來將那些形狀轉換成三角形、三角形網絡、三角形扇形和直線段。

在這裏插入圖片描述

OpenGL頂點數組

定義一個三維頂點位置的數據類型

在這裏插入圖片描述
下面定義該對象的六個面,分六次調用glBegin。
在這裏插入圖片描述

複雜的場景描述需要使用幾百或幾千個座標描述。另外還必須爲各個對象建立各種屬性和觀察參數。因此,對象和場景描述需要使用大量的函數調用,這些對系統資源提出了要求並減慢了圖形程序的執行。

爲了簡化這些問題,OpenGL提供了一種機制來減少處理座標信息的函數調用數量。使用頂點數組(vertex array),可以利用很少的函數調用用來安排場景的描述信息。步驟如下

  1. 引用函數glEnableClientState(GL_VERTEX_ARRAY)激活OpenGL的頂點數組特性
  2. 使用函數glVertexPointer指定頂點座標的位置和數據格式
  3. 使用子程序如glDrawElements顯示場景,該子程序可處理多個圖元而僅需少量的函數調用

在這裏插入圖片描述
第一個命令激活了客戶/服務器系統中客戶端的能力,因爲客戶端保留圖形的數據,頂點數組必須在那裏。單個計算機即是客戶端又是服務器。

函數glVertexPointer提供對象頂點座標的位置和格式。第一個參數指出每一個頂點描述中座標數目。頂點座標的數據類型用第二個參數來表示。除了GL_INT外,還有GL_BYTE、GL_SHORT、GL_FLOAT、GL_DOUBLE來指定。第三個參數用來給出連續頂點之間的字節位移。使用這一參數的目的是允許多種類型的數據捆綁到同一個數組內。最後一個參數指向包含座標值的頂點數組。

立方體頂點的所有索引放在數組vertIndex中,其中每一個索引是對應於該頂點值的數組pt的下標。

glDrawElements第一個參數用來顯示錶面,第二個參數指定元素數量。第三個參數給出索引值的類型。GL_UNSIGNED_BYTE另外兩種可用的索引類型是GL_UNSIGNED_SHORT和GL_UNSIGNED_INT。

像素陣列圖元

矩形的網格圖案可以通過數字化一張照片或其他圖形來獲得,也可以使用圖形程序來生成。陣列中每一顏色映射到一個或多個屏幕像素位置。

像素陣列的參數包括指向顏色矩陣指針、矩陣的大小以及將要影響的屏幕區域。實現像素矩陣的另一種方法是爲矩陣中的每一個元素賦值爲0或1。此時,陣列簡化成位圖(bitmap),有時也稱爲掩模(mask),它指出一個像素是否被賦予預定顏色。

在這裏插入圖片描述

OpenGL像素陣列函數

OpenGL中有兩個函數用於定義矩形陣列的圖案。一個函數定義位圖,一個定義像素圖。

OpenGL也提供若干函數用於存儲、複製和管理像素值陣列。

OpenGL位圖函數

glBitmap(width, height, x0, y0 ,xOffset, yOffset, bitShape)

參數width和height分別給出陣列bitShape的列數和行數。bitShape的每一個元素賦值爲0或1。值爲1表示對應像素用前面設定的顏色顯示,否則對應像素不受位圖影響。

x0和y0定義矩形陣列原點的位置。原點位置指定爲bitShape的左下角。而x0和y0可正、可負。另外,需要指定幀緩存中應用圖案的位置。該位置稱爲當前光柵位置(current raster position)。而位圖在將原點置於當前光柵位置後顯示。xOffset和yOffset的值用作位圖顯示後更新幀緩存當前光柵位置的座標位移。

可以使用下面的子程序來設定當前光柵位置

glRasterPos*()

在這裏插入圖片描述
bitShape的陣列值從矩陣網格的底部開始逐行指定。接着使用OpenGL函數glPixelStorei設定位圖的存儲模式。該函數中使用參數值1表明數據值用字節邊界對齊,glRasterPos用來設定當前光柵位置爲(30,40)。最後函數glBitmap指定位陣列在陣列bitShape中給出,並且該陣列有9列、10行。這個陣列的原點座標在(0.0,0.0)(0.0,0.0),即在網格的左下角。我們給出座標位移爲(20.0,15.0)(20.0, 15.0)

在這裏插入圖片描述

OpenGL像素圖函數

glDrawPixels(width , heigh, dataFormat, dataType, pixMap)

將用彩色陣列定義的圖案應用到一塊幀緩存的像素位置。其中width和height也分別指出像素位圖的列數和行數。參數dataFormat用一個OpenGL常量賦值,指出如何爲陣列指定值。例如GL_BLUE爲藍色,GL_BGR可按照藍、綠、紅次序指定顏色分量。

參數dataType設定爲OpenGL常量GL_BYTEGL_INTGL_FLOAT,以指出陣列中顏色的數據類型。該顏色陣列的左下角映射到由glRasterPos設定當前光柵位置。

一個128×128128\times 128的RGB彩色陣列定義的像素圖

glDrawPixels(128, 128, GL_RGB, GL_UNSIGNED_BYTE, colorShape)

由於OpenGL提供了若干個緩存,將某緩存選爲glDrawPixels子程序的目標可將一個陣列送進緩存。有的緩存存放顏色,有的存放另外的像素數據。例如深度緩存(depth buffer)用來存放對象離開觀察位置的距離,而模板緩存(stencil buffer)用來存放場景的邊界圖案。

OpenGL有4個顏色緩存(color buffer)用於屏幕刷新,立體顯示中左右兩個場景使用兩個顏色緩存。對於立體顯示緩存中的每一個,各有一對前-後雙緩存用於動畫顯示。在OpenGL的特殊實現中,可能不支持立體顯示或雙緩存之一,或是兩者都不支持。如果立體顯示和雙緩存都不支持,則僅有單一的刷新緩存用作前-後顏色緩存(front-left color buffer)。

使用下面的命令可選擇單一的顏色或輔助緩存,或選擇混合緩存來存儲像素圖:

glDrawBuffer(buffer)

參數buffer可賦以多種OpenGL符號常量,來指定一個或多個繪圖緩存。

OpenGL光柵操作

從緩存中取出一塊值或將一塊值複製到另一個緩存區域,可以對像素陣列執行各種其他操作。

術語光柵操作(raster operation)用於描述以某種方式處理一個像素陣列的任何功能。將一個像素陣列的值從一個位置移動到另一個位置的光柵操作也稱爲像素值的塊移動(block transfer)bitblt移動(bit-block transfer),尤其是該功能由硬件實現。

glReadPixels(xmin, ymin, width, height, dataFormat, dataType, array)

提取的矩陣塊的左下角是屏幕座標位置(xmin,ymin),參數width、height、dataFromat和DataType 與glDrawpixels 子程序相同。存入參數array中的數據類型依賴於選擇的緩存。我們可以通過參數dataFormat賦值GL_DEPTH_COMPONENTGL_STENCIL_INDEX來選擇是深度緩存或模板緩存。

glReadBuffer(buffer)

glReadPixels子程序選擇顏色或輔助緩存的特殊組合。指定一個或多個緩存的符號常量與glDrawBuffer子程序中一樣。但不能選擇所有四個顏色緩存。默認選擇是立體觀察狀態所確定的前左-右緩存對組合或僅僅是前-左緩存。

使用下面的函數可將一塊像素數據從OpenGL緩存的一個位置複製到另一個位置。

glCopyPixels(xmin, ymin, width, height, pixelValues)

將一塊像素值從源緩存(source buffer)複製到目標緩存(destination buffer),其左下角映射到當前光柵的位置。源緩存用glReadBuffer命令選擇,而目標緩存用glDrawBuffer命令選擇。提供和接受複製的兩個區域必須在屏幕的邊界內。

爲了實現用glDrawPixles或glCopyPixels將一塊像素放入緩存的不同效果。我們可以使用各種方式將取出的值與緩存的值進行組合。例如使用與(and)或(or)和異或(exclusive or)等邏輯操作來組合兩個塊的像素值。

glEnable(GL_COLOR_LOGIC_OP)

glLogicOp(logicOp)

參數logicOp可被賦值多種符號常量,包括GL_AND、GL_OR、GL_XOR。使用常量GL_COPY_INVERTED可將取來的顏色位值顛倒後再取代目標值。

字符圖元

字體可分成兩類:有襯線(serif)和無襯線(sans serif)。有有襯線字體在字符主筆畫末端帶有細線或者是筆畫加重。而無襯線字體則沒有加重。有襯線字體可讀性較好,而無襯線字體單個字符比較容易被識別。

字體是否爲單一寬度(monospace)或者比例寬度(proportional)而進行分類。單一寬度字體所有字符有相同的寬度,而比例字體有多種字符寬度。

存儲的計算機字體有兩種不同的表示方法,一種表示某種字體字符形狀的簡單方法就是使用矩形網圖案。這樣的字符組稱爲位圖字體(bitmap font或者位圖化的字體)。位圖化的字符集有時也稱爲光柵字體(raster font)。

另外一種更靈活的方法是用直線和曲線線段來描述字符形狀,例如在PostScript中的處理,這種字符稱爲輪廓字體(outline font)比劃字體(stroke font)

在這裏插入圖片描述
位圖字體的定義和顯示最簡單,但是需要將字符網格映射到幀緩存位置。所以位圖子圖通常需要更多的存儲空間。當我們放大字體的時候,會增加邊緣的粗糙程度。

與位圖字體相比,輪廓字體在增加大小的時候其字符的形狀不會改變,輪廓字體需要更少的空間。通過控制字符輪廓的曲線定義,可以產生粗體、斜體或不同尺度的字體,需要更多的時間來處理輪廓字體。

OpenGL字符函數

OpenGL基本庫僅爲顯示單個字符和文字串提供了基本的支持。可以定義位圖字符,並將一個位圖集作爲字庫存儲,一個文字串通過將從字庫中選擇的位圖序列映射到幀緩存的相鄰位置來顯示。

在實用函數工具包(GLUT)中有一些預定義的字庫,所以我們不需要自己定義位圖字形庫,除非GLUT沒有的字體。

GLUT位圖字體由OpenGL的glBitmap函數來繪製。而輪廓字體由折線邊界(GL_LINE_STRIP)生成。

使用下面的函數可顯示GLUT位圖字符:

glutBitmapCharacter(font, character)

其中font用GLUT符號常量賦值,用來指定一特定字形集,參數character賦以ASCII編碼或其他要顯示的字符。

固定寬度或比例間隔字體都可以使用:GLUT_BITMAP_8_BY_13GLUT_BITMAP_9_BY_15

也可用GLUT_BITMAP_TIMES_ROMAN_10GLUT_BITMAP_HELVETICA_10來選擇10磅的比例間隔字體。

利用glutBitmapCharacter顯示的字符以當前光柵位置作爲位圖原點(左下角)。

用下面的函數可顯示一個輪廓字符:

glutStrokeCharacter(font, caharacter)

圖形分割

圖形子部分的名稱有多種說法,有些圖形軟件包稱它們爲結構(structures),另一些則稱爲段(segments)或對象(objects)。同樣,在不同的圖形軟件包中允許的子部分進行的操作也不相同。

OpenGL顯示錶

把對象描述成一個命名的語句序列(或任何其他的命令集)並存儲起來即方便又高效。在OpenGL中使用稱爲**顯示錶(display list)**的結構可以做到這一點。一旦建立了顯示錶,就可以用不同的顯示操作來多次引用該表。

創建和命名OpenGL顯示錶

使用glNewList/glEndList函數對來包圍一組OpenGL命令就可以形成顯示錶。

glNewList(listId, listMode)
.
.
.
glEndList()

該結構用賦予參數ListID的正整數作爲表名來形成一個顯示錶。參數ListMode可賦予OpenGL符號常量GL_COMPILE或GL_COMPILE_AND_EXECUTE之一。如果希望爲以後執行存儲的表,則使用GL_COMPILE,否則,放入表中的命令立即執行,但然後可以在以後在執行它。

顯示錶創建後,立即對包含如座標位置和顏色分量等參數的表示進行賦值計算,從而使表中僅僅存儲參數的值。是不能的修改的名。

執行OpenGL顯示錶

可以用下面的語句執行一個顯示錶:

glCallList(ListID)

下面的程序用於創建並執行一個顯示錶。我們先在xyxy平面上建立以(200,200)爲中心座標、半徑爲150的圓周上六個等距頂點描述的規則六邊形的顯示錶。
在這裏插入圖片描述
在這裏插入圖片描述

如果想要執行多個顯示錶,則可以使用glListBase(offsetValue)glCallLists(nLists, arrayDataType, listIDArray)

刪除OpenGL顯示錶

使用glDeleteLists(startID,nLists) ,參數startID給出最前面的顯示錶標識,而參數nLists給出要刪除的顯示錶總數。

OpenGL顯示窗口重定形函數

爲了允許對顯示窗口尺寸的改變做出反應,GLUT提供下面的函數

glutReshapeFunc(winReshapeFcn)

該函數可以和其他GLUT函數一起放在程序的主過程中,它在顯示窗口尺寸輸入後立即激活。

改變窗口的例子

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import math

TWO_PI = 6.2831853
winWidth = 400
winHeigh = 400
regHex = None 
class screenpt(object):
    
    def __init__(this):
        this.x = 0 
        this.y = 0
    def setCoords(this, xCoord, yCoord):
        this.x = xCoord
        this.y = yCoord
    def getx(this):
        return this.x

    def gety(this):
        return this.y 

def init():
    global winWidth, winHeigh
    global regHex
    global TWO_PI
    circCtr = screenpt()
    hexVertex  = screenpt()
    circCtr.setCoords(winWidth/2, winHeigh/2)
    glClearColor(1.0, 1.0, 1.0, 0.0)
    regHex = glGenLists(1)
    glNewList(regHex, GL_COMPILE)
    glColor3f(1.0, 0.0, 0.0)
    glBegin(GL_POLYGON)
    for k in range(6):
        theta  = TWO_PI * k / 6.0
        hexVertex.setCoords(circCtr.getx()+150*math.cos(theta), circCtr.gety()+150*math.sin(theta))
        glVertex2i(int(hexVertex.getx()), int(hexVertex.gety()))
    glEnd()
    glEndList()

def regHexagon():
    global regHex
    glClear(GL_COLOR_BUFFER_BIT)
    glCallList(regHex)
    glFlush()

def winReshapeFcn(newWidth, newHeight):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0.0, GLdouble(newWidth), 0.0, GLdouble(newHeight) )
    glClear(GL_COLOR_BUFFER_BIT)

if __name__ == "__main__":
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowPosition(100,100)
    glutInitWindowSize(winWidth, winHeigh)
    glutCreateWindow("Reshape 函數 顯示樣例".encode('gbk'))
    init()
    glutDisplayFunc(regHexagon)
    glutReshapeFunc(winReshapeFcn)
    glutMainLoop()

注意:由於python在使用變量的時候不需要指定參數的類型,所以有的時候會出現類型錯誤,要注意類型的轉換。

得到的結果如下
在這裏插入圖片描述
在這裏插入圖片描述

由於命令glLoadIdentity包含在重定形函數中,從而使前面任意的投影參數值對新的投影設置不起作用。開始的時候六邊形的中心在窗口的中心,但是六邊形的位置不受顯示窗口尺寸任何改版的影響,這是因爲六邊形在顯示錶中定義,並且僅僅是最初的中心座標存儲在表中。

折線樣例

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

winWidth = 600
winHeight = 500
xRaster = 25
yRaster = 150

label = ['J', 'a', 'n', 'F', 'e', 'b', 'M', 'a', 'r',
         'A', 'p', 'r', 'M', 'a', 'y', 'J', 'u', 'n',
         'J', 'u', 'l', 'A', 'u', 'g', 'S', 'e', 'p',
         'O', 'c', 't', 'N', 'o', 'v', 'D', 'e', 'c']

dataValue = [420, 342, 324, 310, 262, 185, 190, 196, 217, 241, 312, 438]


def init():
    glClearColor(1.0, 1.0, 1.0, 0.0)
    glMatrixMode(GL_PROJECTION)
    gluOrtho2D(0.0, 600.0, 0.0, 500.0)


def lineGraph():
    global dataValue
    global xRaster
    global label
    x = 30
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(0.0, 0.0, 1.0)
    glBegin(GL_LINE_STRIP)
    for k in range(12):
        glVertex2i(x+k*50, dataValue[k])
    glEnd()
    glColor3f(1.0, 0, 0)

    for k in range(12):
        glRasterPos2i(xRaster + k* 50, dataValue[k]-4)
        glutBitmapCharacter(GLUT_BITMAP_9_BY_15, ord('*'))

    glColor3f(0.0, 0.0, 0.0)
    xRaster = 20
    for mouth in range(12):
        glRasterPos2i(xRaster, yRaster)
        for k in range(3*mouth, 3*mouth+3):
            glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, ord(label[k]))

        xRaster += 50
    glFlush()


def winReshapeFcn(newwidth, newHeight):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0.0, newwidth, 0.0, newHeight)
    glClear(GL_COLOR_BUFFER_BIT)



if __name__ == '__main__':
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowPosition(100, 100)
    glutInitWindowSize(winWidth, winHeight)
    glutCreateWindow("折線圖".encode('gbk'))
    init()
    glutDisplayFunc(lineGraph)
    glutReshapeFunc(winReshapeFcn)
    glutMainLoop()

在這裏插入圖片描述
如果想要生成直方圖

def barChart():
    global dataValue
    global xRaster
    global label
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(1.0, 0.0, 0.0)
    for k in range(12):
        glRecti(20+k*50, 165, 40 + k*50, dataValue[k])
    glColor3f(0.0, 0.0, 0.0)
    xRaster = 20
    for month in range(12):
        glRasterPos2i(xRaster, yRaster)
        for k in range(3*month, 3*month+3):
            glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, ord(label[k]))

        xRaster += 50

    glFlush()

在這裏插入圖片描述

餅狀圖

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import math

winWidth = 400
winHeight = 300
twoPi = 6.283185


class srcPt(object):
    def __init__(self, _x, _y):
        self.x = _x
        self.y = _y


def init():
    glClearColor(1.0, 1.0, 1.0, 1.0)
    glMatrixMode(GL_PROJECTION)
    gluOrtho2D(0.0 , 200, 0.0, 150.0)


def circleMidPoint(circ, r):
    global twoPi
    n = 1000 # 以直代曲,使用1000個線段來模擬圓
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(0.0, 0.0, 1.0)
    glBegin(GL_POLYGON)
    for i in range(n):
        glVertex2f(r*math.cos(twoPi*i/n) + circ.x, r*math.sin(twoPi*i/n)+circ.y)
    glEnd()
    glFlush()

def pieChart():
    global winWidth, winHeight
    radius = winWidth/4
    nSlices = 12
    previousSliceAngle = 0.0
    dataValues = [10.0, 7.0, 13.0, 5.0, 13.0, 14.0, 3.0, 16.0, 5.0, 3.0, 17.0, 8.0]
    dataSum = 0.0
    piePt = srcPt(0.0,0.0)
    circCtr = srcPt(winWidth/2, winHeight/2)
    circleMidPoint(circCtr, radius)
    for k in range(nSlices):
        dataSum += dataValues[k]

    glColor3f(1.0, 1.0, 1.0)
    for k in range(nSlices):
        sliceAngle = twoPi * dataValues[k]/dataSum + previousSliceAngle
        piePt.x = circCtr.x + radius * math.cos(sliceAngle)
        piePt.y = circCtr.y + radius * math.sin(sliceAngle)
        glBegin(GL_LINES)
        glVertex2i(int(circCtr.x), int(circCtr.y))
        glVertex2i(int(piePt.x), int(piePt.y))
        glEnd()
        previousSliceAngle = sliceAngle

def displayFcn():
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(0.0, 0.0, 1.0)
    pieChart()
    glFlush()


def winReshapeFcn(newWidth, newHeight):
    global winHeight, winWidth
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0.0, newWidth, 0.0, newHeight)
    glClear(GL_COLOR_BUFFER_BIT)
    winHeight = newHeight
    winWidth = newWidth

if __name__ == '__main__':
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowPosition(100, 100)
    glutInitWindowSize(winWidth, winHeight)
    glutCreateWindow("餅狀圖".encode('gbk'))

    init()
    glutDisplayFunc(displayFcn)
    glutReshapeFunc(winReshapeFcn)
    glutMainLoop()

在這裏插入圖片描述

drawCurve顯示曲線

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from enum import Enum
import math

winWidth = 600
winHeight = 500


curveName = {'limacon':1,
             'cardioid':2,
             'threeleaf':3,
             'fourleaf':4,
             'spiral':5}
class screenPt():
    def __init__(self, x, y):
        self.x = x
        self.y = y


def init():
    glClearColor(1.0, 1.0, 1.0, 1.0)
    glMatrixMode(GL_PROJECTION)
    gluOrtho2D(0.0, 200.0, 0.0, 150.0)


def lineSegment(pt1, pt2):
    glBegin(GL_LINES)
    glVertex2i(int(pt1.x), int(pt1.y))
    glVertex2i(int(pt2.x), int(pt2.y))
    glEnd()


def drawCurve(curveNum):
    twoPi = 6.283185
    a = 175
    b = 60
    dtheta = 1.0 / a
    x0 = 200
    y0 = 250
    curvePt = [screenPt(0, 0), screenPt(0, 0)]
    curvePt[0].x = x0
    curvePt[0].y = y0
    glColor3f(0.0, 0.0, 0.0)
    if curveNum == curveName['limacon']:
        curvePt[0].x += (a + b)
    elif curveNum == curveName['cardioid']:
        curvePt[0].x += (a + a)
    elif curveNum == curveName['threeleaf']:
        curvePt[0].x += a
    else:
        pass
    theta = dtheta
    while theta < twoPi:

        if curveNum == curveName['limacon']:
            r = a * math.cos(theta) + b
        elif curveNum == curveName['cardioid']:
            r = a * (1 + math.cos(theta))
        elif curveNum == curveName['threeleaf']:
            r = a * math.cos(3 * theta)
        elif curveNum == curveName['fourleaf']:
            r = a * math.cos(2 * theta)
        else:
            r = (a / 4.0) * theta

        curvePt[1].x = x0 + r * math.cos(theta)
        curvePt[1].y = y0 + r * math.sin(theta)
        lineSegment(curvePt[0], curvePt[1])
        curvePt[0].x = curvePt[1].x
        curvePt[0].y = curvePt[1].y
        theta += dtheta


def dispalyFcn():
    glClear(GL_COLOR_BUFFER_BIT)
    print("\nEnter the integer value corresponding to\n")
    print("one of the following curve names.\n")
    print("press any other key to exit.\n")
    print("\n1-limacon, 2-cardiod, 3-threeLeaf, 4-fourLeaf, 5-spiral: ")
    try:
        curveNum = int(input())
        if curveNum <=0 or curveNum >5 :
            return
        drawCurve(curveNum)
        glFlush()

    except TypeError as e:
        print(e)

def winReshapeFcn(newWidth, newHeight):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0.0, newWidth, 0.0, newHeight)
    glClear(GL_COLOR_BUFFER_BIT)

if __name__ == '__main__':
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowPosition(100, 100)
    glutInitWindowSize(winWidth, winHeight)
    glutCreateWindow("繪製曲線".encode('gbk'))

    init()
    glutDisplayFunc(dispalyFcn)
    glutReshapeFunc(winReshapeFcn)
    glutMainLoop()

1

在這裏插入圖片描述

2

在這裏插入圖片描述

3

在這裏插入圖片描述

4

在這裏插入圖片描述

5

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章