Textures in OpenGL ES 2.0 for Android
A few observations/questions:
I don't know how you changed the fragment shader, but the version that is currently posted needs a precision specifier. Just add:
precision mediump float;
to the top, and it should work. Now regarding the black screen here are some questions:
When you change the glClearColor to something not black and comment out all the draw commands, does it still look black? If so, then you have a bigger problem than textures.
Second, if you ignore the texture output and try drawing each sprite as just a flat colored rectangle with no texture data, what do you get? You should be able to see some colored rectangle on the screen.
Finally, you need to bind the texture before you call glDrawElements. (Though this shouldn't matter in this example since you haven't changed the state yet.)
Amplify91
Diving straight into programming with Android. With no formal instruction, it's all books, tutorials, and trial-and-error for me. Not the fastest way to learn, but I'm dedicated.
Updated on June 04, 2022Comments
-
Amplify91 almost 2 years
I'm new to OpenGL and I'm teaching myself by making a 2D game for Android with ES 2.0. I am starting off by creating a "Sprite" class that creates a plane and renders a texture to it. To practice, I have two Sprite objects that are drawn alternating in the same place. I got this much working fine and well with ES 1.0, but now that I've switched to 2.0, I am getting a black screen with no errors. I'm exhausted trying to figure out what I'm doing wrong, but I have a strong feeling it has to do with my shaders. I'm going to dump all the relevant code here and hopefully somebody can give me an answer or some advice as to what I'm doing wrong. And if it's not immediately apparent what I'm doing wrong, perhaps some advice on how to figure it out? Thanks in advance for looking through all the code I'm about to post.
The three classes I'm posting are:
GameRenderer - the renderer for my GLSurfaceView
Shader - creates a shader program object
Sprite - creates a square and draws a texture on it
Also, I'll post my vertex and fragment shader source.Related classes I didn't think were relevant enough to post:
GameActivity
GameView - A GLSurfaceView
GameLoopThread - My main game loop
FPSCounter - outputs the average FPS to logcat every 100 frames.GameRender class:
package com.detour.raw; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.graphics.Bitmap; import android.opengl.GLES20; import android.opengl.GLU; import android.opengl.Matrix; import android.opengl.GLSurfaceView; public class GameRenderer implements GLSurfaceView.Renderer{ private static final String LOG_TAG = GameRenderer.class.getSimpleName(); Context mContext; Bitmap bitmap; private float red = 0.0f; private float green = 0.0f; private float blue = 0.0f; Shader shader; FPSCounter fps; Sprite sprite; Sprite sprite2; int x = 0; private float[] mProjMatrix = new float[16]; private float[] mVMatrix = new float[16]; //int[] vertexShader; //int[] fragmentShader; //int program; //String vShaderSource = ""; //String fShaderSource = ""; public GameRenderer(Context context){ mContext = context; //create objects/sprites sprite = new Sprite(mContext); sprite2 = new Sprite(mContext); fps = new FPSCounter(); } @Override public void onDrawFrame(GL10 gl) { GLES20.glClearColor(red, green, blue, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); if(x>3){ x=0; } if(x%2==0){ sprite.draw(gl); }else{ sprite2.draw(gl); } x++; fps.calculate(); //fps.draw(gl); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES20.glViewport(0, 0, width, height); float ratio = (float)(width/height); Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 0.5f, 10); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // TODO Auto-generated method stub GLES20.glEnable(GLES20.GL_TEXTURE_2D); GLES20.glEnable(GLES20.GL_DEPTH_TEST); GLES20.glClearDepthf(1.0f); GLES20.glDepthFunc(GLES20.GL_LEQUAL); GLES20.glDepthMask(true); GLES20.glEnable(GLES20.GL_CULL_FACE); GLES20.glCullFace(GLES20.GL_BACK); GLES20.glClearColor(red, green, blue, 1.0f); //load sprite/object textures (preferably loop through an array of all sprites). sprite.loadGLTexture(gl, mContext, R.drawable.raw1); sprite2.loadGLTexture(gl, mContext, R.drawable.raw2); Matrix.setLookAtM(mVMatrix, 0, 0, 0, -5.0f, 0.0f, 0f, 0f, 0f, 0.0f, 0.0f); System.gc(); } }
Shader class:
package com.detour.raw; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import android.content.Context; import android.opengl.GLES20; import android.util.Log; public class Shader { public static final String TAG = Shader.class.getSimpleName(); int program; int vertexShader; int fragmentShader; String vShaderSource; String fShaderSource; public Shader(){ //blank constructor //createProgram(); } public Shader(String vs_source, String fs_source){ this.vShaderSource = vs_source; this.fShaderSource = fs_source; createProgram(); } public Shader(int vs_source_id, int fs_source_id, Context context) { StringBuffer vs = new StringBuffer(); StringBuffer fs = new StringBuffer(); try{ InputStream inputStream = context.getResources().openRawResource(vs_source_id); BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); String read = in.readLine(); while (read != null) { vs.append(read + "\n"); read = in.readLine(); } vs.deleteCharAt(vs.length() - 1); inputStream = context.getResources().openRawResource(fs_source_id); in = new BufferedReader(new InputStreamReader(inputStream)); read = in.readLine(); while (read != null) { fs.append(read + "\n"); read = in.readLine(); } fs.deleteCharAt(fs.length() - 1); }catch (Exception e){ Log.d("ERROR-readingShader", "Could not read shader: " + e.getLocalizedMessage()); } this.vShaderSource = vs.toString(); this.fShaderSource = fs.toString(); createProgram(); } private void createProgram(){ program = GLES20.glCreateProgram(); if(program!=0){ vertexShader = createShader(GLES20.GL_VERTEX_SHADER, vShaderSource); fragmentShader = createShader(GLES20.GL_FRAGMENT_SHADER, fShaderSource); GLES20.glAttachShader(program, vertexShader); GLES20.glAttachShader(program, fragmentShader); GLES20.glLinkProgram(program); }else{ Log.e(TAG, "Couldn't create program."); } } private int createShader(int type, String source){ int shader = GLES20.glCreateShader(type); if(shader!=0){ GLES20.glShaderSource(shader, source); GLES20.glCompileShader(shader); } return shader; } public int getProgram(){ return program; }
Sprite class:
package com.detour.raw; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.opengl.GLES20; import android.opengl.GLUtils; public class Sprite { //public static final int FRAME_WIDTH = 64; //public static final int FRAME_HEIGHT = 64; private static final String LOG_TAG = Sprite.class.getSimpleName(); Context mContext; Bitmap bitmap; private int textureLoc; private int vertexLoc; private int[] textures = new int[1]; //private int[] pixels; /*private float textureCoordinates[] = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f};*/ private float vertices[] = { -1.0f, 1.0f,// 0.0f, -1.0f, -1.0f,// 0.0f, 1.0f, -1.0f,// 0.0f, 1.0f, 1.0f// 0.0f }; private short[] indices = { 0, 1, 2, 0, 2, 3}; private FloatBuffer vertexBuffer; //private IntBuffer textureBuffer; private ShortBuffer indexBuffer; Shader shader; int program; String vShaderSource = ""; String fShaderSource = ""; public Sprite(Context context){ mContext = context; ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2); ibb.order(ByteOrder.nativeOrder()); indexBuffer = ibb.asShortBuffer(); indexBuffer.put(indices); indexBuffer.position(0); } public void draw(GL10 gl) { GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_FLOAT, indexBuffer); } public void loadGLTexture(GL10 gl, Context context, int id){ shader = new Shader(R.raw.sprite_vs, R.raw.sprite_fs, mContext); program = shader.getProgram(); GLES20.glUseProgram(program); vertexLoc = GLES20.glGetAttribLocation(program, "a_position"); textureLoc = GLES20.glGetUniformLocation(program, "u_texture"); //texture InputStream is = context.getResources().openRawResource(id); try { bitmap = BitmapFactory.decodeStream(is); } finally { try { is.close(); is = null; } catch (IOException e) { } } //pixels = new int[(bitmap.getWidth()*bitmap.getHeight())]; //bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); /*ByteBuffer byteBuf = ByteBuffer.allocateDirect(pixels.length * 4); byteBuf.order(ByteOrder.nativeOrder()); textureBuffer = byteBuf.asIntBuffer(); textureBuffer.put(pixels); textureBuffer.position(0);*/ GLES20.glDeleteTextures(1, textures, 0); GLES20.glGenTextures(1, textures, 0); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); GLES20.glUniform1i(textureLoc, 0); GLES20.glEnableVertexAttribArray(vertexLoc); GLES20.glVertexAttribPointer(vertexLoc, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT); //GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, FRAME_WIDTH, FRAME_HEIGHT, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuf);//(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); bitmap.recycle(); } }
Vertex shader (sprite_vs.txt):
#version 110 attribute vec2 a_position; varying vec2 v_texcoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texcoord = a_position * vec2(0.5) + vec2(0.5); }
Fragment (pixel) shader (sprite_fs.txt):
#version 110 uniform sampler2D u_texture; varying vec2 v_texcoord; void main() { gl_FragColor = texture2D(u_texture, v_texcoord); }
Thank you so much if you actually took the time to look through this! Hopefully someone else can use this as a resource for themselves in the future, also.
-
Amplify91 almost 13 yearsI'm trying these solutions now, but do I need the precision specifier for both shaders or just the fragment shader?
-
Mikola almost 13 yearsJust the fragment shader. Vertex shaders come with default precision specifiers. I would guess that this your main problem if you are porting your code from GL ES 1.0. The other 2 things are just tests that you should do to help figure out what is going on, and the last item is probably not an issue in this case though it could mess up later on.
-
Amplify91 almost 13 yearsAdding the precision specifier fixed my shaders, thank you :) However, it is still not drawing anything to the screen. glClearColor works fine and will display other colors. When I load my textures, I get an error #1280 on the first, but no errors on the second. I tried rearranging them and it always only does that only for the first. Also, I am binding the textures before glDrawElements aren't I?
-
Mikola almost 13 yearsWell, the call to glDeleteTextures in the initialization is probably a bug, and could end up deleting textures you don't mean to. So I would remove that. Also the way it is currently written, the opengl state will get set to that of the last loaded image, so it won't ever flip between them. Error 1280 means you passed an incorrect enum to one of the opengl methods. Do you know which line it is erroring out on?
-
Amplify91 almost 13 yearsRight now, instead of properly animating I'm just creating 2 sprite objects with different textures and alternating drawing them in the same spot. The 1280 is coming from the first line in my onSurfaceCreated() in the GameRenderer class: "GLES20.glEnable(GLES20.GL_TEXTURE_2D);"
-
Amplify91 almost 13 yearsI'm also getting a 1280 from "GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_FLOAT, indexBuffer);" in draw() of my Sprite class.
-
Mikola almost 13 yearsOh! You don't need that in OpenGL ES 2. Calling bind on a texture automatically sets the texture state. Comment that line out.
-
Amplify91 almost 13 yearsI did, but like I said, I have another 1280 in glDrawElements()
-
Mikola almost 13 yearsAnd for the type in your DrawElements, you need to change your indices to either UNSIGNED_BYTE or UNSIGNED_SHORT. See: khronos.org/opengles/sdk/docs/man
-
Amplify91 almost 13 yearsAHHH!!! Thank you, haha! I see my textures now! I would upvote this answer 1000 times if I could, but I'll settle for awarding the bounty. However, it's upside down and only showing the second image :P (I'm just glad it's working). What were you talking about when you said "the way it is currently written, the opengl state will get set to that of the last loaded image"?
-
Mikola almost 13 yearsThe only showing the second image is what I was talking about. You need to rebind the texture when you draw the sprite, otherwise it will just draw the last texture that was bound to the GPU.
-
Amplify91 almost 13 yearsAll I did was add glBindTexture before glDrawElements in my draw() method. Fantastic :) thank you again for all your help!!!