我要问的是:如何在场景中渲染具有不同网格并独立移动的多个对象?例如,如果我有一辆汽车和一辆摩托车,我可以创建2个顶点缓冲区,并为每个渲染调用保留两者的网格数据,然后只需为每个对象发送不同的着色器矩阵即可吗?还是我需要以某种方式平移这些网格,然后将它们组合成单个网格,以便可以在一次渲染中呈现它们?我正在寻找更高层次的策略/程序结构,而不是代码示例。我想我对它的运作方式有误解。
谢谢!
GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;
在哪里:
GLfloat gCubeVertexData1[36] = {...};
GLfloat gCubeVertexData2[36] = {...};
你还需要顶点缓冲区:
GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;
//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);
GLuint _vertexArray1;
GLuint _vertexArray2;
然后您将执行draw方法中执行的所有步骤,但是在绑定到VAO之后,您将在setupGL函数中执行它。然后在您的draw函数中,您只需绑定到所需的VAO即可。
这里的VAO类似于包含许多属性的配置文件(想象一下智能设备配置文件)。与每次更改颜色、桌面、字体等不同,您只需更改一次并将其保存在一个配置文件名称下。然后您只需要切换配置文件。
所以您只需在setupGL内部进行一次操作,然后在draw中进行切换。
当然,您可以将代码(不包括VAO)放入函数中并调用它。这是正确的,但是根据Apple的说法,VAO更有效率:
现在到代码部分: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);
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 的方法。 - ToolmakerStevevoid RenderedObject::render()
{
...
//set textures/shaders/transformations
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
...
}
如其他回答所提到的,bufferID只是一个GLuint,而不是整个缓冲区的内容。如果您需要更多有关创建顶点缓冲区并填充数据的详细信息,我很乐意补充。
OpenGL
中呈现多个对象的说明。我发现了一个很棒的教程,它描述了如何呈现多个对象,并且可以轻松扩展以呈现不同类型的对象(例如一个立方体和一个金字塔)。GLKit
来呈现对象。我觉得这很有帮助,所以在此转发。希望对你有所帮助!
http://games.ianterrell.com/opengl-basics-with-glkit-in-ios5-encapsulated-drawing-and-animation/
如果网格不同,您将它们保存在不同的顶点缓冲区中。如果它们相似(例如动画、颜色),则向着色器传递参数。如果您不打算在应用程序端对对象进行动画处理,则只需保留VBO的句柄,而不是顶点数据本身。设备端动画是可能的。
使用着色器时,可以使用相同的程序来处理所有对象,而无需为每个对象编译、连接和创建一个程序。要实现这一点,只需将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
我很希望能够对这篇较旧的帖子做出贡献,因为我采用了一种不同的方法来解决这个问题。和提问者一样,我看到了很多“单对象”的例子。我尝试将所有顶点放入单个VBO中,然后保存该对象位置的偏移量(每个对象),而不是缓冲区句柄。它起作用了。可以将偏移量作为参数传递给glDrawElements,如下所示。回想起来似乎很明显,但在我看到它工作之前我并不确定。请注意,我一直在使用“顶点指针”,而不是更常见的“顶点属性指针”。我正在努力转向后者,以便能够利用着色器。 在调用“draw elements”之前,所有对象都会“绑定”到相同的顶点缓冲区。
gl.glVertexPointer( 3, GLES20.GL_FLOAT, 0, vertexBufferOffset );
GLES20.glDrawElements(
GLES20.GL_TRIANGLES, indicesCount,
GLES20.GL_UNSIGNED_BYTE, indexBufferOffset
);
我没有找到任何地方明确说明这个偏移量的目的,所以我冒了个险。另外,还有一个注意点:你必须以字节为单位指定偏移量,而不是顶点或浮点数。也就是说,要乘以四才能得到正确的位置。