Search code examples
javaandroidopengl-estexturescube

Charging multiple textures OpenGL-ES Android


I am making an application for Android with OpenGL-Es following Nehe's tutorial. I have a cube with a texture and my problem is that I want to change it by pressing a button (I have 2 textures in the folder "raw"). I think this is due to my variable imagenes in which I save the route of my images (R.raw.imagen and R.raw.imagen2 on MainActivity.java) only charge the image once at the start of the application, so even if I change the variable later in my function onClick() the texture remains the same.

What I tried to do is making a switch in the class TextureCube.java looking my variable imagenes in the function where I load the texture so it should charge the first image at the start of the application and then if I press the button change it to the other image because of the code onClick().

The image never change because I printed the variable imagenes. I don't know what I am doing wrong.

MainActivity:

 b.setOnClickListener(new OnClickListener() {
           @Override
           public void onClick(View v) {
               if (get_imagenes() == R.raw.imagen) {
                   imagenes = R.raw.imagen2;
                   b.setText("image2");
               } else if (get_imagenes() == R.raw.imagen2) {
                   imagenes = R.raw.imagen;
                   b.setText("image1");
               }
           }
       });

TextureCube:

 // Construct an input stream to texture image
                switch (main.imagenes) {
                    case R.raw.imagen:
                      is = context.getResources().openRawResource(R.raw.imagen);
                        break;
                    case R.raw.imagen2:
                      is = context.getResources().openRawResource(R.raw.imagen2);
                        break;
                }

I left the rest of the code of the application here.Here is my code of the MainActivity:

