2

I'm trying to load a simple 2D texture onto a square and display it in the back of my GLSurfaceView. I've copied over and fixed the code from Draw a 2D Image using OpenGL ES 2.0, but when I install and run the app, the image is split into 4 quadrants which are then rearranged.

Here is the Sprite class I've implemented:

public class Sprite {
//Reference to Activity Context
private final Context mActivityContext;

//Added for Textures
private final FloatBuffer mCubeTextureCoordinates;
private int mTextureUniformHandle;
private int mTextureCoordinateHandle;
private final int mTextureCoordinateDataSize = 2;
private int mTextureDataHandle;

private final String vertexShaderCode =
    //Test
    "attribute vec2 a_TexCoordinate;" +
    "varying vec2 v_TexCoordinate;" +
    //End Test
    "uniform mat4 uMVPMatrix;" +
    "attribute vec4 vPosition;" +
    "void main() {" +
    "  gl_Position = uMVPMatrix * vPosition;" +
    //Test
    "v_TexCoordinate = a_TexCoordinate;" +
    //End Test
    "}";

private final String fragmentShaderCode =
    "precision mediump float;" +
    "uniform vec4 v_Color;" +
    //Test
    "uniform sampler2D u_Texture;" +
    "varying vec2 v_TexCoordinate;" +
    //End Test
    "void main() {" +
    //"gl_FragColor = v_Color;" +
    //"gl_FragColor = (v_Color * texture2D(u_Texture, v_TexCoordinate));" +
    // Just draw the texture, don't apply a color
    "gl_FragColor = texture2D(u_Texture, v_TexCoordinate);" +
    "}";

private final int shaderProgram;
private final FloatBuffer vertexBuffer;
private final ShortBuffer drawListBuffer;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;

// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 2;

static float spriteCoords[] = {
    -0.5f, 0.5f,   // top left
    -0.5f, -0.5f,   // bottom left
    0.5f, -0.5f,   // bottom right
    0.5f,  0.5f  //top right
};

private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; //Order to draw vertices
private final int vertexStride = COORDS_PER_VERTEX * 4; //Bytes per vertex

// Set color with red, green, blue and alpha (opacity) values
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

// Image to draw as a texture
final int textureID = R.raw.quadrants;

public Sprite(final Context activityContext) {
    mActivityContext = activityContext;

    //Initialize Vertex Byte Buffer for Shape Coordinates / # of coordinate values * 4 bytes per float
    ByteBuffer bb = ByteBuffer.allocateDirect(spriteCoords.length * 4);
    //Use the Device's Native Byte Order
    bb.order(ByteOrder.nativeOrder());
    //Create a floating point buffer from the ByteBuffer
    vertexBuffer = bb.asFloatBuffer();
    //Add the coordinates to the FloatBuffer
    vertexBuffer.put(spriteCoords);
    //Set the Buffer to Read the first coordinate
    vertexBuffer.position(0);

    // S, T (or X, Y)
    // Texture coordinate data.
    // Because images have a Y axis pointing downward (values increase as you move down the image) while
    // OpenGL has a Y axis pointing upward, we adjust for that here by flipping the Y axis.
    // What's more is that the texture coordinates are the same for every face.
    final float[] cubeTextureCoordinateData =
        {
            //Front face
            /*0.0f, 0.0f,
              0.0f, 1.0f,
              1.0f, 0.0f,
              0.0f, 1.0f,
              1.0f, 1.0f,
              1.0f, 0.0f*/
            /*
             This was in the code in the aforementioned StackOverflow post,
             but doesn't work either
            -0.5f,  0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f,
            0.5f,  0.5f
            */
            0.5f, 0.5f,
            0.5f, -0.5f,
            -0.5f, -0.5f,
            -0.5f, 0.5f

        };

    mCubeTextureCoordinates = ByteBuffer
        .allocateDirect(cubeTextureCoordinateData.length * 4)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
    mCubeTextureCoordinates.put(cubeTextureCoordinateData).position(0);

    //Initialize byte buffer for the draw list
    ByteBuffer dlb = ByteBuffer.allocateDirect(spriteCoords.length * 2);
    dlb.order(ByteOrder.nativeOrder());
    drawListBuffer = dlb.asShortBuffer();
    drawListBuffer.put(drawOrder);
    drawListBuffer.position(0);

    int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                               vertexShaderCode);
    int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                                 fragmentShaderCode);

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

    //Texture Code
    GLES20.glBindAttribLocation(shaderProgram, 0, "a_TexCoordinate");

    GLES20.glLinkProgram(shaderProgram);

    //Load the texture
    mTextureDataHandle = loadTexture(mActivityContext, textureID);
}

