OpenGL 的基本形狀是三角形,無論是繪制形狀還是填充,都是對(duì)于圖形進(jìn)行操作
對(duì)于一個(gè)平面圖形,繪制的結(jié)果是有正反面的,
著色器語言(GLSL)主要包括兩部分:Vertex shader(定點(diǎn)著色器,負(fù)責(zé)定點(diǎn)位置與坐標(biāo)變換,即決定顯示哪個(gè)部分,以何種位置/姿態(tài)顯示),F(xiàn)ragment shader(片元著色器,負(fù)責(zé)紋理的填充與轉(zhuǎn)換,即決定顯示成什么樣子)
OpenGL ES的屏幕坐標(biāo)系
OpenGL ES是一個(gè)三維的圖形庫,但是三維的圖像要在二維平面顯示,就要經(jīng)過一定的投影變換,將三維的空間以一定的方式顯示在二維屏幕上。
上圖顯示的是OpenGL ES的屏幕坐標(biāo)系,無論是X還是Y軸,取值范圍都是[-1,1],也就是說,即便你的手機(jī)是16:9的屏幕,對(duì)于OpenGL ES來說也是一個(gè)正方形的繪制范圍(是不是很奇怪)
初始化OpenGL ES環(huán)境
OpenGL ES的使用,一般包括如下幾個(gè)步驟:
- EGL Context初始化
- OpenGL ES初始化
- OpenGL ES設(shè)置選項(xiàng)與繪制
- OpenGL ES資源釋放(可選)
- EGL資源釋放
Android平臺(tái)提供了一個(gè)GLSurfaceView,來幫助使用者完成第一步和第五步,由于釋放EGL資源時(shí)會(huì)自動(dòng)釋放之前申請(qǐng)的OpenGL ES資源,所以需要我們自己做的就只有2和3。
使用GLSurfaceView
我們?cè)谥鞑季种幸胍粋€(gè)GLSurfaceView,并讓他充滿整個(gè)布局,并在Activity中獲取他的實(shí)例
<android.opengl.GLSurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
public class MainActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
glSurfaceView= (GLSurfaceView) findViewById(R.id.surface_view);
}
}
獲取實(shí)例以后,我們就可以對(duì)于這個(gè)GLSurfaceView進(jìn)行配置:
glSurfaceView.setEGLContextClientVersion(2);//設(shè)置EGL上下文的客戶端版本,因?yàn)槲覀兪褂玫氖荗penGL ES 2.0,所以設(shè)置為2
glSurfaceView.setRenderer(new GLRenderer());
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//代表渲染模式,選項(xiàng)有兩種(RENDERMODE_WHEN_DIRTY,RENDERMODE_CONTINUOUSLY),一個(gè)是需要渲染(觸控事件,渲染請(qǐng)求)才渲染,一個(gè)是不斷渲染。
GLSurfaceView.Renderer接口
GLRenderer是本文中的關(guān)鍵類,實(shí)現(xiàn)了GLSurfaceView.Renderer這個(gè)接口,用來完成繪制操作。現(xiàn)在我們來看看這個(gè)類的定義:
public class GLRenderer implements GLSurfaceView.Renderer {
//這個(gè)函數(shù)在Surface被創(chuàng)建的時(shí)候調(diào)用,每次我們將應(yīng)用切換到其他地方,再切換回來的時(shí)候都有可能被調(diào)用,在這個(gè)函數(shù)中,我們需要完成一些OpenGL ES相關(guān)變量的初始化
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
//每當(dāng)屏幕尺寸發(fā)生變化時(shí),這個(gè)函數(shù)會(huì)被調(diào)用(包括剛打開時(shí)以及橫屏、豎屏切換),width和height就是繪制區(qū)域的寬和高(上圖黑色區(qū)域)
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
//每一次繪制時(shí)這個(gè)函數(shù)都會(huì)被調(diào)用,之前設(shè)置了GLSurfaceView.RENDERMODE_CONTINUOUSLY,也就是說按照正常的速度,每秒這個(gè)函數(shù)會(huì)被調(diào)用60次,雖然我們還什么都沒做
@Override
public void onDrawFrame(GL10 gl) {
}
}
private Context context;
public GLRenderer(Context context) {
this.context = context;
}
glSurfaceView.setRenderer(new GLRenderer(this));
//用來讀取raw中的文本文件,并且以String的形式返回
public static String readRawTextFile(Context context, int resId) {
InputStream inputStream = context.getResources().openRawResource(resId);
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
我們?cè)趓aw文件夾中創(chuàng)建兩個(gè)文件,fragment_shader.glsl和vertex_shader.glsl,他們分別是片元著色器和頂點(diǎn)著色器的腳本,之前說的可編程管線,就是指OpenGL ES 2.0可以即時(shí)編譯這些腳本,來實(shí)現(xiàn)豐富的功能,兩個(gè)文件的內(nèi)容如下:
vertex_shader.glsl
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
}
- vec4是一個(gè)包含4個(gè)浮點(diǎn)數(shù)(float,我們約定,在OpenGL中提到的浮點(diǎn)數(shù)都是指float類型)的向量,
- attribute表示變?cè)?用來在Java程序和OpenGL間傳遞經(jīng)常變化的數(shù)據(jù),
- gl_Position 是OpenGL ES的內(nèi)建變量,表示頂點(diǎn)坐標(biāo)(xyzw,w是用來進(jìn)行投影變換的歸一化變量),我們會(huì)通過aPosition把要繪制的頂點(diǎn)坐標(biāo)傳遞給gl_Position
fragment_shader.glsl
precision mediump float;
void main() {
gl_FragColor = vec4(0,0.5,0.5,1);
}
- precision mediump float用來指定運(yùn)算的精度以提高效率(因?yàn)檫\(yùn)算量還是蠻大的),
- gl_FragColor 也是一個(gè)內(nèi)建的變量,表示顏色,以rgba的方式排布,范圍是[0,1]的浮點(diǎn)數(shù)
先完成onSurfaceCreated的代碼,使用readRawTextFile把文件讀進(jìn)來,然后創(chuàng)建一個(gè)OpenGL ES程序
String vertexShader = ShaderUtils.readRawTextFile(context, R.raw.vertex_shader);
String fragmentShader= ShaderUtils.readRawTextFile(context, R.raw.fragment_shader);
programId=ShaderUtils.createProgram(vertexShader,fragmentShader);
讀取文件應(yīng)該好理解,創(chuàng)建程序就比較復(fù)雜了,具體的步驟是這樣的,我們先看創(chuàng)建程序之前要做的事情:
- 創(chuàng)建一個(gè)新的著色器對(duì)象
- 上傳和編譯著色器代碼,就是我們之前讀進(jìn)來的String
- 讀取編譯狀態(tài)(可選)
public static int createProgram(String vertexSource, String fragmentSource) {
//我們先創(chuàng)建頂點(diǎn)著色器和片元著色器
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;
}
//然后用GLES20.glCreateProgram()創(chuàng)建程序,如果創(chuàng)建成功,會(huì)返回一個(gè)非零的值
int program = GLES20.glCreateProgram();
if (program != 0) {
GLES20.glAttachShader(program, vertexShader);//把程序和著色器綁定起來
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
//然后用GLES20.glLinkProgram(program)鏈接程序(編譯鏈接)
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);//和之前的類似,是用來獲取鏈接狀態(tài)的
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
//shaderType用來指定著色器類型,取值有GLES20.GL_VERTEX_SHADER和GLES20.GL_FRAGMENT_SHADER
//source就是剛才讀入的代碼
public static int loadShader(int shaderType, String source) {
//如果創(chuàng)建成功,那么shader會(huì)是一個(gè)非零的值
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
//我們用GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0)來獲取編譯的狀態(tài)
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader " + shaderType + ":");
Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
//如果創(chuàng)建失敗,就刪除這個(gè)著色器
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
另外還有一個(gè)打印錯(cuò)誤日志的功能函數(shù):
public static void checkGlError(String label) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, label + ": glError " + error);
throw new RuntimeException(label + ": glError " + error);
}
}
創(chuàng)建好了程序之后,我們獲取之前頂點(diǎn)著色器中,aPosition的引用,以便于傳送頂點(diǎn)數(shù)據(jù)
aPositionHandle= GLES20.glGetAttribLocation(programId,"aPosition");
完成向OpenGL的數(shù)據(jù)傳送OpenGL ES工作在native層(C、C++),如果要傳送數(shù)據(jù),我們需要使用特殊的方法把數(shù)據(jù)復(fù)制過去。
首先定義一個(gè)頂點(diǎn)數(shù)組,這是我們要繪制的三角形的三個(gè)頂點(diǎn)坐標(biāo)(逆時(shí)針),三個(gè)浮點(diǎn)數(shù)分別代表xyz,因?yàn)槭窃谄矫嫔侠L制,我們把z設(shè)置為0
private final float[] vertexData = {
0f,0f,0f,
1f,-1f,0f,
1f,1f,0f
};
如果程序正常工作,那么我們的三角形應(yīng)該出現(xiàn)在這個(gè)區(qū)域(見下圖):
我們使用一個(gè)FloatBuffer將數(shù)據(jù)傳遞到本地內(nèi)存
我們?cè)陬惖臉?gòu)造函數(shù)中把頂點(diǎn)數(shù)據(jù)傳遞過去:
//ByteBuffer用來在本地內(nèi)存分配足夠的大小
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)//申請(qǐng)一個(gè)內(nèi)存,大小是data*4個(gè)字節(jié),因?yàn)閐ata里是float,一個(gè)float是4個(gè)字節(jié)
//設(shè)置存儲(chǔ)順序?yàn)閚ativeOrder(關(guān)于存儲(chǔ)順序的更多資料可以在維基百科上找到)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);//把vertexData放進(jìn)去
vertexBuffer.position(0);//設(shè)定索引位
完成onDrawFrame
完成了上述工作以后,我們就可以畫個(gè)三角形,試試手
@Override
public void onDrawFrame(GL10 gl) {
//清空顏色緩沖區(qū)和深度緩沖區(qū)
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT |GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(programId);//指定使用剛才創(chuàng)建的那個(gè)程序
//啟用頂點(diǎn)數(shù)組,aPositionHandle就是我們傳送數(shù)據(jù)的目標(biāo)位置
GLES20.glEnableVertexAttribArray(aPositionHandle);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
12, vertexBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}
- GLES20.glVertexAttribPointer的原型是這樣的:
glVertexAttribPointer(
int indx,
int size,
int type,
boolean normalized,
int stride,
java.nio.Buffer ptr
)
stride表示步長(zhǎng),因?yàn)橐粋€(gè)頂點(diǎn)三個(gè)坐標(biāo),一個(gè)坐標(biāo)是float(4字節(jié)),所以步長(zhǎng)是12字節(jié)
(當(dāng)然,這個(gè)只在一個(gè)數(shù)組中同時(shí)包含多個(gè)屬性時(shí)才有作用,例如同時(shí)包含紋理坐標(biāo)和頂點(diǎn)坐標(biāo),在只有一種屬性時(shí)(例如現(xiàn)在),和傳遞0是相同效果)
- 最后,我們用GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);把三角形畫出來,glDrawArrays的原型如下
public static native void glDrawArrays(
int mode,
int first,
int count
);
因?yàn)镺penGL會(huì)把整個(gè)屏幕(其實(shí)是整個(gè)可以繪制的區(qū)域,也就是前面黑色的區(qū)域)當(dāng)成輸出,所以我們畫出來的三角形出現(xiàn)了變形。那么橫屏的情況下是什么樣的呢? 來看一下:
解決變形問題
要解決變形,要先掌握Projection Matrix(投影矩陣)的概念,在OpenGL中,投影矩陣用來改變場(chǎng)景在屏幕上的顯示方式(近大遠(yuǎn)小,平行投影等等)。
因?yàn)槲覀兝L制的是二維平面,所以問題還是比較好解決的,假如我們的屏幕是16:9的(橫屏情況),那么只要讓OpenGL的繪制范圍也是16:9的就好了,如下圖所示:
可以看到,我們把OpenGL的繪制區(qū)域橫向拉長(zhǎng)了,拉長(zhǎng)的比例就是(16/9 約1.777)
更多關(guān)于透視和投影變換
可以參考http://blog.csdn.net/popy007/article/details/1797121
那么代碼要如何實(shí)現(xiàn)呢?答案是矩陣,進(jìn)行正交投影的操作,我們并不需要去推導(dǎo)正交矩陣如何求出來,Android 提供的Matrix類中包含這個(gè)方法。
我們先聲明一個(gè)長(zhǎng)度16的float數(shù)組,這是Matrix的標(biāo)準(zhǔn)尺寸
private final float[] projectionMatrix=new float[16];
注意,在OpenGL中,數(shù)組是row-major的,如下所示:
/**
* Matrix math utilities. These methods operate on OpenGL ES format
* matrices and vectors stored in float arrays.
* <p>
* Matrices are 4 x 4 column-vector matrices stored in column-major
* order:
* <pre>
* m[offset + 0] m[offset + 4] m[offset + 8] m[offset + 12]
* m[offset + 1] m[offset + 5] m[offset + 9] m[offset + 13]
* m[offset + 2] m[offset + 6] m[offset + 10] m[offset + 14]
* m[offset + 3] m[offset + 7] m[offset + 11] m[offset + 15]</pre>
*/
創(chuàng)建了projectionMatrix以后,我們還需要更新glsl中頂點(diǎn)著色器的代碼,以便把這個(gè)變換用的矩陣傳遞過去,如下所示:
attribute vec4 aPosition;
uniform mat4 uMatrix;
void main() {
gl_Position = uMatrix*aPosition;
}
uniform 是GLSL中的常量類型,之前的attribute類型是用來在Java代碼和頂點(diǎn)著色器(Vertex Shader)傳遞變量用的,uniform則是給頂點(diǎn)著色器(Vertex Shader)和片元著色器(Fragment Shader)傳遞常量用的。
我們把uMatrix和aPosition做矩陣乘法,就得到了一個(gè)新的頂點(diǎn)位置。
類似的,我們也需要一個(gè)入口,以便給這個(gè)矩陣傳遞數(shù)據(jù)
uMatrixHandle=GLES20.glGetUniformLocation(programId,"uMatrix");
完成正交投影
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
float ratio=width>height?(float)width/height:(float)height/width;
if (width>height){
Matrix.orthoM(projectionMatrix,0,-ratio,ratio,-1f,1f,-1f,1f);
}else Matrix.orthoM(projectionMatrix,0,-1f,1f,-ratio,ratio,-1f,1f);
}
在onSurfaceChanged中,我們獲取了屏幕的寬和高,所以我們?cè)谶@里計(jì)算縮放的比例,需要注意的是橫屏和豎屏的時(shí)候是剛好相反的處理(一個(gè)改變x,一個(gè)改變y)
正交投影方法 : Matrix.orthoM() 方法設(shè)置正交投影;
public static void orthoM(float[] m, int mOffset,float left, float right, float bottom, float top,float near, float far)
左右(x)下上(y)近遠(yuǎn)(z)
更新onDrawFrame
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(programId);
GLES20.glUniformMatrix4fv(uMatrixHandle,1,false,projectionMatrix,0);
GLES20.glEnableVertexAttribArray(aPositionHandle);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
12, vertexBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}
看一下效果吧:
如果是豎屏的情況,那么應(yīng)該是這樣子的:
下面我們來顯示一張圖片,圖片可以看做一個(gè)矩形,所以我們先來畫一個(gè)矩形
之前提到OpenGL的基本形狀是三角形,一個(gè)矩形可以看成由4個(gè)三角形構(gòu)成,如果我們一個(gè)一個(gè)畫,那需要12個(gè)頂點(diǎn),36個(gè)坐標(biāo),效率不高,所以我們采用另外一種方式——頂點(diǎn)索引與glDrawElements配合使用。
什么是頂點(diǎn)索引呢?頂點(diǎn)索引就是給出頂點(diǎn)的下標(biāo)而不給出具體的頂點(diǎn)坐標(biāo),看代碼:
private final float[] vertexData = {
0f,0f,0f,
1f,1f,0f,
-1f,1f,0f,
-1f,-1f,0f,
1f,-1f,0f
};
private final short[] indexData = {
0,1,2,
0,2,3,
0,3,4,
0,4,1
};
我們的繪制區(qū)域是(-1,-1)到(1,1)的平面區(qū)域,vertexData給出了5個(gè)頂點(diǎn),indexData給出了4個(gè)三角形的描述:
聲明一個(gè)ShortBuffer ,用來存放頂點(diǎn)的索引數(shù)據(jù)
private ShortBuffer indexBuffer;
indexBuffer = ByteBuffer.allocateDirect(indexData.length * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(indexData);
indexBuffer.position(0);
然后,使用GLES20.glDrawElements把三角形畫出來,注意如果我們之前的數(shù)組類型是byte,那么就應(yīng)該使用GLES20.GL_UNSIGNED_BYTE,總之類型要對(duì)齊
GLES20.glDrawElements(GLES20.GL_TRIANGLES,indexData.length,GLES20.GL_UNSIGNED_SHORT,indexBuffer);
創(chuàng)建一個(gè)紋理
紋理的創(chuàng)建比較復(fù)雜,我們創(chuàng)建一個(gè)新的工具類,并且加入如下代碼:
public class TextureHelper {
private static final String TAG="TextureHelper";
public static int loadTexture(Context context,int resourceId){
final int[] textureObjectIds=new int[1];
GLES20.glGenTextures(1,textureObjectIds,0);
if (textureObjectIds[0]==0){
Log.d(TAG,"生成紋理對(duì)象失敗");
return 0;
}
BitmapFactory.Options options=new BitmapFactory.Options();
options.inScaled=false;
Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(),resourceId,options);
if (bitmap==null){
Log.d(TAG,"加載位圖失敗");
GLES20.glDeleteTextures(1,textureObjectIds,0);
return 0;
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectIds[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);
bitmap.recycle();
//為與target相關(guān)聯(lián)的紋理圖像生成一組完整的mipmap
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
return textureObjectIds[0];
}
}
GLES20.glGenTextures(1,textureObjectIds,0);生成一個(gè)紋理,放入textureObjectIds中,同樣地,如果生成成功,那么就會(huì)返回一個(gè)非零值
然后我們將bitmap從raw中讀進(jìn)來,這個(gè)就不解釋了
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectIds[0]);的作用是將我們剛生成的紋理和OpenGL的2D紋理綁定,告訴OpenGL這是一個(gè)2D的紋理(貼圖)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINE AR);
這兩句話用來設(shè)置紋理過濾的方式,GL_TEXTURE_MIN_FILTER是指縮小時(shí)的過濾方式,GL_TEXTURE_MAG_FILTER則是放大的
關(guān)于放大和縮小時(shí)的可用方式,參見下圖:GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);
然后將紋理加載到OpenGL中,并且及時(shí)回收bitmapGLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);用于解除和紋理的綁定,等使用時(shí)再綁定
OpenGL紋理坐標(biāo)系
又是一個(gè)新的坐標(biāo)系,
S-T坐標(biāo)系(橙色)就是OpenGL紋理坐標(biāo)系,藍(lán)色的是OpenGL屏幕坐標(biāo)系,對(duì)比一下,明顯的區(qū)別有:
- 原點(diǎn)的位置不一樣
- 取值的范圍不一樣
- Y軸的方向也剛好和T軸相反
- 如果我們要將一張圖貼到整個(gè)顯示區(qū)域,即(-1,-1,0)-(1,1,0),那么對(duì)應(yīng)的關(guān)系就如圖中所示
vertex_shader.glsl
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
uniform mat4 uMatrix;
void main() {
vTexCoord=aTexCoord;
gl_Position = uMatrix*aPosition;
}
- 首先,aTexCoord是一個(gè)二維向量,表示紋理的坐標(biāo),
- varying這個(gè)變量是用來在vertex_shader和fragment_shader之間傳遞值用的,所以名稱要相同,我們把a(bǔ)TexCoord賦值給vTexCoord,
然后來看片元著色器的代碼
fragment_shader.glsl
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
void main() {
//gl_FragColor = vec4(0,0.5,0.5,1);
gl_FragColor = texture2D(sTexture,vTexCoord);
}
在片元著色器中,我們聲明了一個(gè)uniform常量,類型是sampler2D,這個(gè)類型是指一個(gè)二維的紋理數(shù)據(jù)數(shù)組
使用texture2D來處理被插值的紋理坐標(biāo)vTexCoord和紋理數(shù)據(jù)sTexture,得到的顏色值就是要顯示的顏色,交給gl_FragColor
更新Renderer類
首先,利用剛才寫的類來獲得一個(gè)紋理ID,這句話放在onSurfaceCreated里面:
textureId=TextureHelper.loadTexture(context,R.raw.demo_pic);
然后我們加入紋理坐標(biāo)數(shù)據(jù)(參見上圖的對(duì)應(yīng)關(guān)系)
private final float[] textureVertexData = {
0.5f,0.5f,
1f,0f,
0f,0f,
0f,1f,
1f,1f
};
并把它復(fù)制到OpenGL的本地內(nèi)存中
textureVertexBuffer = ByteBuffer.allocateDirect(textureVertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureVertexData);
textureVertexBuffer.position(0);
類似的,我們要獲得剛才的變量、常量引用(handle):
uTextureSamplerHandle=GLES20.glGetUniformLocation(programId,"sTexture");
aTextureCoordHandle=GLES20.glGetAttribLocation(programId,"aTexCoord");
更新onDrawFrame
首先,把紋理坐標(biāo)用類似的方法傳遞過去:
GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
GLES20.glVertexAttribPointer(aTextureCoordHandle,2,GLES20.GL_FLOAT,false,8,textureVertexBuffer);
然后,我們啟用一個(gè)紋理,并把它和剛才生成的紋理ID綁定,再把紋理數(shù)據(jù)引用傳過去,因?yàn)槲覀儐⒂玫氖荊L_TEXTURE0,所以在glUniform1i中第二個(gè)參數(shù)是0(大家可以都改成1試一下,在這里應(yīng)該是一樣的效果):
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId);
GLES20.glUniform1i(uTextureSamplerHandle,0);
目前onDrawFrame的代碼如下:
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(programId);
GLES20.glUniformMatrix4fv(uMatrixHandle,1,false,projectionMatrix,0);
GLES20.glEnableVertexAttribArray(aPositionHandle);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
12, vertexBuffer);
GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
GLES20.glVertexAttribPointer(aTextureCoordHandle,2,GLES20.GL_FLOAT,false,8,textureVertexBuffer);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId);
GLES20.glUniform1i(uTextureSamplerHandle,0);
GLES20.glDrawElements(GLES20.GL_TRIANGLES,indexData.length,GLES20.GL_UNSIGNED_SHORT,indexBuffer);
}
運(yùn)行一下,顯示圖片