public class MainActivity extends AppCompatActivity {
    private GLSurfaceView glView;
    private TextureCube cube;
    int imagenes = R.raw.imagen;
    Button b;
    Bitmap bitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //GLSurfaceView
        glView = new MyGLSurfaceView(this);
        cube = new TextureCube();
        setContentView(glView);
        createButtons();
    }

    public void createButtons() {
        //ButtonB
        LinearLayout ll = new LinearLayout(this);
        b = new Button(this);
        b.setText("Change Texture");
        ll.addView(b);
        ll.setGravity(Gravity.BOTTOM | Gravity.CENTER);
        this.addContentView(ll, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

       b.setOnClickListener(new OnClickListener() {
           @Override
           public void onClick(View v) {
               if (get_imagenes() == R.raw.imagen) {
                   imagenes = R.raw.imagen2;
                   b.setText("image2");
               } else if (get_imagenes() == R.raw.imagen2) {
                   imagenes = R.raw.imagen;
                   b.setText("image1");
               }
           }
       });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    // Call back when the activity is going into the background
    @Override
    protected void onPause() {
        super.onPause();
        glView.onPause();
    }

    // Call back after onPause()
    @Override
    protected void onResume() {
        super.onResume();
        glView.onResume();
    }

    public int get_imagenes() {
        return imagenes;
    }
}

My code of the Renderer:

public class MyGLRenderer implements GLSurfaceView.Renderer {

    private Context context;
    private TextureCube cube;
    private MainActivity main;
    // For controlling cube's z-position, x and y angles and speeds
    float angleX = 0;
    float angleY = 0;
    float speedX = 0;
    float speedY = 0;
    float z = -6.0f;

    int currentTextureFilter = 0;  // Texture filter

    // Lighting (NEW)
    boolean lightingEnabled = false;   // Is lighting on? (NEW)
    private float[] lightAmbient = {0.5f, 0.5f, 0.5f, 1.0f};
    private float[] lightDiffuse = {1.0f, 1.0f, 1.0f, 1.0f};
    private float[] lightPosition = {0.0f, 0.0f, 2.0f, 1.0f};
    // Blending (NEW)
    boolean blendingEnabled = false;  // Is blending on? (NEW)
    // Constructor
    public MyGLRenderer(Context context) {
        this.context = context;   // Get the application context (NEW)
        cube = new TextureCube();
    }

    // Call back when the surface is first created or re-created.
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);  // Set color's clear-value to black
        //gl.glClearColor(0f, 0f, 0f, 1.0f);
        gl.glClearDepthf(1.0f);            // Set depth's clear-value to farthest
        gl.glEnable(GL10.GL_DEPTH_TEST);   // Enables depth-buffer for hidden surface removal
        gl.glDepthFunc(GL10.GL_LEQUAL);    // The type of depth testing to do
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);  // nice perspective view
        gl.glShadeModel(GL10.GL_SMOOTH);   // Enable smooth shading of color
        gl.glDisable(GL10.GL_DITHER);      // Disable dithering for better performance

        // Setup Texture, each time the surface is created (NEW)
        cube.loadTexture(gl, context);    // Load image into Texture (NEW)
        gl.glEnable(GL10.GL_TEXTURE_2D);  // Enable texture (NEW)

        // Setup lighting GL_LIGHT1 with ambient and diffuse lights (NEW)
        gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_AMBIENT, lightAmbient, 0);
        gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE, lightDiffuse, 0);
        gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION, lightPosition, 0);
        gl.glEnable(GL10.GL_LIGHT1);   // Enable Light 1 (NEW)
        gl.glEnable(GL10.GL_LIGHT0);   // Enable the default Light 0 (NEW)

        // Setup Blending (NEW)
        gl.glColor4f(1.0f, 1.0f, 1.0f, 0.5f);           // Full brightness, 50% alpha (NEW)
        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE); // Select blending function (NEW)
    }


    // Call back after onSurfaceCreated() or whenever the window's size changes.
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        if (height == 0) height = 1;   // To prevent divide by zero
        float aspect = (float)width / height;

        // Set the viewport (display area) to cover the entire window
        gl.glViewport(0, 0, width, height);

        // Setup perspective projection, with aspect ratio matches viewport
        gl.glMatrixMode(GL10.GL_PROJECTION); // Select projection matrix
        gl.glLoadIdentity();                 // Reset projection matrix
        // Use perspective projection
        GLU.gluPerspective(gl, 45, aspect, 0.1f, 100.f);

        gl.glMatrixMode(GL10.GL_MODELVIEW);  // Select model-view matrix
        gl.glLoadIdentity();                 // Reset
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // Clear color and depth buffers
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

        if (lightingEnabled) { //Enable lighting
            gl.glEnable(GL10.GL_LIGHTING);
        } else {
            gl.glDisable(GL10.GL_LIGHTING);
        }

        if (blendingEnabled) { //Enable blending
            gl.glEnable(GL10.GL_BLEND);       // Turn blending on (NEW)
            gl.glDisable(GL10.GL_DEPTH_TEST); // Turn depth testing off (NEW)
        } else {
            gl.glDisable(GL10.GL_BLEND);      // Turn blending off (NEW)
            gl.glEnable(GL10.GL_DEPTH_TEST);  // Turn depth testing on (NEW)
        }

        // ----- Render the Cube ----- //
        gl.glLoadIdentity();              // Reset the model-view matrix
        gl.glTranslatef(0.0f, 0.0f, z);   // Translate into the screen (NEW)
        gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f); // Rotate (NEW)
        gl.glRotatef(angleY, 0.0f, 1.0f, 0.0f); // Rotate (NEW)
        cube.draw(gl);

        // Update the rotational angle after each refresh (NEW)
        angleX += speedX;  // (NEW)
        angleY += speedY;  // (NEW)

    }
}

GLSurfaceView:

public class MyGLSurfaceView extends GLSurfaceView {
    MyGLRenderer renderer;
    MainActivity main;

    private final float TOUCH_SCALE_FACTOR = 180.0f / 320.0f;
    private float previousX;
    private float previousY;

    //Allocate and set the renderer
    public MyGLSurfaceView(Context context) {
        super(context);
        renderer = new MyGLRenderer(context);
        this.setRenderer(renderer);
        this.requestFocus();
        this.setFocusableInTouchMode(true);
    }

