本節我們來看一下glDrawElements API的使用,對應的代碼是OpenGL\learn\src\main\java\com\opengl\learn\GlActiveTextureRender.java文件。
所有實例均有提供源碼,下載地址:Opengl ES Source Code。
API中文說明:GLES2.0中文API-glActiveTexture。
我們本節的內容很多都是基於上一節的基礎上用的,只是新加了一部分。glActiveTexture的API是用來激活紋理的,我們本節是直接調用glActiveTexture(GL_TEXTURE0),方法參數只有一個,表示需要激活的紋理ID。比如我們可以在片段着色器中定義多個紋理採樣器,每個採樣器都可以對應一個紋理ID,我們就可以同時激活它們,只要傳入每個紋理的ID就可以了。我們本節就來學習一下紋理基本的操作方法。
我們本節實現的效果圖如下:
我們把一張世界地圖用紋理畫了出來,GlActiveTextureRender類的所有代碼如下:
package com.opengl.learn;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;
import com.lime.common.ESShader;
import com.lime.common.TextureHelper;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_ELEMENT_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_STATIC_DRAW;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_UNSIGNED_SHORT;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindBuffer;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glBufferData;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDisableVertexAttribArray;
import static android.opengl.GLES20.glDrawElements;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenBuffers;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
public class GlActiveTextureRender implements GLSurfaceView.Renderer {
private static final String TAG = GlActiveTextureRender.class.getSimpleName();
private static final int BYTES_PER_FLOAT = 4;
private static final int BYTES_PER_SHORT = 2;
private static final int POSITION_COMPONENT_SIZE = 3;
private static final int COLOR_COMPONENT_SIZE = 4;
private final float[] mVerticesData =
{
-1.0f, 0.5f, 0.0f, // v0
-1.0f, -0.5f, 0.0f, // v1
1.0f, -0.5f, 0.0f, // v2
1.0f, 0.5f, 0.0f, // v3
};
private final short[] mIndicesData =
{
0, 1, 2,
0, 2, 3,
};
private final float[] mColorData =
{
0.5f, 0.5f, 1.0f, 1.0f, // c0
0.5f, 1f, 1.0f, 1.0f, // c1
1.0f, 1.0f, 0.5f, 1.0f, // c2
1.0f, 0.5f, 1.0f, 1.0f // c3
};
private final float[] mTexturePosiontData =
{
0.0f, 0.0f,
0.0f, 0.5f,
0.5f, 0.5f,
0.5f, 0.0f
};
private Context mContext;
private int mProgramObject;
private int mWidth, mHeight;
private FloatBuffer mVertices;
private FloatBuffer mColors;
private FloatBuffer mTextureBuffer;
private ShortBuffer mIndices;
private int aPosition, aColor, aTexturePosition;
private int[] mVBOIds = new int[4];
private int mTexture;
private int mTextureUniform;
public GlActiveTextureRender(Context context) {
mContext = context;
mVertices = ByteBuffer.allocateDirect(mVerticesData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertices.put(mVerticesData).position(0);
mColors = ByteBuffer.allocateDirect(mColorData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mColors.put(mColorData).position(0);
mTextureBuffer = ByteBuffer.allocateDirect(mTexturePosiontData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTextureBuffer.put(mTexturePosiontData).position(0);
mIndices = ByteBuffer.allocateDirect(mIndicesData.length * BYTES_PER_SHORT)
.order(ByteOrder.nativeOrder()).asShortBuffer();
mIndices.put(mIndicesData).position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
String vShaderStr = ESShader.readShader(mContext, "activetexture_vertexShader.glsl");
String fShaderStr = ESShader.readShader(mContext, "activetexture_fragmentShader.glsl");
// Load the shaders and get a linked program object
mProgramObject = ESShader.loadProgram(vShaderStr, fShaderStr);
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
aPosition = glGetAttribLocation(mProgramObject, "aPosition");
aColor = glGetAttribLocation(mProgramObject, "aColor");
aTexturePosition = glGetAttribLocation(mProgramObject, "aTexturePosition");
mTextureUniform = glGetUniformLocation(mProgramObject, "uTextureUnit");
glGenBuffers(4, mVBOIds, 0);
Log.e(TAG, "0: " + mVBOIds[0] + ", 1: " + mVBOIds[1] + ", 2: " + mVBOIds[2]);
// mVBOIds[0] - used to store vertex position
mVertices.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mVerticesData.length,
mVertices, GL_STATIC_DRAW);
// mVBOIds[1] - used to store vertex color
mColors.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mColorData.length,
mColors, GL_STATIC_DRAW);
mTextureBuffer.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[2]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mTexturePosiontData.length,
mTextureBuffer, GL_STATIC_DRAW);
// mVBOIds[2] - used to store element indices
mIndices.position(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[3]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, BYTES_PER_SHORT * mIndicesData.length, mIndices, GL_STATIC_DRAW);
mTexture = TextureHelper.loadTexture(mContext, R.mipmap.world);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void onDrawFrame(GL10 gl) {
glViewport(0, 0, mWidth, mHeight);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(mProgramObject);
drawWatermark();
}
private void drawWatermark() {
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
glEnableVertexAttribArray(aPosition);
glVertexAttribPointer(aPosition, POSITION_COMPONENT_SIZE,
GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
glEnableVertexAttribArray(aColor);
glVertexAttribPointer(aColor, COLOR_COMPONENT_SIZE,
GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[2]);
glEnableVertexAttribArray(mTexture);
glVertexAttribPointer(mTexture, 2,
GL_FLOAT, false, 0, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTexture);
glUniform1i(mTextureUniform, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[3]);
glDrawElements(GL_TRIANGLES, mIndicesData.length, GL_UNSIGNED_SHORT, 0);
glDisableVertexAttribArray(aPosition);
glDisableVertexAttribArray(aColor);
glDisableVertexAttribArray(mTexture);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
幾個頂點屬性和上一節一樣,還是使用VBO來實現的,既然我們要畫紋理,也就需要有紋理座標,所以我們把mVBOIds的長度增加爲4,通過如下的綁定順序,我們就可以看出來,四個緩存的順序是:頂點座標、頂點顏色、紋理座標、索引屬性。
mVertices.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mVerticesData.length,
mVertices, GL_STATIC_DRAW);
// mVBOIds[1] - used to store vertex color
mColors.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mColorData.length,
mColors, GL_STATIC_DRAW);
mTextureBuffer.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[2]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mTexturePosiontData.length,
mTextureBuffer, GL_STATIC_DRAW);
// mVBOIds[2] - used to store element indices
mIndices.position(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[3]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, BYTES_PER_SHORT * mIndicesData.length, mIndices, GL_STATIC_DRAW);
mTexture = TextureHelper.loadTexture(mContext, R.mipmap.world);
要使用glActiveTexture激活紋理,那麼和紋理相關的工作我們都必須作好:1、在片段着色器中定義紋理採樣器;2、傳入紋理座標到片段着色器中,要用紋理座標和採樣器進行採樣,以確定片段顏色。這是兩個和紋理相關最根本的目標,爲了完成它們,我們就需要定義紋理座標,並通過Opengl API賦值傳遞到頂點着色器,並通過varying再傳遞給片段着色器;我們還要解析紋理圖片,並把解析好的Bitmap與申請的緩衝區綁定,設置採樣方式,生成Mipmap紋理等等工作,不過這些都是流程性的東西,代碼中的TextureHelper工具類已經很好的完成了這些,大家可以直接使用。
有了這樣的思路,剩下的就是一步步把值正確的傳入到頂點着色器和片段着色器就可以了。下面我們來仔細看一下頂點座標和紋理座標的定義,這些是我們正確畫圖的保障。
private final float[] mVerticesData =
{
-1.0f, 0.5f, 0.0f, // v0
-1.0f, -0.5f, 0.0f, // v1
1.0f, -0.5f, 0.0f, // v2
1.0f, 0.5f, 0.0f, // v3
};
private final short[] mIndicesData =
{
0, 1, 2,
0, 2, 3,
};
private final float[] mColorData =
{
0.5f, 0.5f, 1.0f, 1.0f, // c0
0.5f, 1f, 1.0f, 1.0f, // c1
1.0f, 1.0f, 0.5f, 1.0f, // c2
1.0f, 0.5f, 1.0f, 1.0f // c3
};
private final float[] mTexturePosiontData =
{
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f
};
我們使用索引緩衝區來繪製,所以要畫出矩形,只需要定義出四個頂點就可以了,本節我們使用GL_TRIANGLES方式繪製矩形,所以兩個三角形一共需要六個頂點座標,所以mIndicesData頂點索引中元素個數是六個;顏色我們上一節已經說過了,需要和頂點對應,四個頂點對應四個顏色,不過本節我們其實是不需要顏色的,大家可以看下片段着色中,最後是把顏色直接註釋掉的。好,最重要的來看一下紋理座標,它指示我們的紋理應該如何放置,來和頂點座標對應。
紋理座標的原點(0,0)是在屏幕的左上角,水平向右爲X軸正方向,豎直向下爲Y軸正方向,而頂點座標是在屏幕中心點,這個區別一定要清楚。那麼我們要把世界地圖正確的鋪在界面上,就和頂點對應,頂點左上角對應紋理左上角(0,0),頂點左下角對應紋理左下角(0,1),頂點右下角對應紋理右下角(1,1),頂點右上角對應紋理右上角(1,0)。這樣紋理就可以正確的畫出來了。
還需要說一點,紋理本身是有大小的,它就是加載進來的紋理圖片的像素值,所以大家在工作細節中,千萬不能像我們這樣直接使用,紋理座標還必須要精確計算,並和頂點對應,才能保證紋理不變形。
正確畫完之後,我們再看一下,當前設置的紋理座標都是1,如果不是1會出現什麼現象?我們把紋理座標改成如下:
private final float[] mTexturePosiontData =
{
0.0f, 0.0f,
0.0f, 0.5f,
0.5f, 0.5f,
0.5f, 0.0f
};
這樣繪製出來的效果如下:
看到了吧,左上角還是一樣,右下角不一樣,說明紋理是從左上角開始採樣的。我們再把紋理座標改爲1.5,繪製出來的效果如下:
private final float[] mTexturePosiontData =
{
0.0f, 0.0f,
0.0f, 1.5f,
1.5f, 1.5f,
1.5f, 0.0f
};
通過這兩個實驗,我們就知道了,紋理都是以頂點區域爲目標填滿的,那麼如何填充就看紋理座標怎麼設置了,當然還要參考紋理採樣方式。如果我們設置紋理長度爲0.5,說明頂點爲1的地方就採樣紋理的0.5的區域,相當於把紋理放大兩倍,然後取1的地方,所以看到的就如上圖0.5那樣,整個放大了;如果設置爲1.5,就是說頂點爲1的地方要對應紋理1.5的地方,那相當於再增加0.5倍的紋理,也就是我們看到的樣子了。
好了,本節關於glActiveTexture API的內容就介紹到這裏,當中還有很多其他相關的API,也需要我們一起掌握。