日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[OpenGL ES 03]3D变换:模型,视图,投影与Viewport

發(fā)布時間:2023/12/13 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [OpenGL ES 03]3D变换:模型,视图,投影与Viewport 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

[OpenGL ES 03]3D變換:模型,視圖,投影與Viewport

羅朝輝 (http://blog.csdn.net/kesalin)

本文遵循“署名-非商業(yè)用途-保持一致”創(chuàng)作公用協(xié)議

系列文章 [OpenGL ES 01]OpenGL ES之初體驗

[OpenGL ES 02]OpenGL ES渲染管線與著色器


前言

本來打算直接寫教程 04 的,但是想到3D 變換涉及的數(shù)學(xué)知識較多,往往是很多初學(xué)者的攔路虎(比如我自己)。再加上OpenGL ES 2.0 不再提供OpenGL ES 1.0中 3D 變換相關(guān)的一些重量級函數(shù),如 glMatrixMode(GL_PROJECTION); glMatrixMode(GL_MODELVIEW); glLoadMatrixf; glMultMatrix 等,這些函數(shù)在 OpenGL ES 2.0 中均需要我們自己去實現(xiàn)。 如果不對線性代數(shù)與幾何知識作一些簡單介紹,恐怕不少人難以理解文中的一些步驟為什么要那么做。因此今天這一篇文章將放棄原定計劃,先來介紹一些 3D 數(shù)學(xué)以及 3D 變換相關(guān)的知識。BTW,原定計劃的代碼示例已經(jīng)寫好了,有興趣的同學(xué)可以先行瀏覽,代碼放在這里,運行效果如下:


?

一,3D數(shù)學(xué)歷史

我們都學(xué)過幾何學(xué),應(yīng)該都知道歐幾里得(公元前3世紀(jì)希臘數(shù)學(xué)家)這位幾何學(xué)鼻祖,正是這位大牛創(chuàng)建了歐幾里得幾何學(xué),他提出了基于 X,Y,Z 三軸的三維空間概念。到了17世紀(jì),又出了位大牛笛卡爾,我們通常所說的笛卡爾坐標(biāo)就是他的創(chuàng)造,笛卡爾坐標(biāo)非常完美地將歐幾里得幾何學(xué)理論與代數(shù)學(xué)聯(lián)系到一塊。正是因為有了笛卡爾坐標(biāo),我們才能夠用簡單的矩陣(Matrix)來表示三維變換。但用矩陣來表示三維變換操作有一個無法解決的問題-萬向節(jié)鎖?。什么是萬向節(jié)鎖呢?簡單地說就是兩個軸旋轉(zhuǎn)到同一個方向上去了,這兩個軸平行了,因此就比原來少了一維(詳情可參考這里)。過了一百多年,漢密爾頓(Sir William Rowan Hamilton)創(chuàng)建了四元數(shù)(quaternion)解決了因為旋轉(zhuǎn)而導(dǎo)致萬向節(jié)鎖的問題,四元數(shù)還有其他用處,但在3D數(shù)學(xué)里主要是用來處理旋轉(zhuǎn)問題。

好吧,或許你看得一頭霧水,不要緊,你只要知道:用矩陣來表示3D變換,但矩陣在表示旋轉(zhuǎn)時可能會導(dǎo)致萬向節(jié)鎖的問題,而使用四元數(shù)可以避免萬向節(jié)鎖就可以了。

?

二,矩陣變換

在前面提到可使用 Matrix 來表示三維變換操作,那么變換又是如何通過 Matrix 實現(xiàn)的呢?下面就來講這個。在這里我推薦一本3D數(shù)學(xué)入門書籍:《3D數(shù)學(xué)基礎(chǔ):圖形與游戲開發(fā)》

通常我們使用 4 維向量 (x, y, z, w) 表示在3D空間中的一個點,最后一維 w 表示齊次坐標(biāo)。齊次坐標(biāo)的含義是兩條平行線在投影平面的無窮遠(yuǎn)處相交于一點,但在 Matrix 中沒有表示無窮大,所以增加了齊次坐標(biāo)這一維。你可以想象下,火車軌道的兩條邊在無限遠(yuǎn)處看起來就相交于一點,齊次坐標(biāo)詳細(xì)的介紹可以參考這篇文章。


矩陣運算規(guī)則

1) 若矩陣 A 和 B 不是互逆矩陣,則不滿足乘法交換律,即 A × B 不等于 B × A;?
2) M × N 階的矩陣只能和 N × O 階的矩陣相乘,即 N 的階數(shù)相等,結(jié)果為 M × O 階的矩陣;?
3) 矩陣 A × B 的運算過程是 A 的每一行依次乘以 B 的每一列作為結(jié)果矩陣中的一行;?
4) 矩陣 A 的逆矩陣?B 滿足 A × B = B × A = 單位矩陣。??
5) 單位矩陣是對角線上的值為1,其余均為 0 的矩陣。單位矩陣不影響坐標(biāo)變換(你可以將下面的3D變換矩陣換成單位矩陣來思考下)。

