OpenGL ES 2.0:如何提高绘制精灵的帧率?

OpenGL ES 2.0: How to improve frame rate of drawing sprites?

我有一个基于 OpenGL ES 2.0 构建的动画背景层,我使用 GLKView 作为图形容器,使用 GLKViewController 作为控制器。对于绘图,我使用 GLKBaseEffect。

我介绍了一个 sprite class,它可以加载 png 文件作为纹理,操纵 sprite (SRT) 和一些额外的属性,如 alpha 混合等。

我想知道如何优化我的程序,因为当显示 50 个精灵(都具有相同的 texture/png-file!)时,我的 iPhone 4S 帧率下降到大约 25 FPS每个尺寸为 128x128 像素。

在接下来的部分中,我列出了程序的重要部分。目前,我为每个帧的 50 个精灵中的每一个调用 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)(目标帧速率为 60);这相当于每秒 3000 次调用。

会不会是瓶颈?我该如何优化它?

这是我初始化精灵数组的方式 (GLKViewController.m):

- (void)initParticles {
    if(sprites==nil) {
        sprites = [NSMutableArray array];
        for (int i=0; i<50; i++) {
            Sprite* sprite = [[Sprite alloc] initWithFile:@"bubble" extension:@"png" effect:effect];
            // configure some sprite properties [abbreviated]
            [sprites addObject:sprite];
        }
    }
}

这是渲染函数(GLKViewController.m):

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    glClearColor(0.0, 0.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    // render the bubbles
    for (Sprite* sprite in sprites) {
        [sprite render];
    }
}

以下是精灵的一些重要部分class (Sprite.m):

- (id)initWithFile:(NSString *)filename extension:(NSString*)extension effect:(GLKBaseEffect *)effect {
    if(self = [self init]) {
        self.effect = effect;
        NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], GLKTextureLoaderOriginBottomLeft, nil];
        NSError* error = nil;
        NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
        self.textureInfo = [GLKTextureLoader textureWithContentsOfFile:path options:options error:&error];
        if (self.textureInfo == nil) {
            NSLog(@"Error loading file: %@", [error localizedDescription]);
            return nil;
        }
        TexturedQuad newQuad;
        newQuad.bl.geometryVertex = GLKVector2Make(0, 0);
        newQuad.br.geometryVertex = GLKVector2Make(self.textureInfo.width, 0);
        newQuad.tl.geometryVertex = GLKVector2Make(0, self.textureInfo.height);
        newQuad.tr.geometryVertex = GLKVector2Make(self.textureInfo.width, self.textureInfo.height);
        newQuad.bl.textureVertex = GLKVector2Make(0, 0);
        newQuad.br.textureVertex = GLKVector2Make(1, 0);
        newQuad.tl.textureVertex = GLKVector2Make(0, 1);
        newQuad.tr.textureVertex = GLKVector2Make(1, 1);
        self.quad = newQuad;
    }
    return self;
}

- (void)render {
    [self applyBaseEffect];
    long offset = (long)&_quad;
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void*) (offset + offsetof(TexturedVertex, geometryVertex)));
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void*) (offset + offsetof(TexturedVertex, textureVertex)));
    glBlendColor(1.0, 1.0, 1.0, self.alpha);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glDisableVertexAttribArray(GLKVertexAttribPosition);
    glDisableVertexAttribArray(GLKVertexAttribTexCoord0);
}

- (void)applyBaseEffect {
    self.effect.texture2d0.name = self.textureInfo.name;
    self.effect.texture2d0.envMode = GLKTextureEnvModeModulate;
    self.effect.texture2d0.target = GLKTextureTarget2D;
    self.effect.texture2d0.enabled = GL_TRUE;
    self.effect.useConstantColor = GL_TRUE;
    self.effect.constantColor = GLKVector4Make(self.tint.r*self.alpha, self.tint.g*self.alpha, self.tint.b*self.alpha, self.alpha);
    self.effect.transform.modelviewMatrix = GLKMatrix4Multiply(GLKMatrix4Identity, [self modelMatrix]);
    [self.effect prepareToDraw];
}

