I am new to OpenGL and trying to learn ES 2.0.
For starters, I’m working on a card game where I need to display several card images. I followed this http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/
I created several classes for processing data and actions.
- MySprite contains texture information, including location and scale factors.
- The dispenser takes all the sprites in one go. This is a crude implementation.
- ShaderHelper controls the creation of shaders and their binding to the program.
- GLRenderer is where rendering is handled (it implements "Renderer".)
Q1
My program displays one image correctly. The problem is that when rendering two images, the first one is replaced with a later one, and the second one is displayed twice instead.
I suspect this is due to the way I create textures in the class MySprite. But I do not know why. You can help?
Q2
I read that if I need to make 2 images, I need to use GL_TEXTURE0and GL_TEXTURE1instead of just using GL_TEXTURE0.
_GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
But since these constants are limited (from 0 to 31), is there a better way to display over 32 small images without losing the uniqueness of the images?
Please direct me in the right direction.
Code
GLRenderer:
public class GLRenderer implements Renderer {
ArrayList<MySprite> images = new ArrayList<MySprite>();
Batcher batch;
int x = 0;
...
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
batch = new Batcher();
MySprite s = MySprite.createGLSprite(mContext.getAssets(), "menu/back.png");
images.add(s);
s.XScale = 2;
s.YScale = 3;
images.add(MySprite.createGLSprite(mContext.getAssets(), "menu/play.png"));
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1);
ShaderHelper.initGlProgram();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mScreenWidth = width;
mScreenHeight = height;
GLES20.glViewport(0, 0, mScreenWidth, mScreenHeight);
batch.setScreenDimension(width, height);
GLES20.glUseProgram(ShaderHelper.programTexture);
}
@Override
public void onDrawFrame(GL10 unused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
batch.begin();
int y = 0;
for (MySprite s : images) {
s.X = x;
s.Y = y;
batch.draw(s);
y += 200;
}
batch.end();
x += 1;
}
}
Dispensers:
public class Batcher {
private final float[] mtrxModel = new float[16];
private static final float[] mtrxProjection = new float[16];
private final float[] mtrxMVP = new float[16];
static float[] uvArray = new float[]{
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f
};
static FloatBuffer uvBuffer;
static FloatBuffer vertexBuffer;
static boolean staticInitialized = false;
static short[] indices = new short[]{0, 1, 2, 0, 2, 3};
static ShortBuffer indicesBuffer;
ArrayList<MySprite> sprites = new ArrayList<MySprite>();
public Batcher() {
if (!staticInitialized) {
uvBuffer = ByteBuffer.allocateDirect(uvArray.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
uvBuffer.put(uvArray)
.position(0);
indicesBuffer = ByteBuffer.allocateDirect(indices.length * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer();
indicesBuffer.put(indices)
.position(0);
float[] vertices = new float[] {
0, 0, 0,
0, 1, 0,
1, 1, 0,
1, 0, 0
};
vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexBuffer.put(vertices)
.position(0);
staticInitialized = true;
}
}
public void setScreenDimension(int screenWidth, int screenHeight) {
Matrix.setIdentityM(mtrxProjection, 0);
Matrix.orthoM(mtrxProjection, 0,
-1f, screenWidth,
screenHeight, -1f,
-1f, 1f);
}
public void begin() {
sprites.clear();
}
public void draw(MySprite sprite) {
sprites.add(sprite);
}
public void end() {
int u_MVPMatrix = GLES20.glGetUniformLocation(ShaderHelper.programTexture, "u_MVPMatrix");
int a_Position = GLES20.glGetAttribLocation(ShaderHelper.programTexture, "a_Position");
int a_texCoord = GLES20.glGetAttribLocation(ShaderHelper.programTexture, "a_texCoord");
int u_texture = GLES20.glGetUniformLocation(ShaderHelper.programTexture, "u_texture");
GLES20.glEnableVertexAttribArray(a_Position);
GLES20.glEnableVertexAttribArray(a_texCoord);
for (int i = 0; i < sprites.size(); i++) {
MySprite ms = sprites.get(i);
Matrix.setIdentityM(mtrxMVP, 0);
Matrix.setIdentityM(mtrxModel, 0);
Matrix.translateM(mtrxModel, 0, ms.X, ms.Y, 0f);
Matrix.scaleM(mtrxModel, 0, ms.getWidth() * ms.XScale, ms.getHeight() * ms.YScale, 0f);
Matrix.multiplyMM(mtrxMVP, 0, mtrxModel, 0, mtrxMVP, 0);
Matrix.multiplyMM(mtrxMVP, 0, mtrxProjection, 0, mtrxMVP, 0);
GLES20.glVertexAttribPointer(a_Position, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
GLES20.glVertexAttribPointer(a_texCoord, 2, GLES20.GL_FLOAT, false, 0, uvBuffer);
GLES20.glUniformMatrix4fv(u_MVPMatrix, 1, false, mtrxMVP, 0);
GLES20.glUniform1i(u_texture, ms.getTextureId());
GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_SHORT, indicesBuffer);
}
}
}
Shaderhelper
public class ShaderHelper {
static final String vs_Image =
"uniform mat4 u_MVPMatrix;" +
"attribute vec4 a_Position;" +
"attribute vec2 a_texCoord;" +
"varying vec2 v_texCoord;" +
"void main() {" +
" gl_Position = u_MVPMatrix * a_Position;" +
" v_texCoord = a_texCoord;" +
"}";
static final String fs_Image =
"precision mediump float;" +
"uniform sampler2D u_texture;" +
"varying vec2 v_texCoord;" +
"void main() {" +
" gl_FragColor = texture2D(u_texture, v_texCoord);" +
"}";
public static int programTexture;
public static int vertexShaderImage, fragmentShaderImage;
public static int loadShader(int type, String shaderCode){
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
public static void initGlProgram() {
vertexShaderImage = ShaderHelper.loadShader(GLES20.GL_VERTEX_SHADER, ShaderHelper.vs_Image);
fragmentShaderImage = ShaderHelper.loadShader(GLES20.GL_FRAGMENT_SHADER, ShaderHelper.fs_Image);
ShaderHelper.programTexture = GLES20.glCreateProgram();
GLES20.glAttachShader(ShaderHelper.programTexture, vertexShaderImage);
GLES20.glAttachShader(ShaderHelper.programTexture, fragmentShaderImage);
GLES20.glLinkProgram(ShaderHelper.programTexture);
}
public static void dispose() {
GLES20.glDetachShader(ShaderHelper.programTexture, ShaderHelper.vertexShaderImage);
GLES20.glDetachShader(ShaderHelper.programTexture, ShaderHelper.fragmentShaderImage);
GLES20.glDeleteShader(ShaderHelper.fragmentShaderImage);
GLES20.glDeleteShader(ShaderHelper.vertexShaderImage);
GLES20.glDeleteProgram(ShaderHelper.programTexture);
}
}
Myprite
public class MySprite {
public int X, Y;
public float XScale, YScale;
private int w, h;
int textureId = -1;
private MySprite(Bitmap bmp, int textureId) {
this.w = bmp.getWidth();
this.h = bmp.getHeight();
this.textureId = textureId;
this.XScale = this.YScale = 1f;
}
public static MySprite createGLSprite(final AssetManager assets, final String assetImagePath) {
Bitmap bmp = TextureHelper.getBitmapFromAsset(assets, assetImagePath);
if (bmp == null) return null;
MySprite ms = new MySprite(bmp, createGlTexture());
Log.d("G1", "image id = " + ms.getTextureId());
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
bmp.recycle();
return ms;
}
private static int createGlTexture() {
final int[] textureHandles = new int[1];
GLES20.glGenTextures(1, textureHandles, 0);
if (textureHandles[0] != 0) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandles[0]);
return textureHandles[0];
} else {
throw new RuntimeException("Error loading texture.");
}
}
...
}