Search code examples
objective-cstructopengl-esvertices

How to fill a Struct right with vertices and index buffer for openGL ES


currently I have problems filling a struct with vertices for a mesh (Screen Filling Quad with a high resolution)

This is the struct:

    typedef struct {
    float Position[3];
    float Color[4];
    float TexCoord[2];
    }Vertex;

Normally i would just fill it with per hand, eg

    const Vertex Vertices[]= {
    {{1,-1,0},{1,0,0,1},{1,1}},
    {{1,1,0},{0,1,0,1},{1,0}},
    {{-1,1,0},{0,0,1,1},{0,0}},
    {{-1,-1,0},{0,0,0,1},{0,1}}
    };

and bind this to my Buffer etc.

Since I need a Mesh with much higher resolution (11x11 mesh that I filled in by hand was not enough) I thought of Filling this via this method.

    - (void) createMesh : (int) width withHeight: (int)height{
        int size = width * height + height +1;
        Vertex Vert[]; //since we will be adding a vertex at the end for the (1,y), (1,v) and the x u

        int sizeInd = width * height * 2 * 3;
        GLubyte Ind[sizeInd]; // width * height * 2 number triangles, 3 indices per triangle

        float x,y,u,v;
        int count = 0;

        // Fill Vertices
        for (int i = 0 ; i <= height ; i++){

            y = ((1.0 - i/height) * -1.0) + ((i/height) * 1.0);
            v = 1.0 - (float) i / (float) height;

            for (int j = 0; j <= width; j++){

                x = (float) j / (float) width;
                u = x; //(float) j/ (float) count;

                //Vert[count]= {{x,y,0},{0,0,0,1.0},{u,v}}; 
                Vert[count].Position[0] = x;
                Vert[count].Position[1] = y;
                Vert[count].Position[2] = 0;

                Vert[count].Color[0] = 0.0;
                Vert[count].Color[1] = 0.0;
                Vert[count].Color[2] = 0.0;
                Vert[count].Color[3] = 1.0;

                Vert[count].TexCoord[0] = u;
                Vert[count].TexCoord[1] = v;
                count++;
            }
        }
        //Fill indices

        count = 0;
        for (int c = 0; c < sizeInd; c++){
            Ind[c] = count;
            c++;
            Ind[c] = count + 1;
            c++;
            Ind[c] = count + width + 1 + 1;
            c++;
            Ind[c] = count + 1;
            c++;
            Ind[c] = count + width + 1 + 1 + 1;
            c++;
            Ind[c] = count + width + 1 + 1;
            count++;
        }

        //Fill buffer
        glGenBuffers(1,&_vertexMeshBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(Vert), Vert, GL_STATIC_DRAW);

        glGenBuffers(1, &_indexMeshBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Ind), Ind, GL_STATIC_DRAW);

    } 

in my render method i draw via

    glDrawElements(GL_TRIANGLES, (sizeof(GLubyte)* width * height * 2 * 3 / sizeof(GLubyte))/*sizeof(Ind)/sizeof(Ind[0])*/,
               GL_UNSIGNED_BYTE, 0);

since the GLubyte Ind does not exist outside of the function, i can't get the size via

    sizeof(Ind)/sizeof(Ind[0]) 

like i would normally do.

So, is this the right way to fill a struct or do i have to do this somehow else? This approach seems right to me, but that might also be because of my lack of proper objective c knowledge in this case

right now, my app crashes on start, when connecting the required hardware, so there might only be a problem with the memory allocation.

Or does anyone have information about how to properly set up a higher resolution mesh in objective c/opengl es?

I already implemented a diamond-step algorithm in c++, and i know that this approach is not perfectly coded, i have just simplified the calls for my personal use.

This is more about the proper usage of the struct type in combination with the matching gles calls.

any help is welcome.

EDIT:

There were a few things wrong with my automatic indices, mainly i forgot to jump at the end of a line to the next, since i don't ant he last element of a row to be a starting point of the next triangle-block

This should be right:

    //Fill indices

    count = 0;
    int jumpBarrier = 1;
    for (int c = 0; c < sizeInd; c++){
        Ind[c] = count;
        c++;
        Ind[c] = count + 1;
        c++;
        Ind[c] = count + width + 1; //; + 1;
        c++;
        Ind[c] = count + 1;
        c++;
        Ind[c] = count + width + 1 + 1;// + 1;
        c++;
        Ind[c] = count + width + 1 ;//+ 1;

        //jump
        if (jumpBarrier == width){
            count++;
            count++;
            jumpBarrier = 1;
        }
        else{
            count++;
            jumpBarrier++;
        }
    }

