如何在OpenGL中将顶点添加到网格对象?

时间:2014-08-13 04:37:32

标签: objective-c c macos opengl vertexdata

我是OpenGL的新手,我一直在使用红皮书和超级圣经。在SB中,我已经讨论了使用从文件加载的对象的部分。到目前为止,我认为我无法理解正在发生的事情以及如何做到这一点,但它让我想到在我自己的应用程序中创建自己的网格 - 实质上是一个建模应用程序。我已经做了很多搜索我的参考和互联网,我还没有找到一个很好的教程,关于在自己的应用程序中实现这样的功能。我找到了一个只提供此功能的API,但我试图理解其实现;不只是界面。

到目前为止,我已经创建了一个" app" (我轻轻地使用这个术语),它为您提供了一个视图,您可以单击并添加顶点。顶点无法连接,只是显示在您单击的位置。我担心的是,我在实验过程中偶然发现的这种方法不是我应该实现这个过程的方式。

我正在使用Mac并在Xcode中使用Objective-C和C.

MyOpenGLView.m     #import" MyOpenGLView.h"

@interface MyOpenGLView () {

NSTimer *_renderTimer
Gluint VAO, VBO;

GLuint totalVertices;
GLsizei bufferSize;

}

@end

@implementation MyOpenGLView

/* Set up OpenGL view with a context and pixelFormat with doubleBuffering */

/* NSTimer implementation */

- (void)drawS3DView {

    currentTime = CACurrentMediaTime();

    NSOpenGLContext *currentContext = self.openGLContext;
    [currentContext makeCurrentContext];
    CGLLockContext([currentContext CGLContextObj]);

    const GLfloat color[] = {
        sinf(currentTime * 0.2),
        sinf(currentTime * 0.3),
        cosf(currentTime * 0.4),
        1.0
    };

    glClearBufferfv(GL_COLOR, 0, color);

    glUseProgram(shaderProgram);

    glBindVertexArray(VAO);

    glPointSize(10);
    glDrawArrays(GL_POINTS, 0, totalVertices);

    CGLFlushDrawable([currentContext CGLContextObj]);
    CGLUnlockContext([currentContext CGLContextObj]);

}

#pragma mark - User Interaction

- (void)mouseUp:(NSEvent *)theEvent {

    NSPoint mouseLocation = [theEvent locationInWindow];
    NSPoint mouseLocationInView = [self convertPoint:mouseLocation fromView:self];

    GLfloat x = -1 + mouseLocationInView.x * 2/(GLfloat)self.bounds.size.width;
    GLfloat y = -1 + mouseLocationInView.y * 2/(GLfloat)self.bounds.size.height;

    NSOpenGLContext *currentContext = self.openGLContext;
    [currentContext makeCurrentContext];
    CGLLockContext([currentContext CGLContextObj]);

    [_renderer addVertexWithLocationX:x locationY:y];

    CGLUnlockContext([currentContext CGLContextObj]);

}

- (void)addVertexWithLocationX:(GLfloat)x locationY:(GLfloat)y {

    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    GLfloat vertices[(totalVertices * 2) + 2];

    glGetBufferSubData(GL_ARRAY_BUFFER, 0, (totalVertices * 2), vertices);

    for (int i = 0; i < ((totalVertices * 2) + 2); i++) {
        if (i == (totalVertices * 2)) {
            vertices[i] = x;
        } else if (i == (totalVertices * 2) + 1) {
            vertices[i] = y;
        }
    }

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    totalVertices ++;

}

@end

该应用程序应该获取鼠标单击的位置,并提供它是一个顶点位置。对于每个添加的顶点,我首先绑定VBO以确保它是活动的。接下来,我创建一个新数组来保存当前顶点位置(totalVertices)加上一个顶点的空间(x和y为+2)。然后我使用glGetBufferSubData从VBO返回数据并将其放入此数组中。使用for循环我将X和Y数字添加到数组的末尾。最后,我将这些数据发送回GPU进入VBO并调用totalVertices ++,这样我就知道下次想要添加顶点时数组中有多少个顶点。

这让我想到了一个问题:我这样做了吗?换句话说,我是否应该在CPU端保留BufferData的副本,以便我不必调用GPU并将数据发送回来进行编辑?这样,我就不会调用glGetBufferSubData,我只是创建一个更大的数组,将新顶点添加到末尾,然后调用glBufferData以使用更新的顶点数据重新分配VBO。

**我试图将我的思维过程包括在内,以便像我这样非常缺乏编程经验的人能够理解我想要做的事情。我不希望任何人因我对我所做的事情的解释而被冒犯。 **

1 个答案:

答案 0 :(得分:1)

我当然会避免回读数据。不仅因为额外的数据复制,还避免了CPU和GPU之间的同步。

当您进行OpenGL调用时,您可以想象驱动程序构建GPU命令,将其排队等待以后提交到GPU,然后返回。然后,这些命令将在稍后提交给GPU。我们的想法是GPU可以从CPU上的任何运行中独立运行,包括您的应用程序。 CPU和GPU以最小的依赖性并行运行是非常理想的性能。

对于大多数glGet*()调用,此异步执行模型会中断。他们通常必须等到GPU完成所有(或至少一些)待处理命令才能返回数据。因此,只有GPU正在运行时CPU可能会阻塞,这是不可取的。

出于这个原因,你绝对应该保留数据的CPU副本,这样你就不必阅读它了。

除此之外,还有一些选择。这将取决于您的使用模式,特定平台的性能特征等。为了真正实现最大化,我们无法实现多个变体,并对它们进行基准测试。

对于你所描述的内容,我可能会从与C ++中的std::vector类似的东西开始。您分配了一定量的内存(通常称为容量),该内存大于您目前所需的内存量。然后,您可以在不重新分配的情况下添加数据,直到填满分配的容量。此时,您可以将容量加倍。

将此应用于OpenGL,您可以通过使用NULL作为数据指针调用glBufferData()来保留一定量的内存。跟踪您分配的容量,并通过调用glBufferSubData()填充缓冲区。在示例代码中添加单个点时,只需使用新点即可调用glBufferSubData()。只有当您的容量不足时,才能使用新容量调用glBufferData(),然后使用您已有的所有数据填充它。

在伪代码中,初始化看起来像这样:

int capacity = 10;
glBufferData(GL_ARRAY_BUFFER,
    capacity * sizeof(Point), NULL, GL_DYNAMIC_DRAW);
std::vector<Point> data;

然后每次添加一个点:

data.push_back(newPoint);
if (data.size() <= capacity) {
    glBufferSubData(GL_ARRAY_BUFFER,
        (data.size() - 1) * sizeof(Point), sizeof(Point), &newPoint);
} else {
    capacity *= 2;
    glBufferData(GL_ARRAY_BUFFER,
        capacity * sizeof(Point), NULL, GL_DYNAMIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER,
        0, data.size() * sizeof(Point), &data[0]);
}

作为glBufferSubData()的替代方案,glMapBufferRange()是另一个考虑更新缓冲区数据的选项。更进一步,您可以考虑使用多个缓冲区,并循环使用它们,而不是只更新一个缓冲区。这就是基准测试发挥作用的地方,因为没有一种方法可以最好地适用于每个可能的平台和用例。