頂點(diǎn)和著色器
頂點(diǎn)
一個(gè)頂點(diǎn)就是代表幾何對(duì)象的一個(gè)拐角點(diǎn),這個(gè)點(diǎn)有很多附加屬性,最重要的屬性就是位置,代表這個(gè)頂點(diǎn)在空間中的定位
在代碼中定義二維頂點(diǎn)
用 float[] 存儲(chǔ)頂點(diǎn),這個(gè)數(shù)組通常稱(chēng)為頂點(diǎn)屬性 (attribute) 數(shù)組。因?yàn)樗鼈兌x在二維坐標(biāo)系里,所以每個(gè)頂點(diǎn)要用兩個(gè)浮點(diǎn)數(shù)進(jìn)行標(biāo)記,分別表示 x 軸和 y 軸的位置
由于之后會(huì)給頂點(diǎn)加三維數(shù)據(jù)、甚至顏色數(shù)據(jù),到時(shí)候一個(gè)頂點(diǎn)占據(jù)的就不是固定的兩個(gè)數(shù)據(jù)了,因此,可以用常量表示每個(gè)頂點(diǎn)的位置信息占 float[] 的幾個(gè)數(shù)據(jù),顏色信息占 float[] 的幾個(gè)數(shù)據(jù)
在OpenGL里,只能繪制點(diǎn)、直線以及三角形
點(diǎn)和直線可以用于某些特殊效果;只有三角形才能用來(lái)構(gòu)建擁有復(fù)雜的對(duì)象和紋理的場(chǎng)景。把組成三角形的點(diǎn)依次放在 float[] 中,再告訴OpenGL如何連接這些點(diǎn)形成三角形,再由足夠多的三角形組成復(fù)雜的對(duì)象
當(dāng)定義三角形的時(shí)候,以逆時(shí)針的順序排列頂點(diǎn)稱(chēng)為卷曲順序(winding order)。都使用這種一致的卷曲順序,可以?xún)?yōu)化性能。使用卷曲順序可以指出一個(gè)三角形屬于任何給定物體的前面或者后面,OpenGL可以忽略那些無(wú)論如何都無(wú)法被看到的后面的三角形
// 使用 float[] 存儲(chǔ)頂點(diǎn)屬性
float[] vertex = new float[]{
// 第一個(gè)三角形的頂點(diǎn)位置信息
-0.8f, -0.8f,
0.8f, 0.8f,
-0.8f, 0.8f,
// 第二個(gè)三角形的頂點(diǎn)位置信息
-0.8f, -0.8f,
0.8f, -0.8f,
0.8f, 0.8f,
// 中間的線條的頂點(diǎn)位置信息
-0.8f, 0f,
0.8f, 0f,
// 上下兩個(gè)點(diǎn)的頂點(diǎn)位置信息
0f, -0.5f,
0f, 0.5f
};
關(guān)于坐標(biāo)寫(xiě)法,無(wú)論是 x 還是 y 坐標(biāo),OpenGL 都會(huì)把屏幕映射到 [-1,1] 的范圍內(nèi),這就意味著屏幕的左邊對(duì)應(yīng)x軸的-1,而屏幕的右邊對(duì)應(yīng)+1,屏幕的底邊會(huì)對(duì)應(yīng)y軸的-1,而屏幕的頂邊就對(duì)應(yīng)+1,因此,頂點(diǎn)坐標(biāo)范圍要在 [-1,1] 的范圍內(nèi)
使頂點(diǎn)數(shù)據(jù)可以被OpenGL存取
完成了頂點(diǎn)的定義,但是,OpenGL 還不能存取它們,因?yàn)檫@些代碼的運(yùn)行環(huán)境與OpenGL運(yùn)行的環(huán)境使用了不同的語(yǔ)言
在模擬器或者手機(jī)設(shè)備上編譯和運(yùn)行Java代碼的時(shí)候,并不是直接運(yùn)行在硬件上的,而是運(yùn)行在虛擬機(jī)上,OpenGL是作為 native 系統(tǒng)庫(kù)直接運(yùn)行在硬件上,沒(méi)有虛擬機(jī),而運(yùn)行在虛擬機(jī)上的代碼不能直接訪問(wèn) native 環(huán)境,除非通過(guò)特定的API
有兩種方法可以與OpenGL通信:
- 使用Java本地接口 (JNI)
- 把內(nèi)存從Java堆復(fù)制到本地堆
使用Java本地接口 (JNI)
使用Java本地接口 (JNI),這個(gè)技術(shù)已經(jīng)由Android軟件開(kāi)發(fā)包提供了,當(dāng)調(diào)用android.opengl.GLES20包里的方法時(shí),實(shí)際上就是在后臺(tái)使用 JNI 調(diào)用本地系統(tǒng)庫(kù)
把內(nèi)存從Java堆復(fù)制到本地堆
就是改變內(nèi)存分配的方式,Java 有一個(gè)特殊的類(lèi)集合,它們可以分配本地內(nèi)存塊,并且把 Java 的數(shù)據(jù)復(fù)制到本地內(nèi)存。本地內(nèi)存可以被本地環(huán)境存取,不受垃圾回收器的管控,如下圖所示