3D空間的物體投影到2D平面上時,就需要使用到齊次坐標(biāo),因此我們需要使用 4 × 4 的 Matrix 來表示變換。在編程語言中,這樣的 Matrix 可用大小為 16 的一維數(shù)組或4?× 4 的二維數(shù)組來表示。由于矩陣乘法不滿足乘法交換律,用數(shù)組表示 Matrix 又分為兩種形式:行主序和列主序,它們在本質(zhì)上是等價的,只不過是一個是右乘(行主序,矩陣放右邊)和一個是左乘(列主序,矩陣放左邊)。OpenGL 使用列主序矩陣,即列矩陣因此我們總是倒過來算的(左乘矩陣,變換效果是按從右向左的順序進(jìn)行): 投影矩陣 × 視圖矩陣 × 模型矩陣 × 3D位置。

4× 4列矩陣的數(shù)組表示:數(shù)字表示數(shù)組下標(biāo)對應(yīng)的行列位置:


那么

平移矩陣可表示為:


平移矩陣 × 列矩陣(a, b, c, 1) = 列矩陣(a + x, b + y, c + z, 1)。

縮放矩陣可表示為:


縮放矩陣 × 列矩陣(a, b, c, 1) = 列矩陣(a × sx, b × sy, c × sz, 1)。

繞 X 軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣可表示為:

?

繞 X 軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣 × 列矩陣(a, b, c, 1) = 列矩陣(a, b × cos(θ) - c × sin(θ), b × -sin(θ) + c × cos(θ), 1)。

繞 Y 軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣可表示為:


繞 Y 軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣 × 列矩陣(a, b, c, 1) = 列矩陣(a × cos(θ) - c × sin(θ), b , a × -sin(θ) + c × cos(θ), 1)。

繞 Z 軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣可表示為:

?

繞 Z 軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣 × 列矩陣(a, b, c, 1) = 列矩陣(a × cos(θ) - b × sin(θ),? a × -sin(θ) + b × cos(θ), c, 1)。

?

三,OpenGL 中的實現(xiàn)

OpenGL 使用右手規(guī)則進(jìn)行旋轉(zhuǎn),因此逆時針方向的選擇是正角度的,而順時針方向的旋轉(zhuǎn)是負(fù)角度的。還記得中學(xué)學(xué)物理時候的右手規(guī)則么?忘記了的話,看下圖:

?

注意:

前面說到矩陣乘法不滿足乘法交換律,因此你對一個3D坐標(biāo)先進(jìn)行旋轉(zhuǎn),然后進(jìn)行平移(平移矩陣 × 旋轉(zhuǎn)矩陣 × 3D坐標(biāo));與先進(jìn)行平移,然后進(jìn)行旋轉(zhuǎn)(旋轉(zhuǎn)矩陣 × 平移矩陣 × 3D坐標(biāo))得到的效果是大為迥異的。如下圖所示:


