Renderice varios objetos con OpenGL ES 2.0

Estoy tratando de aprender OpenGL ES 2.0 para desarrollar un juego de iPhone. He leído varios tutoriales y algunas de las especificaciones de OpenGL ES 2.0. Todos los ejemplos que he visto han creado una sola malla, la han cargado en un búfer de vértice y luego la han renderizado (con la traducción, rotación, gradiente, etc. esperados).

Mi pregunta es la siguiente: ¿cómo renderiza varios objetos en su escena que tienen diferentes mallas y se mueven de forma independiente? Si tengo un automóvil y una motocicleta, por ejemplo, ¿puedo crear 2 búferes de vértice y mantener los datos de malla para ambos para cada llamada de renderizado, y luego enviar diferentes matrices para el sombreador para cada objeto? ¿O necesito traducir de alguna manera las mallas y luego combinarlas en una sola malla para que puedan renderizarse en una sola pasada? Estoy buscando más estructura de programa / estrategia de alto nivel en lugar de ejemplos de código. Creo que tengo el modo mental equivocado de cómo funciona esto.

¡Gracias!

preguntado el 27 de agosto de 11 a las 23:08

Gracias por las respuestas rápidas, ahora lo entiendo. -

Esta es una pregunta muy bien formulada. -

La mayoría de los ejemplos rotan y traducen una vez con una vista de objeto. Para dibujar dos objetos en diferentes ángulos y posiciones, aplique las operaciones inversas después del primer objeto. p.ej. translate1, rotate1, draw1, -rotate1, -translate1, translate2, rotate2, draw2 -rotate2, -translate2, publicaré un ejemplo pronto -

6 Respuestas

La mejor manera que encontré para hacer esto es usando VAO además de VBO.

Primero responderé su pregunta usando solo VBO.

En primer lugar, suponga que tiene las dos mallas de sus dos objetos almacenados en las siguientes matrices:

GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;

dónde:

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

Y también tienes que vertix buffers:

GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;

Ahora, para dibujar esos dos cubos (sin VAO), tienes que hacer algo así: en la función de dibujo (de la plantilla 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);

Esto responderá a su pregunta. Pero ahora, para usar VAO, su código de función de dibujo es mucho más simple (lo cual es bueno porque es la función repetida):

Primero definirá a los VAO:

GLuint _vertexArray1;
GLuint _vertexArray2;

y luego realizará todos los pasos realizados anteriormente en el método de dibujo, lo hará en la función setupGL pero después de vincularse al VAO. Luego, en su función de dibujo, simplemente se vincula al VAO que desea.

VAO aquí es como un perfil que contiene muchas propiedades (imagina un perfil de dispositivo inteligente). En lugar de cambiar el color, el escritorio, las fuentes, etc., cada vez que desee cambiarlos, hágalo una vez y guárdelo con un nombre de perfil. Luego, simplemente cambia el perfil.

Así que haces eso una vez, dentro de setupGL, luego cambias entre ellos en el dibujo.

Por supuesto, puede decir que podría haber puesto el código (sin VAO) en una función y llamarlo. Eso es cierto, pero los VAO son más eficientes según Apple:

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

Ahora al código:

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

Luego, finalmente en su método de dibujo:

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


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

Respondido 28 Jul 12, 15:07

Gracias, usar VAO es una excelente manera: Ahmed Kotb

Si si tengo que crear 100 objetos necesito 100 VAOs? - MateriaObjetivo

Tenga en cuenta que no se garantiza que VAO esté disponible en todos los ES 2.0 dispositivos. (Está garantizado si ES 3.0) Si está programando para 2.0, luego llame glGetString(GL_EXTENSIONS)y busque una cadena OES_vertex_array_object en la respuesta khronos.org/opengles/sdk/1.1/docs/man/glGetString.xml Especialmente en Android, con la amplia gama de dispositivos, para que pueda informar al usuario que su aplicación no se puede ejecutar, o recurrir a un enfoque exclusivo de VBO. - ToolmakerSteve

Mantienes búferes de vértice / índice separados para diferentes objetos, sí. Por ejemplo, podría tener una clase RenderedObject y cada instancia tendría su propio búfer de vértice. Un RenderedObject puede tomar sus vértices de la malla de una casa, uno puede provenir de una malla de caracteres, etc.

Durante el renderizado, establece la transformación / rotación / sombreado apropiado para el búfer de vértice con el que está trabajando, tal vez algo como:

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

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

Como se mencionó en otra respuesta, el bufferID es solo un GLuint, no todo el contenido del búfer. Si necesita más detalles sobre cómo crear búferes de vértices y llenarlos con datos, me complace agregarlos también.

Respondido 28 ago 11, 04:08

