OpenGL ES 3.0從畫圓中瞭解座標系統

以前學習了繪製點、線、三角形,都很完美的展現出來了,因此有點小膨脹,想畫一個圓形證實本身OpenGL已經入門了。java

畫一個圓形其實和畫一個三角形沒有太大區別,由於一個圓形也就是由無數個相同頂點的三角形組成的,三角形個數趨向於無限大的時候,整個圖案也就越趨向於圓。頂點數據就不能手寫了,能夠靠代碼生成。git

private float[] createPositions() {
    // 繪製的半徑
    float radius = 0.8f;
    ArrayList<Float> data = new ArrayList<>();
    data.add(0.0f); //設置圓心座標
    data.add(0.0f);
    data.add(0.0f);
    float angDegSpan = 360f / 360; // 分紅360份
    for (float i = 0; i < 360 + angDegSpan; i += angDegSpan) {
        data.add((float) (radius * Math.sin(i * Math.PI / 180f)));
        data.add((float) (radius * Math.cos(i * Math.PI / 180f)));
        data.add(0.0f);
    }
    float[] f = new float[data.size()];
    for (int i = 0; i < f.length; i++) {
        f[i] = data.get(i);
    }
    return f;
}
複製代碼

把圓分紅了 360 份。圓形的頂點數據也分爲了三部分了,以原心做爲咱們的中心點,中間的 360 個點用來繪製三角形,最後一個點使得咱們的圖形閉合。github

GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, 362);
複製代碼

當信心滿滿地運行後,現實卻打了臉:數組

圓形變成了一個橢圓。。。 爲何呢?app

OpenGL但願在每次頂點着色器運行後,咱們可見的全部頂點都爲標準化設備座標(Normalized Device Coordinate, NDC)。也就是說,每一個頂點的xyz座標都應該在**-1.01.0**之間,超出這個座標範圍的頂點都將不可見。學習

而在上面的例子中,假設實際手機分辨率以像素爲單位是720x1280,咱們默認使用OpenGL佔用整個顯示屏。 設備在豎屏模式下,那麼[-1,1]的範圍對應的高有1280像素,而寬卻只有720像素。正由於標準化設備座標假定座標空間是一個正方形,而實際的設備屏幕不是正方形,由於寬高之間的比例,使得繪製結果與預料結果不一致。spa

如何解決這個問題?在OpenGL中使用了正交投影的方式解決這個問題。3d

在學習正交投影前先學習下OpenGL的座標系統。code

座標系統

OpenGL中咱們一般會本身設定一個座標的範圍,以後再在頂點着色器中將這些座標變換爲標準化設備座標。而後將這些標準化設備座標傳入光柵器(Rasterizer),將它們變換爲屏幕上的二維座標或像素。orm

將座標變換爲標準化設備座標,接着再轉化爲屏幕座標的過程一般是分步進行的,也就是相似於流水線那樣子。在流水線中,物體的頂點在最終轉化爲屏幕座標以前還會被變換到多個座標系統(Coordinate System)。將物體的座標變換到幾個過渡座標系(Intermediate Coordinate System)的優勢在於,在這些特定的座標系統中,一些操做或運算更加方便和容易,這一點很快就會變得很明顯。對咱們來講比較重要的總共有5個不一樣的座標系統:

  • 局部空間(Local Space,或者稱爲物體空間(Object Space))
  • 世界空間(World Space)
  • 觀察空間(View Space,或者稱爲視覺空間(Eye Space))
  • 裁剪空間(Clip Space)
  • 屏幕空間(Screen Space)

這就是一個頂點在最終被轉化爲片斷以前須要經歷的全部不一樣狀態。

爲了將座標從一個座標系變換到另外一個座標系,咱們須要用到幾個變換矩陣,最重要的幾個分別是模型(Model)觀察(View)投影(Projection)三個矩陣。咱們的頂點座標起始於局部空間(Local Space),在這裏它稱爲局部座標(Local Coordinate),它在以後會變爲世界座標(World Coordinate)觀察座標(View Coordinate)裁剪座標(Clip Coordinate),並最後以**屏幕座標(Screen Coordinate)**的形式結束。下面的這張圖展現了整個流程以及各個變換過程作了什麼:

首先了解下OpenGL是一個右手座標系,簡單來講,就是正x軸在你的右手邊,正y軸朝上,而正z軸是朝向後方的。想象你的屏幕處於三個軸的中心,則正z軸穿過你的屏幕朝向你。座標系畫起來以下:

局部空間

局部空間座標是 OpenGL 繪製座標的起點,接下來全部的轉換操做都是在局部空間座標基礎上進行的。

