christiaanderidder
christiaanderidder

Reputation: 675

Drawing a sphere in OpenGL ES 2.0

I'm trying to draw a sphere in openGL ES 2.0 on Android. I already looked at the related questions and tried some of their code but I still can't get it to work.

Based on the Android developer examples and this code found on gamedev.net I came up with the code below. However it is not drawing correctly; When using glDrawArrays() rendering works but the results are not correct, when using glDrawElements() I get an GL_INVALID_OPERATION error. I listed the contents of my buffers below.

Sphere.java

public class Sphere
{
    private int stacks;
    private int slices;
    private float radius;

    //Buffers
    private FloatBuffer vertexBuffer;
    private FloatBuffer colorBuffer;
    private ShortBuffer indexBuffer;

    //Buffer sizes in aantal bytes
    private int vertexBufferSize;
    private int colorBufferSize;
    private int indexBufferSize;

    private int vertexCount;

    private int program;

    static final int FLOATS_PER_VERTEX = 3; // Het aantal floats in een vertex (x, y, z)
    static final int FLOATS_PER_COLOR = 4;  // Het aantal floats in een kleur (r, g, b, a)
    static final int SHORTS_PER_INDEX = 2;  
    static final int BYTES_PER_FLOAT = 4;   
    static final int BYTES_PER_SHORT = 2;   

    static final int BYTES_PER_VERTEX = FLOATS_PER_VERTEX * BYTES_PER_FLOAT;
    static final int BYTES_PER_COLOR = FLOATS_PER_COLOR * BYTES_PER_FLOAT;
    static final int BYTES_PER_INDEX_ENTRY = SHORTS_PER_INDEX * BYTES_PER_SHORT;

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

    public Sphere(float radius, int stacks, int slices)
    {
        this.stacks = stacks;
        this.slices = slices;
        this.radius = radius;

        vertexCount         = (stacks+1) * (slices+1);
        vertexBufferSize    = vertexCount * BYTES_PER_VERTEX;
        colorBufferSize     = vertexCount * BYTES_PER_COLOR;
        indexBufferSize     = vertexCount * BYTES_PER_INDEX_ENTRY;

        program = GLHelpers.createProgram();
        if (program == 0) {
            return;
        }
        GLHelpers.checkGlError("program");

        // Setup vertex-array buffer. Vertices in float. A float has 4 bytes.
        vertexBuffer = ByteBuffer.allocateDirect(vertexBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
        colorBuffer = ByteBuffer.allocateDirect(colorBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
        indexBuffer = ByteBuffer.allocateDirect(indexBufferSize).order(ByteOrder.nativeOrder()).asShortBuffer();    

        generateSphereCoords(radius, stacks, slices);

        vertexBuffer.position(0);
        colorBuffer.position(0);
        indexBuffer.position(0);
    }


    public void draw(float[] modelViewProjectionMatrix)
    {
        GLES20.glUseProgram(program);

        GLHelpers.checkGlError("useprogram");

        int positionHandle = GLES20.glGetAttribLocation(program, "a_Position");
        GLES20.glEnableVertexAttribArray(positionHandle);
        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, BYTES_PER_VERTEX, vertexBuffer);
        GLHelpers.checkGlError("pos");

        //int colorHandle = GLES20.glGetAttribLocation(program, "a_Color");
        //GLES20.glEnableVertexAttribArray(colorHandle);
        //GLES20.glVertexAttribPointer(colorHandle, 4, GLES20.GL_FLOAT, false, BYTES_PER_COLOR, colorBuffer);
        //GLHelpers.checkGlError("color");

        int matrixHandle = GLES20.glGetUniformLocation(program, "u_Matrix");
        GLES20.glUniformMatrix4fv(matrixHandle, 1, false, modelViewProjectionMatrix, 0);

        /*
         * When using glDrawArrays rendering works but the results are not correct, when using glDrawElements I get an GL_INVALID_OPERATION error.
         */
        GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexBuffer.capacity(), GLES20.GL_SHORT, indexBuffer);
        //GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);

        GLHelpers.checkGlError("draw");

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(positionHandle);
        //GLES20.glDisableVertexAttribArray(colorHandle);
    }