在第一種情況下,我們通常稱旋轉(zhuǎn)是在 local space 中進(jìn)行,因為它是繞著物體自己的中心點進(jìn)行的,而在后一種情況下的旋轉(zhuǎn)通常稱為是在 world space 中進(jìn)行的。我們知道點是可以在坐標(biāo)空間之間相互轉(zhuǎn)換的,這是一個很重要的概念。OpenGL 中物體最初是在本地坐標(biāo)空間中,然后轉(zhuǎn)換到世界坐標(biāo)空間,再到 camera 視圖空間,再到投影空間,這一系列轉(zhuǎn)換都是靠 matrix 計算來實現(xiàn)。

上面的這個過程在 OpenGL 及 OpenGL ES 1.0 中,對應(yīng)的代碼類似于:

glViewport (0, 0, (GLsizei) w, (GLsizei) h);   a)glMatrixMode (GL_PROJECTION);            b)glLoadIdentity ();glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);   c)glMatrixMode (GL_MODELVIEW);             d)glClear (GL_COLOR_BUFFER_BIT);glColor3f (1.0, 1.0, 1.0);glLoadIdentity (); /* clear the matrix *//* viewing transformation */gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);    e)glScalef (1.0, 2.0, 1.0); /* modeling transformation */  f)glutWireCube (1.0);                          g)glFlush ();

說明:

a) 是用于viewport(視口)變換,viewport 變換發(fā)生在投影到2D 投影平面之后,該變換是將投影之后歸一化的點映射到屏幕上一塊區(qū)域內(nèi)的坐標(biāo)。視口變換的目的是指定投影之后圖像在屏幕上顯示的區(qū)域。如下示意圖所示:


視口變換 glViewport(x, y, width, height); x,y 是投影平面描繪在屏幕或窗口上的起始位置(注意屏幕坐標(biāo)以左上方為原點),width和height是以像素為單位,指投影平面在屏幕上描繪的區(qū)域大小。如果投影平面的寬高比,與width/height比不相同(如上面的右圖),那么描繪的場景就會扭曲。

從裁剪到屏幕的整個過程如下圖所示,w 就是前面提到的齊次坐標(biāo)那一維,從 Clip Space 到 Normalized Device Space 就是投影規(guī)范化的過程,從 Normalized Device Space 到 Window Space 就是 viewport 變換過程。


該轉(zhuǎn)換內(nèi)部計算公式為:


?(xw, yw)是屏幕坐標(biāo),(x, y, width, height)是傳入的參數(shù),(xnd, ynd)是投影之后經(jīng)歸一化之后的點(上圖中 Normalized Device Space 空間的點)。因此 viewport 變換就是將投影之后歸一化的點轉(zhuǎn)換為真正可用于在屏幕上進(jìn)行渲染的屏幕坐標(biāo);

b) 是說明下面的 matrix 是用于投影變換的,在本例中,是通過語句 c)?glFrustum?來設(shè)置透視投影變換的。投影變換有兩種:正交投影和透視投影,后面會有詳細(xì)介紹;

d) 是說明下面的 matrix 是用于模型視圖變換,注意,OpenGL 和 OpenGL ES 都將模型變換與視圖變換結(jié)合在一起,而不是分開為兩個,這是因為模型變換等價于視圖變換的逆變換。視圖變換是將物體轉(zhuǎn)換到觀察者(一般稱之為 camera)的視線空間中。你可以想象一下,照相時,你可以:A)照相機(jī)不懂,旋轉(zhuǎn)自己的頭找個側(cè)面像,也可以B)自己不動,照相機(jī)旋轉(zhuǎn)一定的角度來達(dá)到同樣的效果。下面的兩幅圖分別描述了情形A)和情形B):

情形A):旋轉(zhuǎn)物體,相機(jī)不動


情形B):旋轉(zhuǎn)相機(jī),物體不動


在 OpenGL 中,我們在設(shè)置場景(scene)的時候通常是采取情形B)的做法,因此在語句 e) 處,我們設(shè)置相機(jī)的位置和朝向,來設(shè)定視圖變換,之后的語句 f) glScale 是設(shè)定在模型變換的,最后語句 g) 在本地空間描繪物體。

注意

