Search code examples
javaandroidopengl-esglslesopengl-es-3.0

Untextured Quad not rendering in OpenGL ES 3.0


I've recently coded a simple game using the javax.swing framework and want to release it as an android application. I did some research and discovered that most apps use java and OpenGL ES. I have some experience with OpenGL but am familiarizing myself with OpenGL ES. However, whenever I try to render just a simple quad (using GLES30 vertex arrays) I only see my red background (which I've specified with GL10.glClearColor()). I'm most likely missing something very obvious but I've spent my time researching and have not found tangible results.

This is my MainActivity code:

public class MainActivity extends Activity {

    private Screen screen;
    private Sketcher s;

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

        screen = findViewById(R.id.surface_view);
        screen.setEGLContextClientVersion(3);

        s = new Sketcher(getAssets());

        screen.setRenderer(s);
        screen.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    @Override
    public void onPause() {
        super.onPause();
        screen.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        screen.onResume();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        s.free();
    }
} 

Please keep in mind that the Screen class only copies the constructor of the GLSurfaceView and makes the onPause() and onResume() methods public.

The Sketcher class:

public class Sketcher implements GLSurfaceView.Renderer {

    private Player p;

    private Shader pShader;

    private AssetManager am;

    Sketcher(AssetManager am) {
        this.am = am;
    }

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        p = new Player();
        //pShader = new Shader(am, "playerVertexShader.txt", "playerFragmentShader.txt");
        //pShader.setUniforms();

        GLES10.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT);

        //pShader.activate();

        GLES30.glBindVertexArray(p.getVaoID());
        GLES20.glEnableVertexAttribArray(0);

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, p.getVertexCount(), GLES20.GL_UNSIGNED_INT, 0);

        GLES20.glDisableVertexAttribArray(0);
        GLES30.glBindVertexArray(0);

        //pShader.deactivate();
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES10.glViewport(0, 0, width, height);
    }

    void free() {
        p.free();
        //pShader.free();
    }
}

The Player class:

public class Player {

    public static final float RADIUS = 0.5f;

    private static final float ACC = 0.02f;
    private static final float MAX_SPEED = 2.0f;

    private static final int PRECISION = 10;

    private float x, y;
    private float vx, vy;

    private int vaoID;
    private int vBufferID;
    private int iBufferID;

    public Player() {
        int[] vao = new int[1];
        int[] buffers = new int[2];

        GLES30.glGenVertexArrays(1, vao, 0);
        vaoID = vao[0];

        GLES30.glGenBuffers(2, buffers, 0);
        vBufferID = buffers[0];
        iBufferID = buffers[1];

        ByteBuffer vBuffer = ByteBuffer.allocateDirect(Float.BYTES * 8).order(ByteOrder.nativeOrder());
        vBuffer.putFloat(-RADIUS);
        vBuffer.putFloat(-RADIUS);
        vBuffer.putFloat( RADIUS);
        vBuffer.putFloat(-RADIUS);
        vBuffer.putFloat( RADIUS);
        vBuffer.putFloat( RADIUS);
        vBuffer.putFloat(-RADIUS);
        vBuffer.putFloat( RADIUS);
        vBuffer.flip();

        /*
        vBuffer.put(0.0f);
        vBuffer.put(0.0f);

        for (int i = 0; i < PRECISION; i++) {
            double angle = Math.PI / PRECISION * i;

            vBuffer.put((float)(Math.sin(angle) * RADIUS));
            vBuffer.put((float)(Math.cos(angle) * RADIUS));
        }
        */

        ByteBuffer iBuffer = ByteBuffer.allocateDirect(Integer.BYTES * 6).order(ByteOrder.nativeOrder());
        iBuffer.putInt(0);
        iBuffer.putInt(1);
        iBuffer.putInt(2);
        iBuffer.putInt(0);
        iBuffer.putInt(2);
        iBuffer.putInt(3);
        iBuffer.flip();
        /*
        for (int i = 1; i < PRECISION - 1; i++) {
            iBuffer.put(0);
            iBuffer.put(i + 1);
            iBuffer.put(i);
        }

        iBuffer.put(0);
        iBuffer.put(1);
        iBuffer.put(PRECISION - 1);
        */

        GLES30.glBindVertexArray(vaoID);

        GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vBufferID);
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vBuffer.capacity(), vBuffer, GLES20.GL_STATIC_DRAW);
        GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 0, 0);

        GLES20.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, iBufferID);
        GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, iBuffer.capacity(), iBuffer, GLES20.GL_STATIC_DRAW);

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        GLES30.glBindVertexArray(0);
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }

    public int getVaoID() {
        return vaoID;
    }

    public int getVertexCount() {
        return 6;
    }

    public void free() {
        GLES30.glDeleteVertexArrays(1, new int[] {vaoID}, 0);
        GLES30.glDeleteBuffers(2, new int[] {vBufferID, iBufferID}, 0);
    }
}

I commented out a lot of the more game-relevant code because I had to simplify until I hit rock-bottom (drawing a simple untextured quad).

Here is the shader code:

#version 300 es

layout(location = 0) in vec2 vertex;

void main() {
    gl_Position = vec4(vertex, 0.0, 1.0);
}


#version 300 es

out vec4 fragment;

void main() {
    fragment = vec4(0.0, 0.0, 0.0, 1.0);
}

Solution

  • When you create the Index buffers, then you create a "byte" buffer. This means each index is stored in a single byte:

    ByteBuffer iBuffer = ByteBuffer.allocateDirect(Integer.BYTES*6).order(ByteOrder.nativeOrder());
           iBuffer.putInt(0);
           iBuffer.putInt(1);
    
    GLES20.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, iBufferID);
    GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, iBuffer.capacity(), iBuffer, GLES20.GL_STATIC_DRAW);
    

    Thus the specified index type has to be GL_UNSIGNED_BYTE when you draw the mesh (glDrawElements)

    GLES20.glDrawElements(GLES20.GL_TRIANGLES, p.getVertexCount(), GLES20.GL_UNSIGNED_BYTE, 0);
    

    rather than GL_UNSIGNED_INT:

    GLES20.glDrawElements(GLES20.GL_TRIANGLES, p.getVertexCount(), GLES20.GL_UNSIGNED_INT, 0);


    Alternatively it is also, it is also possible to use a IntBuffer which corresponds to the element type GL_UNSIGNED_INT.
    Or use a ShortBuffer in combination with GL_UNSIGNED_SHORT.


    Indeed in the fragment shader the Precision qualifiers are missing, they are not optional (OpenGL ES). For example add a default precision qualifier:

    precision medium float;