0
点赞
收藏
分享

微信扫一扫

OpenGL ES 3.0 数据可视化 2:多重采样绘制光滑圆点

本文档继续讨论上一篇文档OpenGL ES 3.0 数据可视化 1:绘制圆点存在的锯齿问题,尝试使用多重采样(Multisampling)进行抗锯齿并期望得到光滑圆点,首先介绍基于OpenGL ES 1.0 GL_POINT_SMOOTH的实现,接着详细描述OpenGL ES 3.0实现多重采样的步骤。完整代码托管在GitHub: MultisamplingRoundPoint。

1、OpenGL ES 1.0绘制光滑圆点

原书代码基于glfw框架无法直接在iOS上运行,简单起见,现使用OpenGL ES 1.0接口作朴素实现。仔细观察最右边的圆点,可见OpenGL ES 1.0通过指定GL_POINT_SMOOTH及混合(blend)的实现在iPad Air 2上运行依然存在锯齿。

关键代码如下所示。

typedef struct {
    GLfloat x, y, z; //position
    GLfloat r, g, b, a; //color and alpha channels
} Vertex;

void drawPoint(Vertex v1, GLfloat size) {
    glPushMatrix();
        glPointSize(size);

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);

        glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &v1);
        glColorPointer(4, GL_FLOAT, sizeof(Vertex), &v1.r);

        glDrawArrays(GL_POINTS, 0, 1);

        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);

    glPopMatrix();
}

void drawPointsDemo(int width, int height) {
    GLfloat size = 30.0f;
    for (GLfloat x = -.9f; x <= 1.0f; x += 0.15f, size += 10) {
        Vertex v1 = {x, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f};
        drawPoint(v1, size);
    }
}

- (void)layoutSubviews {
    // ...省略配置EAGLContext代码
    GLuint renderbuffer;
    glGenRenderbuffersOES(1, &renderbuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, renderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:layer];

    GLint width, height;
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, 
        GL_RENDERBUFFER_WIDTH_OES, 
        &width);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, 
        GL_RENDERBUFFER_HEIGHT_OES, 
        &height);

    GLuint framebuffer;
    glGenFramebuffersOES(1, &framebuffer);
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, framebuffer);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, 
        GL_COLOR_ATTACHMENT0_OES, 
        GL_RENDERBUFFER_OES, 
        renderbuffer);

    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, 
        GL_ONE_MINUS_SRC_ALPHA);

    glViewport(0, 0, width, height);
    glClear(GL_COLOR_BUFFER_BIT);

    drawPointsDemo(width, height);

    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

@end

同时,根据WWDC讲座,也侧面说明了在比如iPhone 6这些现代iPhone上,OpenGL ES 1.0这种固定功能渲染管线已无硬件支持,相反,当开发者运行OpenGL ES 1.0代码时,通过可编程渲染管线模拟老版本图形管线的行为,比如,开发者调用glEnable(GL_LIGHT),OpenGL ES驱动为之生成功能相同的着色器代码,并缓存到一个哈希表中,在glDraw系列函数调用时才开始执行这些着色器代码,如下两图所示。

那么,提示OpenGL ES 1.0绘制光滑圆点的GL_POINT_SMOOTH代码很可能对应于OpenGL ES 3.0的实现就是Coverage多重采样。

在iPhone 7已到手的年代,还是让我们使用更现代的接口实现如下功能吧。

glEnable(GL_POINT_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, 
    GL_ONE_MINUS_SRC_ALPHA);

2、OpenGL ES 3.0实现多重采样抗锯齿

在OpenGL ES 3.0中实现多重采样的编程类似于OpenGL ES 2.0,简要描述如下所示。区别是以前是苹果等厂家自行添加的拓展函数,如今纳入标准接口,故出现部分函数去除OES、APPLE等后缀,甚至完全改名。

  1. 配置单采样(Single sampled)渲染及帧缓冲区
  2. 配置多重采样(Multisampled)渲染及帧缓冲区
  3. 绑定多重采样渲染及帧缓冲区
  4. 设置视口为单采样渲染缓冲区大小
  5. 绘制
  6. 绑定多重采样帧缓冲区为读缓冲区GL_READ_FRAMEBUFFER
  7. 绑定单采样帧缓冲区为绘制缓冲区GL_DRAW_FRAMEBUFFER
  8. 绑定单采样渲染缓冲区
  9. 交换前后帧缓冲区

在开始修改代码前,先查询OpenGL ES 3.0支持的多重采样倍数,观察其最高数值,避免超出其支持范围,导致报错。

GLint maxSupportSamples;
glGetIntegerv(GL_MAX_SAMPLES, &maxSupportSamples);
printf("max support samples = %d\n", maxSupportSamples);

