在Android中使用OpenGL ES進行開發第(三)節:繪製圖形


1、前期基礎知識儲備
筆者計劃寫三篇文章來詳細分析OpenGL ES基礎的同時也是入門關鍵的三個點:編程

①OpenGL ES是什麼?與OpenGL的關係是什麼?——概念部分canvas

②使用OpenGLES繪製2D/3D圖形的第一步:定義圖形;——運用部分ide

③使用OpenGLES繪製出②步驟中定義好的圖形:——運用部分,難點所在函數

經過這三篇文章的分析,就像給萬丈高樓墊定了基石,萬丈高樓平地起,後面利用OpenGLES作各類效果,各類變換都是創建在這三步的圖形編程理解之上的。工具

今天開始第三節——繪製圖形部分的分析,重難點所在!orm

在前面的兩篇文章《在Android中使用OpenGL ES進行開發第(一)課:概念先行》《在Android中使用OpenGL ES進行開發第(二)課:定義圖形》中,筆者詳細分析了OpenGL ES2.0相關的重要概念和實現了一個三角形的頂點座標的定義,那麼接下來,本節文章就來說第二篇文章中定義好的圖形具體繪製出來,這也是筆者這三篇文章的重難點所在。對象

 

正如第一篇文章中說起的,使用OpenGL ES2.0的時候,儘管寫最簡單的程式(如基本做圖、三角形、矩形或動做translate、rotate、scale等),必定要寫shader(着色器/渲染器)才能運做,本來這些在OpenGL ES1.x時是系統作的事,如今要人力進行實現,於是提升了開發的難度。索引

2、上代碼,具體實現
第一步:渲染器類中初始化圖形—簡單;生命週期

public class MyGLRenderer2 implements GLSurfaceView.Renderer {內存

...
private Triangle mTriangle;

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// initialize a triangle
mTriangle = new Triangle();
}
...
}
第二步:爲draw()方法作準備—建立着色器對象(shader),放入程式(Program)中,將程式連接至GLES;

着色器相關概念(重點):咱們在第一篇文章中花了大量筆墨來介紹着色器(shader),那麼着色器究竟是什麼?

①頂點着色器(Vertex Shader):用來渲染圖形頂點的 OpenGL ES 代碼(glsl語言編寫);生成每一個頂點最終位置,針對每一個頂點都會執行一次;一旦最終位置肯定了,OpenGL就能夠把這些可見頂點的集合組裝成點、直線和三角形;

②片元着色器(Fragment Shader):使用顏色或紋理(texture)渲染圖形表面的 OpenGL ES 代碼(glsl語言編寫);爲組成點、直線和三角形的每一個片元生成最終顏色/紋理,針對每一個片元都會執行一次;一個片元是一個小的、單一顏色的長方形區域,相似於計算機屏幕上的一個像素;

(一旦最終顏色生成,OpenGL就會把它們寫到一塊稱爲幀緩衝區的內存塊中,而後Android就會把這個幀緩衝區顯示在屏幕上)

③程式(Program):一個OpenGL ES 對象,包含了你但願用來繪製圖形所要用到的着色器,最後頂點着色器和片元着色器都要放入到程式中,而後才能使用;簡單來講就是將兩個着色器變爲一個對象。

以上三個,你須要至少一個頂點着色器(Vertex Shader)來定義一個圖形頂點,以及一個片元着色器(Fragment Shader)爲該圖形上色。這些着色器必須被編譯而後再添加到一個OpenGLES Program當中,並利用這個 progrem 來繪製形狀。

這段代碼在本來的OpenGL ES1.X中是由系統實現的,因此寫起來1.x的代碼比較簡單,可是在2.x中咱們採用人力的方式實現,雖然有些複雜,可是,經過編寫頂點及片元着色器程序,來完成一些頂點變換和紋理顏色計算工做,能夠實現更加靈活、精細化的計算與渲染。

 

熟悉着色器概念以後,那麼咱們代碼具體實現分三步走:

①圖形類中,建立兩個GLSL代碼段——頂點着色器代碼段+片元着色器代碼;

OpenGL ES實現3D繪圖和普通的2D繪圖即view利用canvas來繪製不同,OpenGL須要加載GLSL程式,讓GPU進行繪製。因此須要定義shader代碼,並在初始化的時候加載。

public class Triangle {

/**
* 頂點着色器代碼
* attribute變量(屬性變量)只能用於頂點着色器中
* uniforms變量(一致變量)用來將數據值從應用程其序傳遞到頂點着色器或者片元着色器。 。
* varying變量(易變變量)是從頂點着色器傳遞到片元着色器的數據變量。
* gl_Position (必須)爲內建變量,表示變換後點的空間位置。
*/
private final String vertexShaderCode =
"attribute vec4 vPosition;" + // 應用程序傳入頂點着色器的頂點位置
"void main() {" +
" gl_Position = vPosition;" + // 設置這次繪製此頂點位置
"}";

/**
* 片元着色器代碼
*/
private final String fragmentShaderCode =
"precision mediump float;" + // 設置工做精度
"uniform vec4 vColor;" + // 應用程序傳入着色器的顏色變量
"void main() {" +
" gl_FragColor = vColor;" + // 顏色值傳給 gl_FragColor內建變量,完成片元的着色
"}";
...
}
OpenGL最本質的概念之一就是着色器,它是圖形硬件設備所執行的一類特殊函數。理解着色器最好的辦法就是把它看作是專爲圖形處理單元(即GPU)編譯的一種小型程序。OpenGL在其內部包含了全部的編譯器工具,能夠直接從着色器源碼建立GPU所須要的編譯代碼並執行。