局部空間座標就是咱們本身定義的起始座標點,是相對於原點 (0,0,0)(0,0,0) 的。

此時所在的空間就是局部空間,也就是說咱們在局部空間裏面定義物體的起始座標。

世界空間

若是咱們將咱們全部的物體導入到程序當中,它們有可能會全擠在世界的原點(0, 0, 0)上,這並非咱們想要的結果。咱們想爲每個物體定義一個位置,從而能在更大的世界當中放置它們。世界空間中的座標正如其名:是指頂點相對於(遊戲)世界的座標。若是你但願將物體分散在世界上擺放(特別是很是真實的那樣),這就是你但願物體變換到的空間。物體的座標將會從局部變換到世界空間;該變換是由模型矩陣(Model Matrix)實現的。

模型矩陣是一種變換矩陣,它能經過對物體進行位移、縮放、旋轉來將它置於它本應該在的位置或朝向。你能夠將它想像爲變換一個房子,你須要先將它縮小(它在局部空間中太大了),並將其位移至郊區的一個小鎮,而後在y軸上往左旋轉一點以搭配附近的房子。

觀察空間

觀察空間常常被人們稱之OpenGL的攝像機(Camera)(因此有時也稱爲攝像機空間(Camera Space)或視覺空間(Eye Space))。觀察空間是將世界空間座標轉化爲用戶視野前方的座標而產生的結果。所以觀察空間就是從攝像機的視角所觀察到的空間。而這一般是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方。這些組合在一塊兒的變換一般存儲在一個觀察矩陣(View Matrix)裏,它被用來將世界座標變換到觀察空間。

從平常生活的經驗中能夠很容易地瞭解到,隨着攝像機位置、姿態的不一樣,就算是對同一 個場景進行拍攝,獲得的畫面也是迥然不一樣的。 所以攝像機的位置、姿態在 OpenGL ES 3.0 應用程序的開發中就顯得很是重要,因此先介紹一下攝像機的設置方法。

攝像機的設置須要給出 3 方面的信息,包括攝像機的位置、觀察的方向以及 up 方向,具體狀況如圖所示。

  • 攝像機的位置很容易理解,用其在 3D 空間中的座標來表示。
  • 攝像機觀察的方向能夠理解爲攝像機鏡頭的指向,用一個觀察目標點來表示(經過攝像機位置與觀察目標點能夠肯定一個向量,此向量即表明了攝像機觀察的方向)。
  • 攝像機的 up 方向能夠理解爲攝像機頂端的指向,用一個向量來表示。

經過攝像機拍攝場景與人眼觀察現實世界很相似,所以,經過人眼對現實世界觀察的切身感覺能夠幫助讀者理解攝像機的各個參數。

裁剪空間

在一個頂點着色器運行的最後,OpenGL指望全部的座標都能落在一個特定的範圍內,且任何在這個範圍以外的點都應該被裁剪掉(Clipped)。被裁剪掉的座標就會被忽略,因此剩下的座標就將變爲屏幕上可見的片斷。這也就是裁剪空間(Clip Space)名字的由來。

由於將全部可見的座標都指定在-1.0到1.0的範圍內不是很直觀,因此咱們會指定本身的座標集(Coordinate Set)並將它變換回標準化設備座標系,就像OpenGL指望的那樣。

爲了將頂點座標從觀察變換到裁剪空間,咱們須要定義一個投影矩陣(Projection Matrix),它指定了一個範圍的座標,好比在每一個維度上的-1000到1000。投影矩陣接着會將在這個指定的範圍內的座標變換爲標準化設備座標的範圍(-1.0, 1.0)。全部在範圍外的座標不會被映射到在-1.0到1.0的範圍之間,因此會被裁剪掉。在上面這個投影矩陣所指定的範圍內,座標(1250, 500, 750)將是不可見的,這是因爲它的x座標超出了範圍,它被轉化爲一個大於1.0的標準化設備座標,因此被裁剪掉了。

由投影矩陣建立的觀察箱(Viewing Box)被稱爲平截頭體(Frustum),每一個出如今平截頭體範圍內的座標都會最終出如今用戶的屏幕上。將特定範圍內的座標轉化到標準化設備座標系的過程(並且它很容易被映射到2D觀察空間座標)被稱之爲投影(Projection),由於使用投影矩陣能將3D座標投影(Project)到很容易映射到2D的標準化設備座標系中。

一旦全部頂點被變換到裁剪空間,最終的操做——透視除法(Perspective Division)將會執行,在這個過程當中咱們將位置向量的x,y,z份量分別除以向量的齊次w份量;透視除法是將4D裁剪空間座標變換爲3D標準化設備座標的過程。這一步會在每個頂點着色器運行的最後被自動執行。

