本節書摘來自異步社群《opengl超級寶典(第5版)》一書中的第1章,第1.4節3d程式設計的基本原則,作者 【美】richard s. wright , jr.nicholas haemel,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視
1.4 3d程式設計的基本原則
現在,我們對實時3d的基本概念已經有了相當程度的認識。我們讨論了一些術語以及pc上的一些示例應用程式。那麼,如何在自己的計算機上建立這樣的圖像呢?好吧,這正是本書剩餘部分的任務所在。不過,讀者還需要知道一些基礎知識,這正是我們接下來将要讨論的。
1.4.1 并非工具包
opengl基本上是一種底層渲染api(應用程式接口)。我們不能告訴它“在什麼地方繪制什麼”——我們需要自己動手,通過載入三角形,應用必要的變換和正确的紋理、着色器并在必要時應用混合模式來組合一個模型。這使我們能夠進行大量的底層控制。與使用高層工具包相比,使用opengl這樣的底層api的動人之處在于,我們不能僅僅是重制許許多多的标準3d渲染算法,我們可以創造自己的算法,甚至可以發現一些新的捷徑、性能技巧和藝術視覺技術。
1.4.2 坐标系統
現在,讓我們考慮如何在三維中對物體進行描述。在指定一個物體的位置和大小之前,需要一個參考幀對它進行測量和定位。當我們在一個簡單的平面計算機螢幕上繪制點和線時,我們根據行和列指定一個位置。例如,标準的vga螢幕從左向右為640個像素,自上而下為480個像素。為了在螢幕的中間指定一個位置,可以指定(320,240)的點,也就是距離螢幕左邊320個像素、距離螢幕頂端240個像素的位置。
在opengl或幾乎所有的3d api中建立一個用于繪圖的視窗時,必須指定希望使用的坐标系統以及指定的坐标如何映射到實際的螢幕像素。首先,我們讨論在二維繪圖中應該怎樣做,然後把這個原則擴充到三維圖形中。
2d笛卡爾坐标
在二維繪圖中,最為常用的坐标系統是笛卡爾坐标系統。笛卡爾坐标由一個x坐标和一個y坐标構成。x坐标測量水準方向的位置,而y坐标則測量垂直方向的位置。
笛卡爾坐标系統的原點(origin)是(x=0,y=0)。笛卡爾坐标用括号内的一個坐标對來表示,第一個是x坐标,第二個是y坐标,中間由一個逗号分隔。例如,原點就寫成(0,0)。圖1.17所示描述了二維的笛卡爾坐标系統。帶刻度的x和y線被稱為“軸”,可以從負無窮延伸到正無窮。這張圖是我們在學校時經常使用的真實笛卡爾坐标系統。今天,當我們在繪圖時指定坐标系統,不同的視窗映射模式可能會導緻坐标的解釋不一緻。在本書後面章節,我們将看到如何使用不同的方式把真實的坐标空間映射到視窗坐标。
x軸和y軸是垂直的(直角相交),它們共同定義了一個xy平面。簡而言之,平面就是指一個扁平的表面。在任何坐标系統中,兩條軸(或兩條線)如果以直角相交,那麼它們就定義了一個平面。很顯然,如果一個系統隻有兩個軸,那麼就隻有一個平面可以用來繪圖。
圖1.17 笛卡爾平面
坐标裁剪
視窗是以像素為機關進行度量的。開始在視窗中繪制點、線和形狀之前,必須告訴opengl如何把指定的坐标對翻譯為螢幕坐标。我們可以通過指定占據視窗的笛卡爾空間區域完成這個任務,這個區域稱為裁剪區域。在二維空間中,裁剪區域就是視窗内部最小和最大的x和y值。另一種方法是根據視窗指定原點的位置。圖1.22所示顯示了兩種常見的裁剪區域。
圖1.18 兩個裁剪區域
在第一個例子中,也就是圖1.18所示的左側,視窗x坐标的範圍自左向右為0到+150,y坐标的範圍從上而下為0到+100,螢幕正中的點用(75,50)來表示。在第二個例子所示的裁剪區域中,x坐标的範圍自左向右為-75到+75,y坐标的範圍從上而下為-50到+50。在這個例子中,螢幕正中的點就是原點(0,0)。我們還可以使用opengl函數(或用于gdi繪圖的普通windows函數)上下反轉或左右反轉坐标系統。事實上,在windows視窗的預設映射中,坐标y的值始終為正,并且從上而下遞增。這種預設的映射模式在自上而下繪制文本時非常有用,但在繪制圖形時則顯得不太友善。
視口:把繪圖坐标映射到視窗坐标
裁剪區域的寬度和高度很少正好與視窗的寬度和高度(以像素為機關)相比對。是以,坐标系統必須從邏輯笛卡兒坐标映射到實體螢幕像素坐标。這個映射是通過一種叫做視口(viewport)的設定來指定的。視口就是視窗内部用于繪制裁剪區域的客戶區域。視口簡單地把裁剪區域映射到視窗中的一個區域。通常,視口被定義為整個視窗,但這并非嚴格必須的。例如,我們可能隻希望在視窗的下半部分進行繪圖。
圖1.19所示是個很大的視窗,其大小為300×200像素,它的視口被定義為整個使用者區域。如果這個視窗的裁剪區域被設定為沿x軸0至150,沿y軸0至100,我們所看到這個視窗的邏輯坐标将被映射到一個更大的螢幕坐标系統中。邏輯坐标系統的每個增量将與視窗實體坐标系統(像素)的兩個增量相比對。
圖1.19 視口被定義為裁剪區域大小的兩倍
與此形成對照的是,圖1.20所示顯示了一個與裁剪區域相比對的視口。我們所看到的這個視窗仍然是300×200像素。但是,現在可視區域将占據視窗的左下部分。
我們可以使用視口來縮小或放大視窗中的圖像,也可以通過把視口設定為大于視窗的使用者區域,進而隻顯示裁剪區域的一部分。
圖1.20 視口被定義為與裁剪區域相同的大小
頂點——空間中的一個位置
在2d和3d中,當我們繪制一個物體時,實際上都是用一些更小的稱為圖元(primitives)的形狀來組成這個物體。圖元是一維或二維的實體或表面,如點、直線和多邊形(平面多邊的形狀)。在3d空間中,我們把圖元組合在一起建立3d物體。例如一個三維立方體是由6個二維的正方形組成,每個正方形代表一個獨立的面。正方形(或其他任何圖元)的每個角稱為頂點(vertex)。這些頂點就在3d空間中指定了一個特定的坐标。頂點其實也就是2d或3d空間中的一個坐标。建立實體3d幾何圖形其實不過就是一種連線遊戲罷了。我們将在第3章讨論所有的opengl圖元以及如何使用它們。
3d笛卡爾坐标
現在,我們把二維坐标系統擴充到三維空間中,并增加深度分量。圖1.21所示的笛卡爾坐标系統增加了一個新的軸:z軸。z軸同時垂直于x軸和y軸。它代表了一條從螢幕的中心朝向讀者的直線(我們已經旋轉了這個坐标系統的視角,把y軸向左旋轉,把x軸向下和後旋轉。否則,z軸将直接面向我們,無法看到)。現在,我們用3個坐标(x,y,z)來指定三維空間中的一個位置。例如,圖1.21所示顯示了一個點(-4,4,4)。
圖1.21 三維笛卡爾坐标
1.4.3 投影:從3d到2d
我們已經知道如何在3d空間中使用笛卡爾坐标來表示位置。但是,不管我們覺得自己的眼睛所看到的三維圖像有多麼真實,螢幕上的像素實際上隻是二維的。那麼opengl是如何把這些笛卡爾坐标翻譯為可以在螢幕上繪圖的二維坐标呢?簡而言之,答案就是“三角法和簡單的矩陣操縱”。簡單?事實上或許并非如此,但是如果我們花很長的篇幅來讨論其中的概念,我們很可能會失去很多對這些細節不感興趣的讀者。而且,如果讀者已經忘了大學裡所學習的線性代數,那麼他們可能無法了解這種“簡單”的技巧。在第4章,我們将對此稍作讨論。至于更深入的讨論,讀者可以參考附錄a“更多閱讀建議”中的參考部分。幸運的是,當我們使用opengl建立圖形時,并不需要對數學有深入的了解。但是,我們在這方面的造詣越深,能夠利用opengl所發揮的威力也就越大。
我們真正需要了解的第一個概念稱為投影(projection)。用于建立幾何圖形的3d坐标将投影到一個2d表面(視窗背景)。這有點像在一塊玻璃後面用一支黑色的筆描繪一些物體的外形。當物體消失或者移動玻璃時,我們仍然可以看到這個邊緣帶角的物體的外形,在圖1.22中,背景中的一座房子被描繪到一塊扁平的玻璃上。通過指定投影,我們可以指定在視窗中顯示的視景體(viewing volume),并指定如何對它進行變換。
圖1.22 3d圖像投影到2d表面
正投影
在opengl中,絕大多數情況下,我們所關心的是兩種主要類型的投影。第一種稱為正投影(orthographic projection)或平行投影。使用這種投影時,需要指定一個正方形或長方形的視景體。視景體之外的任何物體都不會被繪制。而且,所有實際大小相同的物體在螢幕上都具有相同的大小,不管它們是遠是近。這種類型的投影(如圖1.23所示)最常用于建築設計、計算機輔助設計或2d圖形中。此外,在3d圖形場景中,我們也常常需要使用正投影,在場景的頂部添加文本或者2d覆寫圖。
圖1.23 正投影的視景體
我們還可以在正投影中通過指定遠、近、左、右、頂和底裁剪平面來指定視景體。在這個視景體中出現的物體和圖形将被投影(考慮它們的方向)到一個在螢幕上出現的2d圖像。
透視投影
第二種投影是透視投影(perspective projection),它更為常見。在這種投影中,遠處的物體看上去比近處的物體更小一些。它的視景體(如圖1.24所示)看上去有點像一個頂部被削平的金字塔。剩下來的這個形狀稱為平截頭體(frustum)。靠近視景體前面的物體看上去比較接近它們的原始大小。但是,當靠近視景體後部的物體被投影到視景體的前部時,它們看上去就顯得比較小。在模拟和3d動畫中,這種投影能夠獲得最大程度的逼真感。
圖1.24 透視投影的視景體(平截頭體)