如上代码在iPad Air 2(iOS 9.3.4)上得到4倍。那么,接下来的实现直接使用4倍,查看最大采样倍数下对生成圆点的影响。

2.1、细述编程步骤

1、配置单采样(Single sampled)渲染及帧缓冲区
和前面开发的常规OpenGL ES程序一样,先配置普通采样的渲染、帧缓冲区。

GLuint defaultRenderbuffer[1], defaultFramebuffer[1];
glGenRenderbuffers(1, defaultRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, defaultRenderbuffer[0]);
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];

glGenFramebuffers(1, defaultFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer[0]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, defaultRenderbuffer[0]);

2、配置多重采样(Multisampled)渲染及帧缓冲区
多重采样渲染缓冲区的内存分配与单采样不同,在此需要手动指定其格式等信息。同时,需要知道单采样渲染缓冲区的大小。

GLint width, height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

有了渲染缓冲区大小信息后,才开始配置多重采样信息。然而,相应的多重采样帧缓冲区与多重采样渲染缓冲区的连接并无变化。

GLuint msaaRenderbuffer[1], msaaFramebuffer[1];
glGenRenderbuffers(1, msaaRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer[0]);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 
    4/* 采样倍数 */, 
    GL_RGBA8 /* 渲染缓冲区格式 */, 
    width, height);

glGenFramebuffers(1, msaaFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer[0]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRenderbuffer[0]);

为了确保多重采样相关缓冲区是否成功,在此查询帧缓冲区状态。

// Test the framebuffer for completeness.
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    NSLog(@"failed to make complete framebuffer object %x", 
        glCheckFramebufferStatus(GL_FRAMEBUFFER));
}

3、绑定多重采样渲染及帧缓冲区
在绘制前,需要将当前渲染缓冲区、帧缓冲区指定成前面给多重采样定制的缓冲区。

glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer[0]);
glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer[0]);

4、设置视口为单采样渲染缓冲区大小
至于视口的设置在前或在后,并不影响绘制结果。

5、绘制
绘制部分内容和正常一样。

6、绑定读缓冲区GL_READ_FRAMEBUFFER
由前面的绘制写在了多重采样帧缓冲区的颜色附着,故令其充当最终成像的数据源。

glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFramebuffer[0]);

7、绑定绘制缓冲区GL_DRAW_FRAMEBUFFER
绘制缓冲区为最终成像的缓冲区,比如将图像显示到屏幕。

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFramebuffer[0]);

接着,需要将多重采样帧缓冲区中的颜色、深度和模版等附着拷贝到单采样帧缓冲区。

glBlitFramebuffer(0, 0, width, height,
                  0, 0, width, height,
                  GL_COLOR_BUFFER_BIT,
                  GL_LINEAR);

8、绑定单采样渲染缓冲区
至此,多重采样相关的缓冲区都完成了它们的使命,数据也在上一步操作中拷贝到了单采样渲染缓冲区,此时,应该将它设置为使用状态。

glBindRenderbuffer(GL_RENDERBUFFER, defaultRenderbuffer[0]);

9、交换前后帧缓冲区
和普通采样程序一样,为了最终显示在屏幕上,需要通知iOS、Android等系统交换前后帧缓冲区。

[context presentRenderbuffer:GL_RENDERBUFFER];

2.2、成像效果比较

Renderbuffer #2确认上述代码确实创建了多重采样相关数据。同时,也表明了Renderbuffer #1是单采样缓冲区。接下来,比较GL_RBGA8与GL_RBGA两个internal format宏定义的区别。

1、4倍GL_RBGA8格式的多重采样

理论上,这两个缓冲区的数据是一样的,因此,它们看起来也是一样的,对我而言。

2、屏蔽混合(Blend)的多重采样
注释如下代码,再次运行,比较成像质量。

//  glEnable(GL_BLEND);
//  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

仔细观察可发现,无Blend情况下,圆点越大则锯齿现象越明显。

3、4倍GL_RBGA格式的多重采样
修改GL_RGBA8成GL_RBGA时,glCheckFramebufferStatus检查帧缓冲区状态不完整,也无法进行后续有效的绘制,所以多重采样的internal format参数与glTexImage2D显示RGBA图片时略有区别。

glRenderbufferStorageMultisample(GL_RENDERBUFFER, 
    4, 
    GL_RGBA,
    width,
    height);

小结

从以上两份代码的表现可推断,单纯绘制点并配合多重采样的实现在Retina屏上并不会像Windows默认DPI下有相对明显的视觉改善。继续探讨多重采样及绘制光滑圆点前,下一篇文档OpenGL ES 3.0 数据可视化 3:多次绘制调用(glDraw*)的性能问题先谈谈当前代码存在的运行性能问题及OpenGL ES工作方式的简要介绍

举报

相关推荐

0 条评论