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.
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:
malloc
is used to create the buffer on the memory. This is mandatory for large data sets or your stack may overflow.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)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.