內容參考自 官方資料 和 Android OpenGL ES從白痴到入門。
下篇博文:Android OpenGL 開發---EGL 的使用
OpenGL 與 OpenGL ES
OpenGL(Open Graphics Library,譯名:開放圖形庫或者“開放式圖形庫”)是用於渲染 2D、3D 矢量圖形的跨語言、跨平台的應用程序編程接口(API)。OpenGL 不僅語言無關,而且平台無關。OpenGL 純粹專注於渲染,而不提供輸入、音頻以及窗口相關的 API。這些都有硬件和底層操作系統提供。OpenGL 的高效實現(利用了圖形加速硬件)存在於 Windows,部分 UNIX 平台和 Mac OS,可以便捷利用顯卡等設備。
OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對手機、PDA和游戲主機等嵌入式設備而設計。經過多年發展,現在主要有兩個版本,OpenGL ES 1.x 針對固定管線硬件的,OpenGL ES 2.x 針對可編程管線硬件。Android 2.2 開始支持 OpenGL ES 2.0,OpenGL ES 2.0 基於 OpenGL 2.0 實現。一般在 Android 系統上使用 OpenGL,都是使用 OpenGL ES 2.0,1.0 僅作了解即可。
EGL
EGL(Embedded Graphics Library)實際上是OpenGL和設備(又或者叫操作系統)間的中間件,因為 OpenGL 是平台無關的,是標准的,但設備是千奇百怪的,要對接就需要一個中間件做協調。也就是說一個設備要支持 OpenGL,那么它需要開發一套相對應的 API 來對接。在 Android 中就是 EGL。EGL 主要負責初始化 OpenGL 的運行環境和設備間的交互,簡單的說就是 OpenGL 負責繪圖,EGL 負責和設備交互。
實際上,OpenGL ES 定義了一個渲染圖形的 API,但沒有定義窗口系統。因為不同的操作系統,窗口機制可能不相同。
為了讓 GLES 能夠適合各種平台,通常 GLES 都與特定庫結合使用,這些庫可創建和訪問操作系統的窗口。在 Android 系統中,這個庫是 EGL。調用 GLES 渲染紋理多邊形,調用 EGL 將渲染放到屏幕上。
由上面的內容可知,在 Android 系統中,GLESxxx(如 GLES20、GLES30 等等)實現的是標准的 OpenGL 接口,而 EGLxxx(如 EGL10、EGL14 等等)實現的是 OpenGL 如何與 Android 系統交互。
坐標系
作為一個 Android 小開發。應該知道坐標系的概念,物體的位置都是通過坐標系確定的。OpenGL ES 采用的是右手坐標,選取屏幕中心為原點,從原點到屏幕邊緣默認長度為 1,也就是說默認情況下,從原點到(1,0,0)的距離和到(0,1,0)的距離在屏幕上展示的並不相同。坐標系向右為 X 正軸方向,向左為 X 負軸方向,向上為 Y 軸正軸方向,向下為 Y 軸負軸方向,屏幕面垂直向上為 Z 軸正軸方向,垂直向下為 Z 軸負軸方向。
通俗的講,在 OpenGL 中,世界就是一個坐標系,一個只有 X、Y 和 Z 三個緯度的世界,其它的東西都需要你自己來建設,你能用到的原材料就只有點、線和面(三角形),當然還會有其他材料,比如陽光(光照)和顏色(材質)。
相機
OpenGL 中的“相機”和現實世界中的相機不是一個東西,但概念的相同的,都是捕獲世界的景像呈現到二維平面上。可以將這里的“相機”想像成人眼,人眼看到的是什么樣子,相機呈現的就是什么樣子。
紋理
紋理是表示物體表面的一幅或幾幅二維圖形,也稱紋理貼圖(texture)。當把紋理按照特定的方式映射到物體表面上的時候,能使物體看上去更加真實。當前流行的圖形系統中,紋理繪制已經成為一種必不可少的渲染方法。在理解紋理映射時,可以將紋理看做應用在物體表面的像素顏色。在真實世界中,紋理表示一個對象的顏色、圖案以及觸覺特征。紋理只表示對象表面的彩色圖案,它不能改變對象的幾何結構。
簡單使用
本文中,我們主要是使用 GLSurfaceView。並且了解 OpenGL ES 的使用流程,其使用主流程可以概括如下:
在 Android 中,使用 OpenGL 最簡單的辦法便是使用官方提供的 GLSurfaceView 組件。其功能包括但不限於:
- 管理一個 surface,這個 surface 就是一塊特殊的內存,能直接排版到 android 的視圖 view 上。
- 管理一個 EGL display,它能讓 opengl 把內容渲染到上述的 surface 上。
- 用戶可以自定義渲染器(render)。
- 讓渲染器在獨立的線程里運作,和 UI 線程分離。傳統的 View 及其實現類,渲染等工作都是在主線程上完成的。
- 支持按需渲染(on-demand)和連續渲染(continuous)。
- 一些可選功能,如調試。
SurfaceView 實質是將底層顯存 Surface 顯示在界面上,而 GLSurfaceView 做的就是在這個基礎上增加 OpenGL 繪制環境。
使用 GLSurfaceView
首先,在 MainActivity 中使用 GLSurfaceView。可以將 GLSurfaceView 理解成畫布。
public class MainActivity extends Activity {
GLSurfaceView glsv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
glsv = findViewById(R.id.glsv);
// 設置 OpenGL 版本(一定要設置)
glsv.setEGLContextClientVersion(2);
// 設置渲染器(后面會講,可以理解成畫筆)
glsv.setRenderer(new MyRenderer());
// 設置渲染模式為連續模式(會以 60 fps 的速度刷新)
glsv.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
}
xml 布局如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.opengl.GLSurfaceView
android:id="@+id/glsv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
現在,OpenGL 的基本環境已經搭好了(即畫布已經有了),便可以作畫了(定義畫筆)。在 OpenGL 中,着色器 shader 就相當於畫筆。主要有兩種着色器:頂點着色器(Vertex Shader)和片元着色器(Fragment Shader)。頂點着色器可以叫做點着色器,其負責定義待渲染(繪制)的圖形的頂點信息;而片元着色器也可以叫做片着色器,其定義了如何填充圖形。比如現在我們想要繪制一個三角形,那么可以使用點着色器來定義三角形的三個頂點,三個頂點確定了,三角形的形狀也就確定了。而片着色器可以定義填充顏色,即可以定義三角形三條邊圍成的區域,所呈現出的樣子。
使用 GLSurfaceView 時,如果我們想要定義着色器,就得繼承 GLSurfaceView.Renderer 類(Renderer 的意思便是渲染,即渲染器)。而 OpenGL ES 2.0 是針對可編程管線硬件的,自然與編程息息相關。
首先,我們需要定義着色器的構建程序。程序如何寫,后面后詳講。
public class MyRenderer implements GLSurfaceView.Renderer {
// 點着色器的腳本
private static final String VERTEX_SHADER_SOURCE
= "attribute vec2 vPosition;\n" // 頂點位置屬性vPosition
+ "void main(){\n"
+ " gl_Position = vec4(vPosition,0,1);\n" // 確定頂點位置
+ "}";
// 片着色器的腳本
private static final String FRAGMENT_SHADER_SOURCE
= "precision mediump float;\n" // 聲明float類型的精度為中等(精度越高越耗資源)
+ "uniform vec4 uColor;\n" // uniform的屬性uColor
+ "void main(){\n"
+ " gl_FragColor = uColor;\n" // 給此片元的填充色
+ "}";
}
然后,我們定義三個變量,其代表 OpenGL 中相關的索引。
public class MyRenderer implements GLSurfaceView.Renderer {
// 程序索引
private int program;
// 頂點位置索引
private int vPosition;
// 片元所用顏色
private int uColor;
}
第三步,定義構建着色器的方法,代碼比較固定。
public class MyRenderer implements GLSurfaceView.Renderer {
/**
* 使用指定代碼構建 shader
*
* @param shaderType shader 的類型,包括 GLES20.GL_VERTEX_SHADER(點着色器)和 GLES20.GL_FRAGMENT_SHADER(片着色器)
* @param sourceCode shader 的創建腳本
*
* @return 創建的 shader 的索引,創建失敗會返回 0
*/
private int loadShader(int shaderType,String sourceCode) {
// 創建一個空的 shader 對象,並返回一個非 0 的引用標識。
int shader = GLES20.glCreateShader(shaderType);
// 創建失敗
if(shader == 0) {
return 0;
}
// 若創建成功則加載 shader,指定源碼
GLES20.glShaderSource(shader, sourceCode);
// 編譯 shader 的源代碼。
GLES20.glCompileShader(shader);
// 存放編譯成功 shader 的數量的數組
int[] compiled = new int[1];
// 獲取 Shader 的編譯情況
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
//若編譯失敗則顯示錯誤日志並刪除此shader
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
}
說明
- 使用 glShaderSource 方法指定着色器對象的源碼時,着色器對象的原有內容將被完全替換
- 使用 glCompileShader 編譯 shader 的源代碼時。應了解以下內容 --- Shader 編譯器是可選的,如果不確定是否支持,可以調用 glGet 方法。使用參數 GL_SHADER_COMPILER 來查詢。當 glShaderSource、glCompileShader、glGetShaderPrecisionFormat 和 glReleaseShaderCompiler 不支持時,會生成 GL_INVALID_OPERATION。
- glCompileShader 會編譯已存儲在由着色器指定的着色器對象中的源代碼字符串,並將編譯結果保存。可以通過 glGetShaderiv 來查詢。無論編譯是否成功,有關編譯的信息都可以通過調用 glGetShaderInfoLog 從着色器對象的信息日志中獲取。
- glGetShaderiv 可以查詢 shader 對象的狀態信息。參數包括 GL_SHADER_TYPE、GL_DELETE_STATUS、GL_COMPILE_STATUS、GL_INFO_LOG_LENGTH(存儲日志信息所需的字符緩沖區大小)、GL_SHADER_SOURCE_LENGTH(存儲着色器源碼所需的字符緩沖區的大小)。
第四步,定義創建 program 的方法。
public class MyRenderer implements GLSurfaceView.Renderer {
/**
* 創建程序的方法
*/
private int createProgram(String vertexSource, String fragmentSource) {
//加載頂點着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
// 加載片元着色器
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
// 創建程序
int program = GLES20.glCreateProgram();
if(program == 0){
return 0;
}
// 若程序創建成功則向程序中加入頂點着色器與片元着色器
// 向程序中加入點着色器
GLES20.glAttachShader(program, vertexShader);
// 向程序中加入片着色器
GLES20.glAttachShader(program, pixelShader);
// 鏈接程序
GLES20.glLinkProgram(program);
// 存放鏈接成功 program 數量的數組
int[] linkStatus = new int[1];
// 獲取 program 的鏈接情況
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
// 若鏈接失敗則報錯並刪除程序
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
return program;
}
}
說明
- glCreateProgram 函數將創建一個空的 program 對象,並返回一個非零的引用標識。program 對象可以附加和移除着色器對象,一般包含頂點着色器和片元着色器。program 會根據附加的着色器對象創建一些可執行文件,並使之成為當前渲染環境的一部分。
- glAttachShader 將着色器對象附加到指定的 program 對象上,以便在鏈接時生成可執行文件。附加操作在着色器對象生成后,就可以進行。這意味着可以在着色器對象加載源碼之前,就將它附加到 program 對象。
- 多個相同類型的着色器對象可能不會附加到單個程序對象。但是,單個着色器對象可能會附加到多個程序對象。
- 如果着色器對象在附加到程序對象時被刪除,它將被標記為刪除狀態(實際未被刪除)。調用 glDetachShader 方法,將它從所連接的所有程序對象中分離出來之后,刪除操作才會進行。
- glLinkProgram 執行鏈接操作,並保存鏈接狀態。shader 將根據源碼創建可執行文件,所有與 program 相關的用戶定義的 uniform 變量將被初始化為 0,並且生成一個可以訪問的地址。可以通過調用 glGetUniformLocation 來查詢。所有未綁定到頂點屬性索引的 attribute(屬性),此時都將被鏈接器綁定。分配的位置可以通過調用 glGetAttribLocation 來查詢。
- 對於附加的着色器對象,鏈接操作之后,program 可以自由修改、編譯、分離、刪除以及附加其他着色器對象。
第五步,獲取圖形頂點,此步驟用來定義圖形形狀:
public class MyRenderer implements GLSurfaceView.Renderer {
/**
* 獲取圖形的頂點
*
* @return 頂點 Buffer
*/
private FloatBuffer getVertices() {
float vertices[] = {
0.0f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f,
};
// 創建頂點坐標數據緩沖
// vertices.length*4是因為一個float占四個字節
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
// 設置字節順序
vbb.order(ByteOrder.nativeOrder());
// 轉換為Float型緩沖
FloatBuffer vertexBuf = vbb.asFloatBuffer();
// 向緩沖區中放入頂點坐標數據
vertexBuf.put(vertices);
// 設置緩沖區起始位置
vertexBuf.position(0);
// 返回數據結果
return vertexBuf;
}
}
說明
- Android 的 OpenGL 底層是用 C/C++ 實現的,所以和 Java 的數據類型字節序列有一定的區別,主要是數據的大小端問題。ByteBuffer.order() 方法設置以下數據的大小端順序,順序設置為 native 層的數據順序。使用 ByteOrder.nativeOrder() 可以得到 native 層的大小端數據順序。
第六步,使用,進行具體繪制操作。主要是實現繼承自 GLSurfaceView.Renderer 的三個方法:
public class MyRenderer implements GLSurfaceView.Renderer {
/**
* 當 GLSurfaceView 中的 Surface 被創建的時候(界面顯示)回調此方法,一般在這里做一些初始化
*
* @param gl10 1.0 版本的 OpenGL 對象,這里用於兼容老版本,用處不大
* @param eglConfig egl 的配置信息(GLSurfaceView 會自動創建 egl,這里可以先忽略)
*/
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
// 初始化着色器
// 基於頂點着色器與片元着色器創建程序
program = createProgram(verticesShader, fragmentShader);
// 獲取着色器中屬性的位置引用 id(傳入的字符串是着色器腳本中定義的屬性名)
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
uColor = GLES20.glGetUniformLocation(program, "uColor");
// 設置清屏顏色,格式 RGBA,真正執行清屏是在 glClear() 方法調用后
GLES20.glClearColor(1.0f, 0, 0, 1.0f);
}
/**
* 當 GLSurfaceView 中的 Surface 被改變的時候回調此方法(一般是大小變化)
*
* @param gl10 同 onSurfaceCreated()
* @param width Surface 的寬度
* @param height Surface 的高度
*/
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
// 設置繪圖的窗口大小
GLES20.glViewport(0,0,width,height);
}
/**
* 當 Surface 需要繪制的時候回調此方法,根據 GLSurfaceView.setRenderMode() 設置的渲染模式不同回調的策略也不同:
* GLSurfaceView.RENDERMODE_CONTINUOUSLY : 固定一秒回調60次(60fps)
* GLSurfaceView.RENDERMODE_WHEN_DIRTY : 當調用GLSurfaceView.requestRender()之后回調一次
*
* @param gl10 同 onSurfaceCreated()
*/
@Override
public void onDrawFrame(GL10 gl10) {
// 獲取圖形的頂點坐標
FloatBuffer vertices = getVertices();
// 清屏
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
// 使用某套shader程序
GLES20.glUseProgram(program);
// 為畫筆指定頂點位置數據(vPosition),數據傳入 GPU 中。vPosition 可以理解成在 GPU 中的位置,而 vertices 是在 CPU 緩沖區中的數據
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertices);
// 設置渲染器允許訪問 GPU 中的數據
GLES20.glEnableVertexAttribArray(vPosition);
// 設置屬性 uColor(顏色 索引,R,G,B,A) 的數值
GLES20.glUniform4f(uColor, 0.0f, 1.0f, 0.0f, 1.0f);
// 繪制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);
}
}
說明
- glGetAttribLocation 用於獲取頂點屬性索引(分配的位置)
- glClearColor 只是設置清屏顏色,格式 RGBA,並不會執行清屏操作。真正執行清屏是在 glClear(GLbitfield mask) 方法調用后。標准 OpenGL 中,該方法有 4 種標志位。但是在 Android 中,只有三種標志位。
- GL_COLOR_BUFFER_BIT: 顏色緩沖
- GL_DEPTH_BUFFER_BIT: 深度緩沖
- GL_STENCIL_BUFFER_BIT: 模板緩沖
- GL_ACCUM_BUFFER_BIT: 累積緩沖(Android 的 OpenGL ES 版本中不存在這種標志位)
- glUseProgram 設置 program 為當前渲染狀態的一部分。如果 program 為 0,則當前渲染狀態指向無效的 program,任何 glDrawArrays 或 glDrawElements 命令都會提示未定義。program 對象和與之關聯的數據,可以在共享上下文的環境中共享。調用 glUseProgram 之后,program 已在使用中,此時會執行鏈接操作。如果鏈接成功,glLinkProgram 還會將生成的可執行文件安裝為當前渲染狀態的一部分。如果鏈接失敗,其鏈接狀態將設置為 GL_FALSE。但可執行文件和關聯狀態將保持為當前上下文狀態的一部分。直到 program 將其刪除為止。program 將其刪除后,在成功重新鏈接之前,它不能成為當前狀態的一部分。在 OpenGL ES 中,以下情況鏈接可能會失敗:
- 點着色器和片着色器都不在程序對象中
- 超過支持的活動屬性變量的數量
- 超出統一變量(uniform)的存儲限制和數量限制
- 點着色器或片着色器的主要功能缺失
- 在片着色器中實際使用的變量在點着色器中沒有以相同方式聲明(或者根本沒有聲明)
- 未正確賦值函數或變量名稱的引用
- 共享的全局聲明有兩種不同的類型或兩種不同的初始值
- 一個或多個附加的着色器對象尚未成功編譯(glCompileShader 方法),或者未成功加載預編譯的着色器二進制文件(通過 glShaderBinary 方法)
- 綁定通用屬性矩陣會導致,矩陣的某些行落在允許的最大值 GL_MAX_VERTEX_ATTRIBS 之外,找不到足夠的連續頂點屬性槽來綁定屬性矩陣
- 默認情況下,出於性能考慮,所有頂點着色器的屬性(Attribute)變量都是關閉的。這意味着數據在着色器端是不可見的,哪怕數據已經上傳到 GPU,由 glEnableVertexAttribArray 啟用指定屬性之后,才可在頂點着色器中訪問頂點的屬性數據。我們可以將 CPU 看作客戶端,GPU 看作服務器端。glVertexAttribPointer 只是建立了 CPU 和 GPU 之間的邏輯連接,從而實現了 CPU 數據上傳至 GPU。但是,數據在 GPU 端是否可見,即着色器能否讀取到數據。是由是否啟用了對應的屬性決定,這就是 glEnableVertexAttribArray 的功能,允許頂點着色器讀取 GPU(服務器端)的數據。
- glUniform4f 是 glUniform 的帶后綴形式,因為 OpenGL ES 是由 C 語言編寫的,但是 C 語言不支持函數的重載(native 層),所以會有很多名字相同后綴不同的函數版本存在。
- glDrawArrays 采用頂點數組方式繪制圖形。該函數根據頂點數組中的坐標數據和指定的模式,進行繪制。OpenGL ES 2.0 以后,參數有如下幾種(每種模式后都會帶上一張圖說明):
- GL_POINTS:點模式。單獨的將頂點畫出來
- GL_LINES:直線模式。單獨地將直線畫出來
- GL_LINE_LOOP:環線模式。連貫地將直線畫出來,會自動將最后一個頂點和第一個頂點通過直線連接起來
- GL_LINE_STRIP:連續直線模式。連貫地將直線畫出來。即 P0、P1 確定一條直線,P1、P2 確定一條直線,P2、P3 確定一條直線。
- GL_TRIANGLES:三角形模式。這個參數意味着 OpenGL 使用三個頂點來組成圖形。所以,在開始的三個頂點,將用頂點1,頂點2,頂點3來組成一個三角形。完成后,再用下一組的三個頂點(頂點4,5,6)來組成三角形,直到數組結束。
- GL_TRIANGLE_STRIP:連續三角形模式。用上個三角形開始的兩個頂點,和接下來的一個點,組成三角形。也就是說,P0,P1,P2這三個點組成一個三角形,P1,P2,P3這三個點組成一個三角形,P2,P3,P4這三個點組成一個三角形。
- GL_TRIANGLE_FAN:三角形扇形模式。跳過開始的2個頂點,然后遍歷每個頂點,與它們的前一個,以及數組的第一個頂點一起組成一個三角形。也就是說,對於 P0,P1,P2,P3,P4 這 5 個頂點。繪制邏輯如下:
- 跳過P0, P1, 從 P2 開始遍歷
- 找到 P2, 與 P2 前一個點 P1,與列表第一個點 P0 組成三角形:P0、P1、P2
- 找到 P3, 與 P3 前一個點 P2,與列表第一個點 P0 組成三角形:P0、P2、P3
- 找到 P4, 與 P4 前一個點 P3,與列表第一個點 P0 組成三角形:P0、P3、P4
最后,附上完整版的 Renderer 代碼:
public class MyRenderer implements GLSurfaceView.Renderer {
private int program;
private int vPosition;
private int uColor;
private int loadShader(int shaderType,String sourceCode) {
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {
GLES20.glShaderSource(shader, sourceCode);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
int program = GLES20.glCreateProgram();
if (program != 0) {
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, pixelShader);
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
private FloatBuffer getVertices() {
float vertices[] = {
0.0f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f,
};
// vertices.length*4是因為一個float占四個字節
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuf = vbb.asFloatBuffer();
vertexBuf.put(vertices);
vertexBuf.position(0);
return vertexBuf;
}
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
program = createProgram(verticesShader, fragmentShader);
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
uColor = GLES20.glGetUniformLocation(program, "uColor");
GLES20.glClearColor(1.0f, 0, 0, 1.0f);
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
GLES20.glViewport(0,0,width,height);
}
@Override
public void onDrawFrame(GL10 gl10) {
FloatBuffer vertices = getVertices();
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(program);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertices);
GLES20.glEnableVertexAttribArray(vPosition);
GLES20.glUniform4f(uColor, 0.0f, 1.0f, 0.0f, 1.0f);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);
}
private static final String verticesShader
= "attribute vec2 vPosition; \n"
+ "void main(){ \n"
+ " gl_Position = vec4(vPosition,0,1);\n"
+ "}";
private static final String fragmentShader
= "precision mediump float; \n"
+ "uniform vec4 uColor; \n"
+ "void main(){ \n"
+ " gl_FragColor = uColor; \n"
+ "}";
}
下一篇,我們來講講 EGL 的使用。