把內(nèi)存從Java堆復(fù)制到本地堆的代碼:
// 表示每個(gè)浮點(diǎn)數(shù)都占用4個(gè)字節(jié),把存儲(chǔ)頂點(diǎn)的內(nèi)存從Java堆復(fù)制到本地堆的時(shí)候使用
private static final int BYTES_PER_FLOAT = 4;
......
mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertex);
mVertexBuffer.position(0);
使用 ByteBuffer.allocateDirect() 分配了一塊本地內(nèi)存,這塊內(nèi)存不會(huì)被垃圾回收器管理。這個(gè)方法需要知道要分配多少字節(jié)的內(nèi)存塊,因?yàn)轫旤c(diǎn)都存儲(chǔ)在一個(gè)浮點(diǎn)數(shù)組里,并且每個(gè)浮點(diǎn)數(shù)有4個(gè)字節(jié),所以這塊內(nèi)存的大小應(yīng)該是 vertex.length * BYTES_PER_FLOAT
order(ByteOrder.nativeOrder()) 方法告訴字節(jié)緩沖區(qū) ( byte buffer)按照本地字節(jié)序( native byte order)組織它的內(nèi)容,本地字節(jié)序是指,當(dāng)一個(gè)值占用多個(gè)字節(jié)時(shí),比如32位整型數(shù),字節(jié)按照從最重要位到最不重要位或者相反順序排列,可以認(rèn)為這與從左到右或者從右到左寫(xiě)一個(gè)數(shù)類(lèi)似。知道這個(gè)排序并不重要,重要的是作為一個(gè)平臺(tái)要使用同樣的排序,調(diào)用order(ByteOrder.nativeOrder())可以保證這一點(diǎn)
調(diào)用asFloatBuffer()得到一個(gè)可以反映底層字節(jié)的FloatBuffer類(lèi)實(shí)例,就可以直接使用浮點(diǎn)數(shù)操作
調(diào)用vertexData.put(vertex) 把數(shù)據(jù)從Java的內(nèi)存復(fù)制到本地內(nèi)存了。當(dāng)進(jìn)程結(jié)束的時(shí)候,這塊內(nèi)存會(huì)被釋放掉,一般情況下不用關(guān)心它
著色器
OpenGL管道
要想把頂點(diǎn)轉(zhuǎn)變?yōu)閳D像顯示到屏幕上,需要在OpenGL的管道( pipeline)中傳遞,這就需要使用著色器( shader),這些著色器會(huì)告訴圖形處理單元(GPU)如何繪制數(shù)據(jù)

