Textures in OpenGL ES 2.0 for Android

11,115

A few observations/questions:

  1. 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:

  2. 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.

  3. 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.

  4. 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.)

Share:
11,115
Amplify91
Author by

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, 2022

Comments

  • Amplify91
    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
    Amplify91 almost 13 years
    I'm trying these solutions now, but do I need the precision specifier for both shaders or just the fragment shader?
  • Mikola
    Mikola almost 13 years
    Just 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
    Amplify91 almost 13 years
    Adding 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
    Mikola almost 13 years
    Well, 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
    Amplify91 almost 13 years
    Right 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
    Amplify91 almost 13 years
    I'm also getting a 1280 from "GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_FLOAT, indexBuffer);" in draw() of my Sprite class.
  • Mikola
    Mikola almost 13 years
    Oh! You don't need that in OpenGL ES 2. Calling bind on a texture automatically sets the texture state. Comment that line out.
  • Amplify91
    Amplify91 almost 13 years
    I did, but like I said, I have another 1280 in glDrawElements()
  • Mikola
    Mikola almost 13 years
    And 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
    Amplify91 almost 13 years
    AHHH!!! 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
    Mikola almost 13 years
    The 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
    Amplify91 almost 13 years
    All I did was add glBindTexture before glDrawElements in my draw() method. Fantastic :) thank you again for all your help!!!