- (GLKMatrix4)modelMatrix {
    GLKMatrix4 modelMatrix = GLKMatrix4Identity;
    modelMatrix = GLKMatrix4Translate(modelMatrix, self.position.x, self.position.y, 0);
    modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotation, 0, 0, 1);
    modelMatrix = GLKMatrix4Scale(modelMatrix, self.scale, self.scale, 0);
    modelMatrix = GLKMatrix4Translate(modelMatrix, -self.normalSize.width/2, -self.normalSize.height/2, 0);
    return modelMatrix;
}

EDIT-1:这里有一些性能指标(似乎是 GPU 绑定的)

EDIT-2:当我添加 view.drawableMultisample 行时,我的 iPhone 4S 的帧速率从 25 提高到 45,无论我使用哪条行 (none / 4x) .奇怪 - 我的渲染代码似乎不受 MSAA 的影响,相反。

GLKView *view = (GLKView*)self.view;
view.context = context;
view.drawableMultisample = GLKViewDrawableMultisampleNone;
//view.drawableMultisample = GLKViewDrawableMultisample4x;

要提高性能,就必须避免冗余代码。可能你为每个精灵设置了着色器程序。但是你总是使用同一个着色器来绘制精灵,所以你可以避免每次都设置它。您可以对顶点和纹理坐标使用这种优化。对于每个精灵,只需计算矩阵 MVP(模型-视图-投影)。

伪代码中的方法'drawFrame':

...
[set sprite program shader]
[set vertex coordinates]
[set texture coordinates]
[set textures ]
for each sprite
   [calculate matrix MVP]
   [draw sprite]
end for
...

另一个优化是使用顶点缓冲区对象,但在你的情况下我认为它的优先级低于 sprite 批处理优化。

我在 Android 上工作,所以我无法帮助您提供示例代码。尽管如此,我还是建议你看看 Libgdx 看看他们是如何实现 sprite batch 的。

您的性能计数器似乎表明您受 GPU 填充率限制。您可以检查您的 bubble.png 资产是否充分利用了它的纹理 space,不必要的过度绘制可能是您问题的一部分。

您可以在 photoshop/gimp 中打开它并自动裁剪图像,如果它从边缘移除了任何纹素,那么您就浪费了一些 space。如果你能消除浪费space你就可以减少透支。

您提到气泡为 128x128,但不清楚这是纹理尺寸还是屏幕尺寸。减小屏幕尺寸将提高性能。

除此之外,您可能需要深入研究 Sprite class 并查看片段着色器实际在做什么。选择更高效的纹理格式并启用 mipmapping 也可能有所帮助。

又经过两个小时的深入研究,我终于找到了性能大幅下降的原因。如评论中所述,性能下降的重要性随着视图控制器中包含的按钮的增加而增加。因为我用圆角和阴影装饰我的按钮,所以我照顾了......这是我在 UIView class 上的原始类别的一部分,用于装饰 UI 元素:

#import "UIView+Extension.h"

@implementation UIView (Extension)

- (void)styleViewWithRoundedEdges:(BOOL)rounded shadowed:(BOOL)shadowed {
    if (rounded) {
        self.layer.cornerRadius = 3.0;
    }
    if (shadowed) {
        self.layer.shadowColor = [UIColor blackColor].CGColor;
        self.layer.shadowOffset = CGSizeMake(2.0, 2.0);
        self.layer.shadowOpacity = 0.25;
        self.layer.shadowRadius = 1.0;
    }
}

@end

我一关闭阴影,帧率就变成恒定的 60 FPS。

快速搜索将我定向到 this SO thread

添加这两行解决了问题:

self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = UIScreen.mainScreen.scale;

我现在在 iPhone 4S 上以 60 FPS 轻松渲染 100 个精灵与线性 + 径向背景渐变着色器 :-D。

但是非常感谢你们的耐心和帮助!让我知道什么时候可以 return 这个忙!