mi pregunta tiene un retraso de 2 años, pero: si tiene diferentes búferes de vértice para diferentes objetos, también debe vincular el programa a todos estos objetos. En mi caso, donde obtuviste fácilmente más de 1000 objetos, debes llamar a setVertexAttribPointer cada vez. En mi caso, esto tuvo el resultado de que se volvió muy lento (de hecho, el 60-70% del tiempo se dedicó a "setVertexAttribPointer" <- ¿lo sabe o hice algo mal? - Frame91

Tengo entendido que esto puede resultar ineficaz. Por ejemplo: si quieres dibujar 10 pájaros, esto será muy lento. Tengo entendido (y no soy un experto en esto) que debería intentar fusionar objetos similares, posiblemente todos los objetos, para minimizar el número de llamadas de dibujo. Desafortunadamente, no estoy seguro de cómo se puede lograr esto, ¡aunque la teoría tiene perfecto sentido! - AlvindeDiaspar

Sí, el costo de cambiar los búferes de vértice / índice puede resultar bastante caro si se hace con demasiada frecuencia. En el caso de las aves de 10K, es probable que solo haya un puñado de modelos distintos utilizados para representar a las aves. Tendría un búfer de vértice para cada tipo de modelo y volvería a dibujar el búfer con diferentes transformaciones para cada instancia. Así que configure el búfer de vértice Sparrow, dibuje 2K Sparrows, luego configure el Falcon VB y dibuje 3K Falcons y así sucesivamente. - TaylorP

Me doy cuenta de que esta es una publicación anterior, pero estaba tratando de encontrar instrucciones sobre cómo renderizar múltiples objetos dentro OpenGL. Encontré un gran tutorial, que describe cómo renderizar múltiples objetos y podría extenderse fácilmente para renderizar objetos de diferentes tipos (es decir, un cubo, una pirámide).

El tutorial que estoy publicando también describe cómo renderizar objetos usando GLKit. Lo encontré útil y pensé en volver a publicarlo aquí. ¡Espero que te ayude a ti también!

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

Respondido el 29 de Septiembre de 12 a las 07:09

El enlace está roto, ¿nueva URL? - ToolmakerSteve

Parece que es posible que el nombre del sitio del tutorial no se haya renovado. Pero, el código creado en el tutorial está en GitHub aquí: github.com/ianterrell/GLKit-Cube-Tutorial Eso es lo mejor que pude encontrar. Parece que su sitio web con los tutoriales ahora está fuera de línea, por lo que parece. Con suerte, trasladará el contenido del tutorial a un nuevo hogar. :-) - MikeyE

Si las mallas son diferentes, las mantiene en búferes de vértice diferentes. Si son similares (por ejemplo, animación, color), pasa argumentos al sombreador. Solo tiene que mantener los identificadores de los VBO, no los datos del vértice en sí, si no planea animar el objeto en el lado de la aplicación. Animación del lado del dispositivo is posible.

Respondido 28 ago 11, 03:08

Con suerte, estoy contribuyendo a esta publicación anterior, porque me comprometí a resolver este problema de una manera diferente. Al igual que el autor de la pregunta, he visto muchos ejemplos de "un objeto". Me comprometí a colocar todos los vértices en un solo VBO y luego guardar el desplazamiento en la posición de ese objeto (por objeto), en lugar de un controlador de búfer. Funcionó. El desplazamiento se puede dar como parámetro a glDrawElements como se muestra a continuación. Parece obvio en retrospectiva, pero no me convencí hasta que lo vi funcionar. Tenga en cuenta que he estado trabajando con "puntero de vértice" en lugar del "puntero de atributo de vértice" más actual. Estoy trabajando en este último para poder aprovechar los sombreadores. Todos los objetos se "enlazan" al mismo búfer de vértice, antes de llamar a "dibujar elementos".

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

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

No encontré en ningún lugar explicado cuál era el propósito de esta compensación, así que me arriesgué. Además, esto es así: tienes que especificar el desplazamiento en bytes, no en vértices o flotantes. Es decir, multiplique por cuatro para obtener la posición correcta.

Respondido el 03 de diciembre de 13 a las 09:12

Cuando se usan sombreadores, es posible usar el mismo programa para todos los objetos sin tener que compilar, vincular y crear uno para cada uno. Para hacer esto, simplemente almacene el valor de GLuint en el programa y luego para cada objeto "glUseProgram (programId);". Como resultado de la experiencia personal, utilizo un singleton para administrar las estructuras de GLProgram ... (incluido a continuación :))

@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

Tenga en cuenta que una vez que se pasan los espacios de datos (atributos / uniformes), NO tiene que pasarlos en cada ciclo de renderizado, sino solo cuando se invalidan. Esto da como resultado una importante ganancia de rendimiento de la GPU.

Por el lado de VBO de las cosas, la respuesta anterior explica la mejor manera de lidiar con esto. Según el lado de orientación de la ecuación, necesitará un mecanismo para anidar tdobjects entre sí (similar a UIView y los niños en iOS) y luego evaluar las rotaciones relativas a los padres, etc.

¡Buena suerte!

Respondido 27 Oct 14, 18:10

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.