注:任何一種OpenGL程序本質上均可以被分爲兩部分:CPU端運行的部分,採用C++、Java之類的語言編寫;以及GPU端運行的部分,使用GLSL語言編寫。

更多着色器代碼解析內容,感興趣的讀者能夠參考1文章、2文章、3文章

②渲染器類中,建立輔助方法,用於編譯①中建立的兩個代碼段。

該輔助方法中,咱們傳入兩個參數,第一個參數是着色器的類型,包含兩個頂點着色器(GLES20.GL_VERTEX_SHADER)和片元着色器(GLES20.GL_FRAGMENT_SHADER),第二個參數就是①中定義好的兩個着色器代碼段。

public class MyGLRenderer2 implements GLSurfaceView.Renderer
...

/**
* 加載並編譯着色器代碼
* 渲染器類型type={GLES20.GL_VERTEX_SHADER, GLES20.GL_FRAGMENT_SHADER}
* 渲染器代碼 GLSL
*/
public static int loadShader(int type, String shaderCode){

int shader = GLES20.glCreateShader(type);
//加載shader代碼 glShaderSource 和 glCompileShader
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);

return shader;
}
}
調用glCreateShader()方法-傳入渲染器類型參數type,建立對應的着色器對象;

調用glShaderSource()方法-傳入着色器對象和字符串shaderCode定義的源代碼,將兩者關聯起來;

調用glCompileShader()方法-傳入着色器對象,對其進行編譯。

③圖形類中,咱們傳入具體的參數到②中定義的方法裏面,獲得兩個着色器的對象,而後將兩個着色器對象放入到程式(Program)中。最後將程式與GLES連接好。

public class Triangle() {
...

private final int mProgram;

public Triangle() {
...

// 加載編譯頂點渲染器
int vertexShader = MyGLRenderer2.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);

// 加載編譯片元渲染器
int fragmentShader = MyGLRenderer2.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);

// 建立空的程式 - create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();

// attach shader 代碼 - add the vertex shader to program
GLES20.glAttachShader(mProgram, vertexShader);

// attach shader 代碼 - add the fragment shader to program
GLES20.glAttachShader(mProgram, fragmentShader);

// 連接GLSL程式 - creates OpenGL ES program executables
GLES20.glLinkProgram(mProgram);
}
}
作完以上兩個步驟,就爲圖形類中定義draw()方法作好了準備—draw()方法真是千呼萬喚始出來啊!下一步中,咱們將開始作最後的拼接,並把數據連接到OpenGL中。

第三步:圖形類中建立draw()方法,拿到連接至GLES的程式(Program),設置形狀的頂點位置和表面的顏色值;

下面的代碼爲形狀的頂點着色器和形狀着色器設置了位置和顏色值,而後執行繪製函數:

public class Triangle {

// 繪製形狀的頂點數量
private static final int COORDS_PER_VERTEX = 3;

...

private int mPositionHandle; //變量 用於存取attribute修飾的變量的位置編號
private int mColorHandle; //變量 用於存取uniform修飾的變量的位置編號

private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

public void draw() {
// 使用GLSL程式 - Add program to OpenGL ES environment
GLES20.glUseProgram(mProgram);

// 獲取shader代碼中的變量索引 get handle to vertex shader's vPosition member
// Java代碼中須要獲取shader代碼中定義的變量索引,用於在後面的繪製代碼中進行賦值
// 變量索引在GLSL程式生命週期內(連接以後和銷燬以前)都是固定的,只需獲取一次
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

// 綁定vertex座標值 調用glVertexAttribPointer()告訴OpenGL,它能夠在
// 緩衝區vertexBuffer中獲取vPosition的數據
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);

// 啓用vertex Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);

// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

// Set color for drawing the triangle
GLES20.glUniform4fv(mColorHandle, 1, color, 0);

// 經過 GLES20.glDrawArrays 或者 GLES20.glDrawElements 開始繪製
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}
draw()方法裏面的代碼是否是也很是複雜,不要緊,咱們捋一捋就能夠看清楚,整段代碼實際上能夠分爲三部分:

①1行代碼-在draw()方法中拿到連接至GLES中的程式(Program);

②3行代碼-從程式中取出頂點着色器,開始對頂點的位置進行設置;

③3行代碼-從程式中取出片元着色器,開始對圖形表面顏色進行設置。

第四步:渲染器類中調用圖形類中定義好的draw()方法,進行具體繪製

public class MyGLRenderer2 implements GLSurfaceView.Renderer {

@Override public void onDrawFrame(GL10 gl) { ... mTriangle.draw(); }}總結:到此爲止,筆者的OpenGL ES2.0的入門三課就已經結束了,覆蓋面是有的,可是並無仔細深刻,其實本來打算也是經過這三篇文章來普及一下基礎的OpenGL ES2.0用法,後續還會繼續寫提升篇的文章。(寫着寫着,發現想寫的還有不少,以後會再加上)

相關文章
相關標籤/搜索