EDIT #2

duh, inner for-loop incremented i instead of j, fixed that, vertices and indices now get created just as they should be.

EDIT #3

So I managed to get around this problem by using a modification of this algorithm to just print everything in a text file and then copying everything in my project where i need them.

If someone still wants to explain where i did something wrong and how to actually fill this struct right, you are welcome to answer this question for further learnings.


Solution

  • About creating your mesh:

    To begin with you truly overcomplicate your most basic code. Also some comments will help you in debugging your code. Take a look at this remake:

    - (void)createMesh:(int)width height:(int)height {
        int horizontalVertexCount = width+1; // one more then the number of surfaces
        int verticalVertexCount = height+1; // one more then the number of surfaces
    
        int vertexCount = horizontalVertexCount * verticalVertexCount; // a number of unique vertices
    
        // generate a buffer
        size_t vertexBufferSize = sizeof(Vertex)*vertexCount;
        Vertex *vertexBuffer = malloc(vertexBufferSize);
    
        // iterate through vertices and fill them
        for(int h=0; h<verticalVertexCount; h++) {
    
            // generate y position coordinate
    
            GLfloat y = h;
            y /= verticalVertexCount; // normalzie to be in range of [0,1]
            y = 2.0f*y - 1; // converting the range [0,1] to [-1,1]
    
            // generate y texture coordinate
    
            GLfloat v = h;
            v /= verticalVertexCount; // normalzie to be in range of [0,1]
            v = 1.0f - v; // converting the range [0,1] to [1,0]
    
            for(int w=0; h<horizontalVertexCount; w++) {
                // generate x position coordinate
    
                GLfloat x = w;
                x /= horizontalVertexCount; // normalzie to be in range of [0,1]
    
                // generate x texture coordinate
    
                GLfloat u = x;
    
                /*
                 The next segment may be replaced by using access as:
    
                 vertexBuffer[h*horizontalVertexCount + w].Position[0] = x;
                 */
    
                Vertex *currentVertex = vertexBuffer + h*horizontalVertexCount + w;
                currentVertex->Position[0] = x;
                currentVertex->Position[1] = y;
                currentVertex->Position[2] = .0f;
    
                currentVertex->Color[0] = .0f;
                currentVertex->Color[1] = .0f;
                currentVertex->Color[2] = .0f;
                currentVertex->Color[3] = 1.0f;
    
                currentVertex->TexCoord[0] = u;
                currentVertex->TexCoord[1] = v;
            }
        }
    
        // create index buffer
        int numberOfSurfaces = width*height;
        int indicesPerSurface = 2*3; // 2 triangles per surface
        size_t indexBufferSize = sizeof(GLushort)*numberOfSurfaces*indicesPerSurface;
        GLushort *indexBuffer = malloc(indexBufferSize);
    
        for(int h=0; h<height; h++) {
            for(int w=0; w<width; w++) {
                int surfaceIndex = h*width + w;
                int firstIndexIndex = surfaceIndex*indicesPerSurface;
    
                indexBuffer[firstIndexIndex] = h*horizontalVertexCount + w; // upper left
                indexBuffer[firstIndexIndex] = h*horizontalVertexCount + (w + 1); // upper right
                indexBuffer[firstIndexIndex] = (h+1)*horizontalVertexCount + w; // lower left
    
                indexBuffer[firstIndexIndex] = h*horizontalVertexCount + (w + 1); // upper right
                indexBuffer[firstIndexIndex] = (h+1)*horizontalVertexCount + (w+1); // lower right
                indexBuffer[firstIndexIndex] = (h+1)*horizontalVertexCount + w; // lower left
            }
        }
    
        //Fill buffer
        glGenBuffers(1,&_vertexMeshBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, _vertexMeshBuffer);
        glBufferData(GL_ARRAY_BUFFER, vertexBufferSize, vertexBuffer, GL_STATIC_DRAW);
    
        glGenBuffers(1, &_indexMeshBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexMeshBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, GL_STATIC_DRAW);
    
        // release the memory
        free(vertexBuffer);
        free(indexBuffer);
    }
    

    I am not sure the code I posted works correctly but should be very easy to debug with a simple breakpoint in case it does not. Do not try to optimize your code until you need to which mostly is after the current code works.

    Do note a few things in this example:

    • A malloc is used to create the buffer on the memory. This is mandatory for large data sets or your stack may overflow.
    • The index buffer is used as short because a byte can store only up to 256 unique indices so your code will fail for large meshes anyway. (Larger types then short are not generally supported for indexing)
    • Some code such as accessing the vertices by pointer is just for demo. You can change them to a direct access.

    About the Vertex:

    I insist that in C languages such a data structure is done as you did but you could continue a bit further. The position, color and texture coordinates should be structures. And to support a more natural access to the values. Something like this should be interesting:

    typedef union {
        struct {
            GLfloat x, y;
        };
        struct {
            GLfloat u, v;
        };
    }Vector2f;
    
    typedef struct {
        GLfloat x, y, z;
    }Vector3f;
    
    typedef union {
        struct {
            Vector3f vector3f;
            GLfloat _w;
        };
        struct {
            GLfloat x, y, z, w;
        };
        struct {
            GLfloat r, g, b, a;
        };
    }Vector4f;
    
    typedef struct {
        Vector3f position;
        Vector4f color;
        Vector2f textureCoordinates;
    }Vertex;
    

    If you are not used to the concept of union I suggest you put that on your TO-READ-ABOUT list. In this case it allows you to access the same data via multiple properties: If you have a Vector4f myVector then myVector.x is always the same as myVector.r, this is done so the code is clearer on what you are doing. You can see that GLSL (shader language) uses the same principles. The usage can then be:

                currentVertex->position.x = x;
                currentVertex->position.y = y;
                currentVertex->position.z = .0f;
    
                currentVertex->color.r = .0f; // could use .x
                currentVertex->color.g = .0f; // could use .y
                currentVertex->color.b = .0f; // could use .z
                currentVertex->color.a = 1.0f; // could use .w
    

    After you have a nice structure all you will ever need is sizeof (you are already using it) and offsetof (which I hope you are using when assigning pointers to attributes). By doing this you will not need to change the drawing code if you edit your structure. For instance if at some points you chose to add normals in your Vertex structure you will simply add another vector in the structure and no other code should need to change for your program to work.

    Wrapping the data:

    So after you have your structures you would want to pack these into some objects. At this point I suggest you to go to full Objective-C and create a new class which holds all the data you need for drawing the object. This means you will have an ids of the vertex buffer and index buffer, drawing mode, offsets, stride and vertex count. A basic implementation should be something like this:

    @interface MeshVertexObject : NSObject
    
    @property (nonatomic) GLuint vertexBufferID; // assigned when generating a mesh
    @property (nonatomic) GLuint indexBufferID; // assigned when generating a mesh
    @property (nonatomic) GLuint vertexCount; // assigned when generating a mesh
    @property (nonatomic) GLenum drawMode; // assigned when generating a mesh. GL_TRIANGLES
    
    @property (nonatomic, readonly) void *positionPointer; // depending on the Vertex structure
    @property (nonatomic, readonly) void *colorPointer; // depending on the Vertex structure
    @property (nonatomic, readonly) void *texturePointer; // depending on the Vertex structure
    
    @property (nonatomic, readonly) GLint positionStride; // depending on the Vertex structure
    @property (nonatomic, readonly) GLint colorStride; // depending on the Vertex structure
    @property (nonatomic, readonly) GLint textureStride; // depending on the Vertex structure
    
    
    @end
    
    @implementation MeshVertexObject
    
    - (void)dealloc {
        // remove all the GL data bound to this calss on destructor
        if(self.vertexBufferID > 0) {
            GLuint vBuffer = self.vertexBufferID;
            self.vertexBufferID = 0;
            glDeleteBuffers(1, &vBuffer);
        }
        if(self.indexBufferID > 0) {
            GLuint iBuffer = self.indexBufferID;
            self.indexBufferID = 0;
            glDeleteBuffers(1, &iBuffer);
        }
    }
    
    - (void *)positionPointer {
        return (void *)offsetof(Vertex, position);
    }
    - (void *)colorPointer {
        return (void *)offsetof(Vertex, color);
    }
    - (void *)texturePointer {
        return (void *)offsetof(Vertex, textureCoordinates);
    }
    
    - (GLint)positionStride {
        return sizeof(Vertex);
    }
    - (GLint)colorStride {
        return sizeof(Vertex);
    }
    - (GLint)textureStride {
        return sizeof(Vertex);
    }
    
    @end
    

    This is now the class that would hold your mesh generating method and would possibly include other shape generators such as for cubes, spheres...

    But then the rabbit hole goes even deeper... Now that you can build a system on the system you can create more complex objects that also include texture ids (or again rather Texture objects that include these ids) and the mesh objects. Then also append the data for their location and orientation on the scene and conveniences to generate the matrices you use in drawing pipeline...

    The conclusion:

    You are doing it right or at least going correct way. But if you want to continue building a system, if you want to be able to debug your code and mostly be able to maintain your code then keep the modules as simple as possible and as commented as they can be and just keep building upwards.