頂點(diǎn)著色器
頂點(diǎn)著色器的主要作用是:為每個(gè)頂點(diǎn)生成最終位置
由于著色器使用的語(yǔ)言是GLSL,因此最好使用 Android Studio 安裝 GLSL Support 插件,這樣關(guān)鍵字才會(huì)高亮
頂點(diǎn)著色器代碼
新建 raw/draw1_vertex.vert 文件,寫(xiě)入如下代碼
attribute vec4 a_Position;
void main(){
gl_Position=a_Position;
gl_PointSize=10.0;
}
attribute
一個(gè)頂點(diǎn)會(huì)有幾個(gè)屬性,比如顏色和位置,關(guān)鍵字 "attribute" 就是把這些屬性放進(jìn)著色器的手段,例如上面的 "attribute vec4 a_Position" 就會(huì)把前面定義過(guò)的頂點(diǎn)位置賦值給 a_Position
vec4
對(duì)于我們定義過(guò)的每一個(gè)頂點(diǎn),頂點(diǎn)著色器都會(huì)被調(diào)用一次,當(dāng)它被調(diào)用的時(shí)候,它會(huì)在a_Position 屬性里接收當(dāng)前頂點(diǎn)的位置,這個(gè)屬性被定義成vec4類(lèi)型
一個(gè)vec4是包含4個(gè)分量的向量,如果這個(gè)vec4代表位置,可以認(rèn)為這4個(gè)分量是x、y、z和w坐標(biāo),x、y和z對(duì)應(yīng)一個(gè)三維位置,而w是一個(gè)特殊的坐標(biāo),默認(rèn)情況下,OpenGL都是把向量的前三個(gè)坐標(biāo)設(shè)為0,并把最后一個(gè)坐標(biāo)設(shè)為1
main
是著色器的入口
gl_Position
頂點(diǎn)著色器一定要給輸出變量 gl_Position 賦值,因?yàn)?OpenGL 會(huì)把 gl_Position 中存儲(chǔ)的值作為當(dāng)前頂點(diǎn)的最終位置,并把這些頂點(diǎn)組裝成點(diǎn)、直線和三角形
gl_PointSize
指定點(diǎn)的大小
片段著色器
片段著色器的主要作用是:為每個(gè)片段生成最終顏色
對(duì)于基本圖元(點(diǎn)、直線和三角形)的每個(gè)片段,片段著色器都會(huì)被調(diào)用一次,因此,如果一個(gè)三角形被映射到10000個(gè)片段,片段著色器就會(huì)被調(diào)用10000次
光柵化(Rasterization)技術(shù)
將頂點(diǎn)幾何信息轉(zhuǎn)換成柵格組成的圖像的過(guò)程
光柵化的目的是找出一個(gè)幾何單元所覆蓋的像素,根據(jù)三角形頂點(diǎn)的位置,確定需要多少個(gè)像素點(diǎn)才能構(gòu)成這個(gè)三角形,以及每個(gè)像素點(diǎn)應(yīng)該得到哪些信息
片段著色器代碼
新建 raw/draw1_fragment.frag 文件,寫(xiě)入如下代碼
precision mediump float;
uniform vec4 u_Color;
void main(){
gl_FragColor=u_Color;
}
精度限定符
在這個(gè)片段著色器中,"precision mediump float;" 定義了所有浮點(diǎn)數(shù)據(jù)類(lèi)型的默認(rèn)精度,就像在Java 代碼中選擇浮點(diǎn)數(shù)還是雙精度浮點(diǎn)數(shù)一樣,可以選擇lowp、mediump和 highp
頂點(diǎn)著色器同樣可以改變其默認(rèn)的精度,但是,對(duì)于一個(gè)頂點(diǎn)的位置而言,精確度是最重要的,因此默認(rèn)頂點(diǎn)著色器的精度設(shè)置成最高級(jí)highp
高精度數(shù)據(jù)類(lèi)型更加精確,但是這是以降低性能為代價(jià)的,對(duì)于片段著色器,選擇 mediump,是基于速度和質(zhì)量的權(quán)衡
uniform
這次我們傳遞一個(gè) uniform 類(lèi)型,叫做 u_Color,它不像 attribute 類(lèi)型,每個(gè)頂點(diǎn)都要設(shè)置一個(gè),一個(gè) uniform 會(huì)讓每個(gè)頂點(diǎn)都使用同一個(gè)值,除非我們?cè)俅胃淖兯?/p>
u_Color也是一個(gè) vec4,四個(gè)分量分別對(duì)應(yīng)紅色、綠色、藍(lán)色和阿爾法
gl_FragColor
片段著色器一定要給 gl_GragColor賦值,OpenGL會(huì)使用這個(gè)顏色作為當(dāng)前片段的最終顏色,例如上面的,把 u_Color 賦值給那個(gè)特殊的輸出變量 gl_FragColor
加載著色器
就是普通的讀取著色器代碼的文本內(nèi)容
public class FileUtil {
public static String getRawText(Context context, int rawId) {
InputStream inputStream = context.getResources().openRawResource(rawId);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuilder builder = new StringBuilder();
String line;
try {
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
} catch (Exception e) {
e.printStackTrace();
}
return builder.toString();
}
}
編譯著色器
public class ShaderHelper {
private static final String TAG = "ShaderHelper";
/**
* 編譯頂點(diǎn)著色器
*
* @param context
* @param rawId 頂點(diǎn)著色器代碼資源id
* @return
*/
public static int compileVertexShader(Context context, int rawId) {
return compileShader(context, GLES20.GL_VERTEX_SHADER, rawId);
}
/**
* 編譯片段著色器
*
* @param context
* @param rawId 片段著色器代碼資源id
* @return
*/
public static int compileFragmentShader(Context context, int rawId) {
return compileShader(context, GLES20.GL_FRAGMENT_SHADER, rawId);
}
/**
* 編譯著色器
*
* @param context
* @param type 著色器類(lèi)型,分為頂點(diǎn)著色器和片段著色器
* @param rawId 著色器代碼資源id
* @return
*/
private static int compileShader(Context context, int type, int rawId) {
// 更加type創(chuàng)建一個(gè)新的著色器對(duì)象,返回一個(gè)整型值,這個(gè)整型值就是OpenGL對(duì)象的引用
// 無(wú)論后面什么時(shí)候想要引用這個(gè)對(duì)象,就要把這個(gè)整型值傳回OpenGL
int shaderId = GLES20.glCreateShader(type);
// 檢查著色器對(duì)象是否成功創(chuàng)建,返回值為 0 則創(chuàng)建失敗
if (shaderId == 0) {
Log.d(TAG, "compileShader: glCreateShader filed!");
}
// 得到著色器代碼
String shaderSource = FileUtil.getRawText(context, rawId);
// 告訴OpenGL讀入字符串shaderSource引用的源代碼,并把它與shaderId所引用的著色器對(duì)象關(guān)聯(lián)起來(lái)
GLES20.glShaderSource(shaderId, shaderSource);
// 告訴OpenGL編譯先前上傳到shaderId的源代碼
GLES20.glCompileShader(shaderId);
// 檢查OpenGL是否能成功地編譯這個(gè)著色器
int[] status = new int[1];
// 告訴OpenGL讀取與shaderId關(guān)聯(lián)的編譯狀態(tài),并把編譯結(jié)果寫(xiě)入status的第0個(gè)元素
GLES20.glGetShaderiv(shaderId, GL_COMPILE_STATUS, status, 0);
if (status[0] != GL_TRUE) {
// 通過(guò)glGetShaderiv方法獲取編譯狀態(tài)的時(shí)候,OpenGL只給出一個(gè)簡(jiǎn)單的是或否的回答
// 如果失敗了,可以通過(guò)調(diào)用glGetShaderInfoLog(shaderId)獲得關(guān)于著色器信息日志
String shaderInfoLog = GLES20.glGetShaderInfoLog(shaderId);
Log.d(TAG, "compileShader: glCompileShader failed! log=" + shaderInfoLog);
// 編譯著色器失敗,就刪除這個(gè)著色器,并返回0
GLES20.glDeleteShader(shaderId);
return 0;
}
return shaderId;
}
}
生成OpenGL程序
我們已經(jīng)加載并編譯了一個(gè)頂點(diǎn)著色器和一個(gè)片段著色器,下一步就是把它們綁定在一起放入一個(gè)OpenGL程序
public class ProgramHelper {
private static final String TAG = "ProgramHelper";
/**
* 通過(guò)頂點(diǎn)和片段著色器生成OpenGL程序
*
* @param vertexId
* @param fragmentId
* @return
*/
public static int getProgram(int vertexId, int fragmentId) {
// 新建程序?qū)ο?,返回的整形值表示程序?qū)ο蟮囊? int program = GLES20.glCreateProgram();
// 檢查程序是否創(chuàng)建成功
if (program == 0) {
Log.d(TAG, "getProgram: create program failed!");
return 0;
}
// 將頂點(diǎn)著色器和片段著色器都附加到程序?qū)ο笊? GLES20.glAttachShader(program, vertexId);
GLES20.glAttachShader(program, fragmentId);
// 鏈接程序,把著色器聯(lián)合起來(lái)
GLES20.glLinkProgram(program);
// 檢查鏈接是否成功,鏈接成功并不能保證執(zhí)行成功
int[] status = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
// 如果鏈接失敗,打印這個(gè)程序的信息日志
String programInfoLog = GLES20.glGetProgramInfoLog(program);
Log.d(TAG, "getProgram: glLinkProgram failed! log=" + programInfoLog);
// 鏈接失敗,意味著這個(gè)程序無(wú)法使用,刪除它并返回0
GLES20.glDeleteProgram(program);
return 0;
}
// 正確創(chuàng)建程序后,代碼中就不再使用著色器引用了,因此刪除它
GLES20.glDeleteShader(vertexId);
GLES20.glDeleteShader(fragmentId);
return program;
}
/**
* 驗(yàn)證程序應(yīng)用程序有效性,在glUseProgram方法后調(diào)用
*
* @param program
*/
public static void validate(int program) {
// 是一個(gè)比較耗時(shí)的操作,一般只是用作調(diào)試
if (LogUtil.isDebug) {
int[] status = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_VALIDATE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
String programInfoLog = GLES20.glGetProgramInfoLog(program);
Log.d(TAG, "getProgram: program is invalidate log=" + programInfoLog);
// 程序無(wú)效,意味著這個(gè)程序無(wú)法使用,刪除它并返回0
GLES20.glDeleteProgram(program);
}
}
}
}
在渲染類(lèi)中調(diào)用OpenGL程序
public class MyRenderer implements GLSurfaceView.Renderer {
// 代表位置信息占據(jù)頂點(diǎn)數(shù)組幾個(gè)數(shù)據(jù)
private static final int POSITION_COMPONENT_COUNT = 2;
// 表示每個(gè)浮點(diǎn)數(shù)都占用4個(gè)字節(jié),把存儲(chǔ)頂點(diǎn)的內(nèi)存從Java堆復(fù)制到本地堆的時(shí)候使用
private static final int BYTES_PER_FLOAT = 4;
private final FloatBuffer mVertexBuffer;
private Context mContext;
private int u_color;
private int a_position;
private int program;
public MyRenderer(Context context) {
mContext = context;
// 使用 float[] 存儲(chǔ)頂點(diǎn)屬性
float[] vertex = new float[]{
// 第一個(gè)三角形的頂點(diǎn)位置信息
-0.8f, -0.8f,
0.8f, 0.8f,
-0.8f, 0.8f,
// 第二個(gè)三角形的頂點(diǎn)位置信息
-0.8f, -0.8f,
0.8f, -0.8f,
0.8f, 0.8f,
// 中間的線條的頂點(diǎn)位置信息
-0.8f, 0f,
0.8f, 0f,
// 上下兩個(gè)點(diǎn)的頂點(diǎn)位置信息
0f, -0.5f,
0f, 0.5f
};
// 將Java堆內(nèi)存的頂點(diǎn)數(shù)據(jù)復(fù)制到本地堆
mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertex);
}
/**
* 當(dāng)Surface被創(chuàng)建的時(shí)候,GLSurfaceView會(huì)調(diào)用這個(gè)方法
* 程序第一次運(yùn)行、設(shè)備被喚醒或者從其他activity切換回來(lái)時(shí),可能會(huì)被調(diào)用,因此可能會(huì)被調(diào)用多次
*
* @param gl10
* @param eglConfig
*/
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
// 設(shè)置清空屏幕用的顏色
glClearColor(1f, 1f, 1f, 1f);
// 頂點(diǎn)著色器和片段著色器的引用
int vertexShader = ShaderHelper.compileVertexShader(mContext, R.raw.draw1_vertex);
int fragmentShader = ShaderHelper.compileFragmentShader(mContext, R.raw.draw1_fragment);
// 利用頂點(diǎn)著色器和片段著色器生成OpenGL程序
program = ProgramHelper.getProgram(vertexShader, fragmentShader);
//將應(yīng)用程序設(shè)置為活動(dòng)程序,告訴OpenGL在繪制任何東西到屏幕上的時(shí)候要使用program程序
glUseProgram(program);
// 獲取在著色器中定義的uniform的位置,uniform的位置不能指定,當(dāng)OpenGL把著色器鏈接成一個(gè)程序的時(shí)候
// 它用一個(gè)位置編號(hào)把著色器中定義的uniform關(guān)聯(lián)起來(lái)了,這些位置編號(hào)用來(lái)給著色器發(fā)送數(shù)據(jù)
u_color = glGetUniformLocation(program, "u_Color");
// 獲取在著色器中定義的attribute的位置,如果不調(diào)用glBindAttribLocation()方法
// 給attribute分配位置編號(hào),OpenGL就會(huì)自動(dòng)分配
a_position = glGetAttribLocation(program, "a_Position");
// 告訴OpenGL從這個(gè)緩沖區(qū)中讀取數(shù)據(jù)之前,確保它會(huì)從開(kāi)頭處開(kāi)始讀取數(shù)據(jù)
mVertexBuffer.position(0);
// 關(guān)聯(lián)attribute與頂點(diǎn)數(shù)據(jù)的數(shù)組,也就是告訴OpenGL到哪里找到屬性a_Position對(duì)應(yīng)的數(shù)據(jù)
glVertexAttribPointer(a_position, 2, GL_FLOAT, false, 0, mVertexBuffer);
// 使能頂點(diǎn)數(shù)組,每個(gè)attribute都要調(diào)用一次,相當(dāng)于一個(gè)開(kāi)關(guān),調(diào)用后,OpenGL才知道到哪里找到屬性a_Position對(duì)應(yīng)的數(shù)據(jù)
glEnableVertexAttribArray(a_position);
}
/**
* Surface被創(chuàng)建以后,每次Surface尺寸變化時(shí),這個(gè)方法都會(huì)被GLSurfaceView調(diào)用
* 例如在橫屏、豎屏來(lái)回切換的時(shí)候,Surface尺寸會(huì)發(fā)生變化
*
* @param gl10
* @param width
* @param height
*/
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
// 設(shè)置視口(viewport)尺寸,告訴OpenGL可以用來(lái)渲染的surface的大小
glViewport(0, 0, width, height);
}
/**
* 當(dāng)繪制一幀時(shí),這個(gè)方法會(huì)被GLSurfaceView調(diào)用,在這個(gè)方法中,一定要繪制一些東西,即使只是清空屏幕
* 因?yàn)?,在這個(gè)方法返回后,渲染緩沖區(qū)會(huì)被交換并顯示在屏幕上,如果什么都沒(méi)畫(huà),可能會(huì)看到閃爍效果
*
* @param gl10
*/
@Override
public void onDrawFrame(GL10 gl10) {
// 清空屏幕,會(huì)擦除屏幕上的所有顏色,并用之前glClearColor()調(diào)用定義的顏色填充整個(gè)屏幕
glClear(GL_COLOR_BUFFER_BIT);
// 調(diào)用glUniform4f()更新著色器代碼中的u_Color的值,與屬性不同,uniform 的分量沒(méi)有默認(rèn)值
// 因此,如果一個(gè)uniform在著色器中被定義為vec4類(lèi)型,需要提供所有四個(gè)分量的值
glUniform4f(u_color, 0f, 1f, 0f, 1f);
// 一個(gè)參數(shù)告訴OpenGL想要畫(huà)的形狀
// 第二個(gè)參數(shù)告訴OpenGL從頂點(diǎn)數(shù)組的哪里開(kāi)始讀頂點(diǎn)
// 第三個(gè)參數(shù)是告訴OpenGL讀入幾個(gè)頂點(diǎn)
glDrawArrays(GL_TRIANGLES, 0, 6);
glUniform4f(u_color, 0.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_LINES, 6, 2);
glUniform4f(u_color, 0f, 0f, 1f, 1f);
glDrawArrays(GL_POINTS, 8, 1);
glDrawArrays(GL_POINTS, 9, 1);
}
}
glVertexAttribPointer參數(shù)說(shuō)明
| 參數(shù) | 說(shuō)明 |
|---|---|
| int index | 屬性位置,通過(guò) glGetAttribLocation() 方法獲取的值 |
| int size | 屬性占幾個(gè)Float,我們?yōu)槊總€(gè)頂點(diǎn)只傳遞了兩個(gè)分量,代表 x 和 y 坐標(biāo),但是在著色器中,a_Position被定義為vec4,它有4個(gè)分量。如果分量沒(méi)有被指定值,默認(rèn)情況下,OpenGL 會(huì)把前3個(gè)分量設(shè)為0,最后一個(gè)分量設(shè)為1 |
| int type | 數(shù)據(jù)的類(lèi)型,一般都是 GL_FLOAT |
| boolean normalized | 只有使用整型數(shù)據(jù)的時(shí)候,這個(gè)參數(shù)才有意義,因此可以暫時(shí)把它安全地忽略掉 |
| int stride | 當(dāng)一個(gè)數(shù)組存儲(chǔ)多于一個(gè)屬性時(shí),例如同時(shí)存儲(chǔ)顏色和位置信息,應(yīng)該填入 (顏色+位置) 占的 Float個(gè)數(shù) * Float 所占字節(jié)數(shù) |
| Buffer ptr | 填入 從Java堆內(nèi)存的數(shù)據(jù)復(fù)制到本地堆的 Buffer |
效果
最好在真機(jī)上調(diào)試,我運(yùn)行在 Genymotion 模擬器上的時(shí)候,沒(méi)有任何報(bào)錯(cuò)信息,但中間的橫線畫(huà)不出來(lái)
