Подтвердить что ты не робот

Рендеринг нескольких объектов с помощью OpenGL ES 2.0

Я пытаюсь изучить OpenGL ES 2.0 для разработки iPhone. Я прочитал несколько руководств и некоторые из спецификаций OpenGL ES 2.0. Все примеры, которые я видел, создали единую сетку, загрузили ее в буфер вершин и затем отобрали ее (с ожидаемым переводом, вращением, градиентом и т.д.).

Мой вопрос заключается в следующем: как вы визуализируете несколько объектов в вашей сцене, которые имеют разные ячейки и движутся независимо? Например, если у меня есть автомобиль и мотоцикл, могу ли я создать 2 вершинных буфера и сохранить данные сетки для обоев для каждого вызова рендеринга, а затем просто отправить в разные матрицы для шейдера для каждого объекта? Или мне нужно каким-то образом перевести сеточки, а затем объединить их в одну сетку, чтобы они могли быть отображены за один проход? Я ищу более стратегическую/стратегическую структуру высокого уровня, а не примеры кода. Я думаю, что у меня просто неправильный умственный способ, как это работает.

Спасибо!

4b9b3361

Ответ 1

Вы поддерживаете отдельные буферы вершин/индекс для разных объектов, да. Например, у вас может быть класс RenderedObject, и каждый экземпляр должен иметь собственный буфер вершин. Один объект RenderedObject может принимать вершины из домашней сетки, может быть, из символьной сетки и т.д.

Во время рендеринга вы устанавливаете соответствующее преобразование/поворот/затенение для буфера вершин, с которым вы работаете, возможно, что-то вроде:

void RenderedObject::render()
{
    ...
    //set textures/shaders/transformations

    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
    ...
}

Как уже упоминалось в другом ответе, bufferID - это просто GLuint, а не все содержимое буфера. Если вам нужна дополнительная информация о создании буферов вершин и заполнении их данными, я с удовольствием добавлю их.

Ответ 2

Лучший способ, которым я нашел это, - использовать VAO в дополнение к VBOs.

Сначала я отвечу на ваш вопрос, используя только VBOs.

Прежде всего, предположим, что у вас есть две ячейки из ваших двух объектов, хранящихся в следующих массивах:

GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;

где:

GLfloat gCubeVertexData1[36] = {...};
GLfloat gCubeVertexData2[36] = {...};

И вы также должны использовать буферы с вертикальным расположением:

GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;

Теперь, чтобы нарисовать эти два куба (без VAO), вы должны сделать что-то вроде этого: в функции рисования (из шаблона OpenGLES):

//Draw first object, bind VBO, adjust your attributes then call DrawArrays
glGenBuffers(1, &_vertexBufferCube1);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube1);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData1), gCubeVertexData1, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));


glDrawArrays(GL_TRIANGLES, 0, 36);



//Repeat for second object:
glGenBuffers(1, &_vertexBufferCube2);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube2);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData2), gCubeVertexData2, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glUseProgram(_program);

glDrawArrays(GL_TRIANGLES, 0, 36);

Это ответит на ваш вопрос. Но теперь, чтобы использовать VAO, ваш код функции draw намного проще (что хорошо, потому что это повторяющаяся функция):

Сначала вы определяете VAO:

GLuint _vertexArray1;
GLuint _vertexArray2;

а затем вы сделаете все шаги, ранее сделанные в методе draw, вы сделаете это в функции setupGL, но после привязки к VAO. Затем в вашей функции рисования вы просто привязываетесь к VAO, который вам нужен.

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

Итак, вы делаете это один раз, внутри setupGL, затем переключаетесь между ними в режиме рисования.

Конечно, вы можете сказать, что вы могли бы поместить код (без VAO) в функцию и вызвать его. Это правда, но VAO более эффективны в соответствии с Apple:

http://developer.apple.com/library/ios/#documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html#//apple_ref/doc/uid/TP40008793-CH107-SW1

Теперь к коду:

