第7章 二維幾何變換
應用於對象幾何描述並改變它的位置、方向或大小的操作稱爲幾何變換(geometric transformation)。
幾何變換有時也稱爲建模變換(modeling transformation),但有些圖形系統將兩者區分開來。建模變換一般用於構造場景或給出由多個部分組合而成的複雜對象的層次式描述等。
基本的二維幾何變換
平移、旋轉和縮放是所有圖形軟件包中都包含的幾何變換函數。可能包括在圖形軟件包中的其他變換函數有反射和錯切操作。
二維平移
通過將位移量加到一個點的座標上來生成一個新的座標位置,可以實現一次平移(translation)。實際上,我們將該點從原始位置沿一直線路徑移動到新位置。
將平移距離(translation distance)和加到原始座標上獲得一個新的座標位置,可以實現一個二維位置的平移。
一對平移距離稱爲平移向量(translation vector)或位移向量(shift vector)。
如果用矩陣表示
這樣就可以使用矩陣形式來表示二維平移方程:
下面的程序演示了平移操作。輸入的平移向量用來將一個多邊形的n個頂點從世界座標系的一個位置移動到另一個位置,而OpenGL子程序用來重新生成平移後的多邊形。
class wcPt2D(
public: GLfloat x,y;
};
void translatePolygon (wcPt2D"verts, GLint nVerts, GLfloat tx, GLfloat ty){
GLint k;
for (k=0;k<nVerts:k++){
verts [k].x = verts [k].x + tx;
verts [k].y =verts [k].y + ty;
}
g1Begin (GL_POLYGON);
for (k=0;k< nVerts;k++)
g1Vertex2f (verts [k].x, verts [k].y);
g1End();
}
二維旋轉
通過指定一個旋轉軸(rotation axis)和一個旋轉角度(rotation angle),可以進行一次旋轉(rotation)變換。在將對象的所有頂點按指定角度繞指定旋轉軸旋轉後,該對象的所有點都旋轉到新位置。
對象的二維旋轉通過在平面上沿圓路徑將對象重定位來實現。此時,我們將對象繞與平面垂直的旋轉軸(與軸平行)旋轉。二維旋轉的參數有旋轉角和稱爲旋轉點(rotation point 或pivot point)的位置,對象繞該點旋轉。基準點是旋轉軸與平面的交點。正角度定義繞基準點的逆時針旋轉,而負角度將對象沿順時針方向旋轉。
爲了簡化模型,我們先假設基準點爲原點。
因此旋轉後用角度和表示爲
在極座標系中,點的原始座標爲
所以可以得到:
如果用矩陣的形式表示:
其中,旋轉矩陣爲:
但現在OpenGL、Java、PHIGS和GKS都按標準列向量方式表示。
任意的旋轉位置旋轉點的變換方程:
線段的旋轉可以通過用於每個線段端點,並重新繪製新端點間的線段而得到。多邊形的旋轉則是將每個頂點旋轉指定的旋轉角,並使用新的頂點來生成多邊形而實現旋轉。曲線的旋轉通過重新定位定義的點並重新繪製曲線而完成。例如圓或橢圓,可以通過將中心位置沿指定旋轉角對着的弧移動而繞非中心軸旋轉。橢圓可通過旋轉其長軸和短軸來實現繞其中心位置的旋轉。
class wcPt2D {}
public:
GLfloat x,y;
};
void rotatePolygon (wcPt2D* verts, GLint nVerts, wePt2D pivPt, GLdouble theta)
{
wcPt2D* vertsRot;
GLint k;
for (k=0;k< nVerta:k++){
vertsRot [k].x=pivPt.x+(verts [k].x-pivPt.x) * cos (theta)-(verts [k].y-pivpt.y)* sin (theta);
vertsRot [k].y=pivPt.y+(verts [k],x-pivPt.x )* sin (theta)+(verts [k].y-pivPt.y)* cos (theta):
]
g1Begin { GL_POLYGON}:
for(k=0;k<nVerts;k++)
g1Vertex2f (verteRot [k].x, verteRot [k].y):
g1End();
二維縮放
改變一個對象的大小,可使用縮放(scaling)變換。一個簡單的二維縮放操作可通過將縮放係數(scaling factor)和,與對象座標位置相乘而得:
矩陣形式如下
或者是
當和相同的值的時候,就會產生保持對象相對比例一致的一致縮放(uniform scaling)。當兩個值不相等的時候就是差值縮放(differential scaling)。
當縮放係數的絕對值小於1時,縮放後的對象向原點靠近;而縮放係數絕對值大於1時,縮放後的座標位置遠離原點。
我們可以選擇一個在縮放變換後不改變位置的點,稱爲固定點(fixed point),以控制縮放後對象的位置。固定點的座標可以選擇對象的中點等位置或任何其他空間位置。這樣,多邊形通過縮放每個頂點到固定點的距離而相對於固定點進行縮放。對於座標爲的頂點,縮放後的座標可計算爲
可以得到
其中和都是常數。
class wePt2D {
public:
GLfloat x,y;
};
void scalePolygon (wcPt2D· verts, GLint nVerts, wcpt2D fixedPt, GLfloat sx, GLfloat sy)
{
wcPt2D vertsNew;
GLint k;
for (k-0;k<nVerts;k++){
vertsNew[k].x=verts [k].x* sx+ fixedPt.x*(1-sx);
vertaNew[k].y=verts [k].y*sy+fixedPt.y*(1: sy):
}
g1Begin (GL POLYGON];
for (k-0;k<nVerte;k++)
g1Vertex2f (vertsNew [k].x, vertsNew [k].y);
g1End():
矩陣表示和齊次座標
每個基本變換(平移、旋轉和縮放)都可以表示爲普通矩陣形式
和都是座標的列向量。矩陣是一個包含乘法系數的的矩陣,是包含平移項的兩元素列矩陣。
對於平移,是單位矩陣。對於旋轉或縮放,包含與基準點或縮放固定點相關的平移項。
齊次座標
如果將矩陣表達式擴充爲矩陣,就可以把二維兒何變換的乘法和平移組合成單一矩陣表示。這時將變換矩陣的第三列用於平移項,而所有的變換公式可表達爲矩陣乘法。但爲了這樣操作,必須解釋二維座標位置到三元列向量的矩陣表示。標準的實現技術是將二維座標位置表示擴充到三維表示,稱爲齊次座標(homogeneous coordinate),這裏的齊次參數(homogeneous parameter)h是一個非零值,因此
普通的二維齊次座標表示可寫爲。最簡單的。因此每個二維位置都可以用齊次座標來表示來表示。
二維平移矩陣
使用齊次座標方法,座標位置的二維平移可表示爲下面的矩陣乘法:
該平移操作可簡寫爲
二維旋轉矩陣
二維的旋轉變換公式爲
其中旋轉變換操作的旋轉參數
二維縮矩陣
相對於座標原點的縮放變換可以表示爲
逆變換
對於平移變換,我們通過對平移距離取負值而得到逆矩陣。因此,如果二維平移距離是和,則其逆平移矩陣是
逆旋轉通過用旋轉角度的負角取代該旋轉角來實現。例如,繞座標系原點的角度爲0的二維旋轉有如下的逆變換矩陣
將縮放係數用其倒數取代就得到了縮放變換的逆矩陣。對以座標系原點爲中心、縮放參數爲和的二維縮放,其逆變換矩陣爲
二維複合變換
利用矩陣表達式,可以通過計算單個變換的矩陣乘積,將任意的變換序列組成複合變換矩陣(composite transformation matrix)。形成變換矩陣的乘積經常稱爲矩陣的合併(concatenation) 或複合(composition)。由於一個座標位置用齊次列矩陣表示,我們必須用表達任意變換順序的矩陣來前乘該列矩陣。由於場景中許多位置用相同的順序變換,先將所有變換矩陣相乘形成一個複合矩陣將是高效率的方法。因此,如果我們要對點位置P進行兩次變換,變換後的位置將用下式計算:
複合二維平移
假如將兩個連續的平移向量和用於座標位置P,那麼最後的變換位置可以計算爲
這表示兩個連續平移是相加的。
複合二維旋轉
並且
複合二維縮放
或者是
通用二維基準點旋轉
當圖形軟件包僅提供繞座標系原點的旋轉函數時,我們可通過完成下列平移-旋轉-平移操作序列來實現繞任意選定的基準點的旋轉。
- 平移對象使基準點位置移動到座標原點;
- 繞座標原點旋轉;
- 平移對象使基準點回到其原始位置。
該等式可以使用下列形式表示:
其中。
通用二維基準點縮放
在只有相對於座標原點縮放的縮放函數時,縮放的變換序列:
- 平移對象使固定點與座標原點重合;
- 對於座標原點進行縮放;
- 使用步驟1的反向平移將對象返回到原始位置。
即
通用二維定向縮放
參數和沿x和y方向縮放對象,可以通過在應用縮放變換之前,將對象所希望的縮放方向旋轉到與座標軸一致來實現在其他方向上縮放對象。
首先完成旋轉操作,使和的方向分別與x和y軸重合。然後應用縮放變換,再進行反向旋轉回到其原始位置。從這三個變換的乘積得到的複合矩陣爲
矩陣合併特性
矩陣相乘符合結合律,對於任何三個矩陣,和。矩陣積可以寫成
因此,依靠變換的描述順序,我們既可以使用從左到右(前乘),也可以使用從右到左(後乘)的結合分組來求矩陣乘積。有些圖形軟件包要求變換按應用的順序描述。
二維剛體變換
如果一個變換矩陣僅包含平移和旋轉參數,則它是一個剛體變換矩陣(rigid-body transformation matrix)。二維剛體變換矩陣的一般形式爲
其中,4個元素是多重旋轉項,元素和是平移項。座標位置的剛體變換有時也稱爲剛體運動(rigid motion)。變換後的座標位置間的所有角度和距離都不變化。
因此上述左上角的矩陣是一個正交矩陣(orthogonal matrix)的特性。說明,如果將子矩陣的每一行(或每一列)作爲向量,那麼兩個行向量和形成單位向量的正交組。這樣的一組向量也稱爲正交向量組。每個向量具有單位長度。
並且向量相互垂直
二維複合矩陣編程
其他二維變換
反射
產生對象鏡像的變換稱爲反射(reflection)。對於二維反射而言,其反射鏡像通過將對象繞反射軸旋轉而生成。我們選擇的反射軸(axis of reflection)可以是在平面內的一條直線或者是垂直平面的一條直線。當反射軸是平面內的一條直線時,繞這個軸的旋轉路徑在垂直於平面的平面中;而對於垂直於平面的反射軸,旋轉路徑在平面內。下面舉出一些普通的反射例子。
關於直線(軸)的反射,可以由下列的變換矩陣完成。
對於(y軸)的反射,翻動的座標而保持座標不變,這種變換的矩陣是
關於的平面內任意直線 的反射,可以使用平移-旋轉-反射變換的組合來完成的。
通常,我們先平移直線使其經過原點。然後將直線旋轉到座標軸之一,並進行相對於座標軸的反射。最後利用逆旋轉和逆平移變換將直線還原到原來位置。
錯切
錯切(shear) 是一種使對象形狀發生變化的變換,經過錯切的對象好像是由已經相互滑動的內部夾層組成。兩種常用的錯切變換是移動座標值的錯切和移動座標值的錯切。
相對於軸的方向錯切由下列變換矩陣產生:
該矩陣將座標位置轉換成
可以將任意實數賦給錯切參數.。然後將座標位置水平地移動與其到軸的距離(值)成正比的量。
錯切操作可以表示爲基本變換的序列。
幾何變換的光柵方法
光柵系統的特殊功能爲特定的二維變換提供了另一種方法。光柵系統將圖像信息作爲顏色圖案存儲在幀緩衝器中。控制矩形像素數組的光柵功能通常稱爲光柵操作(raster operation),將一塊像素從一個位置移動到另一個位置的過程也稱爲像素值的塊移動(block transfer,bitblt或pixblt)。圖形軟件中通常包含完成某些光柵操作的子程序。
90°倍數的旋轉可以很容易地利用重新安排像素矩陣的元素而實現。通過首先將陣列的每一行的像素值顛倒,然後交換其行和列來將對象逆時針旋轉90°;通過顛倒陣列的每一行中元素的順序,然後將行的順序顛倒來得到180°的旋轉。
像素塊的光柵縮放採用類似方法實現。我們用指定的和值對原始塊中的像素區域進行縮放,並將縮放的矩形映射到一組目標像素上,然後按照其與縮放像素區域的重疊區域,設置每個目標像素的亮度。
OpenGL光柵變換
像素顏色值的矩形數組從一個緩存到另一個的平移可以作爲如下的OpenGL複製操作來完成:
g1CopyPixe1s (xmin, ymin, width, height. GL_COLOR):
前面4個參數給出了像素塊的位置和尺寸。而OpenGL符號常量GL_COLOR指定要複製的顏色值。該像素數組複製到刷新緩存中由當前光柵位置指定的左下角的一個矩形區域內。像素顏色值依賴於顏色模式的當前設定,按RGBA或顏色表進行復制。提供複製的區域(源)和複製目標區域均應位於屏幕座標邊界內。該平移可作用於任何刷新緩存或不同緩存之間。g1CopyPixels函數的源緩存用g1ReadBuffer 子程序選擇,而目標緩存用g1DrawBuffer子程序選擇。
緩存中的一個RGB顏色塊可以用下列函數存入一個數組:
g1ReadPixels (xmin, ymin, width. height, GL_RGB.GL_UNSIGNED_BYTE, colorArray):
如果顏色表索引存於像素位置,則將GL_COLOR_INDEX取代GL_RGB。爲了旋轉顏色值,必須如前一節所述重新安排顏色數組的行與列。然後使用下列語句將旋轉後的數組放回緩存:
g1DrawPixe1s (width,height,GL_RGB,GL_UNSIGNED_BYTE, colorArray);
該數組的左下角放到當前光柵位置。我們用g1ReadBuffer選擇包含原來的像素值塊的源緩存,用g1DrawBuffer指定目標緩存。
二維縮放變換通過指定縮放因子然後引用g1Copypixe1s或g1Drawpixe1s按OpenGL中的光柵操作來完成。對於光柵操作,使用下列函數來設定縮放因子:
g1Pixe1zoom(sx,sy):
這裏,參數sx和sy可賦以任何非零浮點值。大於1.0的正值增太源數組元素的尺對,而小於1.0的正值減少元素尺寸。sx或sy中有負值或兩個都爲負值則生成該數組元素的反射及縮放。因此,如果sx=sy=-3.0,則源數組相對於當前光柵位置反射且數組的每一顏色元素映射到目標緩存中的3×3像素塊。如果目標像素的中心位於一數組縮放顏色元素的矩形區域,則用該數組元素給它賦值。中心在縮放數組元素左邊界或上邊界的目標像素也賦以該元素的顏色。sx和sy的默認值均爲1.0。
二維座標系間的變換
非笛卡兒系統的例子有極座標系統、球面座標系統、橢圓座標系統和拋物線座標系統。
給出了一個在笛卡兒座標系中用座標原點及方向角指定的笛卡兒座標系。爲了將對象描述從座標變換到座標,必須建立把軸疊加到軸的變換,這需要分兩步進行:
- 將系統的座標原點平移到系統的原點
- 將軸旋轉到軸上
座標原點的平移可以使用下列矩陣操作表示:
爲了將兩個系統的軸重合,可以順時針旋轉:
把這兩個變換矩陣合併起來,就給出了將對象描述從系統轉換到系統的完整複合矩陣。
OpenGL二維幾何變換函數
在OpenGL的核心庫中,每一種基本的幾何變換都有一個獨立的函數。由於OpenGL是作爲三維圖形應用編程接口(APl)來設計的,所有變換都在三維空間中定義。在內部,所有座標均使用4元素列向量表示,而所有變換均使用4×4矩陣表示。因此,二維變換可以通過在OpenGL中選擇使第三維(z)不改變的z值來實現。
基本的OpenGL幾何變換
平移矩陣用下列子程序構造
g1Translate*(tx,ty,tz)
平移參數tx、ty和tz可賦予任意的實數值,附加於該函數的單個後綴碼或者是f(浮點)或者是d。
旋轉矩陣下列函數生成:
glRotate*(theta,vx,vy,vz)
向量的分量可以有任意的浮點數值。該向量用於定義通過座標原點的旋轉軸的方向。
用下列函數可得到相對於座標原點的4×4縮放矩陣:
glScale*(sx, sy, sz)
OpenGL矩陣操作
將該矩陣看做建模觀察矩陣(modelview matrix),它用於存儲和組合幾何變換,也用於將幾何變換與向觀察座標系的變換進行組合。建模觀察模式用下列語句指定:
g1MatrixMode (GL_MODELVIEW)
該語句指定一個4×4建模觀察矩陣作爲當前矩陣(current matrix)。
在這個調用後的OpenGL變換子程序用來修改建模觀察矩陣,而後該矩陣用來變換場景中的座標位置。用g1MatrixMode函數還可以設定另外兩個模式:紋理模式(texture mode)和顏色模式(color mode)。紋理模式用於映射表面的紋理圖案,而顏色模式用於從一個顏色模型轉換到另一個。後面幾章將討論觀察、投影、紋理和顏色變換。
建立建模觀察模式(或任何其他模式)後,調用變換子程序所生成的矩陣要與該模式的當前矩陣相乘。另外,我們可以對當前矩陣的元素賦值,OpenGL庫中有兩個函數可用於此目的。使用下列函數可設定當前矩陣爲單位矩陣:
glLoadIdentity():
也可以爲當前矩陣的元素賦其他值:
g1LoadMatrix*(elements16):
參數elements16指定了一個單下標、16元素的浮點值數組,而後綴f或d用來指定數據類型。該數組的元素必須按列優先順序指定。即先列出第一列的4個元數,接着列出第二列的4個元素,然後是第三列,而最後是第四列。
也可以將指定的矩陣與當前矩陣合併:
glMultMatrix*(otherElements16):
實例
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import math
winWidth, winHeight = 600, 600
xwcMin, xwcMax = 0.0, 300.0
ywcMin, ywcMax = 0.0, 300.0
pi = 3.14159
matComposite = [[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0]]
def init():
glClearColor(1.0, 1.0, 1.0, 0.0)
def get3x3Matfloat():
temp = [[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0]]
return temp
# 矩陣初始化
def matrix3x3SetIndentity(matIdent3x3):
for row in range(3):
for col in range(3):
matIdent3x3[row][col] = (row == col)
# 顯示三角形
def triangle(verts):
glBegin(GL_TRIANGLES)
for k in range(3):
glVertex2f(verts[k][0], verts[k][1])
glEnd()
def matrix3x3PreMultiply(m1, m2):
mattemp = get3x3Matfloat()
for row in range(3):
for col in range(3):
mattemp[row][col] = m1[row][0] * m2[0][col] + m1[row][1] * m2[1][col] + m1[row][2] * m2[2][col]
for row in range(3):
for col in range(3):
m2[row][col] = mattemp[row][col]
def rotate2D(pivotPt, theta):
global matComposite
matRot = get3x3Matfloat()
matrix3x3SetIndentity(matRot)
matRot[0][0] = math.cos(theta)
matRot[0][1] = -math.sin(theta)
matRot[0][2] = pivotPt[0] * (1-math.cos(theta)) + pivotPt[1] * math.sin(theta)
matRot[1][0] = math.sin(theta)
matRot[1][1] = math.cos(theta)
matRot[1][2] = pivotPt[0] * (1-math.cos(theta)) - pivotPt[1]*math.sin(theta)
matrix3x3PreMultiply(matRot, matComposite)
def scale2D(sx, sy, fixedPt):
global matComposite
matScale = get3x3Matfloat()
matrix3x3SetIndentity(matScale)
matScale[0][0] = sx
matScale[0][2] = (1-sx)*fixedPt[0]
matScale[1][1] = sy
matScale[1][2] = (1-sy) * fixedPt[1]
matrix3x3PreMultiply(matScale, matComposite)
def translate2D(tx, ty):
global matComposite
matTrans1 = get3x3Matfloat()
matrix3x3SetIndentity(matTrans1)
matTrans1[0][2] = tx
matTrans1[1][2] = ty
matrix3x3PreMultiply(matTrans1, matComposite)
def transformVerts2D(nVerts, verts):
global matComposite
for k in range(nVerts):
temp = matComposite[0][0] * verts[k][0] + matComposite[0][1] * verts[k][1] + matComposite[0][2]
verts[k][1] = matComposite[1][0] * verts[k][0] + matComposite[1][1] * verts[k][1] + matComposite[1][2]
verts[k][0] = temp
def dispalyFcn():
global pi,matComposite
nVerts = 3
verts = [[50.0, 25.0],[150.0, 25.0],[100.0, 100.0]]
xsum , ysum = 0,0
for k in range(nVerts):
xsum += verts[k][0]
ysum += verts[k][1]
centroidPt = [0,0]
centroidPt[0] = xsum/nVerts
centroidPt[1] = ysum/nVerts
pivPt = centroidPt[:]
fixedPt = centroidPt[:]
tx = 0.0
ty = 100.0
sx = 0.5
sy = 0.5
theta = pi/2.0
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(0.0, 0.0, 1.0)
triangle(verts)
matrix3x3SetIndentity(matComposite)
scale2D(sx, sy, fixedPt)
rotate2D(pivPt, theta)
translate2D(tx, ty)
transformVerts2D(nVerts, verts)
glColor3f(1.0, 0.0, 0.0)
triangle(verts)
glFlush()
def winReshapeFcn(newWidth, newHeight):
global xwcMax, xwcMin, ywcMax, ywcMin
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluOrtho2D(xwcMin, xwcMax, ywcMin, ywcMax)
glClear(GL_COLOR_BUFFER_BIT)
if __name__ == '__main__':
glutInit()
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
glutInitWindowPosition(50, 50)
glutInitWindowSize(winWidth, winHeight)
glutCreateWindow("幾何變換".encode('gbk'))
init()
glutDisplayFunc(dispalyFcn)
glutReshapeFunc(winReshapeFcn)
glutMainLoop()