Search code examples
spriteopengl-es-2.0opacityparticles

Changing opacity of individual items in openGL ES 2.0 Quad Batch


Overview

In my app (which is a game), I make use of the batching of items to reduce the number of draw calls. So, I'll, create for example, a Java object called platforms which is for all the platforms in the game. All the enemies are batched together as are all collectible items etc....

This works really well. At present I am able to size and position the individual items in a batch independently of each other however, I've come to the point where I really need to change the opacity of individual items also. Currently, I can change only the opacity of the entire batch.

Batching

I am uploading the vertices for all items within the batch that are to be displayed (I can turn individual items off if I don't want them to be drawn), and then once they are all done, I simply draw them in one call.

The following is an idea of what I'm doing - I realise this may not compile, it is just to give an idea for the purpose of the question.

public void draw(){

    //Upload vertices
    for (count = 0;count<numOfSpritesInBatch;count+=1){

        vertices[x] = xLeft;        
        vertices[(x+1)] = yPTop;    
        vertices[(x+2)] = 0;    
        vertices[(x+3)] = textureLeft; 
        vertices[(x+4)] = 0;
        vertices[(x+5)] = xPRight;
        vertices[(x+6)] = yTop;
        vertices[(x+7)] = 0;
        vertices[(x+8)] = textureRight;
        vertices[x+9] = 0; 
        vertices[x+10] = xLeft; 
        vertices[x+11] = yBottom;
        vertices[x+12] = 0; 
        vertices[x+13] = textureLeft;
        vertices[x+14] = 1;
        vertices[x+15] = xRight;
        vertices[x+16] = yTop;
        vertices[x+17] = 0;
        vertices[x+18] = textureRight;
        vertices[x+19] = 0; 
        vertices[x+20] = xLeft;
        vertices[x+21] = yBottom;
        vertices[x+22] = 0;
        vertices[x+23] = textureLeft;
        vertices[x+24] = 1; 
        vertices[x+25] = xRight;
        vertices[x+26] = yBottom;
        vertices[x+27] = 0;
        vertices[x+28] = textureRight;
        vertices[x+29] = 1;

        x+=30;
    }

    vertexBuf.rewind();
    vertexBuf.put(vertices).position(0);

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texID);

    GLES20.glUseProgram(iProgId);

    Matrix.multiplyMM(mvpMatrix2, 0, mvpMatrix, 0,  mRotationMatrix, 0);

    mMVPMatrixHandle = GLES20.glGetUniformLocation(iProgId, "uMVPMatrix");

    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix2, 0);

    vertexBuf.position(0);
    GLES20.glVertexAttribPointer(iPosition, 3, GLES20.GL_FLOAT, false, 5 * 4, vertexBuf);
    GLES20.glEnableVertexAttribArray(iPosition);
    vertexBuf.position(3);
    GLES20.glVertexAttribPointer(iTexCoords, 2, GLES20.GL_FLOAT, false, 5 * 4, vertexBuf);
    GLES20.glEnableVertexAttribArray(iTexCoords);

    //Enable Alpha blending and set blending function   
    GLES20.glEnable(GLES20.GL_BLEND); 
    GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);

    //Draw it
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,  6 * numOfSpritesInBatch);

    //Disable Alpha blending
    GLES20.glDisable(GLES20.GL_BLEND);
}

Shaders

String strVShader =  
     "uniform mat4 uMVPMatrix;" +
     "attribute vec4 a_position;\n"+
     "attribute vec2 a_texCoords;" +
     "varying vec2 v_texCoords;" +
     "void main()\n" +
     "{\n" +
     "gl_Position = uMVPMatrix * a_position;\n"+  
     "v_texCoords = a_texCoords;" +
     "}";

String strFShader =
    "precision mediump float;" +
    "uniform float opValue;"+
    "varying vec2 v_texCoords;" +
    "uniform sampler2D u_baseMap;" +
    "void main()" +
    "{" +
    "gl_FragColor = texture2D(u_baseMap, v_texCoords);" +
    "gl_FragColor *= opValue;"+
    "}";

Currently, I have a method in my Sprite class that allows me to change the opacty. For example, something like this:

spriteBatch.setOpacity(0.5f); //Half opacity

This works, but changes the whole batch - not what I'm after.

Application

I need this because I want to draw small indicators when the player destroys an enemy - which show the score obtained from that action. (The type of thing that happens in many games) - I want these little 'score indicators' to fade out once they appear. All the indicators would of course be created as a batch so they can all be drawn with one draw call.

The only other alternatives are:

  • Create 10 textures at varying levels of opacity and just switch between them to create the fading effect. Not really an option as way too wasteful.
  • Create each of these objects separately and draw each with their own draw call. Would work, but with a max of 10 of these objects on-screen, I could potentially be drawing using 10 draw calls just for these items - while the game as a whole currently only uses about 20 draw calls to draw a hundreds of items.

I need to look at future uses of this too in particle systems etc.... so I would really like to try to figure out how to do this (be able to adjust each item's opacity separately). If I need to do this in the shader, I would be grateful if you could show how this works. Alternatively, is it possible to do this outside of the shader?

Surely this can be done in some way or another? All suggestions welcome....


Solution

  • The most direct way of achieving this is to use a vertex attribute for the opacity value, instead of a uniform. This will allow you to set the opacity per vertex, without increasing the number of draw calls.

    To implement this, you can follow the pattern you already use for the texture coordinates. They are passed into the vertex shader as an attribute, and then handed off to the fragment shader as a varying variable.

    So in the vertex shader, you add:

    ...
    attribute float a_opValue;
    varying float v_opValue;
    ...
        v_opValue = a_opValue;
    ...
    

    In the fragment shader, you remove the uniform declaration for opValue, and replace it with:

    varying float v_opValue;
    ...
        gl_FragColor *= v_opValue;
    ...
    

    In the Java code, you extend the vertex data with an additional value for the opacity, to use 6 values per vertex (3 position, 2 texture coordinates, 1 opacity), and update the state setup accordingly:

    vertexBuf.position(0);
    GLES20.glVertexAttribPointer(iPosition, 3, GLES20.GL_FLOAT, false, 6 * 4, vertexBuf);
    GLES20.glEnableVertexAttribArray(iPosition);
    vertexBuf.position(3);
    GLES20.glVertexAttribPointer(iTexCoords, 2, GLES20.GL_FLOAT, false, 6 * 4, vertexBuf);
    GLES20.glEnableVertexAttribArray(iTexCoords);
    vertexBuf.position(5);
    GLES20.glVertexAttribPointer(iOpValue, 1, GLES20.GL_FLOAT, false, 6 * 4, vertexBuf);
    GLES20.glEnableVertexAttribArray(iOpValue);