public void draw(float[] mvpMatrix) {
    //Add program to OpenGL ES Environment
    GLES20.glUseProgram(shaderProgram);

    //Get handle to vertex shader's vPosition member
    mPositionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition");

    //Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    //Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);

    //Get Handle to Fragment Shader's v_Color member
    mColorHandle = GLES20.glGetUniformLocation(shaderProgram, "v_Color");

    //Set the Color for drawing the triangle
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    //Set Texture Handles and bind Texture
    mTextureUniformHandle = GLES20.glGetAttribLocation(shaderProgram, "u_Texture");
    mTextureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "a_TexCoordinate");

    //Set the active texture unit to texture unit 0.
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

    //Bind the texture to this unit.
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);

    //Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
    GLES20.glUniform1i(mTextureUniformHandle, 0);

    //Pass in the texture coordinate information
    mCubeTextureCoordinates.position(0);
    GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, mCubeTextureCoordinates);
    GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);

    //Get Handle to Shape's Transformation Matrix
    mMVPMatrixHandle = GLES20.glGetUniformLocation(shaderProgram, "uMVPMatrix");

    //Apply the projection and view transformation
    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

    //Draw the triangle
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

    //Disable Vertex Array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

public static int loadTexture(final Context context, final int resourceId)
{
    final int[] textureHandle = new int[1];

    GLES20.glGenTextures(1, textureHandle, 0);

    if (textureHandle[0] != 0)
        {
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inScaled = false;   // No pre-scaling

            // Read in the resource
            final Bitmap bitmap = BitmapFactory
                .decodeResource(context.getResources(), resourceId, options);

            // Bind to the texture in OpenGL
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);

            // Set filtering
            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);

            // Load the bitmap into the bound texture.
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

            // Recycle the bitmap, since its data has been loaded into OpenGL.
            bitmap.recycle();
        }

    if (textureHandle[0] == 0)
        {
            throw new RuntimeException("Error loading texture.");
        }

    return textureHandle[0];
    }
}

As an example, the image at http://changesuk.net/wp-content/uploads/2009/05/4-quadrants.jpg is changed so that the numbers are all oriented correctly, but are in the pattern

4 | 3
-----
2 | 1

If I change the order in which the coordinates of the square are added (e.g. starting with "bottom left"), the quadrants of the image are shifted but are also rotated about their respective centers (in the case of the above image, the numbers will all be on their sides or heads). I've looked over every line of code and can't understand what's going on here. Has anyone encountered this kind of behavior before, or can someone give an explanation as to what might be causing it?

Community
  • 1
  • 1

2 Answers2

2

I've been viewing SO for a while now, but never have I had the chance to answer a question. I know this question is 5 moths old, but I'll try to answer it anyway.

Basically, what you're doing is you're not properly aligning the corners of your texture with the corners of you vertices. You get that

4 | 3
-----
2 | 1

because the center of your texture is the bottom right of your square, and the texture gets repeated, hence the 4 and 2 are on the right and the 3 is on top.

The way to fix this is to add a new vec4 in your vertex shader

    uniform vec4 pos;

and fill it with x, y, width and height

    int pos = GLES20.glGetUniformLocation(mProgram, "pos");
    GLES20.glUniform4f(pos, .5f, .5f, 1, 1); // x, y, width, height

Now change this

    v_TexCoordinate = a_TexCoordinate;

to this

    v_TexCoordinate = vec2((a_TexCoordinate.x - pos.x)/pos.z, (a_TexCoordinate.y - pos.y)/pos.w);

That should do the trick.

Alexandre Marcondes
  • 5,859
  • 2
  • 25
  • 31
P0rter
  • 88
  • 8
1

I tried P0rter solution, but it didn't worked for me.

And to be clear, the problem is not in arangement of quadrants, but it started to draw in center. The other sides are just default repeat. If you turn on this, when loading texture, it will draw only bottom right quadrant.

    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
        GLES20.GL_TEXTURE_WRAP_S,
        GLES20.GL_CLAMP_TO_EDGE
    );
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
        GLES20.GL_TEXTURE_WRAP_T,
        GLES20.GL_CLAMP_TO_EDGE
    );

I got another solution. I don't exactly know what I am doing, so i just paste, what worked for me.

public class Sprite extends GLObject {

    final static float COORDS_SQUARE[] = {
        -0.5f, 0.5f, 0.0f,
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f,
        0.5f, 0.5f, 0.0f
    };


