使用OpenGL ES 2.0渲染多个对象

时间:2011-08-27 23:56:18

标签: opengl-es-2.0

我正在尝试学习OpenGL ES 2.0来做一些iPhone游戏开发。我已经阅读了多个教程和一些OpenGL ES 2.0规范。我见过的所有例子都创建了一个网格,将其加载到顶点缓冲区然后渲染(使用预期的平移,旋转,渐变等)。

我的问题是:如何在场景中渲染具有不同网格并独立移动的多个对象?如果我有一辆汽车和一辆摩托车,我可以创建2个顶点缓冲区并为每个渲染调用保留两个网格数据,然后为每个对象发送不同的着色器矩阵吗?或者我是否需要以某种方式转换网格然后将它们组合成一个网格,以便它们可以一次渲染?我正在寻找更多的高级策略/程序结构而不是代码示例。我想我的错误心理模式是如何运作的。

谢谢!

6 个答案:

答案 0 :(得分:12)

我发现这样做的最好方法是使用除VBO之外的VAO。

我将首先使用VBO回答您的问题。

首先,假设您将两个对象的两个网格存储在以下数组中:

GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;

其中:

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

你还必须使用vertix缓冲区:

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中执行一次,然后在draw中切换它们。

当然,您可以说您可以将代码(没有VAO)放入函数中并调用它。这是事实,但根据Apple的说法,VAO更有效率:

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

答案 1 :(得分:10)

为不同的对象维护单独的顶点/索引缓冲区,是的。例如,您可能有一个RenderedObject类,每个实例都有自己的顶点缓冲区。一个RenderedObject可能从房屋网格中获取它的顶点,一个可能来自角色网格等。

在渲染过程中,为您正在使用的顶点缓冲区设置适当的变换/旋转/着色,可能类似于:

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

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

正如其他答案所述,bufferID只是一个GLuint而不是缓冲区的全部内容。如果您需要有关创建顶点缓冲区和填充数据的更多详细信息,我很乐意添加它们。

答案 2 :(得分:4)

我意识到这是一篇较旧的帖子,但我试图找到有关如何在OpenGL内呈现多个对象的说明。我找到了一个很棒的教程,它描述了如何渲染多个对象,并且可以很容易地扩展为渲染不同类型的对象(即一个立方体,一个金字塔)。

我发布的教程还介绍了如何使用GLKit呈现对象。我发现它很有帮助,并认为我会在这里重新发布它。我希望它对你有帮助!

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

答案 3 :(得分:3)

如果网格不同,则将它们保存在不同的顶点缓冲区中。如果它们相似(例如动画,颜色),则将参数传递给着色器。如果您不打算在应用程序端设置动画对象,则只需将句柄保留在VBO中,而不是顶点数据本身。设备端动画 是可能的。

答案 4 :(得分:0)

我希望能为这篇老帖做贡献,因为我承诺以不同的方式解决这个问题。像提问者一样,我看过很多“一个对象”的例子。我承诺将所有顶点放在一个VBO中,然后将偏移量保存到该对象的位置(每个对象),而不是缓冲区句柄。有效。偏移量可以作为glDrawElements的参数给出,如下所示。回想起来似乎很明显,但在我看到它起作用之前我并不相信。请注意,我一直在使用“顶点指针”而不是更新的“顶点属性指针”。我正致力于后者,所以我可以利用着色器。   在调用“绘制元素”之前,所有对象都“绑定”到相同的顶点缓冲区。

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

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

我没有发现拼写出这个偏移的目的是什么,所以我抓住了机会。此外,这个问题:您必须以字节为单位指定偏移量,而不是顶点或浮点数。也就是说,乘以4得到正确的位置。

答案 5 :(得分:0)

使用着色器时,可以对所有对象使用相同的程序,而无需为每个对象编译,链接和创建一个。为此,只需将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

请注意,一旦传入了数据空间(属性/制服),您就不必在每个渲染周期中传递它们,但只有在无效时才传递它们。这会带来严重的GPU性能提升。

根据VBO的一面,上面的答案阐明了如何最好地处理这个问题。根据等式的方向,你需要一种机制来将tdobjects嵌套在彼此之内(类似于UIView和iOS下的子项),然后评估父母的相对旋转等。

祝你好运!