    // Handler for key event
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent evt) {
        switch(keyCode) {
            case KeyEvent.KEYCODE_DPAD_LEFT:   // Decrease Y-rotational speed
                renderer.speedY += 0.1f;
                break;
            case KeyEvent.KEYCODE_DPAD_RIGHT:  // Increase Y-rotational speed
                renderer.speedY -= 0.1f;
                break;
            case KeyEvent.KEYCODE_DPAD_UP:     // Decrease X-rotational speed
                renderer.speedX += 0.1f;
                break;
            case KeyEvent.KEYCODE_DPAD_DOWN:   // Increase X-rotational speed
                renderer.speedX -= 0.1f;
                break;
            case KeyEvent.KEYCODE_A:           // Zoom out (decrease z)
                renderer.z -= 0.2f;
                break;
            case KeyEvent.KEYCODE_Z:           // Zoom in (increase z)
                renderer.z += 0.2f;
                break;
            case KeyEvent.KEYCODE_B:  // Toggle Blending on/off (NEW)
                renderer.blendingEnabled = !renderer.blendingEnabled;
                break;
            case KeyEvent.KEYCODE_L:  // Toggle lighting on/off (NEW)
                renderer.lightingEnabled = !renderer.lightingEnabled;
                break;
        }
        return true;
    }

    // Handler for touch event
    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        float currentX = event.getX();
        float currentY = event.getY();
        float deltaX, deltaY;
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                // Modify rotational angles according to movement
                deltaX = currentX - previousX;
                deltaY = currentY - previousY;
                renderer.angleX += deltaY * TOUCH_SCALE_FACTOR;
                renderer.angleY += deltaX * TOUCH_SCALE_FACTOR;
                break;
        }
        // Save current x, y
        previousX = currentX;
        previousY = currentY;
        return true;  // Event handled
    }
}

And here TextureCube:

public class TextureCube {
    private FloatBuffer vertexBuffer; //Buffer for vertex-array
    private FloatBuffer texBuffer;    //Buffer for texture-coords-array (NEW)
    private MainActivity main = new MainActivity();

    private float[] vertices = { //Vertices for a face
            -1.0f, -1.0f, 0.0f,  //left-bottom-front
            1.0f, -1.0f, 0.0f,  //right-bottom-front
            -1.0f, 1.0f, 0.0f,  //left-top-front
            1.0f, 1.0f, 0.0f   //right-top-front
    };

    float[] texCoords = { // Texture coords
            0.0f, 1.0f,  //left-bottom
            1.0f, 1.0f,  //right-bottom
            0.0f, 0.0f,  //left-top
            1.0f, 0.0f   //right-top (
    };
    int[] textureIDs = new int[1]; //new


    public TextureCube() {
        // Setup vertex-array buffer
        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
        vbb.order(ByteOrder.nativeOrder()); // Use native byte order
        vertexBuffer = vbb.asFloatBuffer(); // Convert from byte to float
        vertexBuffer.put(vertices);         // Copy data into buffer
        vertexBuffer.position(0);           // Rewind

        // Setup texture-array buffer
        ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * 4);
        tbb.order(ByteOrder.nativeOrder());
        texBuffer = tbb.asFloatBuffer();
        texBuffer.put(texCoords);
        texBuffer.position(0);
    }

    // Draw the cube
    public void draw(GL10 gl) {
        gl.glFrontFace(GL10.GL_CCW);    // Front face in counter-clockwise orientation
        gl.glEnable(GL10.GL_CULL_FACE); // Enable cull face
        gl.glCullFace(GL10.GL_BACK);    // Cull the back face (don't display)

        //Enable vertex and texture client
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);  // Enable texture-coords-array (NEW)
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texBuffer); // Define texture-coords buffer (NEW)

        //Draw all the faces
        //Front
        gl.glPushMatrix();
        gl.glTranslatef(0.0f, 0.0f, 1.0f);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        gl.glPopMatrix();

        //Left
        gl.glPushMatrix();
        gl.glRotatef(270.0f, 0.0f, 1.0f, 0.0f);
        gl.glTranslatef(0.0f, 0.0f, 1.0f);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        gl.glPopMatrix();

        //Back
        gl.glPushMatrix();
        gl.glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
        gl.glTranslatef(0.0f, 0.0f, 1.0f);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        gl.glPopMatrix();

        //Right
        gl.glPushMatrix();
        gl.glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
        gl.glTranslatef(0.0f, 0.0f, 1.0f);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        gl.glPopMatrix();

        //Top
        gl.glPushMatrix();
        gl.glRotatef(270.0f, 1.0f, 0.0f, 0.0f);
        gl.glTranslatef(0.0f, 0.0f, 1.0f);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        gl.glPopMatrix();

        //Bottom
        gl.glPushMatrix();
        gl.glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
        gl.glTranslatef(0.0f, 0.0f, 1.0f);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        gl.glPopMatrix();

        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);  // Disable texture-coords-array (NEW)
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glDisable(GL10.GL_CULL_FACE);
    }

    // Load an image into GL texture
    public void loadTexture(GL10 gl, Context context) {

        gl.glGenTextures(1, textureIDs, 0); // Generate texture-ID array new

        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[0]);   // Bind to texture ID

        // Set up texture filters
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);


        InputStream is = new InputStream() {
            @Override
            public int read() throws IOException {
                return 0;
            }
        };
        // Construct an input stream to texture image
        switch (main.imagenes) {
            case R.raw.imagen:
              is = context.getResources().openRawResource(R.raw.imagen);
                break;
            case R.raw.imagen2:
              is = context.getResources().openRawResource(R.raw.imagen2);
                break;
        }

        Log.d("prueba","imagenes"+main.imagenes);

        Bitmap bitmap;
        try {
            // Read and decode input as bitmap
                bitmap = BitmapFactory.decodeStream(is);
        } finally {
            try {
                    is.close();
            } catch (IOException e) {
            }
        }

        // Build Texture from loaded bitmap for the currently-bind texture ID
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
    }
}