В setupGL:

glGenVertexArraysOES(1, &_vertexArray1); //Bind to first VAO
glBindVertexArrayOES(_vertexArray1);

glGenBuffers(1, &_vertexBufferCube1); //All steps from this one are done to first VAO only
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube1);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData1), gCubeVertexData1, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));

glGenVertexArraysOES(1, &_vertexArray2); // now bind to the second
glBindVertexArrayOES(_vertexArray2);

glGenBuffers(1, &_vertexBufferCube2); //repeat with the second mesh
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube2);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData2), gCubeVertexData2, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));


glBindVertexArrayOES(0);

Затем, наконец, в методе рисования:

glBindVertexArrayOES(_vertexArray1);
glDrawArrays(GL_TRIANGLES, 0, 36);


glBindVertexArrayOES(_vertexArray2);    
glDrawArrays(GL_TRIANGLES, 0, 36);

Ответ 3

Я понимаю, что это более старая запись, но я пытался найти инструкции о том, как отображать несколько объектов в OpenGL. Я нашел отличный учебник, в котором описывается, как визуализировать несколько объектов и может быть легко распространен для рендеринга объектов разных типов (т.е. Одного куба, одной пирамиды).

В учебнике, которое я публикую, также описывается, как визуализировать объекты с помощью GLKit. Я счел это полезным и подумал, что я его отпишу здесь. Надеюсь, это тоже поможет!

http://games.ianterrell.com/opengl-basics-with-glkit-in-ios5-encapsulated-drawing-and-animation/

Ответ 4

Если сетки разные, вы храните их в разных буферах вершин. Если они похожи (например, анимация, цвет), вы передаете аргументы шейдеру. Вам нужно только держать дескрипторы в VBOs, а не сами данные вершин, если вы не планируете анимировать объект со стороны приложения. Возможно анимация на стороне устройства.

Ответ 5

Я надеюсь, что внесет свой вклад в эту более старую должность, потому что я решил решить эту проблему по-другому. Как и вопросник, я видел множество примеров с одним объектом. Я решил разместить все вершины в одном VBO, а затем сохранить смещение в этой позиции объекта (для каждого объекта), а не в буфере. Это сработало. Смещение может быть задано как параметр для glDrawElements, как показано ниже. Это кажется очевидным в ретроспективе, но я не был убежден, пока не увидел, что он работает. Обратите внимание, что я работал с "вершинным указателем", а не с более текущим "указателем атрибута вершин". Я работаю над последним, поэтому могу использовать шейдеры. Все объекты "связывают" с одним и тем же буфером вершин, до вызова "draw elements".

        gl.glVertexPointer( 3, GLES20.GL_FLOAT, 0, vertexBufferOffset );

        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, indicesCount,
                GLES20.GL_UNSIGNED_BYTE, indexBufferOffset
        );

Я нигде не нашел объяснений, какова была цель этого смещения, поэтому я рискнул. Кроме того, это gotcha: вы должны указать смещение в байтах, а не в вершинах или поплавках. То есть, умножьте на четыре, чтобы получить правильную позицию.

Ответ 6

При использовании шейдеров можно использовать одну и ту же программу для всех объектов без необходимости компиляции, связывания и создания одного для каждого. Для этого просто сохраните значение GLuint для программы, а затем для каждого объекта "glUseProgram (programId);". В результате личного опыта я использую singleton для управления структурами GLProgram.. (см. Ниже:))

@interface TDShaderSet : NSObject {

    NSMutableDictionary     *_attributes;
    NSMutableDictionary     *_uniforms;
    GLuint                  _program;

}

    @property (nonatomic, readonly, getter=getUniforms) NSMutableDictionary *uniforms;
    @property (nonatomic, readonly, getter=getAttributes) NSMutableDictionary *attributes;

    @property (nonatomic, readonly, getter=getProgram) GLuint program;

    - (GLint) uniformLocation:(NSString*)name;
    - (GLint) attribLocation:(NSString*)name;

