我正在尝试学习OpenGL ES 2.0来做一些iPhone游戏开发。我已经阅读了多个教程和一些OpenGL ES 2.0规范。我见过的所有例子都创建了一个网格,将其加载到顶点缓冲区然后渲染(使用预期的平移,旋转,渐变等)。
我的问题是:如何在场景中渲染具有不同网格并独立移动的多个对象?如果我有一辆汽车和一辆摩托车,我可以创建2个顶点缓冲区并为每个渲染调用保留两个网格数据,然后为每个对象发送不同的着色器矩阵吗?或者我是否需要以某种方式转换网格然后将它们组合成一个网格,以便它们可以一次渲染?我正在寻找更多的高级策略/程序结构而不是代码示例。我想我的错误心理模式是如何运作的。
谢谢!
答案 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更有效率:
现在代码:
在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下的子项),然后评估父母的相对旋转等。
祝你好运!