    /**
     * Number of coordinates per point in this array.
     */
    protected static final int COORDINATES_PER_VERTEX = 3;

    final static float TEXTURE_COORDS[] = {
        0.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f,
        1.0f, 1.0f
    };

    /**
     * Number of coordinates per point in this array.
     */
    protected static final int COORDINATES_PER_TEXTURE_VERTEX = 2;

    /**
     * Order to draw COORDINATES_VERTICES
     */
    protected static final short DRAW_ORDER[] = {
        0, 1, 2, 0, 3, 2
    };

    private static final String VERTEX_SHADER_CODE =
        "attribute vec2 aTexCoordinate;" +
            "varying vec2 vTexCoordinate;" +
            "uniform mat4 uMVPMatrix;" +
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = uMVPMatrix * vPosition;" +
            "  vTexCoordinate = aTexCoordinate;" +
            "}";

    private static final String FRAGMENT_SHADER_CODE =
        "precision mediump float;" +
            "uniform sampler2D uTexture;" +
            "varying vec2 vTexCoordinate;" +
            "void main() {" +
            "  gl_FragColor = texture2D(uTexture, vTexCoordinate);" +
            "}";

    private final int mProgram;

    private final FloatBuffer vertexBuffer;
    private final ShortBuffer drawListBuffer;
    private final FloatBuffer mTextureCoordinates;
    private int mTextureDataHandle;

    public Sprite(final Context context, @DrawableRes int resID) {

        //Initialize Vertex Byte Buffer for Shape Coordinates / # of coordinate values * 4 bytes per float
        ByteBuffer vb = ByteBuffer.allocateDirect(COORDS_SQUARE.length * 4);
        //Use the Device's Native Byte Order
        vb.order(ByteOrder.nativeOrder());
        //Create a floating point buffer from the ByteBuffer
        vertexBuffer = vb.asFloatBuffer();
        //Add the coordinates to the FloatBuffer
        vertexBuffer.put(COORDS_SQUARE);
        //Set the Buffer to Read the first coordinate
        vertexBuffer.position(0);

        ByteBuffer tcb = ByteBuffer.allocateDirect(TEXTURE_COORDS.length * 4);
        tcb.order(ByteOrder.nativeOrder());
        mTextureCoordinates = tcb.asFloatBuffer();
        mTextureCoordinates.put(TEXTURE_COORDS);
        mTextureCoordinates.position(0);

        //Initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(DRAW_ORDER.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(DRAW_ORDER);
        drawListBuffer.position(0);

        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);

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

        //Texture Code
        GLES20.glBindAttribLocation(mProgram, 0, "aTexCoordinate");

        GLES20.glLinkProgram(mProgram);

        //Load the texture
        mTextureDataHandle = loadTexture(context, resID);
    }

    public void draw(float[] mvpMatrix) {
        //Add program to OpenGL ES Environment
        GLES20.glUseProgram(mProgram);

        //Get handle to vertex shader's vPosition member
        int positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        int textureUniformHandle = GLES20.glGetAttribLocation(mProgram, "uTexture");
        int textureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoordinate");
        int MVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        //Enable a handle to the triangle COORDINATES_VERTICES
        GLES20.glEnableVertexAttribArray(positionHandle);

        //Prepare the triangle coordinate data
        vertexBuffer.position(0);
        GLES20.glVertexAttribPointer(
            positionHandle, COORDINATES_PER_VERTEX,
            GLES20.GL_FLOAT, false,
            0, vertexBuffer
        );

        //Set the active texture unit to texture unit 0.
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        //Bind the texture to this unit.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);

        //Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
        GLES20.glUniform1i(textureUniformHandle, 0);

        //Pass in the texture coordinate information
        mTextureCoordinates.position(0);
        GLES20.glVertexAttribPointer(
            textureCoordinateHandle, COORDINATES_PER_TEXTURE_VERTEX,
            GLES20.GL_FLOAT, false,
            0, mTextureCoordinates
        );
        GLES20.glEnableVertexAttribArray(textureCoordinateHandle);

        //Apply the projection and view transformation
        GLES20.glUniformMatrix4fv(MVPMatrixHandle, 1, false, mvpMatrix, 0);

        //Draw the triangle
        drawListBuffer.position(0);
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, DRAW_ORDER.length, GLES20.GL_UNSIGNED_SHORT,
            drawListBuffer);

        //Disable Vertex Array
        GLES20.glDisableVertexAttribArray(positionHandle);
    }
}

In my GLObject class are some shared methods for loading texture, etc, nothing important.

l0v3
  • 963
  • 7
  • 26