@end


@interface TDProgamManager : NSObject

    + (TDProgamManager *) sharedInstance;
    + (TDProgamManager *) sharedInstanceWithContext:(EAGLContext*)context;

    @property (nonatomic, readonly, getter=getAllPrograms) NSArray *allPrograms;

    - (BOOL) loadShader:(NSString*)shaderName referenceName:(NSString*)refName;

    - (TDShaderSet*) getProgramForRef:(NSString*)refName;

@end

@interface TDProgamManager () {

    NSMutableDictionary     *_glPrograms;
    EAGLContext             *_context;

}

@end


@implementation TDShaderSet

    - (GLuint) getProgram
    {
        return _program;
    }

    - (NSMutableDictionary*) getUniforms
    {
        return _uniforms;
    }

    - (NSMutableDictionary*) getAttributes
    {
        return _attributes;
    }

    - (GLint) uniformLocation:(NSString*)name
    {
        NSNumber *number = [_uniforms objectForKey:name];
        if (!number) {
            GLint location = glGetUniformLocation(_program, name.UTF8String);
            number = [NSNumber numberWithInt:location];
            [_uniforms setObject:number forKey:name];
        }
        return number.intValue;
    }

    - (GLint) attribLocation:(NSString*)name
    {
        NSNumber *number = [_attributes objectForKey:name];
        if (!number) {
            GLint location = glGetAttribLocation(_program, name.UTF8String);
            number = [NSNumber numberWithInt:location];
            [_attributes setObject:number forKey:name];
        }
        return number.intValue;
    }

    - (id) initWithProgramId:(GLuint)program
    {
        self = [super init];
        if (self) {
            _attributes = [[NSMutableDictionary alloc] init];
            _uniforms = [[NSMutableDictionary alloc] init];
            _program = program;
        }
        return self;
    }

@end


