卐 4-3D图形的数学
目錄
- Vectors(向量)
- Common Vector Operators(常用的向量操作)
- Dot Product(點(diǎn)積)
- Cross Product(叉積)
- Length of a Vector(向量的長(zhǎng)度)
- Reflection and Refraction(反射和折射)
- Matrices(矩陣)
- Matrix Construction and Operators(矩陣的構(gòu)建與操作)
- Understanding Transformations(理解變換)
- Coordinate Spaces in OpenGL(OpenGL中的坐標(biāo)空間)
- Object Coordinates(對(duì)象坐標(biāo)系)
- World Coordinates(世界坐標(biāo)系)
- View Coordinates(視圖坐標(biāo)系)
- Clip and Normalized Device Space(裁剪與歸一化設(shè)備空間)
- Coordinate Transformations(坐標(biāo)轉(zhuǎn)換)
- The Identity Matrix(單位矩陣)
- The Translation Matrix(平移矩陣)
- The Rotation Matrix(旋轉(zhuǎn)矩陣)
- Euler Angles(歐拉角)
- The Scaling Matrix(縮放矩陣)
- Concatenating Transformations(串聯(lián)變換)
- Quaternions(四元數(shù))
- The Model-View Transform(模型-視圖變換)
- The Lookat Matrix(Lookat矩陣)
- Projection Transformations(投影變換)
- Perspective Matrices(透視矩陣)
- Orthographic Matrices(正交矩陣)
- Interpolation,Lines,Curves,and Splines(插值、直線、曲線和樣條曲線)
- Curves(曲線)
- Splines(樣條曲線)
- Summary(總結(jié))
Vectors(向量)
一個(gè)xyz三元組可以用一個(gè)向量來(lái)表示(事實(shí)上,對(duì)于數(shù)學(xué)上純粹的心來(lái)說(shuō),一個(gè)位置實(shí)際上也是一個(gè)向量)。當(dāng)涉及到操作三維幾何體時(shí),向量可能是需要理解的最重要的基礎(chǔ)概念。這三個(gè)值(x、y和z)組合表示兩個(gè)重要值:方向和幅值。
向量是OpenGL操作的基礎(chǔ),因此各種大小的向量都是GLSL中的一級(jí)類(lèi)型,并被命名為vec3和vec4(分別表示三元素向量和四元素向量)。向量可以表示的第二個(gè)量是大小。向量的大小是向量的長(zhǎng)度。對(duì)于x軸向量(1, 0, 0),向量的長(zhǎng)度是1。長(zhǎng)度為1的向量稱(chēng)為單位向量。如果一個(gè)向量不是一個(gè)單位向量,我們想把它縮放成一個(gè),我們稱(chēng)之為歸一化。對(duì)向量進(jìn)行歸一化會(huì)對(duì)其進(jìn)行縮放,使其長(zhǎng)度變?yōu)?,然后稱(chēng)該向量為歸一化向量。當(dāng)我們只想表示一個(gè)方向而不是一個(gè)大小時(shí),單位向量很重要。
此外,如果向量長(zhǎng)度出現(xiàn)在我們將要使用的方程中,當(dāng)這些長(zhǎng)度為1時(shí),它們會(huì)變得簡(jiǎn)單得多!幅值也很重要;例如,它可以告訴我們?cè)诮o定的方向上需要走多遠(yuǎn),我們需要離鱷魚(yú)多遠(yuǎn)。向量(和矩陣)是3D圖形中非常重要的概念,它們是GLSL語(yǔ)言(編寫(xiě)著色器的語(yǔ)言)中的頭等公民。然而,在C++語(yǔ)言中,情況并非如此。為了允許你在C++程序中使用它們,vmath庫(kù),它包含可以表示類(lèi)似于它們的GLSL對(duì)應(yīng)的向量和矩陣的類(lèi)。例如,vmath::vec3可以表示三分量浮點(diǎn)向量(x, y, z),vmath::vec4可以表示四分量浮點(diǎn)向量(x, y, z, w),依此類(lèi)推。添加w坐標(biāo)以使向量齊次(homogeneous),但通常設(shè)置為1.0。稍后,x、y和z值可能會(huì)除以w,當(dāng)它為1.0時(shí),實(shí)際上只剩下xyz值。vmath中的類(lèi)實(shí)際上是具有類(lèi)型定義的模板類(lèi),用于表示常見(jiàn)類(lèi)型,例如單精度和雙精度浮點(diǎn)值以及有符號(hào)和無(wú)符號(hào)整數(shù)變量。
vmath::vec3與vmath::vec4的定義如下:
typedef Tvec3<float> vec3; typedef Tvec4<float> vec4;定義一個(gè)三元素向量簡(jiǎn)單如下:
vmath::vec3 vVector;所有vmath類(lèi)都定義了大量構(gòu)造函數(shù)和復(fù)制運(yùn)算符,這意味著您可以按如下方式聲明和初始化向量:
vec3 vmath::vVertex1(0.0f, 0.0f, 1.0f); vec4 vmath::vVertex2 = vec4(1.0f, 0.0f, 1.0f, 1.0f); vec4 vmath::vVertex3(vVertex1, 1.0f);vec3 vmath::vVerts[] = { vmath::vec3(-0.5f, 0.0f, 0.0f),vmath::vec3(0.5f, 0.0f, 0.0f),vmath::vec3(0.0f, 0.5f, 0.0f) };vmath庫(kù)還包括許多與數(shù)學(xué)相關(guān)的函數(shù),并覆蓋其類(lèi)上的大多數(shù)運(yùn)算符,以允許向量和矩陣進(jìn)行加、減、乘、轉(zhuǎn)置等操作。
在這里我們需要小心,不要過(guò)分地掩飾第四個(gè)w分量。大多數(shù)情況下,當(dāng)您使用頂點(diǎn)位置指定幾何體時(shí),只需存儲(chǔ)一個(gè)三分量頂點(diǎn)并將其發(fā)送到OpenGL即可。對(duì)于許多方向向量,例如曲面法線(垂直于用于照明計(jì)算的曲面的向量),三分量向量就足夠了。但是,我們將很快深入研究矩陣世界,要變換3D頂點(diǎn),必須將其乘以4×4變換矩陣。規(guī)則是你必須將一個(gè)四分量向量乘以一個(gè)4×4矩陣;如果你嘗試使用一個(gè)4×4矩陣的三分量向量,鱷魚(yú)會(huì)吃掉你!更多關(guān)于這一切意味著什么。本質(zhì)上,如果你要對(duì)向量做你自己的矩陣運(yùn)算,那么在很多情況下你可能需要四個(gè)分量向量。
Common Vector Operators(常用的向量操作)
向量的行為與加法、減法、一元求反等運(yùn)算的預(yù)期相同。這些運(yùn)算符執(zhí)行每個(gè)分量的計(jì)算,并生成與其輸入大小相同的向量。vmath向量類(lèi)重載加法、減法和一元求反運(yùn)算符以及其他幾個(gè)運(yùn)算符,以提供此類(lèi)功能。
vmath::vec3 a(1.0f, 2.0f, 3.0f); vmath::vec3 b(4.0f, 5.0f, 6.0f); vmath::vec3 c;c = a + b; c = a - b; c += b; c = -c;然而,在下面的小節(jié)中,從數(shù)學(xué)角度解釋了更多關(guān)于向量的操作。它們?cè)趘math庫(kù)中也有實(shí)現(xiàn),下面將對(duì)其進(jìn)行概述。
Dot Product(點(diǎn)積)
設(shè)有兩向量V1(x1,y1,z1)和V2(x2,y2,z2),則有:
V1●V2 = |V1||V2|cosθ = x1x2 + y1y2 + z1z2
其中,θ是向量V1與V2的夾角。
一對(duì)單位向量之間的點(diǎn)積是一個(gè)值(介于?1.0和+1.0),表示它們之間夾角的余弦。一個(gè)稍微高級(jí)一點(diǎn)的函數(shù)vmath::angle實(shí)際上返回這個(gè)角度(弧度)。
float angle(const vmath::vec3& u, const vmath::vec3& v);Cross Product(叉積)
V1 × V2 = |V1||V2|sinθ×n = (y1z2 - z1y2, z1x2 - x1z2, x1y2 - y1x2)
其中,θ是向量V1與V2的夾角;n是一個(gè)單位向量,它的方向由V1和V2按右手定則產(chǎn)生(如下圖中V3)。
叉積的應(yīng)用很多,從尋找三角形的曲面法線到構(gòu)造變換矩陣。
Length of a Vector(向量的長(zhǎng)度)
設(shè)有向量V(x, y, z),則它的長(zhǎng)度為√(x2 + y2 + z2)。它的長(zhǎng)度也等于√(V●V)。
vmath庫(kù)中也包含計(jì)算這個(gè)長(zhǎng)度的函數(shù):
Reflection and Refraction(反射和折射)
上圖中,入射光線Rin,反射光線Rreflect,折射光線Rrefract。其中, η是折射因子。
反射光計(jì)算公式:Rreflect = Rin - (2N●Rin)N
折射光計(jì)算公式:
k = 1 - η2(1 - (N●R)2)
當(dāng)k<0時(shí),Rrefract = 0;
當(dāng)k>=0時(shí),Rrefract = ηR - (η(N●R) + √k)N
其中,R和N都是單位長(zhǎng)度的向量。
vmath庫(kù)有兩個(gè)函數(shù)完成上述方程式,它們的源代碼如下:
template <typename T, const int len> static inline vecN<T,len> reflect(const vecN<T,len>& I, const vecN<T,len>& N) {return I - 2 * dot(N, I) * N; }template <typename T, const int len> static inline vecN<T,len> refract(const vecN<T,len>& I, const vecN<T,S>& N, T eta) {T d = dot(N, I);T k = T(1) - eta * eta * (T(1) - d * d);if (k < 0.0)return vecN<T,N>(0);elsereturn eta * I - (eta * d + sqrt(k)) * N; }Matrices(矩陣)
如果在空間中有一個(gè)點(diǎn)由x、y和z坐標(biāo)表示,并且如果圍繞某個(gè)任意點(diǎn)和方向旋轉(zhuǎn)若干度,則需要知道該點(diǎn)的位置,就使用矩陣。為什么?因?yàn)樾碌膞坐標(biāo)不僅取決于舊的x坐標(biāo)和其他旋轉(zhuǎn)參數(shù),而且還取決于y和z坐標(biāo)是什么。變量和解之間的這種依賴關(guān)系正是矩陣擅長(zhǎng)解決的問(wèn)題。
我們可以把一些矩陣看作是列向量表。
矩陣可以相乘和相加,但也可以與向量和標(biāo)量值相乘。將一個(gè)點(diǎn)(由向量表示)乘以一個(gè)矩陣(表示變換)得到一個(gè)新的變換點(diǎn)(另一個(gè)向量)。矩陣變換實(shí)際上不太難理解,對(duì)矩陣變換的理解是許多3D任務(wù)的基礎(chǔ)。
vmath::mat2 m1;// 2×2 matrix vmath::mat3 m2;// 3×3 matrix vmath::mat4 m3;// 4×4 matrix與GLSL中一樣,vmath中的矩陣類(lèi)定義了常見(jiàn)的運(yùn)算符,如加法、減法、一元求反、乘法和除法,以及構(gòu)造函數(shù)和關(guān)系運(yùn)算符。同樣,vmath中的矩陣類(lèi)是使用模板構(gòu)建的,包括單精度和雙精度浮點(diǎn)以及有符號(hào)和無(wú)符號(hào)整數(shù)矩陣類(lèi)型的類(lèi)型定義。
Matrix Construction and Operators(矩陣的構(gòu)建與操作)
對(duì)于一個(gè)4×4矩陣,OpenGL不是用一個(gè)二維數(shù)組存儲(chǔ)矩陣的浮點(diǎn)值,而是用一個(gè)包含16個(gè)元素的一維數(shù)組表示。默認(rèn)OpenGL是以列為主(column-major or column-primary)布局的。也就是說(shuō),對(duì)于一個(gè)4×4矩陣,前4個(gè)元素代表矩陣的第一列,接下來(lái)的4個(gè)元素代表矩陣的第二列,以此類(lèi)推。
GLfloat matrix[16];// Nice OpenGL-friendly matrix GLfloat matrix[4][4];// Not as convenient for OpenGL programmersOpenGL可以使用第二種變體,但第一種變體更有效。
下面的數(shù)組代表上圖的矩陣:
事實(shí)上,vmath庫(kù)在內(nèi)部將矩陣表示為它自己的向量類(lèi)的數(shù)組,每個(gè)向量包含一列矩陣。
假設(shè)有矩陣A和B,以及向量V,則有:
A·(B·V) = (A·B)·V (矩陣乘法滿足結(jié)合律)
我們可以用我們喜歡的任何方式將變換序列組合在一起,因?yàn)榫仃嚦朔ㄊ窍嗦?lián)的,但是矩陣在乘法中出現(xiàn)的順序很重要,因?yàn)榫仃嚦朔ú粷M足交換律。
旋轉(zhuǎn)與平移執(zhí)行的先后順序?qū)ξ矬w的影響:
圖(a)中正方形先繞z軸相對(duì)于原點(diǎn)旋轉(zhuǎn)θ角,然后沿著旋轉(zhuǎn)后的新x軸(即x1軸)平移,圖(b)中相同的正方形先沿x軸平移,然后繞z軸相對(duì)于新原點(diǎn)旋轉(zhuǎn)θ角。正方形的最終位置不同是因?yàn)?strong>每次變換都是相對(duì)于最后一次執(zhí)行的變換執(zhí)行的。在圖(a)中,正方形首先相對(duì)于原點(diǎn)旋轉(zhuǎn)。在圖(b)中,正方形平移后,圍繞新平移的原點(diǎn)進(jìn)行旋轉(zhuǎn)。
Understanding Transformations(理解變換)
仔細(xì)想想,大多數(shù)3D圖形都不是真正的3D。我們使用3D概念和術(shù)語(yǔ)來(lái)描述事物的外觀;然后這些3D數(shù)據(jù)被“擠壓”到2D電腦屏幕上。我們稱(chēng)之為將三維數(shù)據(jù)壓縮成二維數(shù)據(jù)投影的過(guò)程。每當(dāng)我們想要描述頂點(diǎn)處理期間發(fā)生的變換類(lèi)型(正交(orthographic)或透視(perspective))時(shí),我們都會(huì)提到投影(projection),但投影只是OpenGL中發(fā)生的變換類(lèi)型之一。變換還允許旋轉(zhuǎn)對(duì)象;移動(dòng)它們;甚至拉伸、收縮和扭曲它們。
Coordinate Spaces in OpenGL(OpenGL中的坐標(biāo)空間)
| Model space | Positions relative to a local origin.Also sometimes known as object space. |
| World space | Positions relative to a global origin(i.e.,their location within the world). |
| View space | Positions relative to the viewer.Also sometimes called camera or eye space. |
| Clip space | Positions of vertices after projection into a nonlinear homogeneous coordinate. |
| Normalized device coordinate(NDC) space | Vertex coordinates are said to be in NDC after their clip space coordinates have been divided by their own w component. |
| Window space | Positions of vertices in pixels, relative to the origin of the window. |
Object Coordinates(對(duì)象坐標(biāo)系)
大多數(shù)頂點(diǎn)數(shù)據(jù)通常在對(duì)象空間(object space)(也稱(chēng)為模型空間(model space))中開(kāi)始使用。在對(duì)象空間中,頂點(diǎn)的位置相對(duì)于局部原點(diǎn)進(jìn)行解釋。考慮一個(gè)宇宙飛船模型。模型的起源可能會(huì)在某個(gè)合乎邏輯的地方,比如飛行器的鼻尖、重心或飛行員可能坐的位置。在3D建模程序中,返回原點(diǎn)并充分縮小應(yīng)顯示整個(gè)宇宙飛船。模型的原點(diǎn)通常是可以旋轉(zhuǎn)模型以將其放置到新方向的點(diǎn)。將原點(diǎn)放置在遠(yuǎn)離模型的位置是沒(méi)有意義的,因?yàn)閲@該點(diǎn)旋轉(zhuǎn)對(duì)象將應(yīng)用顯著的平移和旋轉(zhuǎn)。
World Coordinates(世界坐標(biāo)系)
世界空間,它相對(duì)于固定的全局原點(diǎn)存儲(chǔ)坐標(biāo)的位置。繼續(xù)宇宙飛船的類(lèi)比,這可能是一個(gè)運(yùn)動(dòng)場(chǎng)或其他固定物體的中心,如附近的行星。一旦進(jìn)入世界空間,所有對(duì)象都存在于一個(gè)公共框架中。通常,這是執(zhí)行照明和物理計(jì)算的空間。
View Coordinates(視圖坐標(biāo)系)
本章中的一個(gè)重要概念是視圖坐標(biāo),通常也稱(chēng)為相機(jī)(camera)或眼睛(eye)坐標(biāo)。視圖坐標(biāo)相對(duì)于觀察者的位置(因此稱(chēng)為“相機(jī)”和“眼睛”),而不考慮可能發(fā)生的任何變換;你可以將它們視為“絕對(duì)”坐標(biāo)。因此,眼睛坐標(biāo)表示一個(gè)虛擬的固定坐標(biāo)系,用作公共參考系。下圖顯示了兩個(gè)視點(diǎn)的視圖坐標(biāo)系。在左側(cè),視圖坐標(biāo)表示為場(chǎng)景的觀察者所看到的坐標(biāo)(即,垂直于監(jiān)視器)。在右側(cè),視圖坐標(biāo)系稍微旋轉(zhuǎn),以便更好地查看z軸的關(guān)系。從觀察者的角度來(lái)看,正x和y分別指向右側(cè)和上方。正z值從原點(diǎn)向用戶移動(dòng),負(fù)z值從視點(diǎn)向屏幕移動(dòng)。屏幕位于z坐標(biāo)0處。
使用OpenGL在3D空間繪制時(shí),使用笛卡爾坐標(biāo)系。在沒(méi)有任何變換的情況下,使用剛才描述的眼睛坐標(biāo)系(eye coordinate system)。
Clip and Normalized Device Space(裁剪與歸一化設(shè)備空間)
裁剪空間是OpenGL執(zhí)行裁剪的坐標(biāo)空間。當(dāng)頂點(diǎn)著色器寫(xiě)入gl_Position時(shí),該坐標(biāo)被視為在裁剪空間(clip space)中。這始終是一個(gè)四維齊次坐標(biāo)(four-dimensional homogenous coordinate)。退出剪輯空間后,頂點(diǎn)的所有四個(gè)組件將被w分量相除。顯然,在此之后,w等于1.0。如果在此除法之前w不是1.0,則x、y和z分量將通過(guò)w的倒數(shù)進(jìn)行有效縮放。這允許透視縮短和投影等效果。將除法結(jié)果視為在歸一化設(shè)備坐標(biāo)空間(NDC空間)中。顯然,如果裁剪空間坐標(biāo)的結(jié)果w分量為1.0,則裁剪空間和NDC空間將變得相同。
Coordinate Transformations(坐標(biāo)轉(zhuǎn)換)
如前所述,通過(guò)將坐標(biāo)的向量表示乘以變換矩陣,可以將坐標(biāo)從一個(gè)空間移動(dòng)到另一個(gè)空間。變換用于操縱模型及其內(nèi)的特定對(duì)象。這些變換將對(duì)象進(jìn)行移動(dòng)、旋轉(zhuǎn)和縮放。上圖說(shuō)明了將應(yīng)用于對(duì)象的三種最常見(jiàn)的建模轉(zhuǎn)換。圖(a)顯示了平移,其中對(duì)象沿給定軸移動(dòng)。圖(b)顯示了一個(gè)旋轉(zhuǎn),一個(gè)對(duì)象圍繞其中一個(gè)軸旋轉(zhuǎn)。最后,圖?顯示了縮放的效果,對(duì)象的尺寸增加或減少了指定的量。縮放可以不均勻地進(jìn)行(不同的尺寸可以按不同的量進(jìn)行縮放),因此可以使用縮放來(lái)拉伸和收縮對(duì)象。
這些標(biāo)準(zhǔn)變換中的每一個(gè)都可以表示為一個(gè)矩陣,你可以通過(guò)該矩陣乘以頂點(diǎn)坐標(biāo)來(lái)計(jì)算變換后的位置。接下來(lái)小節(jié)討論這些矩陣的構(gòu)造,包括數(shù)學(xué)構(gòu)造和使用vmath庫(kù)中提供的函數(shù)。
The Identity Matrix(單位矩陣)
單位矩陣除斜對(duì)角線為1其余都為0。所有的單位矩陣都是平方的,如4×4。將頂點(diǎn)乘以單位矩陣等于將其乘以1。所有單位矩陣都是它本身的轉(zhuǎn)置矩陣。
可以這樣構(gòu)建單位矩陣:
// Using a raw array: GLfloat m1[] = { 1.0f, 0.0f, 0.0f, 0.0f, // X Column0.0f, 1.0f, 0.0f, 0.0f, // Y Column0.0f, 0.0f, 1.0f, 0.0f, // Z Column0.0f, 0.0f, 0.0f, 1.0f };// W Column // Or using the vmath::mat4 constructor: vmath::mat4 m2 { vmath::vec4(1.0f, 0.0f, 0.0f, 0.0f), // X Columnvmath::vec4(0.0f, 1.0f, 0.0f, 0.0f), // Y Columnvmath::vec4(0.0f, 0.0f, 1.0f, 0.0f), // Z Columnvmath::vec4(0.0f, 0.0f, 0.0f, 1.0f) };// W Column // use vmath library functions vmath::mat2 m2 = vmath::mat2::identity(); vmath::mat3 m3 = vmath::mat3::identity(); vmath::mat4 m4 = vmath::mat4::identity();The Translation Matrix(平移矩陣)
實(shí)際上,位置向量幾乎總是使用四個(gè)分量編碼,其中w分量為1.0,而方向向量要么簡(jiǎn)單地使用三個(gè)分量編碼,要么使用四個(gè)分量編碼,其中w為0。因此,將四分量方向向量乘以平移矩陣根本不會(huì)改變它。
vmath庫(kù)包含兩個(gè)函數(shù),它們將使用三個(gè)單獨(dú)的組件或三維向量為您構(gòu)建4×4轉(zhuǎn)換矩陣:
The Rotation Matrix(旋轉(zhuǎn)矩陣)
可以將這三個(gè)矩陣相乘生成一個(gè)復(fù)合變換矩陣,然后在單個(gè)矩陣-向量相乘運(yùn)算中圍繞三個(gè)軸中的每個(gè)軸旋轉(zhuǎn)給定的量。
注意angle的單位是度數(shù)而非弧度。此函數(shù)在內(nèi)部將度轉(zhuǎn)換為弧度,因?yàn)榕c計(jì)算機(jī)不同,許多程序員更喜歡用度來(lái)思考。
Euler Angles(歐拉角)
歐拉角是一組表示空間方向的三個(gè)角。每個(gè)角度表示圍繞定義幀的三個(gè)正交向量之一的旋轉(zhuǎn)(例如,x、y和z軸)。如前所述,矩陣變換的執(zhí)行順序很重要,因?yàn)橐圆煌捻樞驁?zhí)行某些變換(如旋轉(zhuǎn))將產(chǎn)生不同的結(jié)果。這是由于矩陣乘法的非交換性質(zhì)(non-commutative nature)。
設(shè)定xyz-軸為參考系的參考軸。稱(chēng)xy-平面與XY-平面的相交為交點(diǎn)線,用英文字母(N)代表。zxz順規(guī)的歐拉角可以靜態(tài)地這樣定義:
α 是x-軸與交點(diǎn)線的夾角,β 是z-軸與Z-軸的夾角,γ 是交點(diǎn)線與X-軸的夾角。
繞X軸旋轉(zhuǎn):
繞Y軸旋轉(zhuǎn):
繞Z軸旋轉(zhuǎn):
將方向表示為一組三個(gè)角度有一些優(yōu)點(diǎn)。例如,這種類(lèi)型的表示相當(dāng)直觀,如果你計(jì)劃將角度連接到用戶界面,這一點(diǎn)很重要。另一個(gè)好處是,插值角度、在每個(gè)點(diǎn)構(gòu)造旋轉(zhuǎn)矩陣以及在最終動(dòng)畫(huà)中看到平滑一致的運(yùn)動(dòng)非常簡(jiǎn)單。
然而,歐拉角也有一個(gè)嚴(yán)重的陷阱——萬(wàn)向鎖(gimbal lock)。
當(dāng)旋轉(zhuǎn)一個(gè)角度使一個(gè)軸重新定向以與另一個(gè)軸對(duì)齊時(shí),會(huì)發(fā)生萬(wàn)向節(jié)鎖定。任何圍繞兩個(gè)現(xiàn)在共線的軸的進(jìn)一步旋轉(zhuǎn)都將導(dǎo)致模型的相同變換,從而從系統(tǒng)中移除自由度。因此,歐拉角不適合串聯(lián)變換或累積旋轉(zhuǎn)。
為了避免這種情況,我們的vmath::rotate函數(shù)能夠獲取旋轉(zhuǎn)角度和旋轉(zhuǎn)軸。當(dāng)然,將三個(gè)旋轉(zhuǎn)疊加在一起(x、y和z軸各一個(gè))可以在必要時(shí)使用歐拉角,但最好使用角度軸表示旋轉(zhuǎn),或使用四元數(shù)表示變換并根據(jù)需要將其轉(zhuǎn)換為矩陣。
The Scaling Matrix(縮放矩陣)
縮放變換通過(guò)按指定的因子沿三個(gè)軸擴(kuò)展或收縮所有頂點(diǎn)來(lái)更改對(duì)象的大小。
例如,一個(gè)10×10×10立方體在x和z軸方向上綻放,如下圖:
Concatenating Transformations(串聯(lián)變換)
正如您所了解的,坐標(biāo)變換可以用矩陣表示,向量從一個(gè)空間到另一個(gè)空間的變換涉及一個(gè)簡(jiǎn)單的矩陣-向量乘運(yùn)算。乘以一系列矩陣可以應(yīng)用一系列變換。在每次矩陣-向量相乘之后,不必存儲(chǔ)中間向量。相反,首先將構(gòu)成一組相關(guān)變換的所有矩陣相乘,生成一個(gè)表示整個(gè)變換序列的矩陣是可能的,并且通常更可取。然后,可以使用該矩陣將向量直接從源坐標(biāo)空間轉(zhuǎn)換到目標(biāo)坐標(biāo)空間。記住,順序很重要。使用vmath或GLSL編寫(xiě)代碼時(shí),應(yīng)始終將矩陣與向量相乘,并按倒序讀取變換序列。例如,考慮下面的代碼序列:
vmath::mat4 translation_matrix = vmath::translate(4.0f, 10.0f, -20.0f); vmath::mat4 rotation_matrix = vmath::rotate(45.0f, vmath::vec3(0.0f, 1.0f, 0.0f)); vmath::vec4 input_vertex = vmath::mat4(....); vmath::vec4 transformed_vertex = translation_matrix * rotation_matrix * input_vertex;該代碼首先將模型繞y軸旋轉(zhuǎn)45°,然后將其在x軸上平移4個(gè)單位,在y軸上平移10個(gè)單位,在z軸上平移負(fù)20個(gè)單位。這會(huì)將模型放置在特定方向,然后將其移動(dòng)到位。是先旋轉(zhuǎn),后平移。我們可以將此代碼重寫(xiě)如下:
vmath::mat4 translation_matrix = vmath::translate(4.0f, 10.0f, -20.0f);vmath::mat4 rotation_matrix = vmath::rotate(45.0f, vmath::vec3(0.0f, 1.0f, 0.0f));vmath::mat4 composite_matrix = translation_matrix * rotation_matrix;vmath::vec4 input_vertex = vmath::vec4(...);vmath::vec4 transformed_vertex = composite_matrix * input_vertex;// 先旋轉(zhuǎn),再平移Quaternions(四元數(shù))
四元數(shù)是一種四維量,在某些方面類(lèi)似于復(fù)數(shù)。它有一個(gè)實(shí)部和三個(gè)虛部(與復(fù)數(shù)的一個(gè)虛部相比)。正如復(fù)數(shù)有一個(gè)虛部i一樣,四元數(shù)有三個(gè)虛部i、j和k。數(shù)學(xué)上,四元數(shù)q表示為
q = (x + yi + zj + wk)
性質(zhì)?:i2 = j2 = k2 = ijk = -1。
性質(zhì)?:i = jk、j = ik、k = ji。
與復(fù)數(shù)一樣,四元數(shù)的乘法是非交換的。四元數(shù)的加法和減法定義為簡(jiǎn)單的矢量加減法,各項(xiàng)按分量進(jìn)行加減。其他函數(shù)(如一元否定和幅值)的行為也與四分量向量的預(yù)期相同。雖然四元數(shù)是一個(gè)四分量實(shí)體,但通常將四元數(shù)表示為實(shí)標(biāo)量部分和三分量虛矢量部分。這種表述通常書(shū)面寫(xiě)作:q = (r, v)。
好的,很好,但這不是可怕的數(shù)學(xué)章節(jié),對(duì)嗎?這是關(guān)于計(jì)算機(jī)圖形、OpenGL和所有有趣的東西。這就是四元數(shù)真正有用的地方。回想一下,我們的旋轉(zhuǎn)函數(shù)以一個(gè)角度和一個(gè)軸為中心旋轉(zhuǎn)。
我們可以將這兩個(gè)量表示為四元數(shù),在實(shí)部填充角度,在向量部填充軸,得到一個(gè)表示繞任意軸旋轉(zhuǎn)的四元數(shù)。
旋轉(zhuǎn)序列可以由一系列四元數(shù)相乘表示,生成一個(gè)四元數(shù),一次編碼整個(gè)批次。雖然可以生成一組表示圍繞各個(gè)笛卡爾軸旋轉(zhuǎn)的矩陣,然后將它們相乘,這種方法容易受到萬(wàn)向節(jié)鎖的影響。如果對(duì)一系列四元數(shù)執(zhí)行相同的操作,則萬(wàn)向節(jié)鎖定不會(huì)發(fā)生。為了便于編寫(xiě)代碼,vmath包括vmath::quaternion類(lèi),該類(lèi)實(shí)現(xiàn)了這里描述的大部分功能。
The Model-View Transform(模型-視圖變換)
在一個(gè)簡(jiǎn)單的OpenGL應(yīng)用程序中,最常見(jiàn)的轉(zhuǎn)換之一是將模型從模型空間(model space)轉(zhuǎn)換到視圖空間(view space),以便對(duì)其進(jìn)行渲染。實(shí)際上,我們首先將模型移動(dòng)到世界空間(即相對(duì)于世界原點(diǎn)放置),然后再?gòu)哪抢镆苿?dòng)到視圖空間(相對(duì)于觀察者放置)。這個(gè)過(guò)程確定了場(chǎng)景的有利位置。默認(rèn)情況下,透視投影中的觀察點(diǎn)位于原點(diǎn)(0,0,0),看向負(fù)z軸(進(jìn)入監(jiān)視器或屏幕)。該觀察點(diǎn)相對(duì)于眼睛坐標(biāo)系(eye coordinate system)移動(dòng),以提供特定的有利位置。當(dāng)觀察點(diǎn)位于原點(diǎn)時(shí),如在透視投影中,使用正z值繪制的對(duì)象位于觀察者后面。然而,在正交投影中,假定觀察者在正z軸上無(wú)限遠(yuǎn),并且可以看到視體(viewing volume)內(nèi)的一切。
由于此變換將頂點(diǎn)從模型空間(有時(shí)也稱(chēng)為對(duì)象空間)直接帶入視圖空間,并有效地繞過(guò)世界空間,因此通常稱(chēng)為模型-視圖變換,對(duì)此變換進(jìn)行編碼的矩陣稱(chēng)為模型-視圖矩陣。
模型變換實(shí)質(zhì)上是將對(duì)象放置到世界空間中。每個(gè)對(duì)象都可能有自己的模型變換,通常由一系列縮放、旋轉(zhuǎn)和平移操作組成。將模型空間中頂點(diǎn)的位置乘以模型變換的結(jié)果是世界空間中的一組位置。這種轉(zhuǎn)換有時(shí)被稱(chēng)為模型-世界轉(zhuǎn)換(model-world transform)。
視圖轉(zhuǎn)換允許您將觀察點(diǎn)放置在任意位置,并朝任意方向觀察。確定查看變換類(lèi)似于將攝影機(jī)放置并指向場(chǎng)景。在總體方案中,必須在任何其他建模轉(zhuǎn)換之前應(yīng)用視圖轉(zhuǎn)換。原因是它似乎相對(duì)于眼睛坐標(biāo)系移動(dòng)了當(dāng)前工作坐標(biāo)系。然后,所有后續(xù)變換都基于新修改的坐標(biāo)系進(jìn)行。將坐標(biāo)從世界空間移動(dòng)到視圖空間的變換有時(shí)稱(chēng)為世界-視圖變換(world-view transform)。
通過(guò)將模型-世界和世界-視圖變換矩陣相乘,將它們連接在一起,得到模型-視圖矩陣(即,從模型到視圖空間獲取坐標(biāo)的矩陣)。這樣做有一些好處。首先,場(chǎng)景中可能有許多模型,每個(gè)模型中都有許多頂點(diǎn)。如前所述,使用單復(fù)合變換將模型移動(dòng)到視圖空間比先將其移動(dòng)到世界空間,然后再移動(dòng)到視圖空間更有效。第二個(gè)優(yōu)勢(shì)更多地與單精度浮點(diǎn)數(shù)字的數(shù)值精度有關(guān):世界可能很大,在世界空間中執(zhí)行的計(jì)算將具有不同的精度,具體取決于頂點(diǎn)離世界原點(diǎn)的距離。但是,如果在視圖空間中執(zhí)行相同的計(jì)算,則精度取決于頂點(diǎn)離觀察者的距離,這可能是您想要的——對(duì)靠近觀察者的對(duì)象應(yīng)用了大量的精度,但犧牲了距離觀察者很遠(yuǎn)的精度。
The Lookat Matrix(Lookat矩陣)
如果你在一個(gè)已知的位置有一個(gè)有利的位置,并且你想看一個(gè)東西,你會(huì)希望把你的虛擬相機(jī)放在那個(gè)位置,然后把它指向正確的方向。要正確定位相機(jī),您還需要知道它向上的方向;否則,相機(jī)可能會(huì)繞著它的前向軸旋轉(zhuǎn),即使從技術(shù)上講它仍然指向正確的方向,這幾乎肯定不是你想要的。因此,給定一個(gè)原點(diǎn)、一個(gè)感興趣點(diǎn)和一個(gè)我們認(rèn)為要上升的方向,我們想要構(gòu)造一系列變換,理想地烘烤成一個(gè)矩陣,這將表示一個(gè)旋轉(zhuǎn),它將指向一個(gè)攝像機(jī)在正確的方向上,一個(gè)將原點(diǎn)移動(dòng)到攝像機(jī)中心的平移。該矩陣稱(chēng)為lookat矩陣(lookat matrix),可僅使用本章迄今為止所述的數(shù)學(xué)知識(shí)構(gòu)建。
首先,我們知兩個(gè)位置相減會(huì)得到一個(gè)向量,這個(gè)向量會(huì)將一個(gè)點(diǎn)從第一個(gè)位置移動(dòng)到第二個(gè)位置,而對(duì)向量結(jié)果進(jìn)行歸一化會(huì)得到它的方向。因此,如果我們?nèi)∫粋€(gè)關(guān)注點(diǎn)的坐標(biāo),從中減去我們相機(jī)的位置,然后歸一化得到的向量,我們就有了一個(gè)新的向量,表示從相機(jī)到關(guān)注點(diǎn)的視角方向。我們稱(chēng)之為前向向量(forward vector)。
接下來(lái),我們知道,如果我們?nèi)蓚€(gè)向量的叉積,我們將得到與兩個(gè)輸入向量正交(成直角)的第三個(gè)向量。我們有兩個(gè)矢量,我們剛才計(jì)算的前向矢量(forward vector),和向上矢量(up vector),它代表我們認(rèn)為向上的方向。取這兩個(gè)向量的叉積,得到第三個(gè)向量,該向量與它們中的每一個(gè)向量正交,并且相對(duì)于我們的相機(jī)指向側(cè)面。我們稱(chēng)之為側(cè)向向量(sideways vector)。然而,上方向向量和前方向向量不一定相互正交,我們需要第三個(gè)正交向量來(lái)構(gòu)造旋轉(zhuǎn)矩陣。為了得到這個(gè)向量,我們可以簡(jiǎn)單地再次應(yīng)用相同的過(guò)程,取前向向量和側(cè)向向量的叉積,得到第三個(gè)向量,這第三個(gè)向量與前向向量和側(cè)向向量正交,表示相對(duì)于相機(jī)的上方向(up)。
這三個(gè)向量具有單位長(zhǎng)度,并且彼此正交,因此它們形成一組正交基向量并表示我們的視圖框架。給定這三個(gè)向量,我們可以構(gòu)造一個(gè)旋轉(zhuǎn)矩陣,它將在標(biāo)準(zhǔn)笛卡爾基礎(chǔ)上取一個(gè)點(diǎn),并將其移動(dòng)到相機(jī)的基礎(chǔ)上。在下面的數(shù)學(xué)中,e是眼睛(或相機(jī))的位置,p是關(guān)注點(diǎn),u是上方向向量。
首先,構(gòu)造我們的前向向量f:
f = (p - e) / |p - e|
然后,構(gòu)造側(cè)向向量s:
s = f×u
在相機(jī)參考中構(gòu)造一個(gè)新的上方向向量u′:
u′ = s×f
最后,構(gòu)造一個(gè)旋轉(zhuǎn)矩陣,表示重新定向到我們新構(gòu)造的正交基中:
要將對(duì)象轉(zhuǎn)換為攝影機(jī)的幀,不僅需要正確確定所有對(duì)象的方向,還需要將原點(diǎn)移動(dòng)到攝影機(jī)的位置。我們通過(guò)簡(jiǎn)單地將結(jié)果向量轉(zhuǎn)換為相機(jī)位置的負(fù)數(shù)來(lái)實(shí)現(xiàn)這一點(diǎn)。還記得平移矩陣是如何通過(guò)將偏移量放入矩陣最右邊的列中來(lái)構(gòu)造的嗎?我們也可以在這里這樣做:
終于,我們得到lookat矩陣,就是上面的矩陣T。
由vmath::lookat函數(shù)生成的矩陣可以用作相機(jī)矩陣的基礎(chǔ)——表示相機(jī)位置和方向的矩陣。換句話說(shuō),這可以是你的視圖矩陣。
Projection Transformations(投影變換)
投影變換將在模型-視圖變換后應(yīng)用于頂點(diǎn)。該投影實(shí)際上定義了視體并建立了剪裁平面。剪裁平面是三維空間中的平面方程,OpenGL使用它來(lái)確定觀察者是否可以看到幾何體。更具體地說(shuō),投影變換指定如何將完成的場(chǎng)景(完成所有建模后)投影到屏幕上的最終圖像。你將了解有關(guān)正交投影(orthographic)和透視投影(perspective)兩種類(lèi)型的詳細(xì)信息。
在正交或平行投影中,所有多邊形都以指定的相對(duì)尺寸精確地繪制在屏幕上。直線和多邊形使用平行線直接映射到2D屏幕,這意味著無(wú)論某物離屏幕有多遠(yuǎn),它仍然繪制為相同大小,只是在屏幕上展平。這種類(lèi)型的投影通常用于渲染二維圖像,如藍(lán)圖(blueprints)中的正面、頂部和側(cè)面立面,或二維圖形(如文本或屏幕菜單)。
透視投影顯示的場(chǎng)景更多的是真實(shí)生活中的場(chǎng)景,而不是藍(lán)圖。透視投影的特點(diǎn)是縮短(foreshortening),這使得遠(yuǎn)處的物體看起來(lái)比同樣大小的附近物體小。三維空間中可能平行的線并不總是與觀察者平行。例如,對(duì)于鐵路軌道,軌道是平行的,但使用透視投影,它們似乎在某個(gè)遙遠(yuǎn)的點(diǎn)會(huì)聚。透視投影的好處是,你不必知道直線在哪里會(huì)聚,也不必知道遠(yuǎn)處的物體有多小。您只需使用模型-視圖變換指定場(chǎng)景,然后應(yīng)用透視投影矩陣。線性代數(shù)為你帶來(lái)了所有的魔力。
下圖比較了兩個(gè)不同場(chǎng)景上的正交投影和透視投影。
正如你在左側(cè)顯示的正交投影中所看到的,立方體在遠(yuǎn)離查看器時(shí),其大小似乎不會(huì)發(fā)生變化。然而,在右側(cè)顯示的透視投影中,立方體隨著距離觀察者越來(lái)越遠(yuǎn)而變得越來(lái)越小。
正交投影最常用于二維繪圖目的,其中需要像素和繪圖單位之間的精確對(duì)應(yīng)。您可以將它們用于原理圖布局、文本或二維圖形應(yīng)用程序。如果渲染深度與距視點(diǎn)的距離相比具有非常小的深度,則也可以使用正交投影進(jìn)行三維渲染。透視投影用于渲染包含需要應(yīng)用縮短的開(kāi)闊空間或?qū)ο蟮膱?chǎng)景。在大多數(shù)情況下,透視投影是典型的三維圖形。事實(shí)上,用正交投影觀察3D對(duì)象可能會(huì)有點(diǎn)令人不安。
Perspective Matrices(透視矩陣)
一旦頂點(diǎn)在視圖空間中,我們需要將它們放入裁剪空間,我們可以通過(guò)應(yīng)用投影矩陣來(lái)實(shí)現(xiàn)這一點(diǎn),投影矩陣可以表示透視投影或正交投影(或其他投影)。常用的透視矩陣是平截頭體矩陣。平截頭體矩陣是一種投影矩陣,它生成透視投影,使得裁剪空間的形狀為矩形平截頭體,即截?cái)嗟木匦卫忮F體。其參數(shù)是到近平面和遠(yuǎn)平面的距離以及左、右、上和下剪裁平面的世界空間坐標(biāo)。平截體矩陣采用以下形式:
構(gòu)造透視矩陣的另一種常用方法是直接將視野(field of view)指定為角度(FOV角)(可能以度為單位)、縱橫比(通常通過(guò)將窗口的寬度除以其高度得出)以及近平面和遠(yuǎn)平面的視圖空間位置。這在某種程度上更易于指定,并且只生成對(duì)稱(chēng)的視錐(symmetric frustra)。然而,這幾乎總是你想要的。
static inline mat4 perspective(float fovy, float aspect, float n, float f) { ... }Orthographic Matrices(正交矩陣)
如果希望對(duì)場(chǎng)景使用正交投影,則可以構(gòu)造(稍微簡(jiǎn)單一些的)正交投影矩陣。正交投影矩陣只是將視圖空間坐標(biāo)線性映射到裁剪空間坐標(biāo)的縮放矩陣。構(gòu)造正交投影矩陣的參數(shù)是場(chǎng)景邊界的視圖空間中的左、右、上和下坐標(biāo),以及近平面和遠(yuǎn)平面的位置。
Interpolation,Lines,Curves,and Splines(插值、直線、曲線和樣條曲線)
D = B - A
P = A + tD = A + t(B - A) = (1 - t)A + tB
如果t在0.0和1.0之間,那么P將在A和B之間結(jié)束。超出此范圍的t值會(huì)將P推離線的末端。你們應(yīng)該可以看到,通過(guò)平滑地改變t,我們可以將點(diǎn)P從A移到B,然后再移回來(lái)。這被稱(chēng)為線性插值(linear iterpolation)。A和B(和P)的值可以有任意數(shù)量的維度。例如,它們可以是標(biāo)量值;二維值,如圖形上的點(diǎn);三維值,如三維空間中的坐標(biāo)、顏色等;或者更高維度的數(shù)量,例如矩陣、數(shù)組,甚至整個(gè)圖像。在許多情況下,線性插值沒(méi)有多大意義(例如,兩個(gè)矩陣之間的線性插值通常不會(huì)產(chǎn)生有意義的結(jié)果),但角度、位置和其他坐標(biāo)通常可以安全地插值。
線性插值是圖形中的一種常見(jiàn)操作,GLSL包括一個(gè)專(zhuān)門(mén)用于此目的的內(nèi)置函數(shù),mix:
vec4 mix(vec4 A, vec4 B, float t);mix函數(shù)有幾個(gè)版本,將向量或標(biāo)量的不同維數(shù)作為A和B輸入,并將標(biāo)量或匹配向量作為t輸入。
Curves(曲線)
如果我們只想沿著兩點(diǎn)之間的直線移動(dòng)所有東西,那么這就足夠了。但是,在現(xiàn)實(shí)世界中,對(duì)象以平滑曲線移動(dòng),并平滑地加速和減速。曲線可以由三個(gè)或更多控制點(diǎn)表示。對(duì)于大多數(shù)曲線,有三個(gè)以上的控制點(diǎn),其中兩個(gè)形成端點(diǎn);其他定義了曲線的形狀。考慮下圖所示的簡(jiǎn)單曲線。
有三個(gè)控制點(diǎn)A、B、C,其中A和C是曲線的端點(diǎn),B定義了曲線的形狀。如果我們將點(diǎn)A和點(diǎn)B與一條直線連接起來(lái),將點(diǎn)B和點(diǎn)C與另一條直線連接起來(lái),那么我們可以使用簡(jiǎn)單的線性插值沿這兩條直線進(jìn)行插值,以找到一對(duì)新的點(diǎn)D和E。現(xiàn)在,給定這兩點(diǎn),我們可以用另一條線把它們連接起來(lái),沿著它插值,找到一個(gè)新的點(diǎn),P。當(dāng)我們改變插值參數(shù)t時(shí),點(diǎn)P將沿著從A到D的平滑曲線路徑移動(dòng)。用數(shù)學(xué)表示:
D = A + t(B - A)
E = B + t(C - B)
P = D + t(E - D)
= A + t(B ? A)+ t((B +(t(C ? B))) ? (A + t(B ? A))))
= A +2t(B ? A)+ t2(C ? 2B + A)
你應(yīng)該認(rèn)識(shí)到這是t中的二次方程(quadratic equation)。它描述的曲線稱(chēng)為二次Bézier曲線。實(shí)際上,我們可以使用mix函數(shù)在GLSL中非常容易地實(shí)現(xiàn)這一點(diǎn),因?yàn)槲覀兯龅闹皇菍?duì)前面兩次插值的結(jié)果進(jìn)行線性插值(混合)。
通過(guò)添加第四個(gè)控制點(diǎn),如下圖所示,我們可以將階數(shù)增加1,并生成三次Bézier曲線。
我們現(xiàn)在有四個(gè)控制點(diǎn),A、B、C和D。構(gòu)造曲線的過(guò)程類(lèi)似于二次Bézier曲線。我們從A到B形成第一條線,從B到C形成第二條線,從C到D形成第三條線。沿這三條直線中的每一條進(jìn)行插值會(huì)產(chǎn)生三個(gè)新點(diǎn),即E、F和G。利用這三個(gè)點(diǎn),我們?cè)傩纬蓛蓷l線,一條從E到F,另一條從F到G,沿著這兩條線插值,得到點(diǎn)H和點(diǎn)I,在這兩條線之間我們可以插值找到我們的最終點(diǎn)P。
因此,我們有:
E = A + t(B - A)
F = B + t(C - B)
G = C + t(D - C)
H = E + t(F - E)
I = F + t(G - F)
P = H + t(I - H)
如果你認(rèn)為這些方程看起來(lái)很熟悉,你是對(duì)的:我們的點(diǎn)E,F和G形成了一條二次Bézier曲線,我們用它來(lái)插值到我們的最終點(diǎn)P。如果我們將E、F和G的方程代入H和I的方程中,然后代入P的方程中,通過(guò)展開(kāi)式,我們將得到一個(gè)三次方程,其項(xiàng)包含在t3——因此被稱(chēng)為三次Bézier曲線。同樣,我們可以通過(guò)使用混合函數(shù)在GLSL中進(jìn)行線性插值來(lái)簡(jiǎn)單有效地實(shí)現(xiàn)這一點(diǎn):
正如三次Bézier曲線方程的結(jié)構(gòu)“包括”二次曲線方程一樣,實(shí)現(xiàn)它們的代碼也是如此。事實(shí)上,我們可以將這些曲線層疊在一起,使用一條曲線的代碼構(gòu)建下一條曲線。
vec4 cubic_bezier(vec4 A, vec4 B, vec4 C, vec4 D, float t) {vec4 E = mix(A, B, t); // E = A + t(B - A)vec4 F = mix(B, C, t); // F = B + t(C - B)vec4 G = mix(C, D, t); // G = C + t(D - C)return quadratic_bezier(E, F, G, t); }現(xiàn)在,我們看到了這個(gè)模式,我們可以更進(jìn)一步,產(chǎn)生更高階的曲線。例如,五次Bézier曲線(一條有五個(gè)控制點(diǎn))可以實(shí)現(xiàn)為:
vec4 quintic_bezier(vec4 A, vec4 B, vec4 C, vec4 D, vec4 E, float t) {vec4 F = mix(A, B, t); // F = A + t(B - A)vec4 G = mix(B, C, t); // G = B + t(C - B)vec4 H = mix(C, D, t); // H = C + t(D - C)vec4 I = mix(D, E, t); // I = D + t(E - D)return cubic_bezier(F, G, H, I, t); }理論上,這種分層可以反復(fù)應(yīng)用于任何數(shù)量的控制點(diǎn)。但是,在實(shí)踐中,通常不使用具有四個(gè)以上控制點(diǎn)的曲線。相反,我們使用樣條曲線。
Splines(樣條曲線)
樣條曲線實(shí)際上是由幾個(gè)較小的曲線(如Bézier曲線)組成的長(zhǎng)曲線,這些曲線局部定義了它們的形狀。至少表示曲線端點(diǎn)的控制點(diǎn)在線段之間共享,并且通常一個(gè)或多個(gè)內(nèi)部控制點(diǎn)在相鄰線段之間以某種方式共享或鏈接。任何數(shù)量的曲線都可以通過(guò)這種方式連接在一起,從而形成任意長(zhǎng)的路徑。
這就是將曲線粘在一起形成樣條曲線的原因。這些控制點(diǎn)稱(chēng)為焊縫(welds),中間的控制點(diǎn)稱(chēng)為節(jié)點(diǎn)(knots)。
在上圖中,曲線由十個(gè)控制點(diǎn)A到J定義,它們形成三條三次Bézier曲線。第一個(gè)由A、B、C和D定義,第二個(gè)共享D并進(jìn)一步使用E、F和G,第三個(gè)共享G并添加H、I和J。這種樣條曲線稱(chēng)為三次Bézier樣條曲線,因?yàn)樗怯梢幌盗腥蜝ézier曲線構(gòu)成的。這也被稱(chēng)為三次B樣條(B-spline)——這一術(shù)語(yǔ)對(duì)于過(guò)去閱讀過(guò)大量圖形知識(shí)的人來(lái)說(shuō)可能很熟悉。
要沿樣條曲線插值點(diǎn)P,我們只需將其劃分為三個(gè)區(qū)域,使t的范圍從0.0到3.0。在0.0和1.0之間,我們沿著第一條曲線插值,從A移動(dòng)到D。在1.0和2.0之間,我們沿著第二條曲線插值,從D移動(dòng)到G。當(dāng)t在2.0和3.0之間時(shí),我們沿著G和J之間的最終曲線進(jìn)行插值。因此,t的整數(shù)部分決定了我們要沿其插值的曲線段,而t的分?jǐn)?shù)部分用于沿該段插值。當(dāng)然,我們可以隨心所欲地?cái)U(kuò)展t。例如,如果我們?nèi)∫粋€(gè)介于0.0和1.0之間的值,并將其乘以曲線中的分段數(shù),則無(wú)論曲線中控制點(diǎn)的數(shù)量如何,我們都可以繼續(xù)使用t的原始值范圍。
下面的代碼將沿三次Bézier樣條插值一個(gè)向量,該樣條具有十個(gè)控制點(diǎn)(以及三個(gè)線段):
如果我們使用樣條曲線來(lái)確定對(duì)象的位置或方向,我們會(huì)發(fā)現(xiàn)我們必須非常小心地選擇控制點(diǎn)位置,以保持運(yùn)動(dòng)平滑和流暢。插值點(diǎn)P值的變化率(即其速度)是曲線方程相對(duì)于t的微分。如果這個(gè)函數(shù)是不連續(xù)的,那么P會(huì)突然改變方向,我們的物體會(huì)出現(xiàn)跳躍。此外,P的速度(加速度)的變化率是樣條方程相對(duì)于t的二階導(dǎo)數(shù)。如果加速不平穩(wěn),則P會(huì)突然加速或減速。
具有連續(xù)一階導(dǎo)數(shù)的函數(shù)稱(chēng)為C1連續(xù)函數(shù);類(lèi)似地,具有連續(xù)二階導(dǎo)數(shù)的曲線稱(chēng)為C2連續(xù)曲線。Bézier曲線段都是C1和C2連續(xù)的,但為了確保在樣條曲線的焊縫上保持連續(xù)性,我們需要確保每個(gè)段從前一段在位置、移動(dòng)方向和變化率方面結(jié)束的位置開(kāi)始。在特定方向上的移動(dòng)速率就是一個(gè)速度。因此,我們可以在每個(gè)焊縫處指定速度,而不是為樣條曲線指定任意控制點(diǎn)。如果在計(jì)算該焊縫任一側(cè)的曲線段時(shí),在每個(gè)焊縫處使用相同的曲線速度,則我們將使用C1和C2連續(xù)的樣條函數(shù)。
如果您再看一看上圖,這應(yīng)該是有意義的——沒(méi)有扭結(jié)(kinks),曲線通過(guò)焊縫(點(diǎn)D和點(diǎn)G)平滑。現(xiàn)在查看焊縫兩側(cè)的控制點(diǎn)。例如,以圍繞D的點(diǎn)C和點(diǎn)E為例。C和E形成一條直線,D正好位于中間。事實(shí)上,我們可以把從D到E的線段稱(chēng)為D處的速度,或者VD。
給定點(diǎn)D(焊縫)的位置和曲線在D處的速度VD,則C和E可計(jì)算為:
C = D - VD
E = D + VD
同樣,如果**VA**表示A處的速度,則B可計(jì)算為:
B = A + VA
因此,您應(yīng)該能夠看到,給定三次B樣條曲線焊縫處的位置和速度,我們可以省去所有其他控制點(diǎn),并在評(píng)估每個(gè)控制點(diǎn)時(shí)動(dòng)態(tài)計(jì)算它們。以這種方式表示的三次B樣條(作為一組焊接位置和速度)稱(chēng)為三次Hermite樣條(cube Hermite spline),有時(shí)簡(jiǎn)稱(chēng)為CSP線。cspline是制作平滑自然動(dòng)畫(huà)的非常有用的工具。
Summary(總結(jié))
在本章中,你學(xué)習(xí)了一些對(duì)使用OpenGL創(chuàng)建3D場(chǎng)景至關(guān)重要的數(shù)學(xué)概念。即使你不能在頭腦中處理矩陣,你現(xiàn)在也知道什么是矩陣,以及如何使用它們來(lái)執(zhí)行各種變換。你還學(xué)習(xí)了如何構(gòu)造和操作表示觀察者和視口屬性的矩陣。現(xiàn)在,你應(yīng)該了解如何將對(duì)象放置在場(chǎng)景中,并確定如何在屏幕上查看它們。本章還介紹了參考系的強(qiáng)大概念,你看到了操作參考系并將其轉(zhuǎn)換是多么容易。
最后,我們介紹了本書(shū)附帶的vmath庫(kù)的使用。這個(gè)庫(kù)完全是用便攜式C++編寫(xiě)的,它提供了一個(gè)方便的工具箱,可以與OpenGL一起使用各種數(shù)學(xué)和輔助程序。
令人驚訝的是,在這整章中,我們沒(méi)有涉及一個(gè)新的OpenGL函數(shù)調(diào)用。是的,這是數(shù)學(xué)章節(jié),如果你認(rèn)為數(shù)學(xué)只是公式和計(jì)算,你可能根本沒(méi)有注意到。向量和矩陣及其應(yīng)用對(duì)于能夠使用OpenGL渲染3D對(duì)象和世界至關(guān)重要。然而,需要注意的是,OpenGL并沒(méi)有將任何特定的數(shù)學(xué)約定強(qiáng)加給你,并且本身也不提供任何數(shù)學(xué)功能。如果你使用不同的三維數(shù)學(xué)庫(kù),或者甚至使用自己的三維數(shù)學(xué)庫(kù),你仍然會(huì)發(fā)現(xiàn)自己遵循本章中列出的模式來(lái)操作幾何體和三維世界。
總結(jié)
以上是生活随笔為你收集整理的卐 4-3D图形的数学的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: HDU4324 - Triangle L
- 下一篇: Linux0号进程,1号进程,2号进程