背景
當前3D圖形界主要有兩個:微軟的Direct 3D以及某組織的OpenGL。曾經一度OpenGL幾乎佔據全部3D圖形領域,這在巨人微軟面前簡直就是屌絲逆襲。曾幾什麼時候微軟搞IDE borland公式倒閉了,後來微軟搞瀏覽器,網景公司解散,員工捲鋪蓋走人了,也就是說微軟搞誰,誰倒黴。直到OpenGL的出現,打破了這一魔咒,在與微軟競爭的前期,OpenGL幾乎甩了微軟幾條街,併成爲事實上的工業標準。後來在微軟的大力絞殺下,OpenGL幾乎被徹底趕出了遊戲領域,退居高端圖形領域。基本上如今是微軟的Direct 3D統治遊戲領域,而OpenGL則在高端專業圖形領域佔絕對統治地位。微軟仍是微軟,OpenGL已經不是之前的OpenGL了,等會。。等會。。這句話咋這麼熟悉?想起來了趙本山的小品裏說過:你大爺仍是你大爺,你大媽已經不是你5年前的大媽了,爲何這麼說呢?話說搞OpenGL的那家公司被微軟逼瘋了,沒錯。。解散了。。可是OpenGL並無消失,而是交給某開源組織託管、開發與維護了。哎。。。不說了,都是淚啊。。凡是牽扯到微軟,那就是一部血淚史啊。。逼瘋了無數企業。。可是話有說回來,商場上比爾蓋茨是個儈子手而慈善上這傢伙也不小氣。。大把大把的捐錢。。 程序員
座標系空間 算法
在OpenGL裏面,3D座標系的X軸自左向右增大,y軸自下向上增大,z軸正方向從屏幕中心指向觀察者。
座標系有如下幾種:局部(模型)座標系、世界座標系、相機座標系、屏幕座標系;對應的矩陣變換則有模型變換、視圖(相機)變換、投影變換,其中投影變換分爲正視投影、透視投影。而座標系之間的轉換要用到矩陣。世界座標系至關因而虛擬宇宙,位置固定不變,而局部(模型)座標系是繪圖的一個局部空間,是相對的,相機座標系是以相機的鏡頭(或者人的眼睛)來觀察物體的視覺空間。在3D中畫圖是如今局部座標空間繪製,而後經過矩陣變換轉移到世界座標空間,接着轉換到相機空間,而後在投影,最終會在光柵化的二維屏幕上渲染圖形。
編程
局部座標空間又叫模型空間,繪製圖形是在模型空間繪製,繪製完成後通過模型變換轉換到世界座標系空間。在OpenGL中渲染三維模型是以圖元爲最小單位進行渲染的,圖元有三角形,四邊形等,絕大多數狀況下都是以三角形圖元渲染。圖元如三角形是有3個頂點組成的,那麼爲何最小圖元不是頂點而是諸如三角形呢?這就比如提到一個化學物質人們會說這個物質是由不少原子組成的,而不會說是由電子、中子、原子核組成,由於電子、中子及原子核是一個有機總體;一樣三角形圖形的三個頂點也是一個有機總體。 api
圖一 數組
圖二 瀏覽器
上面圖片一是層次細節LOD地形網格,513像素X513像素,不過並無達到徹底的分辨率,並且不一樣的地方分辨率不一樣,此是後話。圖片二是用OpenGL加載的一個ms3d格式的三維小汽車,這個小汽車是用3d max製做的3ds文件通過MilkShape 3D轉換後獲得的ms3d文件。這兩個三維模型都是以三角形網格渲染而成的,不一樣的是圖一沒有進行文理貼圖,而是以線框的模式渲染的,這樣作是爲了更好的看到3D渲染的細節;對應的圖二則是以平滑模式渲染而且貼有文理。從本質上來講三維圖像的最小單位是頂點,這些頂點以特定的方式送往3D API(典型的如OpenGL 3d api或者D3D api)並以三角形網格的形式進行渲染。固然也能夠選擇其餘多邊形如四邊形進行渲染,可是三角形渲染最爲方便,幾乎全部的3D圖形都是以三角形爲圖元進行渲染。從圖一能夠看出這個地形是有不少小的三角形網格組成的,事實上這個地形網格是以三角形扇的方式組織渲染的。如今咱們看到的這兩個圖形是在屏幕座標空間觀察的,那麼他們的第一站其實就是局部(模型)座標空間,通過一系列3D流水線最終送往二維屏幕進行光柵化處理和渲染。在局部座標系的物體有N個頂點組成,若是變換到世界座標系的話須要對全部頂點作變換,共計N次變換。設物體的任一頂點爲(loc_x ,loc_y , loc_z)。這個座標是在局部座標系下,要想變換到世界座標空間須要將局部座標系的原點移動到對應的位置(world_x,world_y,world_z)處,而且同時移動三維模型的全部頂點座標。很容易獲得最終的座標:(loc_x+world_x,loc_y+world_y,loc_z+world_z)。如今咱們先來看一幅圖片:
咱們驚奇的發現這個計算結果正是咱們想要的,沒錯,你猜對了,在3D圖形裏面頂點的變換都是經過矩陣完成的,而這個平移變換是最簡單的一種矩陣變換,其餘的還有旋轉、縮放等等。上面的圖1、二就是在3D流水線裏通過一些列像上述那樣的矩陣變換最終才從幕後走向臺前展示在你們眼前。由上能夠看出每個頂點經歷了16次乘法運算、12次加法運算,共計28N次計算,固然在矩陣變換之間可能還有進行了諸如光照、紋理等操做。假如一個三維場景共有100萬個頂點,那麼就要作2800萬次計算,這尚未加上後面的相機變換、投影變換所作的矩陣運算以及光柵化、渲染等操做。由此能夠看出來運算量是很是的大。那麼顯卡能承受如此巨大的運算量嗎?後面會提到,3D 圖形庫(如OpenGL)會在圖形進行渲染前將沒必要要的頂點裁剪掉,這樣就不用渲染他們了,從而節省了GPU的運算量。可是光是依靠3D 圖形庫的裁剪功能仍是不夠,雖然裁剪掉了不需進行渲染的頂點,然而這些頂點已經消耗掉了大量的矩陣運算,尤爲是當場景很是細膩的時候也就意味着頂點數目很是巨大。那麼有沒有方法能夠在座標系統進行矩陣變換前就被提早裁剪掉呢?答案是確定的。舉個例子,對於圖一中的地形網格來講,他被渲染到屏幕是有條件的,其一:不在照相機視景體空間內的物體將被忽略不予處理;其二:對於遠處的地面以低分辨率渲染,近處則以高分辨率渲染;其三:粗糙的部分以高分辨率渲染,平坦的部分以低分辨率渲染。假若有一個600X600的網格通過測試不在照相機的視景體內,那麼這36萬個頂點就不用進行後續的大量矩陣運算,而測試所消耗是由8個頂點組成的六面體,這個運算量的消耗無疑是值得的。至於如何進行相機裁剪,在層次細節算法裏面將會詳細說明。在OpenGL以及D3D裏面用戶通常不會直接操做矩陣,OpenGL會將用戶的函數調用解釋爲矩陣,好比用戶調用gluPerspective(參數。。)時,OpenGL會根據函數的參數設置視圖矩陣並與當前的視圖矩陣相乘。 函數
世界座標系空間通過上面的平移操做物體就被移動到世界座標系,這個座標系是固定不變的,至關於虛擬宇宙中心。此時物體還不能呈現於屏幕,還要經歷九九八十一難才能與觀衆見面。進入世界座標系之後,照相機的視點可能不在原點,而且視點可能還不是朝向z軸負方向。此時就要將照相機平移到世界座標系原點,而且調整方向使相機視點朝向z軸負方向。之因此要這樣調整是由於若是相機位於原點並朝向z軸負方向的話會給處理帶來極大的方便,至因而什麼方便呢,我也不知道,反正是專家說的,至於你信不信,反正我是信了。在說明如何變換到相機空間咱們先來看一下幾個矩陣操做。設有世界座標系空間的某一個點A(world_x,world_y,world_z)分別繞x軸 y軸 z軸旋轉angle_x、angle_y、angle_z度到達B點。那麼求其旋轉後的座標。 測試
圖三 spa
這裏爲了更好推導將座標系進行了旋轉。 .net
如圖三A點繞x軸 旋轉angle_x度求其旋轉後的座標。A點繞X軸旋轉所造成的平面一定與y軸 z軸所構成的平面平行,所以將A點 B點投射到y_z平面上獲得A撇點 B撇點,A撇 B撇點在z y平面上的座標顯而易見就是A點 B點對應的 y z座標。如圖可知C角的大小就是angle_x 即C=angle_x;D點對應的那個角是OA與y軸的夾角。
對於B撇點:
對於A撇點:
將第一個方程式展開獲得:
將第二個方程代入第三個方程式獲得:
顯而易見繞x軸旋轉x座標值天然是不變的,也就是說旋轉後頂點座標爲: (world_x, world_y*cos(angle_x)+world_z*sin(angle_x),-world_y*sin(angle_x)+world_z*cos(angle_x));
下面咱們再看一個矩陣運算:
矩陣一
咱們再一次吃驚的發現這個正是咱們所想要的結果,難道冥冥之中矩陣與3D圖形變換有着不解之緣?OpenGL裏面矩陣是按照列優先的原則存儲於一個一維數組裏面,三維頂點不是以三維向量二是以四維向量來表示好比(x,y,z,w)來表示的,w初始默認狀況下爲1,在變換過程當中w的值會跟着發生變化,而且w也有它的用處,此是後話。咱們來看一個更加通常的矩陣:
在用有特殊含義的字符串來填充相應位置後,咱們會發現。。。沒錯。。前三行三列剛好表示x y z座標軸的方向向量,每一列(除了第四列) 的最後一個值默認是0,第四列最後一個值是1,一樣在變換過程當中它可能發生變化,它的一個用處是齊次化座標。那麼一開始的矩陣前三列分別是(1,0,0,0) (0,1,0,0) (0,0,1,0)很顯然這三個座標分別表示x y z軸的方向向量(此時局部座標系的原點與世界座標系原點重合爲(0,0,0)),那麼旋轉後這三列還能表示三個座標軸方向向量嗎?這個矩陣怎麼解釋呢?咱們再來看一個圖:
在上述的矩陣變換中咱們曾經說過繞X軸旋轉物體angle_x度,這至關於物體不動把座標軸繞X軸向相反的方向旋轉angle_x度。如圖A表示繞X軸旋轉的角度,若是以上述變換爲例,那麼A=angle_x。顯然在新的局部座標系下對應的B C點在原來的座標系中的座標分別是
(0,-1*sin(angle_x),1*cos(angle_x)) (0,1*cos(angle_x),1*sin(angle_x)) 再看矩陣一,發現這個正是新的座標系的三個方向向量,這個方向向量是以原座標系爲參考系得來的。在3D中咱們旋轉物體與物體不動旋轉座標系的效果是同樣的,只是四維方式的問題而已。在回過頭來看矩陣二,咱們發現(translate_x ,translate_y,translate_z)是局部矩陣的原點在原座標系下的座標,而(x1,y1,z1)是新的局部座標系的x軸上的一個點在原座標系下的座標,其餘的以此類推,局部座標系的原點定了,三個座標軸上的點也定了,咱們吃驚的發現局部座標系在原座標系爲參考的狀況下已經描繪出來了。若是。。若是原座標系是世界座標系,沒錯,若是成真的話咱們就通過一系列矩陣變換獲得了局部座標系在世界座標系中的位置,因而就刻畫出來了三維物體的頂點座標在世界座標系下的座標值。
如今還記得咱們一開始的問題嗎?你可能已經不記得了,問題是如何將世界座標系變換到相機座標系。
圖片來自3D遊戲編程大師。
如上圖相機位置(cam_x,cam_y,cam_z)與y軸夾角爲angle_y。若是要變換到相機空間的話首先要將相機平移到原點,結合前面所說的也就是設置矩陣的最後一列translate_x=-cam_x, translate_y=-cam_y, translate_z=-cam_z,而後使相機的鏡頭繞y軸旋轉-angle_y度。前面已經講過繞X軸旋轉angle_x度的矩陣方程,那麼繞x軸旋轉-angle_x度的方法就是將前述矩陣的角度設置爲-angle_x就好了,y軸旋轉的也能夠以此類推。
相機座標空間通過上述的矩陣變換已經到達了相機座標空間,如今到了投影的時刻了,前面定義了相機的位置和方向,可是相機的視野不是無限遠的,必須爲它制定一個視景體,在視景體內的物體將被投影到視平面,不在視景體內的物體將被丟棄不處理
如圖即是一個視景體,這個投影是透視投影,所謂透視投影就是給人一中置身於實際場景中的感受,遠處的物體顯得小,近處的物體顯得大。還有一中投影叫正視投影,這個主要用於CAD程序中。三維圖形主要使用透視投影。在OpenGL這個視景體能夠用api函數gluPerspective(angle,fov,w_div_h,near,far)來定義。這個函數將產生一個透視投影矩陣並與當前的投影矩陣相乘。
投影空間通過上述矩陣變換就到了投影空間了,接着就是後續的視口變換,渲染等工做了。
裁剪如今咱們再來看一個地形網格
圖片三
這個地形網格和圖片一是一個程序生成的,不一樣的是前者調節係數是8,後者調節係數是25結果致使了後者的分辨率明顯大於前者。通過前面座標系空間變換的介紹咱們知道這幅地形網格呈如今咱們眼前以前經歷了模型變換,視圖變換,投影變換等。這個地形網格是513*513尺寸,並且沒有達到徹底分辨率,那麼若是達到了徹底分辨率,勢必頂點數目大幅增長,再如 若是地形尺寸是10000*10000呢,頂點數將增長400倍,再如一個實際的場景可能還有大量的樹木,房屋,動物,人車等。除了矩陣運算還有紋理貼圖,光照,霧化等,處理起來至關的消耗GPU和CPU。若是不控制好的話,系統渲染後運行很是卡。其中有一個能夠改善的方法是LOD算法即層次細節算法,這個算法經常使用來繪製大規模實時地形。這個算法其中須要用到一個叫相機裁剪的算法。接下來就說一下相機裁剪。所謂相機裁剪就是在上圖視景體中的物體進行處理,不在裏面的物體被丟棄。前面已經說過渲染的時候OpenGL會自動丟棄不在視景體內的物體以免渲染,而此處的裁剪進一步減小了矩陣運算的次數,也就是說若是物體須要裁剪的話,那麼在模型空間就被裁剪了,而沒有通過後續的各類矩陣變換。那麼必須設計一個算法來檢測某個物體是否被相機裁剪。方法之一是將待處理的物體構造一個AABB包圍盒,而後用包圍盒的頂點與物體頂點所在的平面作相交測試,不想交就被裁剪掉,不然保留。
typedef struc aabb
{
someType min[3];
someType max[3];
} AABB;
這個結構體裏面min[3]裏面保存的是(min_x,min_y,min_z)對應max[3]裏面保存(max_x,max_y,max_z) 這些座標是採用某種方法找到的物體的最小座標或最大座標值,最笨最耗時的辦法就是遍歷物體的每一個頂點找到對應的最小最大座標值
那麼怎麼求視景體的六個面的方程呢?在OpenGL中通過透視投影后前文的視景體變爲了規則的長方體,這個投影空間中頂點的座標形式爲(pro_x,pro_y,pro_z,w),如今到了w值大顯神通的時候了,若是程序員沒有在程序中故意操做矩陣的值,那麼如今這個w就是每一個座標值得各個份量的絕對值的最大範圍,顯而易見若是物體的座標知足-w<x<w;-w<y<w;-w<z<w的話那麼物體在這個投影空間內不然被裁減掉。這個投影空間的左平面的方程爲x=-w,可是咱們把它寫做-x-w=0的形式,這樣他的法向量就是(-1,0,0)也就是指向投影空間的外部,保證其餘六個面的法向量也指向物體外部,這樣是爲了方便後面的相交測試,固然使全部平面方程的法向量指向空間內部也是能夠的,總之保持一致性就能夠了。對於一個普通的平面方程A*x+B*y+C*z+D=0和一個頂點(a,b,c)將其代入原方程,若是A*a+B*b+C*c+D=0的話這個頂點就在平面上,若小於零則在平面一側若大於零則在另外一側,這個方法就能夠用於包圍盒與相機裁剪測試中。咱們的目的是在物體經不通過模型、視圖、投影變換直接進行相交測試,那麼必然要求得相機視景體在通過相機變換 模型變換前的六面體的6個平面方程。那麼從當前的這個投影后的長方體就能夠推導出這個所須要的六面體的六個面的方程。設局部座標系下物體的任意一個頂點是vertex_local=(local_x,local_y,local_z,w)這裏w=1;通過投影變換後的座標vertex_per=(per_x,per_y,per_z);設M是模型變換矩陣,V是相機變換矩陣,P是透視投影變換矩陣。那麼將會有以下的座標變換關係vertex_local*MVP=vertex_per;其中MVP是模型視圖投影三個矩陣的乘積,這個是顯而易見的。這是目前能夠利用的一個等式,咱們就是從這個等式推導出面的方程。這其中的矩陣M V P分別是模型 視圖 投影矩陣,能夠由OpenGL api得到,而後計算其乘積就好了。先來看一個圖片:
在這個矩陣乘法當中一個原始座標乘以一個4*4矩陣獲得一個新的座標。
其中 x_new=X1*x_origin+x2*y_origin+x3*z_origin+x_t*w;
w_new= a*x_origin+b*y_origin+c*z_origin+d*w;
由上述可知投影以後在x軸方向的範圍是[-w_new,w_new]
因此右平面上的點的方程式x_new=w_new;
結合上兩個方程式獲得:x_origin*(X1-a)+y_origin*(X2-b) +z_origin*(X3-c)+w*(x_t-d)=0;
因爲初始w=1;因此 x_origin*(X1-a)+y_origin*(X2-b) +z_origin*(X3-c)+(x_t-d)=0;
其中小括號裏的數據是能夠計算出來的,至關於常量,因而上式的形式就是Ax+By+Cz+D=0。顯然這是個平面的方程,而x y z是初始的點,那麼這個方程就是初始的點所在的平面的方程。一樣道理求得另外5個面的方程,而後作相交測試,即可以進行裁剪了。如今包圍盒aabb 六個平面的方程已經了。
那麼如今如何進行相交測試呢?
如圖一開始最小 最大點是紅色的min max點,通過軸分離後最小 最大點變化爲綠色的min max在知道包圍盒aabb和6個平面的方程後就可使用軸分離方向來測試相交問題。若是aabb最小的點在某個平面外的話,那麼其餘點必定在平面外。可是如今有一個問題就是怎麼定義最大最小點,最大最小分別表示距離平面距離最大最小的點。一開始沒有考慮平面的時候直接比較座標值的大小來肯定最大最小點,如今引入平面方程後就要從新調整aabb獲得新的包圍盒。這裏不試圖給出嚴格的數學證實,只舉一個簡單的例子來講明爲什麼要從新調整aabb。軸分離的話就是分別考慮x y z軸。如今假設考慮x軸,上述的投影后的長方體左平面方程爲-x-w=0;假設aabb.max[]={-3,-3,-3} aabb.min[]={-5,-5,-5}可是如今距離-x-w=0(w<1.0) 最小的點顯然是{-3,-5,-5) 最大點顯然應該是(-5,-3,-3),而後y z軸以此類推,獲得更新後的aabb。將新的aabb.min代入上面的平面方程若是大於0的話,算法馬上return,這個aabb不在視鏡體內,若是小於0的話,在將aabb.max代入,若是大於0的話設置標誌insect=true;而後循環處理另外幾個平面方程。最終若aabb不在包圍盒內將會在循環的過程當中return,若是循環順利結束,而且insect=true,說明aabb與視景體相交,不然aabb徹底在視景體中。
自此,本文就結束了,這只是任務中的一個小部分。。還有不少其餘任務須要作。。
個人CSDN博客:
http://blog.csdn.net/cs_huster?viewmode=contents
郵箱:microland@126.com。歡迎指出文中的錯誤之處,本人也是初學者,由於畢設作這個,因而就開始看這方面的東西。