    private void generateSphereCoords(float radius, int stacks, int slices)
    {
        for (int stackNumber = 0; stackNumber <= stacks; ++stackNumber)
        {
            for (int sliceNumber = 0; sliceNumber < slices; ++sliceNumber)
            {
                float theta = (float) (stackNumber * Math.PI / stacks);
                float phi = (float) (sliceNumber * 2 * Math.PI / slices);
                float sinTheta = FloatMath.sin(theta);
                float sinPhi = FloatMath.sin(phi);
                float cosTheta = FloatMath.cos(theta);
                float cosPhi = FloatMath.cos(phi);
                vertexBuffer.put(new float[]{radius * cosPhi * sinTheta, radius * sinPhi * sinTheta, radius * cosTheta});
            }
        }

        for (int stackNumber = 0; stackNumber < stacks; ++stackNumber)
        {
            for (int sliceNumber = 0; sliceNumber <= slices; ++sliceNumber)
            {
                indexBuffer.put((short) ((stackNumber * slices) + (sliceNumber % slices)));
                indexBuffer.put((short) (((stackNumber + 1) * slices) + (sliceNumber % slices)));
            }
        }
    }
}

GLHelpers.java

public class GLHelpers
{
    private static final String TAG = "GLHelpers";

    private static final String VERTEX_SHADER_CODE =
            "uniform mat4 u_Matrix;" +
            "attribute vec4 a_Position;" +
            "attribute vec4 a_Color;" +
            "varying vec4 v_Color;" +
            "void main() {" +
            "  v_Color = a_Color;" +
            "  gl_Position = a_Position * u_Matrix;" +
            "}";

    private static final String FRAGMENT_SHADER_CODE =
        "precision mediump float;" +
        "varying vec4 v_Color;" +
        "void main() {" +
        "  gl_FragColor = v_Color;" +
        "}";

    private static int loadShader(int shaderType, String source)
    {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
            int[] compiled = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0)
            {
                Log.e(TAG, "Could not compile shader " + shaderType + ":");
                Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

    public static int createProgram()
    {
        int vertexShader = GLHelpers.loadShader(GLES20.GL_VERTEX_SHADER, GLHelpers.VERTEX_SHADER_CODE);
        if (vertexShader == 0)
            return 0;

        int pixelShader = GLHelpers.loadShader(GLES20.GL_FRAGMENT_SHADER, GLHelpers.FRAGMENT_SHADER_CODE);
        if (pixelShader == 0)
            return 0;

        int program = GLES20.glCreateProgram();
        if (program != 0) {
            GLES20.glAttachShader(program, vertexShader);
            GLHelpers.checkGlError("glAttachShader");
            GLES20.glAttachShader(program, pixelShader);
            GLHelpers.checkGlError("glAttachShader");
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] != GLES20.GL_TRUE)
            {
                Log.e(TAG, "Could not link program: ");
                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }

    public static void checkGlError(String glOperation)
    {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR)
        {
            Log.e(TAG, glOperation + ": glError " + error);
            throw new RuntimeException(glOperation + ": glError " + error);
        }
    }
}

The contents of vertexBuffer

X               Y               Z
0.0,            0.0,            1.0,
0.0,            0.0,            1.0,
-0.0,           0.0,            1.0,
-0.0,           -0.0,           1.0,
0.0,            -0.0,           1.0,
0.58778524,     0.0,            0.809017,
0.18163562,     0.559017,       0.809017,
-0.4755283,     0.34549147,     0.809017,
-0.4755282,     -0.34549156,    0.809017,
0.18163571,     -0.55901694,    0.809017,
0.95105654,     0.0,            0.30901697,
0.29389262,     0.90450853,     0.30901697,
-0.769421,      0.55901694,     0.30901697,
-0.76942086,    -0.5590171,     0.30901697,
0.29389274,     -0.9045085,     0.30901697,
0.9510565,      0.0,            -0.30901703,
0.2938926,      0.9045085,      -0.30901703,
-0.7694209,     0.5590169,      -0.30901703,
-0.7694208,     -0.55901706,    -0.30901703,
0.29389274,     -0.9045084,     -0.30901703,
0.5877852,      0.0,            -0.80901706,
0.1816356,      0.55901694,     -0.80901706,
-0.47552824,    0.3454914,      -0.80901706,
-0.47552818,    -0.34549153,    -0.80901706,
0.1816357,      -0.5590169,     -0.80901706,
-8.742278E-8,   -0.0,           -1.0,
-2.7015123E-8,  -8.3144E-8,     -1.0,
7.0726514E-8,   -5.138581E-8,   -1.0,
7.072651E-8,    5.138583E-8,    -1.0,
-2.7015135E-8,  8.3143995E-8,   -1.0,
0.0,            0.0,            0.0,
0.0,            0.0,            0.0,
0.0,            0.0,            0.0,
0.0,            0.0,            0.0,
0.0,            0.0,            0.0,
0.0,            0.0,            0.0

