如何使用 openGL ES 的顶点和索引缓冲区填充结构体

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

目前我在用网格顶点填充结构时遇到问题(高分辨率的屏幕填充四边形)

这是结构:

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

通常我会每手填充它,例如

    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}}
    };

并将其绑定到我的缓冲区等

因为我需要一个更高分辨率的网格(我手工填充的11x11网格不够)所以我想到了通过这种方法填充它。

    - (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);

    } 

在我的渲染方法中,我通过

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

由于 GLubyte Ind 在函数之外不存在,我无法通过

获取大小
    sizeof(Ind)/sizeof(Ind[0]) 

就像我平时做的那样。

那么,这是填充结构的正确方法还是我必须以其他方式执行此操作?这种方法对我来说似乎是正确的,但这也可能是因为我在这种情况下缺乏适当的objective c知识

现在,我的应用程序在连接所需硬件时在启动时崩溃,所以可能只是内存分配有问题。

或者有人知道如何在 objective c/opengl es 中正确设置更高分辨率的网格吗?

我已经在 C++ 中实现了一个菱形步算法,我知道这种方法的编码并不完美,我只是简化了个人使用的调用。

这更多的是关于结构类型与匹配的 gles 调用相结合的正确用法。

欢迎任何帮助。

编辑:

我的自动索引有一些问题,主要是我忘了在一行的末尾跳到下一行,因为我不希望一行的最后一个元素成为下一个三角块

这个应该是对的:

    //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++;
        }
    }

编辑#2

duh,内部 for 循环增加了 i 而不是 j,修复了, 顶点和索引现在按照应有的方式创建。

编辑#3

所以我设法解决了这个问题,方法是使用这个算法的修改来只打印文本文件中的所有内容,然后将我的项目中的所有内容复制到我需要的地方。

如果有人还想解释我哪里做错了以及如何正确填充这个结构,欢迎您回答这个问题以进一步学习。

关于创建网格:

首先,您确实将最基本的代码复杂化了。此外,一些注释将帮助您调试代码。看看这个翻拍:

- (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);
}

我不确定我发布的代码是否正确工作,但如果它不正确,使用一个简单的断点应该很容易调试。在您需要之前不要尝试优化您的代码,这主要是在当前代码工作之后。

请注意此示例中的一些事项:

  • A malloc 用于在内存上创建缓冲区。这对于大型数据集是强制性的,否则您的堆栈可能会溢出。
  • 索引缓冲区用作 short,因为 byte 最多只能存储 256 个唯一索引,因此您的代码无论如何都会对大型网格失败。 (较大的类型然后 short 通常不支持索引)
  • 一些代码如通过指针访问顶点只是为了演示。您可以将它们更改为直接访问。

关于Vertex:

我坚持认为在 C 语言中,这样的数据结构是按照您所做的那样完成的,但您可以继续更进一步。 positioncolortexture 坐标应为 structures。并支持更自然地访问这些值。像这样的东西应该很有趣:

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;

如果您不习惯 union 的概念,我建议您将其放入您的 TO-READ-ABOUT 列表中。在这种情况下,它允许您通过多个属性访问相同的数据:如果您有一个 Vector4f myVector,那么 myVector.x 总是与 myVector.r 相同,这样做是为了让代码更清晰你在做什么。您可以看到 GLSL(着色器语言)使用相同的原理。用法可以是:

            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

在你拥有一个漂亮的结构之后,你所需要的只是sizeof(你已经在使用它)和offsetof(我希望你在为属性分配指针时使用它)。通过这样做,如果您编辑结构,则不需要更改绘图代码。例如,如果在某些时候您选择在 Vertex 结构中添加法线,您只需在结构中添加另一个向量,无需更改其他代码即可使您的程序正常工作。

包装数据:

所以在你有了你的结构之后,你会想把它们打包成一些对象。在这一点上,我建议您使用完整的 Objective-C 并创建一个新的 class 来保存绘制对象所需的所有数据。这意味着您将拥有顶点缓冲区和索引缓冲区的 ID、绘图模式、偏移量、步幅和顶点数。一个基本的实现应该是这样的:

@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

这是现在的 class,它将保存您的网格生成方法,并且可能包括其他形状生成器,例如立方体、球体...

但随后兔子洞变得更深...现在您可以在系统上构建系统,您可以创建更复杂的对象,其中还包括纹理 ID(或者更确切地说 Texture 包含这些的对象ids) 和网格对象。然后还附加数据以了解它们在场景中的位置和方向,并方便地生成您在绘图管道中使用的矩阵...

结论:

你做对了,或者至少是走对了路。但是如果你想继续构建一个系统,如果你希望能够调试你的代码并且主要能够维护你的代码那么保持模块尽可能简单并尽可能多地注释并继续向上构建。