Search code examples
androidopengl-esdepth-bufferopengl-es-3.0

Depth test works when translating, but not when specifying z-coordinates (2D rendering)


Problem

It seems that I've found an issue with depth testing in Android OpenGL ES 3.0. Either that, or I somehow have an implementation flaw. I notice that depth testing works fine when I translate my geometry via modifying the model matrix; however, when I try to do the same through specifying position data in my geometry, the depth test fails. Geometry which is rendered last appears behind previously rendererd geometry.


My objective is to perform 2D rendering such that each pixel corresponds to 1 unit in world space. To do this, I have the following calls to glViewport and Matrix.orthoM to setup the projection matrix:

GLES30.glViewport(0, 0, width, height);
Matrix.orthoM(projectionMatrix, 0, 0, width, 0, height, 0, 10.0f);

This should cause the projection to use the full width and height of a TextureView and give a clip-space of 10 units in depth (z-axis).

During surface creation I make sure to enable depth testing (which uses the default depth function):

GLES30.glEnable(GLES30.GL_DEPTH_TEST);

Last but not least, I also ensure to clear the depth buffer each draw frame:

GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);

The above calls should setup depth buffering. All that remains is to define a view matrix which is done below during the each draw frame before rendering any geometry:

Matrix.setLookAtM(viewMatrix, 0, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

The parameters specify eyePosition = { 0.0f, 0.0f, 1.0f} which is away from the screen (toward the viewer), and the eye is looking at centerPosition = { 0.0f, 0.0f, 0.0f}, which is the origin of the world. This view matrix should only move the camera away from the scene without rotating (since I am rendering in 2D).


Translating geometry with model matrix:

By translating geometry with the model matrix, I can effectively get the depth test to work properly. Geometry renders in order according to a specified z-index.

Inside the draw function the following code executes for each rectangle (piano key) that I wish to draw:

Matrix.setIdentityM(modelMatrix, 0);
if (keys[index].keyType == WHITE_KEY) {
    Matrix.translateM(modelMatrix, 0, 0.0f, 0.0f, keys[index].zIndex);
} else {
    Matrix.translateM(modelMatrix, 0, 0.0f, 0.0f, keys[index].zIndex);
}
Matrix.translateM(modelMatrix, 0, translateX, 0.0f, 0.0f);
GLES30.glUniformMatrix4fv(GLES30.glGetUniformLocation(shaderProgram, "model"), 1, false, modelMatrix, 0);
GLES30.glUniformMatrix4fv(GLES30.glGetUniformLocation(shaderProgram, "view"), 1, false, viewMatrix, 0);
GLES30.glUniformMatrix4fv(GLES30.glGetUniformLocation(shaderProgram, "projection"), 1, false, projectionMatrix, 0);

keys[index].draw();

This gives the proper result. Notice all black keys appear on top even though all keys are rendered in order from left to right.

Depth Test Working

Translating geometry with position data:

I wished to specify z-index in the actual vertex data of the rectangles. So instead of the above render code, I use the following:

Matrix.setIdentityM(modelMatrix, 0);
Matrix.translateM(modelMatrix, 0, translateX, 0.0f, 0.0f);
GLES30.glUniformMatrix4fv(GLES30.glGetUniformLocation(shaderProgram, "model"), 1, false, modelMatrix, 0);
GLES30.glUniformMatrix4fv(GLES30.glGetUniformLocation(shaderProgram, "view"), 1, false, viewMatrix, 0);
GLES30.glUniformMatrix4fv(GLES30.glGetUniformLocation(shaderProgram, "projection"), 1, false, projectionMatrix, 0);

keys[index].draw();

Notice that the only difference is that I am no longer translating the geometry according to the z-index value. Instead, I specify the z-index in the actual vertex data:

float[] vertexData = new float[] {
        x, y, zIndex, //Specify z-index in vertex data.
        0, 0, //Texture coordinates.
        x + width, y, zIndex, //Specify z-index in vertex data.
        1, 0, //Texture coordinates.
        x, y + height, zIndex, //Specify z-index in vertex data.
        0, 1, //Texture coordinates.
        x + width, y + height, zIndex, //Specify z-index in vertex data.
        1, 1  //Texture coordinates.
};

int[] indexData = new int[] {
        0, 1, 3,
        0, 3, 2
};

//Set up VAO, VBO, and EBO with the data above (works properly, but is
//provided for the sake of completion).
FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuffer.put(vertexData).position(0);

IntBuffer indexBuffer = ByteBuffer.allocateDirect(indexData.length * 4)
        .order(ByteOrder.nativeOrder()).asIntBuffer();
indexBuffer.put(indexData).position(0);

GLES30.glGenVertexArrays(1, vao, 0);
GLES30.glGenBuffers(1, vbo, 0);
GLES30.glGenBuffers(1, ebo, 0);

GLES30.glBindVertexArray(vao[0]);

GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[0]);
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexData.length * 4, vertexBuffer, GLES30.GL_STATIC_DRAW);
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER, indexData.length * 4, indexBuffer, GLES30.GL_STATIC_DRAW);

//Point to vertex positions (stride = size of vertex in bytes = 5 components *4 bytes per float = 20)
GLES30.glEnableVertexAttribArray(0);
GLES30.glVertexAttribPointer(0, 2, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);

GLES30.glEnableVertexAttribArray(1);
GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, VERTEX_STRIDE, VERTEX_POSITION_COMPONENTS * 4);

GLES30.glBindVertexArray(0);

When the translations are removed, I receive the following results. Notice that the last keys are rendered behind the previous keys despite the fact we are rendering them from left to right. Also note that zIndex = -1.0f for white keys while zIndex = 0.0f for black keys. This should cause the white keys to be further into the screen compared to black keys. Both keys should be visible inside the clipping-space provided to Matrix.orthoM (since the view matrix was translated away from the screen by 1 unit, the effective zNear and zFar values are 1.0f and -9.0f respectively).

Depth Test Not Working


Question

What do I need to do in order to specify zIndex in the vertex data while having the correct depth test results?


Code

Vertex Shader

#version 300 es

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoord;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0f);

    //Pass texture coordinate to fragment shader.
    TexCoord = texCoord;
}

Fragment Shader

#version 300 es

in vec2 TexCoord;

out vec4 color;

uniform sampler2D texture;

void main()
{
    vec2 flippedTexCoord = vec2(TexCoord.x, 1.0 - TexCoord.y);
    color = texture(texture, flippedTexCoord);
}

Solution

  • You're setting up your position attribute to have only 2 components:

    GLES30.glVertexAttribPointer(0, 2, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
    

    The second argument specifies the number of components. So this should be:

    GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
    

    Or, since you seem to have a constant defined for this purpose, you might as well use it here:

    GLES30.glVertexAttribPointer(0, VERTEX_POSITION_COMPONENTS, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);