The contents of indexBuffer

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

The result when using glDrawArrays():

result

Upvotes: 2

Views: 7849

Answers (2)

christiaanderidder
christiaanderidder

Reputation: 675

As promised the working code for creating the sphere:

    public static Model3D createSphere(float radius, int stacks, int slices)
    {
        int vertexCount = (stacks + 1) * (slices + 1);
        FloatBuffer vertexBuffer        = ByteBuffer.allocateDirect(vertexCount * GLHelpers.BYTES_PER_VERTEX).order(ByteOrder.nativeOrder()).asFloatBuffer();
        FloatBuffer normalBuffer        = ByteBuffer.allocateDirect(vertexCount * GLHelpers.BYTES_PER_NORMAL).order(ByteOrder.nativeOrder()).asFloatBuffer();
        FloatBuffer textureCoordBuffer  = ByteBuffer.allocateDirect(vertexCount * GLHelpers.BYTES_PER_TEXTURE_COORD).order(ByteOrder.nativeOrder()).asFloatBuffer();
        ShortBuffer indexBuffer         = ByteBuffer.allocateDirect(vertexCount * GLHelpers.BYTES_PER_TRIANGLE_INDEX).order(ByteOrder.nativeOrder()).asShortBuffer();

        for (int stackNumber = 0; stackNumber <= stacks; ++stackNumber)
        {
            for (int sliceNumber = 0; sliceNumber <= slices; ++sliceNumber)
            {
                float theta = (float) (stackNumber * Math.PI / stacks);
                float phi = (float) (sliceNumber * 2 * Math.PI / slices);
                float sinTheta = FloatMath.sin(theta);
                float sinPhi = FloatMath.sin(phi);
                float cosTheta = FloatMath.cos(theta);
                float cosPhi = FloatMath.cos(phi);

                float nx = cosPhi * sinTheta;
                float ny = cosTheta;
                float nz = sinPhi * sinTheta;


                float x = radius * nx;
                float y = radius * ny;
                float z = radius * nz;

                float u = 1.f - ((float)sliceNumber / (float)slices);
                float v = (float)stackNumber / (float)stacks;



                normalBuffer.put(nx);
                normalBuffer.put(ny);
                normalBuffer.put(nz);

                vertexBuffer.put(x);
                vertexBuffer.put(y);
                vertexBuffer.put(z);

                textureCoordBuffer.put(u);
                textureCoordBuffer.put(v);
            }
        }

        for (int stackNumber = 0; stackNumber < stacks; ++stackNumber)
        {
            for (int sliceNumber = 0; sliceNumber < slices; ++sliceNumber)
            {
                int second = (sliceNumber * (stacks + 1)) + stackNumber;
                int first = second + stacks + 1;

                //int first = (stackNumber * slices) + (sliceNumber % slices);
                //int second = ((stackNumber + 1) * slices) + (sliceNumber % slices);

                indexBuffer.put((short) first);
                indexBuffer.put((short) second);
                indexBuffer.put((short) (first + 1));

                indexBuffer.put((short) second);
                indexBuffer.put((short) (second + 1));
                indexBuffer.put((short) (first + 1));
            }
        }

        vertexBuffer.rewind();
        normalBuffer.rewind();
        indexBuffer.rewind();
        textureCoordBuffer.rewind();

        Model3D sphere = new Model3D().setVertexBuffer(vertexBuffer)
                                      .setNormalBuffer(normalBuffer)
                                      .setIndexBuffer(indexBuffer)
                                      .setTexture(R.drawable.earth)
                                      .setTextureCoordBuffer(textureCoordBuffer)
                                      .setDiffuseLighting(-3f, 2.3f, 2f);
        return sphere;

    }

Upvotes: 3

Tim
Tim

Reputation: 35923

You're setting vertexCount as lat * lon * bytes per float, which looks very weird to me.

I think you have misnamed this variable, as the number of vertices has nothing to do with bytes per float.

You're using the same variable in glDrawArrays, which seems to me will not have the accurate number of vertices.

Upvotes: 1

Related Questions