Рендеринг нескольких объектов с помощью OpenGL ES 2.0
Я пытаюсь изучить OpenGL ES 2.0 для разработки iPhone. Я прочитал несколько руководств и некоторые из спецификаций OpenGL ES 2.0. Все примеры, которые я видел, создали единую сетку, загрузили ее в буфер вершин и затем отобрали ее (с ожидаемым переводом, вращением, градиентом и т.д.).
Мой вопрос заключается в следующем: как вы визуализируете несколько объектов в вашей сцене, которые имеют разные ячейки и движутся независимо? Например, если у меня есть автомобиль и мотоцикл, могу ли я создать 2 вершинных буфера и сохранить данные сетки для обоев для каждого вызова рендеринга, а затем просто отправить в разные матрицы для шейдера для каждого объекта? Или мне нужно каким-то образом перевести сеточки, а затем объединить их в одну сетку, чтобы они могли быть отображены за один проход? Я ищу более стратегическую/стратегическую структуру высокого уровня, а не примеры кода. Я думаю, что у меня просто неправильный умственный способ, как это работает.
Спасибо!
Ответы
Ответ 1
Вы поддерживаете отдельные буферы вершин/индекс для разных объектов, да. Например, у вас может быть класс RenderedObject, и каждый экземпляр должен иметь собственный буфер вершин. Один объект RenderedObject может принимать вершины из домашней сетки, может быть, из символьной сетки и т.д.
Во время рендеринга вы устанавливаете соответствующее преобразование/поворот/затенение для буфера вершин, с которым вы работаете, возможно, что-то вроде:
void RenderedObject::render()
{
...
//set textures/shaders/transformations
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
...
}
Как уже упоминалось в другом ответе, bufferID - это просто GLuint, а не все содержимое буфера. Если вам нужна дополнительная информация о создании буферов вершин и заполнении их данными, я с удовольствием добавлю их.
Ответ 2
Лучший способ, которым я нашел это, - использовать VAO в дополнение к VBOs.
Сначала я отвечу на ваш вопрос, используя только VBOs.
Прежде всего, предположим, что у вас есть две ячейки из ваших двух объектов, хранящихся в следующих массивах:
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, ваш код функции draw намного проще (что хорошо, потому что это повторяющаяся функция):
Сначала вы определяете 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
Теперь к коду:
В 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);
Ответ 3
Я понимаю, что это более старая запись, но я пытался найти инструкции о том, как отображать несколько объектов в OpenGL
. Я нашел отличный учебник, в котором описывается, как визуализировать несколько объектов и может быть легко распространен для рендеринга объектов разных типов (т.е. Одного куба, одной пирамиды).
В учебнике, которое я публикую, также описывается, как визуализировать объекты с помощью GLKit
. Я счел это полезным и подумал, что я его отпишу здесь. Надеюсь, это тоже поможет!
http://games.ianterrell.com/opengl-basics-with-glkit-in-ios5-encapsulated-drawing-and-animation/
Ответ 4
Если сетки разные, вы храните их в разных буферах вершин. Если они похожи (например, анимация, цвет), вы передаете аргументы шейдеру. Вам нужно только держать дескрипторы в VBOs, а не сами данные вершин, если вы не планируете анимировать объект со стороны приложения. Возможно анимация на стороне устройства.
Ответ 5
Я надеюсь, что внесет свой вклад в эту более старую должность, потому что я решил решить эту проблему по-другому. Как и вопросник, я видел множество примеров с одним объектом. Я решил разместить все вершины в одном VBO, а затем сохранить смещение в этой позиции объекта (для каждого объекта), а не в буфере. Это сработало. Смещение может быть задано как параметр для glDrawElements, как показано ниже. Это кажется очевидным в ретроспективе, но я не был убежден, пока не увидел, что он работает. Обратите внимание, что я работал с "вершинным указателем", а не с более текущим "указателем атрибута вершин". Я работаю над последним, поэтому могу использовать шейдеры. Все объекты "связывают" с одним и тем же буфером вершин, до вызова "draw elements".
gl.glVertexPointer( 3, GLES20.GL_FLOAT, 0, vertexBufferOffset );
GLES20.glDrawElements(
GLES20.GL_TRIANGLES, indicesCount,
GLES20.GL_UNSIGNED_BYTE, indexBufferOffset
);
Я нигде не нашел объяснений, какова была цель этого смещения, поэтому я рискнул. Кроме того, это gotcha: вы должны указать смещение в байтах, а не в вершинах или поплавках. То есть, умножьте на четыре, чтобы получить правильную позицию.
Ответ 6
При использовании шейдеров можно использовать одну и ту же программу для всех объектов без необходимости компиляции, связывания и создания одного для каждого. Для этого просто сохраните значение GLuint для программы, а затем для каждого объекта "glUseProgram (programId);". В результате личного опыта я использую singleton для управления структурами 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 вещей, ответ выше говорит о том, как лучше справиться с этим. На стороне ориентации уравнения вам понадобится механизм для встраивания tdobject внутри друг друга (подобно UIView и дочерним элементам под iOS), а затем для оценки относительных вращений для родителей и т.д.
Удачи!