OpenGL基礎篇之渲染過程

  • 背景

OpenGL是一種跨平臺的圖像渲染方式,這邊主要介紹一下OpenGL在android上的應用,即OpenGLES,之所以用它來渲染是因爲以往的c++渲染方式比較慢而且佔用較大內存,使用OpenGL可以實現實時渲染,而且可以充分利用GPU的內存。

 

  • OpenGL數據傳遞

要使用OpenGL進行渲染,第一步就是怎樣把數據放到GPU裏面了,利用IPC這樣的數據傳遞是不現實的,因爲數據量太大了,所以圖像數據的傳遞是通過Androdi匿名共享內存實現的,在驅動層分配一塊共享內存,把數據傳送到這塊區域,然後通知GPU進程來取就行了。

 

 1、數據構造

OpenGL只能繪製點、線、三角形,根據不同形狀需要的頂點個數也不同,這裏以三角形爲例,要繪製一個三角形,首先需要三個頂點座標,OpenGL本身就支持3D,所以頂點座標是三維的,但是如果只繪製2D圖像,那麼z軸可以寫死在腳本里面,java層傳遞的頂點座標是二維的就行了,下面看個例子:

public float[] VERTEX_DATA = new float[]{
        0.0f, 0.0f, 0.5f, 0.5f,
        -1.0f, -1.0f, 0f, 1.0f,
        1.0f, -1.0f, 1f, 1f,
        1.0f, 1.0f, 1f, 0.0f,
        -1.0f, 1.0f, 0f, 0.0f,
        -1.0f, -1.0f, 0f, 1f
};

上面就是繪製一個矩形包含的包含頂點座標和紋理座標,首先是每一行由頂點座標和紋理座標構成,分別解釋一下頂點座標和紋理座標:

1)頂點座標

頂點座標是我們要繪製的具體位置,Opengl中頂點座標的範圍是[-1,1]的,屏幕左下角是[-1,-1],右上角是[1,1]

 

2)紋理座標

紋理座標是我們要採樣的圖片上面的座標,即要往頂點座標指定的位置繪製的圖片區域,紋理座標和頂點座標不同,範圍是[0,1],屏幕左上角是[0,0],右下角是[1,1]

介紹完座標後我們再來重新看一下上面每一行的數據,以第一行爲例

0.0f ,  0.0f,  0.5f ,  0.5f

前兩個是頂點座標,根據上面的座標系可以知道是屏幕中心點座標,後面兩個是紋理座標,可以看出是圖片區域的中心點,一個頂點座標必須對應一個紋理座標,形象的說頂點座標相當於釘子一樣,而紋理座標相當於把一塊布的指定位置固定在釘子處,這樣釘子所圍成的區域裏面看到的布就是我們屏幕上看到的圖像

 

我們這裏以繪製矩形爲例,一個矩形由兩個三角形或者四個三角形組成,這裏我們是以四個三角形爲例

我們來看一下繪製矩形的過程,O->A->D,  O->D->C,  O->C->B,  O->B->A,像這種繪製方法在opengl中是GL_TRIANGLE_FAN類型,就像一個風扇一樣旋轉,第一個點始終不變,後兩個點每次順移一位,最終閉合形成一個矩形。

還有一種繪製方式是GL_TRIANGLE_TRIPS,這種方式是A->D->C, D->C->B, C->B->A,這種方式用到的點比較少,它不需要中心點,每次三個點都順移一位。

上面兩種方式是常見的繪製三角形方式,這兩種方式優點都是重複利用點,這樣空間就節省了不少,當然最普通的就是一個三角形三個點,畫幾個就乘於幾,不過除非只畫一個三角形,不然不會這樣浪費空間的

 

經過上面的介紹,我們再次回顧最開始展示的數據

        0.0f, 0.0f, 0.5f, 0.5f,
        -1.0f, -1.0f, 0f, 1.0f,
        1.0f, -1.0f, 1f, 1f,
        1.0f, 1.0f, 1f, 0.0f,
        -1.0f, 1.0f, 0f, 0.0f,
        -1.0f, -1.0f, 0f, 1f
是不是看懂了,這個就是採用我們上面介紹的GL_TRIANGLE_FAN方式繪製一個矩形,紋理座標和頂點座標對應就行,比如頂點座標左下角是[-1,-1],那麼對應紋理的左下角就是[0,1],這就是我們繪製一個矩形需要的數據。

 

2、數據傳遞

經過上面的步驟,我們有了繪製一個矩形的數據,接下來就是把數據傳遞給共享內存,然後通知GPU來取了

a) 傳遞數據到共享內存