在這一階段以後,最終的座標將會被映射到屏幕空間中(使用glViewport中的設定),並被變換成片斷。

將觀察座標變換爲裁剪座標的投影矩陣能夠爲兩種不一樣的形式,每種形式都定義了不一樣的平截頭體。咱們能夠選擇建立一個正交投影矩陣(Orthographic Projection Matrix)或一個透視投影矩陣(Perspective Projection Matrix)

正交投影

OpenGL ES 3.0 中,根據應用程序中提供的投影矩陣,管線會肯定一個可視空間區域,稱爲視景體。視景體是由 6 個平面肯定的,這 6 個平面分別爲:上平面(up)、下平面(down)、左平面(left)、右平面(right)、遠平面(far)、近平面(near)。

場景中處於視景體內的物體會被投影到近平面上(視景體外面的物體將被裁剪掉),而後再將近平面上投影出的內容映射到屏幕上的視口中。

因爲正交投影是平行投影的一種,其投影線(物體的頂點與近平面上投影點的連線)是平行的。故其視景體爲長方體,投影到近平面上的圖形不會產生真實世界中「近大遠小」的效果,下圖更清楚地說明了這個問題。

透視投影

現實世界中人眼觀察物體時會有「近大遠小」的效果,咱們看一條無限長的高速公路或鐵路時尤爲明顯,正以下面圖片顯示的那樣:

因爲透視,這兩條線在很遠的地方看起來會相交。所以,要想開發出更加真實的場景,僅使用正交投影是遠遠不夠的,這時能夠採用透視投影。透視投影的投影線是不平行的,他們相交於視點。經過透視投影,能夠產生現實世界中「近大遠小」的效果,大部分 3D 遊戲採用的都是透視投影。

透視投影中,視景體爲錐臺形區域,如圖所示。

從上圖中能夠看出,透視投影的投影線互不平行,都相交於視點。所以,一樣尺寸的物體,近處的投影出來大,遠處的投影出來小,從而產生了現實世界中「近大遠小」的效果。下圖更清楚地說明了這個問題。

把它們都組合到一塊兒

咱們爲上述的每個步驟都建立了一個變換矩陣:模型矩陣、觀察矩陣和投影矩陣。一個頂點座標將會根據如下過程被變換到裁剪座標:

注意矩陣運算的順序是相反的(記住咱們須要從右往左閱讀矩陣的乘法)。最後的頂點應該被賦值到頂點着色器中的gl_Position,OpenGL將會自動進行透視除法和裁剪。

實現畫圓

上面大概瞭解了下OpenGL的座標系統,裏面涉及的知識實在太多,後面慢慢了解。咱們先使用正交投影完成一個完美的圓的繪製。爲了解決圖像拉伸問題,就是要保證近平面的寬高比和視口的寬高比一致,並且是以較短的那一邊做爲 1 的標準,讓圖像保持居中。

OpenGL中經過調用 Matrix 類的 orthoM 方法完成對正交投影的設置,其基本代碼以下。

orthoM(float[] m,                            //存儲生成矩陣元素的float[]類型數組
       int mOffset,                          //填充起始偏移量
       float left,float right,               //near面的left、right
       float bottom,float top,               //near面的bottom、top
       float near,float far)                 //near面、far面與視點的距離
複製代碼
  • orthoM 方法的功能爲根據接收的 6 個正交投影相關參數產生正交投影矩陣,並將矩陣的元素填充到指定的數組中。
  • 參數 left、right 爲近平面左右側邊對應的 x 座標,top、bottom 爲近平面上下側邊對應的 y座標,分別用來肯定左平面、右平面、上平面、下平面的位置。參數 near、far 分別爲視景體近平面與遠平面距視點的距離。

近平面的座標原點位於中心,向右爲 X 軸正方向,向上爲 Y 軸正方向,因此咱們的 left、bottom 要爲負數,而 right、top 要爲正數。同時,近平面和遠平面的距離都是指相對於視點的距離,因此 near、far 要爲正數,並且 far>near。

能夠在 GLSurfaceView 的 surfaceChanged 裏面來設定正交投影矩陣。

float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width;
if (width > height) {
    Matrix.orthoM(mMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, 0f, 10f);
} else {
    Matrix.orthoM(mMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, 0f, 10f);
}
複製代碼

這樣就把近平面和視口的寬高比設置爲一致的了,解決了以前圖像被拉伸的問題。

完整代碼請看Github:OpenGLES-Learning : CircleRenderer

相關文章
相關標籤/搜索