@implementation TDProgamManager {

@private

}

    static TDProgamManager *_sharedSingleton = nil;

    - (NSArray *) getAllPrograms
    {
        return _glPrograms.allValues;
    }

    - (TDShaderSet*) getProgramForRef:(NSString *)refName
    {
        return (TDShaderSet*)[_glPrograms objectForKey:refName];
    }

    - (BOOL) loadShader:(NSString*)shaderName referenceName:(NSString*)refName
    {

        NSAssert(_context, @"No Context available");

        if ([_glPrograms objectForKey:refName]) return YES;

        [EAGLContext setCurrentContext:_context];

        GLuint vertShader, fragShader;

        NSString *vertShaderPathname, *fragShaderPathname;

        // Create shader program.
        GLuint _program = glCreateProgram();

        // Create and compile vertex shader.
        vertShaderPathname = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"vsh"];

        if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
            NSLog(@"Failed to compile vertex shader");
            return NO;
        }

        // Create and compile fragment shader.
        fragShaderPathname = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"fsh"];

        if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
            NSLog(@"Failed to compile fragment shader");
            return NO;
        }

        // Attach vertex shader to program.
        glAttachShader(_program, vertShader);

        // Attach fragment shader to program.
        glAttachShader(_program, fragShader);

        // Bind attribute locations.
        // This needs to be done prior to linking.
        glBindAttribLocation(_program, GLKVertexAttribPosition, "a_position");
        glBindAttribLocation(_program, GLKVertexAttribNormal, "a_normal");
        glBindAttribLocation(_program, GLKVertexAttribTexCoord0, "a_texCoord");

        // Link program.
        if (![self linkProgram:_program]) {

            NSLog(@"Failed to link program: %d", _program);

            if (vertShader) {
                glDeleteShader(vertShader);
                vertShader = 0;
            }
            if (fragShader) {
                glDeleteShader(fragShader);
                fragShader = 0;
            }
            if (_program) {
                glDeleteProgram(_program);
                _program = 0;
            }

            return NO;

        }

        // Release vertex and fragment shaders.
        if (vertShader) {
            glDetachShader(_program, vertShader);
            glDeleteShader(vertShader);
        }

        if (fragShader) {
            glDetachShader(_program, fragShader);
            glDeleteShader(fragShader);
        }

        TDShaderSet *_newSet = [[TDShaderSet alloc] initWithProgramId:_program];

        [_glPrograms setValue:_newSet forKey:refName];

        return YES;
    }

    - (BOOL) compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
    {

        GLint status;
        const GLchar *source;

        source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
        if (!source) {
            NSLog(@"Failed to load vertex shader");
            return NO;
        }

        *shader = glCreateShader(type);
        glShaderSource(*shader, 1, &source, NULL);
        glCompileShader(*shader);

    #if defined(DEBUG)
        GLint logLength;
        glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            GLchar *log = (GLchar *)malloc(logLength);
            glGetShaderInfoLog(*shader, logLength, &logLength, log);
            NSLog(@"Shader compile log:\n%s", log);
            free(log);
        }
    #endif

        glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
        if (status == 0) {
            glDeleteShader(*shader);
            return NO;
        }

        return YES;
    }

    - (BOOL) linkProgram:(GLuint)prog
    {
        GLint status;
        glLinkProgram(prog);

    #if defined(DEBUG)
        GLint logLength;
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            GLchar *log = (GLchar *)malloc(logLength);
            glGetProgramInfoLog(prog, logLength, &logLength, log);
            NSLog(@"Program link log:\n%s", log);
            free(log);
        }
    #endif

        glGetProgramiv(prog, GL_LINK_STATUS, &status);
        if (status == 0) {
            return NO;
        }

        return YES;
    }

    - (BOOL) validateProgram:(GLuint)prog
    {
        GLint logLength, status;

        glValidateProgram(prog);
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);

        if (logLength > 0) {
            GLchar *log = (GLchar *)malloc(logLength);
            glGetProgramInfoLog(prog, logLength, &logLength, log);
            NSLog(@"Program validate log:\n%s", log);
            free(log);
        }

        glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);

        if (status == 0) {
            return NO;
        }

        return YES;
    }

    #pragma mark - Singleton stuff... Don't mess with this other than proxyInit!

    - (void) proxyInit
    {

        _glPrograms = [[NSMutableDictionary alloc] init];

    }

    - (id) init
    {
        Class myClass = [self class];
        @synchronized(myClass) {
            if (!_sharedSingleton) {
                if (self = [super init]) {
                    _sharedSingleton = self;
                    [self proxyInit];
                }
            }
        }
        return _sharedSingleton;
    }

    + (TDProgamManager *) sharedInstance
    {
        @synchronized(self) {
            if (!_sharedSingleton) {
                _sharedSingleton = [[self alloc] init];
            }
        }
        return _sharedSingleton;
    }

    + (TDProgamManager *) sharedInstanceWithContext:(EAGLContext*)context
    {
        @synchronized(self) {
            if (!_sharedSingleton) {
                _sharedSingleton = [[self alloc] init];
            }
            _sharedSingleton->_context = context;
        }
        return _sharedSingleton;
    }

    + (id) allocWithZone:(NSZone *)zone
    {
        @synchronized(self) {
            if (!_sharedSingleton) {
                return [super allocWithZone:zone];
            }
        }
        return _sharedSingleton;
    }

    + (id) copyWithZone:(NSZone *)zone
    {
        return self;
    }

@end

Обратите внимание, что после того, как переданы пространства данных (атрибуты/униформы), вы НЕ ДОЛЖНЫ передавать их в каждом цикле рендеринга, но только при недействительности. Это приводит к серьезному увеличению производительности графического процессора.

На стороне VBO вещей, ответ выше говорит о том, как лучше справиться с этим. На стороне ориентации уравнения вам понадобится механизм для встраивания tdobject внутри друг друга (подобно UIView и дочерним элементам под iOS), а затем для оценки относительных вращений для родителей и т.д.

Удачи!