Рендеринг нескольких объектов с OpenGL ES 2.0 - PullRequest
31 голосов
/ 28 августа 2011

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

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

Спасибо!

Ответы [ 6 ]

12 голосов
/ 28 июля 2012

Лучший способ сделать это - использовать VAO в дополнение к VBO.

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

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

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, ваш код функции рисования намного проще (что хорошо, потому что это повторяющаяся функция):

Сначала вы определите для 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

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

В настройках GL:

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);
10 голосов
/ 28 августа 2011

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

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

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

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

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

4 голосов
/ 27 сентября 2012

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

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

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

3 голосов
/ 28 августа 2011

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

0 голосов
/ 27 октября 2014

При использовании шейдеров можно использовать одну и ту же программу для всех объектов без необходимости компилировать, связывать и создавать по одному для каждого объекта. Для этого просто сохраните значение GLuint в программе, а затем для каждого объекта "glUseProgram (programId);". В результате личного опыта, я использую единый для управления структурами 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, ответ выше объясняет, как лучше всего с этим справиться. Что касается ориентации уравнения, вам понадобится механизм для вложения tdobjects друг в друга (аналогично UIView и потомкам под iOS), а затем оценки относительного поворота к родителям и т. Д.

Удачи!

0 голосов
/ 02 декабря 2013

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

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

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

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

...