寫 OpenGL 代碼時從前到后的順序依次是:設(shè)定 viewport(視口變換),設(shè)定投影變換,設(shè)定視圖變換,設(shè)定模型變換,在本地坐標(biāo)空間描繪物體。而在前面為了便于理解做介紹時,說的順序是OpenGL 中物體最初是在本地坐標(biāo)空間中,然后轉(zhuǎn)換到世界坐標(biāo)空間,再到 camera 視圖空間,再到投影空間。由于模型變換包括了本地空間變換到世界坐標(biāo)空間,所以我們理解3D 變換是一個順序,而真正寫代碼時則是以相反的順序進(jìn)行的,如果從左乘矩陣這點上去理解就很容易明白為什么會是反序的。

有了上面 3D 變換的整體概念,下面來詳細(xì)說說投影變換與視圖變換。

?

四,投影變換

投影變換的目的是確定 3D 空間的物體如何投影到 2D 平面上,從而形成2D圖像,這些 2D 圖像再經(jīng)視口變換就被渲染到屏幕上。前面提到投影變換有兩種:正交投影和透視投影。透視投影用的比較廣泛,它與真實世界更相近:近處的物體看起來要比遠(yuǎn)處的物體大;而正交投影沒有這個效果,正交投影通常用于CAD或建筑設(shè)計。下面是正交投影與透視投影效果示意圖:

正交投影 透視投影

?

? ? ? ?

?

?

?

?

透視投影可以通過兩種方式來表述,OpenGL 及 OpenGL ES 1.0 提供其中一種:?glFrustum,而 glut 輔助庫提供了另外一種:gluPerspective。它們本質(zhì)上是相同的,只不過是不同的表述而已:

視錐體/視景體



glFrustum(left, right, bottom, top, zNear, zFar);

left,right, bootom,top 定義了 near 裁剪面大小,而 zNear 和 zFar 定義了從 Camera/Viewer 到遠(yuǎn)近兩個裁剪面的距離(注意這兩個距離都是正值)。由這六個參數(shù)可以定義出六個裁剪面構(gòu)成的錐體,這個錐體通常被稱之為視錐體或視景體。只有在這個錐體內(nèi)的物體才是可以見的,不在這個錐體內(nèi)的物體就相當(dāng)于不再視線范圍內(nèi),因而會被裁減掉,OpenGL 不會這些物體進(jìn)行渲染。

由于 OpenGL ES 2.0 不提供此函數(shù),因此我們需要自己實現(xiàn)該函數(shù)。其計算公式如下:

假設(shè):l = left, r = right, b = bottom, t = top, n = zNear, f = zFar,有


透視圖


gluPerspective(fovy, aspect, zNear, zFar);

fovy 定義了 camera 在 y 方向上的視線角度(介于 0 ~ 180 之間),aspect 定義了近裁剪面的寬高比 aspect = w/h,而 zNear 和 zFar 定義了從 Camera/Viewer 到遠(yuǎn)近兩個裁剪面的距離(注意這兩個距離都是正值)。這四個參數(shù)同樣也定義了一個視錐體。

在 OpenGL ES 2.0 中,我們也需要自己實現(xiàn)該函數(shù)。我們可以通過三角公式 tan(fovy/2) ?= (h / 2)/zNear 計算出 h ,然后再根據(jù) w = ?h * aspect 計算出 w,這樣就可以得到 left, right, top, bottom, zNear, zFar 六個參數(shù),代入在介紹視錐體時提到的公式即可。?

正交投影在 OpenGL 及 OpenGL ES 1.0 中是由?glOrtho 來提供的,我們可以把正交投影看成是透視投影的特殊形式:即近裁剪面與遠(yuǎn)裁剪面除了Z 位置外完全相同,因此物體始終保持一致的大小,即便是在遠(yuǎn)處看上去也不會變小。


glOrtho(left,?right,?bottom,?top,?zNear,?zFar);

left,right, bootom,top 定義了 near 裁剪面大小,而 zNear 和 zFar 定義了從 Camera/Viewer 到遠(yuǎn)近兩個裁剪面的距離(注意這兩個距離都是正值)。

假設(shè):xmax = right, xmin = left, ymax = top, ymin = bottom, zmax = far, zmin = near,正交投影的計算可分為兩步:首先平移到視錐體的中心,然后縮放。

