2011-08-27 156 views
31

我想學習OpenGL ES 2.0來做一些iPhone遊戲開發。我已閱讀了多個教程和一些OpenGL ES 2.0規範。我看到的所有例子都創建了一個單獨的網格物體,將其加載到頂點緩衝區,然後渲染它(使用期望的平移,旋轉,漸變等)。用OpenGL ES 2.0渲染多個對象

我的問題是:如何渲染多個場景中的物體具有不同的網格並獨立移動?例如,如果我有一輛汽車和一輛摩托車,我可以創建2個頂點緩衝區,併爲每個渲染調用保留周圍的網格數據,然後針對每個對象發送不同的着色器矩陣?或者我需要以某種方式翻譯網格,然後將它們組合成單個網格,以便它們可以一次渲染?我正在尋找更多的高層戰略/程序結構,而不是代碼示例。我認爲我的錯誤思維模式是如何運作的。

謝謝!

+0

感謝您的快速解答,我現在明白了。 –

+4

這是一個很好問的問題。 – 3yanlis1bos

+0

大多數示例都使用一個對象視圖進行旋轉和平移一次。要以不同的角度和位置繪製兩個對象,請在第一個對象之後應用相反的操作。例如。 translate1,rotate1,draw1,-rotate1,-translate1,translate2,rotate2,draw2 -rotate2,-translate2,ill快速發佈示例 –

回答

10

您爲不同的對象維護單獨的頂點/索引緩衝區,是的。例如,你可能有一個RenderedObject類,每個實例都有它自己的頂點緩衝區。一個RenderedObject可能會把它從頂部網格的頂點,一個可能來自一個字符網格等等。

在渲染期間,您爲您正在使用的頂點緩衝區設置適當的變換/旋轉/着色,可能類似於:

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

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

正如在其他答案中提到的,bufferID只是一個GLuint而不是緩衝區的全部內容。如果您需要更多關於創建頂點緩衝區和填充數據的細節,我很樂意添加這些細節。

+2

我的問題推遲了2年,但是:如果對不同對象有不同的頂點緩衝區,您還必須將程序綁定到所有這些對象。就我而言,如果您輕鬆獲得超過1000個對象,則必須每次調用setVertexAttribPointer。在我的情況下,結果是,它變得非常緩慢(實際上,60-70%的時間花在了「setVertexAttribPointer」< - 你知道這個還是我做錯了什麼? – Frame91

+1

我的理解是,這可以例如:如果你想繪製10萬隻鳥,那麼這將會非常緩慢,我的理解是,你應該嘗試合併類似的對象,可能是所有的對象,以減少數量繪製調用不幸的是,我不確定這是如何實現的 - 儘管這個理論非常合理! – AlvinfromDiaspar

+1

是的,如果操作太頻繁,開關頂點/索引緩衝區的開銷會變得非常昂貴。是否只有少數不同的模型用於表示鳥類,每個模型類型都有一個頂點緩衝區,併爲每個實例重新繪製緩衝區,然後設置Sparrow頂點緩衝區,繪製2K Sparrows,然後設置Falc在VB上繪製3K Falcons等等。 – TaylorP

3

如果網格不同,則將它們保存在不同的頂點緩衝區中。如果它們相似(例如動畫,顏色),則將參數傳遞給着色器。如果您不打算在應用程序端動畫化對象,則只需將句柄保留給VBO,而不是頂點數據本身。設備端動畫可能是

12

我發現這樣做的最好方法是使用VAO和VBO。

我會先回答你使用VBOs的問題。

首先,假設你有存儲在以下陣列的兩個對象的兩個網格:

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

這將回答您的問題。但現在使用VAOs,你繪製函數的代碼要簡單得多(這是好事,因爲它是重複功能):

首先,你將它定義成VAOs:

GLuint _vertexArray1; 
GLuint _vertexArray2; 

,然後你會做所有的先前在繪圖方法中完成的步驟,您將在setupGL函數中完成,但綁定到VAO之後。然後在你的繪圖函數中,你只需綁定到你想要的VAO。

VAO這裏就像一個包含很多屬性的配置文件(想象一下智能設備配置文件)。每當你想改變顏色,桌面,字體等等時,不要改變顏色,桌面,字體等等,你只需要做一次,然後將其保存在配置文件名稱下。然後你只需切換配置文件。

所以你在setupGL中做了一次,然後在draw中切換它們。

當然你可以說你可以把代碼(沒有VAO)放在函數中並調用它。這是真的,但VAOs更高效根據蘋果:

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

謝謝,使用VAO是一個很好的方法 –

+1

Si如果我必須創建100個對象我需要100個VAO? – MatterGoal

+0

請注意,VAO不保證可用於所有ES'2.0'設備。 (如果ES'3.0'有保證)如果你編程到2.0,然後調用'glGetString(GL_EXTENSIONS)',並在響應中搜索字符串'OES_vertex_array_object'。 https://www.khronos.org/opengles/sdk/1.1/docs/man/glGetString.xml特別是在Android上,使用各種設備,因此您可以通知用戶您的應用程序無法運行或回退到只有VBO的方法。 – ToolmakerSteve

4

我意識到這是一個較舊的帖子,但我試圖找到工具關於如何在OpenGL內呈現多個對象的說明。我找到了一個很好的教程,它描述瞭如何渲染多個對象,並且可以很容易地擴展以渲染不同類型的對象(即一個立方體,一個金字塔)。

我發佈的教程還介紹瞭如何使用GLKit呈現對象。我發現它很有幫助,並認爲我會在這裏重新發布它。我希望它也能幫助你!

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

+0

鏈接已損壞 - 新的URL? – ToolmakerSteve

+0

看起來教程的網站名稱可能尚未更新。但是,本教程中構建的代碼位於GitHub上:https://github.com/ianterrell/GLKit-Cube-Tutorial這是我能找到的最好的代碼。看起來他的網站上的教程現在已經離線了,看起來很好。希望他會把教程內容移到一個新家。 :-) – MikeyE

0

我希望,促成這一較早的帖子,因爲我答應解決這個問題的不同方式。像問題提問者一樣,我看到了很多「單一對象」的例子。我承諾將所有頂點放置到一個VBO中,然後將偏移量保存到該對象的位置(每個對象),而不是緩衝區句柄。有效。偏移量可以作爲參數給予glDrawElements,如下所示。回想起來似乎很明顯,但直到我看到它的工作,我才確信。請注意,我一直在使用「頂點指針」而不是更新的「頂點屬性指針」。我正在爲後者工作,所以我可以利用着色器。 在調用「繪製元素」之前,所有對象「綁定」到相同的頂點緩衝區。

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

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

我沒有找到任何地方說明這個抵消的目的是什麼,所以我抓住了機會。此外,這個陷阱:你必須以字節爲單位指定偏移量,而不是頂點或浮點數。也就是說,乘以四得到正確的位置。

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方面,上面的答案闡明瞭如何最好地處理這個問題。根據方程的取向一方,您需要一種將對象嵌套在對方內的機制(類似於UIView和iOS下的兒童),然後評估父母等的相對旋轉度。

祝您好運!