Solution

  • At program init, you need to load the 2 bitmaps and upload those as 2 new textures to the GPU (glGenTextures / glBindTexture / GLUtils.texImage2D), this should give you two different textureIDs: textureIDs[0] and textureIDs[1].

    Then when you draw the cube in public void draw(GL10 gl)you need to add a call to glBindTexture using either textureIDs[0] or textureIDs[1] depending on your button state.

    You current code is only loading one of the 2 textures to GPU, the second one is only loaded to RAM when clicking the button. And also you forgot to call glBindTexture in the draw function for the cube.

    -- EDIT --

    Trying to explain better with some come :

    First you need to load the two images to OpenGL textures when program is initialized, instead of loading the second one only when pressing the button.

    This will make things easier to handle and will avoid creating any memory leak. So I create a new loadTextures function wich does that:

    // you need 2 texture IDs now ...
    int NB_GL_TEXTURES = 2;
    int[] textureIDs = new int[NB_GL_TEXTURES];
    
    // tool function to load a texture to OpenGL
    public void loadTexture(GL10 gl, Context context, InputStream is, int GL_id_slot) {
    
        // decode is to a Bitmap
        Bitmap bitmap;
        try {
            bitmap = BitmapFactory.decodeStream(is);
        } finally {
            try {
                is.close();
            } catch (IOException e) {
            }
        }
    
        // tell OpenGL what is the current GL texture
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[GL_id_slot]);
    
        // Set up texture filters for current GL texture
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
    
        // load the bitmap into current GL texture
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
    
        // destroy the bitmap
        bitmap.recycle();
    }
    
    // Loads the two images into two OpenGL textures
    public void loadTextures(GL10 gl, Context context) {
    
        // generate 2 GL textures IDs => textureIDs[0], textureIDs[1]
        gl.glGenTextures(NB_GL_TEXTURES, textureIDs, 0);
    
        // load imagen into GL tex of id textureIDs[0]
        InputStream is_bitmap_0 = context.getResources().openRawResource(R.raw.imagen);
        loadTexture(gl, context, is_bitmap_0, 0);
    
        // load imagen2 into GL tex of id textureIDs[1]
        InputStream is_bitmap_1 = context.getResources().openRawResource(R.raw.imagen2);
        loadTexture(gl, context, is_bitmap_1, 1);
    }
    
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // ...
    
        // Setup the 2 GL Textures, each time the surface is created
        gl.glEnable(GL10.GL_TEXTURE_2D);
        cube.loadTextures(gl, context);
    
        // ...
    }
    

    Next step is to change the cube render code to call glBindTexture at each frame, passing it the correct GL texture ID:

    // Draw the cube
    public void draw(GL10 gl) { 
        // ...
    
        //Enable vertex and texture client
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);  // Enable texture-coords-array (NEW)
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texBuffer); // Define texture-coords buffer (NEW)
    
        // choose which texture to use on the cube
        int GL_id_slot = 0;
        if (main.imagenes == R.raw.imagen)
            GL_id_slot = 0;
        else if (main.imagenes == R.raw.imagen2)
            GL_id_slot = 1;
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[GL_id_slot]);
    
        //Draw all the faces
        // ...
    }