web-dev-qa-db-fra.com

Rendre plusieurs objets avec OpenGL ES 2.0

J'essaie d'apprendre à utiliser OpenGL ES 2.0 pour développer des jeux pour iPhone. J'ai lu plusieurs didacticiels et certaines des spécifications OpenGL ES 2.0. Tous les exemples que j'ai vus ont créé un seul maillage, l'ont chargé dans un vertex buffer, puis l'ont restitué (avec la translation, la rotation, le dégradé, etc. attendus).

Ma question est la suivante: comment restituer plusieurs objets de votre scène qui ont des maillages différents et se déplacent indépendamment? Si j'ai une voiture et une moto par exemple, puis-je créer 2 tampons de vertex et conserver les données de maillage des deux côtés pour chaque appel de rendu, puis simplement envoyer des matrices différentes pour le shader pour chaque objet? Ou dois-je en quelque sorte traduire les maillages puis les combiner en un seul maillage afin de pouvoir les restituer en un seul passage? Je recherche plus de la structure de stratégie/programme de haut niveau plutôt que des exemples de code. Je pense que j'ai juste le mauvais modal mental de la façon dont cela fonctionne.

Merci!

31
Matthew Daugherty

Vous maintenez des tampons de sommets/index distincts pour différents objets, oui. Par exemple, vous pourriez avoir une classe RenderedObject et chaque instance aurait son propre tampon de sommets. Un objet RenderedObject peut prendre ses sommets d'un maillage maison, un autre peut provenir d'un maillage de caractère, etc. 

Pendant le rendu, vous définissez la transformation/rotation/ombrage appropriée pour le tampon de vertex avec lequel vous travaillez, par exemple: 

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

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

Comme mentionné dans cette autre réponse, le bufferID est simplement un GLuint et non le contenu entier du buffer. Si vous avez besoin de plus de détails sur la création de tampons de vertex et leur remplissage avec des données, je serais ravi de les ajouter également. 

10
TaylorP

Je me rends compte que c’est un article plus ancien, mais j’essayais de trouver des instructions sur la façon de rendre plusieurs objets dans OpenGL. J'ai trouvé un bon tutoriel, décrivant comment rendre plusieurs objets et pouvant être facilement étendu à des objets de différents types (par exemple, un cube, une pyramide). 

Le tutoriel que je publie explique également comment rendre des objets à l'aide de GLKit. Je l'ai trouvé utile et j'ai pensé le republier ici. J'espère que ça vous aide aussi!

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

4
MikeyE

Si les maillages sont différents, vous les conservez dans des tampons de vertex différents. S'ils sont similaires (par exemple, animation, couleur), vous transmettez des arguments au shader. Vous ne devez conserver que les descripteurs des VBO, pas les données de sommet même si vous ne prévoyez pas d'animer l'objet du côté de l'application. Animation côté périphérique est possible.

3
Kornel Kisielewicz

J'espère contribuer à ce poste plus ancien, car je me suis engagé à résoudre ce problème différemment. À l'instar du demandeur, j'ai vu beaucoup d'exemples "à objet unique". Je me suis engagé à placer tous les sommets dans un seul VBO, puis à enregistrer le décalage dans la position de cet objet (par objet), plutôt que dans un tampon. Ça a marché. Le décalage peut être donné comme paramètre à glDrawElements comme ci-dessous. Cela semble évident rétrospectivement, mais je n’étais pas convaincu avant de voir que cela fonctionnait. Veuillez noter que je travaille avec un "pointeur de sommet" plutôt que le "pointeur d'attribut de sommet" plus récent. Je travaille vers ce dernier afin de pouvoir utiliser les shaders. Tous les objets se "lient" au même tampon de vertex, avant d'appeler "draw elements". 

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

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

Je n'ai trouvé nulle part où était précisé le but de cette compensation, alors j'ai tenté ma chance. En outre, ceci est: vous devez spécifier le décalage en octets, et non en vertices ou flottants. Autrement dit, multipliez par quatre pour obtenir la position correcte.

0
Javaneer

Il est possible, lors de l'utilisation de shaders, d'utiliser le même programme pour tous les objets sans avoir à en compiler, en lier et en créer un pour chacun. Pour ce faire, stockez simplement la valeur GLuint dans le programme, puis pour chaque objet "glUseProgram (programId);". Par conséquent, j’utilise un singleton pour gérer les structures de GLProgram .. (inclus ci-dessous :)) 

@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

Notez qu'une fois que les espaces de données (attributs/uniformes) sont passés, vous NE devez PAS les passer à chaque cycle de rendu, mais seulement lorsqu'ils sont invalidés. Cela entraîne un sérieux gain de performances du processeur graphique.

Du point de vue de VBO, la réponse ci-dessus explique comment le gérer au mieux. Selon le côté orientation de l'équation, vous aurez besoin d'un mécanisme pour imbriquer les objets les uns dans les autres (similaire à UIView et aux enfants sous iOS), puis évaluer les rotations relatives par rapport aux parents, etc.

Bonne chance !

0
Samuel Colak