floatBuffer = ByteBuffer.allocateDirect(vertexData.length * BYTE_PER_FLOAT)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(vertexData);

//最後記得一定要put進去,否則數據是沒有放到native memory中的

可以看到傳遞數據還是比較方便的,直接調用接口分配一塊共享內存,然後把我們構建的數據放進去

b) 通知GPU取數據

把數據放進去之後還沒結束,因爲GPU也是一臉懵逼,不知道你這數據幹嘛用的,所以接下來我們要告訴它怎麼用這些數據,先來看一段shader腳本:

attribute vec2 posCoord;
attribute vec2 texCoord;
varying vec2 texcoordOut;
uniform mat4 u_Matrix;

void main()
{
   texcoordOut = texCoord;
   gl_Position = u_Matrix * vec4(posCoord.x, posCoord.y, 0.0, 1.0);
}

上面的腳本就是傳遞給GPU使用教程,只要GPU按照這個腳本獲取數據就能達到我們的目的了,簡單介紹一下幾個變量:

1)attribute變量是只能在vertex  shader中使用的變量。(不能在fragment shader中聲明attribute變量,也不能在fragment shader中使用),一般用attribute變量來表示一些頂點的數據,如:頂點座標,法線,紋理座標,頂點顏色等。它是一種可變變量

2)uniform變量是一種常量,vertext shader和fragment shader都能使用,類似java裏面的final變量

3)varying變量是傳遞變量,只能在vertex shander中賦值,fragment shander中使用,它在兩者中的聲明必須一致

針對上面的介紹,我們知道頂點座標和紋理座標是一種可變變量,即類似於for循環一樣每次取指定位置的值放入腳本里面執行,所以上面的posCoord就是代表頂點座標,texCoord就是代表紋理座標,接下來就是怎樣給這兩個變量賦值的問題了。

a) 首先肯定是要找到這個變量的引用了,如下:

private int positionCoordinates;
private int textureCoordinates;

private static final String POSITION_COORDINATES = "posCoord";
private static final String TEXTURE_COORDINATES = "texCoord";

mVertexShader = MTGLUtil.compileShader(GLES20.GL_VERTEX_SHADER, getVertexShaderResource());
mFragmentShader = MTGLUtil.compileShader(GLES20.GL_FRAGMENT_SHADER, getFragmentShaderResource());
mProgram = MTGLUtil.buildProgram(mVertexShader, mFragmentShader);
GLES20.glDeleteShader(mVertexShader);
GLES20.glDeleteShader(mFragmentShader);

positionCoordinates = glGetAttribLocation(mProgram, POSITION_COORDINATES);
textureCoordinates = glGetAttribLocation(mProgram, TEXTURE_COORDINATES);

可以看到並不是很複雜,首先把我們的vertex shader和fragment shander加載進GPU,然後把這兩個腳本拿去構建出一個整體,即program結構,返回這個結構的引用,這個引用就是最終包含腳本所有內容的地址了,接下來獲取所有腳本相關的內容都是通過這個引用實現。

因爲頂點座標和紋理座標在腳本里的類型是attribute,所以這邊通過特定的api獲取到變量的引用,獲取方式很簡單,把變量名作爲key傳進去就行了。

 

b) 拿到變量的引用後就開始賦值了

floatBuffer.position(dataOffset);
glVertexAttribPointer(attributeLocation, componentCount, GL_FLOAT, false, stride, floatBuffer);
glEnableVertexAttribArray(attributeLocation);
floatBuffer.position(0);

在我們前期的準備工作下,現在我們的數據已經放在共享內存裏了,這裏通過floatBuffer變量進行引用,我們通過glVertexAttribPointer來將變量和數據綁定起來,先介紹一下函數的參數,glVertexAttribPointer(變量引用,變量維度,變量每個維度數據類型,是否歸一化,採樣間隔,數據源),簡單來說就是從一個數組中每隔stride個位置讀取componentCount個數據,每個數據作爲GPU循環調用腳本時傳入的變量值。

到這一步,GPU總算不懵逼了,首先它拿到了腳本,知道自己該做什麼了,然後根據我們綁定的變量和數據去共享內存裏取到數據,再渲染到屏幕上,最終我們就看到一個矩形了

 

  • 總結

經過上面的介紹,我們知道了GPU展示圖像的過程,也瞭解了openGL在android上的基本使用,但是這裏我們只繪製了一個矩形,還沒介紹矩形裏面的圖像是怎麼來的,這個後面繼續介紹。

上面所有的代碼都封裝成了基本工具類,有興趣的可以查看

https://github.com/yjp123456/CommonLib

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章