Как добавить вершины к объекту сетки в OpenGL?

Я новичок в OpenGL и использую Красную книгу и Супер Библия. В СБ я попал в раздел об использовании объектов, загружаемых из файлов. Пока что я не думаю, что у меня есть проблемы с пониманием того, что происходит и как это сделать, но это заставило меня задуматься о создании собственной сетки в моем собственном приложении - по сути, моделирующем приложении. Я провел много поисков по обоим ссылкам, а также по интернету, и мне еще предстоит найти хорошее руководство по внедрению таких функций в собственное приложение. Я нашел API, который просто предоставляет эту функциональность, но я пытаюсь понять реализацию; не только интерфейс.

До сих пор я создал "приложение" (я использую этот термин слегка), которое дает вам представление, что вы можете нажать и добавить вершины. Вершины не соединяются, просто отображаются там, где вы щелкаете. Меня беспокоит то, что этот метод, на который я наткнулся при экспериментировании, не является способом, которым я должен реализовывать этот процесс.

Я работаю на Mac и использую Objective-C и C в Xcode.

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) плюс пространство для еще одной вершины (+ 2 для x и y). Затем я использовал glGetBufferSubData, чтобы вернуть данные из VBO и поместить их в этот массив. Используя цикл for, я добавляю числа X и Y в конец массива. Наконец, я отправляю эти данные обратно в графический процессор в VBO и вызываю totalVertices++, чтобы я знал, сколько вершин у меня в массиве, когда я в следующий раз захочу добавить вершину.

Это подводит меня к моему вопросу: я делаю это правильно? Другими словами, должен ли я хранить копию данных BufferData на стороне процессора, чтобы мне не приходилось вызывать графический процессор и отправлять данные для редактирования? Таким образом, я бы не вызывал glGetBufferSubData, я просто создал бы больший массив, добавил новую вершину в конец, а затем вызвал glBufferData, чтобы перераспределить VBO с обновленными данными вершин.

** Я пытался включить свой мыслительный процесс, чтобы такой человек, как я, очень неопытный в программировании, мог с надеждой понять, что я пытаюсь сделать. Я не хочу, чтобы кого-то обидели мои объяснения того, что я сделал. **

1 ответ

Решение

Я бы конечно не стал читать данные обратно. Не только из-за дополнительной копии данных, но и во избежание синхронизации между CPU и GPU.

Когда вы делаете вызов OpenGL, вы можете представить, как драйвер создает команду GPU, ставит ее в очередь для последующей отправки в GPU и затем возвращает. Эти команды будут затем переданы в графический процессор позже. Идея состоит в том, что графический процессор может работать максимально независимо от того, что работает на процессоре, который включает ваше приложение. CPU и GPU, работающие параллельно с минимальными зависимостями, очень желательны для производительности.

Для большинства glGet*() вызовы, эта модель асинхронного выполнения выходит из строя. Им часто приходится ждать, пока графический процессор выполнит все (или хотя бы некоторые) ожидающие команды, прежде чем они смогут вернуть данные. Таким образом, процессор может блокироваться, пока работает только графический процессор, что нежелательно.

По этой причине вам обязательно следует сохранять копию данных вашего ЦП, чтобы вам никогда не приходилось их читать.

Помимо этого, есть несколько вариантов. Все будет зависеть от вашей модели использования, характеристик производительности конкретной платформы и т. Д. Чтобы по-настоящему извлечь максимальную пользу из этого, невозможно реализовать несколько вариантов и сравнить их.

Для того, что вы описываете, я бы, вероятно, начал с чего-то, что работает аналогично std::vector в C++. Вы выделяете определенный объем памяти (обычно называемый емкостью), который больше, чем вам нужно в данный момент. Затем вы можете добавлять данные без перераспределения, пока не заполните выделенную емкость. В этот момент вы можете, например, удвоить емкость.

Применяя это к OpenGL, вы можете зарезервировать определенный объем памяти, вызвав glBufferData() с NULL в качестве указателя данных. Следите за выделенной емкостью и заполняйте буфер вызовами 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() еще один вариант для обновления данных буфера. Продвинувшись дальше, вы можете изучить использование нескольких буферов и циклически их использовать вместо обновления только одного буфера. Именно здесь в игру вступает бенчмаркинг, потому что нет единого подхода, который будет наилучшим для каждой возможной платформы и варианта использования.

Другие вопросы по тегам