Search code examples
androidgraphicsopengl-esopengl-es-2.0graphics2d

Drop in quality of texture rendering using OpenGL ES2.0


I'm drawing a texture on to a square using OpenGL ES2.0 [ ANdroid ] . The texture scrolls infinitely in vertical direction.

The rendering quality drops very rapidly after a few frames and the texture quality is deteriorated.

Screenshot #1 Initial Image

Screenshot #2 After a few frames

How to correct this issue?

Code:

package gamedev.soursugar.infinitescroll;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * Created by soursugar on 19/12/14.
 */
public class Background {

    private final String vertexShaderCode =
            "uniform mat4 uMVPMatrix;"      +
                    "attribute vec4 vPosition;"     +
                    "attribute vec2 TexCoordIn;"    +
                    "varying vec2 TexCoordOut;"     +
                    "void main() {"                 +
                    "   gl_Position = uMVPMatrix * vPosition;"  +
                    "   TexCoordOut = TexCoordIn;"  +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;"  +
                    "uniform vec4 vColor;"      +
                    "uniform sampler2D TexCoordIn;" +
                    "uniform float scroll;" +
                    "varying vec2 TexCoordOut;" +
                    "void main() {" +
                    "   gl_FragColor = texture2D(TexCoordIn, vec2(TexCoordOut.x, TexCoordOut.y + scroll));" +
                    "}";


    private int[] textures = new int[1];

    private byte[] indices = {
            0, 1, 2,
            0, 2, 3
    };

    private float[] texture = {
            0.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 0.0f,
            1.0f, 1.0f
    };

    private float[] vertices = {
            -1f, -1f, 0f,
            -1f, 1f, 0f,
            1f, 1f, 0f,
            1f, -1f, 0f
    };

    private final FloatBuffer vertexBuffer;
    private final ByteBuffer indexBuffer;
    private final FloatBuffer textureBuffer;

    private final int mProgram;
    private int mPositionHandle;
    private int mMVPMatrixHandle;

    static final int COORDS_PER_VERTEX = 3;
    static final int COORDS_PER_TEXTURE = 2;
    private final int vertexStride = COORDS_PER_VERTEX * 4;
    private final int textureStride = COORDS_PER_TEXTURE * 4;

    private float scroll = 0.00f;

    public Background() {
        ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        vertexBuffer = byteBuf.asFloatBuffer();

        byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        textureBuffer = byteBuf.asFloatBuffer();
        textureBuffer.put(texture);
        textureBuffer.position(0);

        indexBuffer = ByteBuffer.allocateDirect(indices.length);
        indexBuffer.order(ByteOrder.nativeOrder());
        indexBuffer.put(indices);
        indexBuffer.position(0);

        int vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
        GLES20.glShaderSource(vertexShader, vertexShaderCode);
        GLES20.glCompileShader(vertexShader);

        int fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        GLES20.glShaderSource(fragmentShader, fragmentShaderCode);
        GLES20.glCompileShader(fragmentShader);

        mProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mProgram, vertexShader);
        GLES20.glAttachShader(mProgram, fragmentShader);

        GLES20.glLinkProgram(mProgram);

    }

    private void reloadVertexBuffer() {
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
    }

    public void loadTexture(int texture, Context context, int width, int height) {
        Bitmap bitmap = null;
        float w = 0.75f;

        try{
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inScaled=false;
            bitmap = BitmapFactory.decodeStream(context.getAssets().open("background.png"));
            w = (1.0f/height)*width;
            Log.d("Width", String.valueOf(w));

        } catch (Exception e) {
            Log.d("Exception from SourSugar", "Error in decoding stream");
        }

        vertices[0] = -w;
        vertices[3] = -w;
        vertices[6] = w;
        vertices[9] = w;
        reloadVertexBuffer();

        GLES20.glGenTextures(1, textures, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
    }

    public void draw(float[] mvpMatrix, int frameCount) {
        //if(frameCount==25 || frameCount==50)
            //scroll = 0f;

        scroll -= 0.005f;
        GLES20.glUseProgram(mProgram);

        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        GLES20.glEnableVertexAttribArray(mPositionHandle);


        int vsTextureCoord = GLES20.glGetAttribLocation(mProgram, "TexCoordIn");


        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
        GLES20.glVertexAttribPointer(vsTextureCoord, COORDS_PER_TEXTURE, GLES20.GL_FLOAT, false, textureStride, textureBuffer);
        GLES20.glEnableVertexAttribArray(vsTextureCoord);


        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
        int fsTexture = GLES20.glGetUniformLocation(mProgram, "TexCoordOut");
        int fsScroll = GLES20.glGetUniformLocation(mProgram, "scroll");

        GLES20.glUniform1i(fsTexture, 0);
        GLES20.glUniform1f(fsScroll, scroll);

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

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

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_BYTE, indexBuffer);
        GLES20.glDisableVertexAttribArray(mPositionHandle);

    }

}

Solution

  • The problem is related to this line in your fragment shader:

    "gl_FragColor = texture2D(TexCoordIn, vec2(TexCoordOut.x, TexCoordOut.y + scroll));"
    

    Roughly speaking, floating point numbers can represent either small numbers accurately, or large numbers inaccurately. When you add two floating point numbers together, the result will usually have the accuracy of the larger-magnitude (less accurate) of the two. So when the value of scroll gets too large in magnitude, the accuracy of the texture coordinate (TexCoordOut.y + scroll) is reduced.

    You can fix this by changing the Java code to make your scroll value wrap around in the range of 0.0 - 1.0

    scroll -= 0.005f;
    if(scroll < 0.0f)
        scroll += 1.0f;
    

    Since you are making your texture wrap around in T, you can discard the integer part and it won't make any difference.