平移矩陣:(圖中的2min 應(yīng)為 zmin)

?

縮放矩陣:


正交投影矩陣 R = S ×?T:


?

五,視圖變換

視圖變換的目的是為了讓我們能觀察到某個角度的場景(從觀察者的角度來說)或者說是為了將物體從世界坐標(biāo)轉(zhuǎn)換到相機(jī)視線所在視圖空間中來(從3D物體角度來說)。這可以通過設(shè)定觀察者的位置和朝向來實現(xiàn)的或?qū)ξ矬w進(jìn)行3D變換來實現(xiàn),通常前面一種方式來實現(xiàn)(即設(shè)定觀察者的位置與朝向)。如下圖所示,xyz坐標(biāo)軸表示的是世界坐標(biāo),藍(lán)白色區(qū)域為視圖空間,視圖變換就是要將長方體從世界空間中轉(zhuǎn)換到視圖空間的坐標(biāo)體系中去,然后再投影規(guī)范化,然后再經(jīng) viewport 轉(zhuǎn)換映射到屏幕上渲染出來。


在 OpenGL 中,我們可以通過工具庫提供的 gluLookAt 這個函數(shù)來實現(xiàn)此功能。該函數(shù)的原型為:

gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz);

eye 表示 camera/viewer 的位置, center 表示相機(jī)或眼睛的焦點(它與 eye 共同來決定 eye 的朝向),而 up 表示 eye 的正上方向,注意 up 只表示方向,與大小無關(guān)。通過調(diào)用此函數(shù),就能夠設(shè)定觀察的場景,在這個場景中的物體就會被 OpenGL 處理。在 OpenGL 中,eye 的默認(rèn)位置是在原點,指向 Z 軸的負(fù)方向(屏幕往里),up 方向為 Y 軸的正方向。在接下來的教程 04 中,使用的就是這個默認(rèn)設(shè)置。

OpenGL ES 2.0 也沒有提供該函數(shù),glulookat 的內(nèi)部實現(xiàn)其實就是先旋轉(zhuǎn)到與觀察者視線相同的方向,然后再平移到觀察者所在的位置。其實現(xiàn)偽碼如下:

[csharp] view plaincopy print?
  • Matrix4?GetLookAtMatrix(Vector3?eye,?Vector3?at,?Vector3?up){??
  • ????Vector3?forward,?side;??
  • ????forward?=?at?-?eye;??
  • ????normalize(forward);??
  • ????side?=?cross(forward,?up);??
  • ????normalize(side);??
  • ????up?=?cross(side,?forward);??
  • ??
  • ????Matrix4?res?=?Matrix4(??
  • ??????????????????????????side.x,?up.x,?-forward.x,?0,??
  • ??????????????????????????side.y,?up.y,?-forward.y,?0,??
  • ??????????????????????????side.z,?up.z,?-forward.z,?0,??
  • ??????????????????????????0,?0,?0,?1);??
  • ????translate(res,?Vector3(0?-?eye));??
  • ????return?res;??
  • }??
  • 上面代碼中的 cross 是叉積,normalize 是規(guī)范化,Matrix4 是列主序,translate 是平移。

    ?

    六,后記

    3D 變換是對初學(xué)者來說是比較困難的,我盡量寫得明白點,但效果如何就不得而知了。寫這一篇花了我不少時間,但對四元數(shù)和萬向節(jié)鎖也只是提及而已,未詳細(xì)介紹,以后再單獨介紹吧。Nate Robin 寫了一個3D 變換的可視化教程工具,對于理解投影,視圖,模型變換非常有幫助,強(qiáng)烈建議下載運行該程序,并調(diào)整相關(guān)參數(shù)看看效果。下面?zhèn)鲝埥貓D以誘惑你去下載:點此進(jìn)入下載頁面(Windows 和 Mac 版本都有)

    轉(zhuǎn)載于:https://www.cnblogs.com/zhoug2020/p/6084717.html

    總結(jié)

    以上是生活随笔為你收集整理的[OpenGL ES 03]3D变换:模型,视图,投影与Viewport的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。