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上运行依然存在锯齿。

ES 1.0 GL_POINT_SMOOTH效果

关键代码如下所示。

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

Starting with the iPhone 3GS, newer devices are equipped with the SGX series of GPUs. The SGX series features support for the OpenGL ES2.0 and newer devices support the OpenGL ES3.0 rendering API and vertex and pixel shaders. The Fixed-function pipeline is not supported natively on such GPUs, but instead is emulated by generating vertex and pixel shaders with analogous functionality on the fly.
摘自Unit手册Unity - Manual: iOS Hardware Guide

同时,根据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格式的多重采样

单采样颜色缓冲区

4倍多重采样颜色缓冲区

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

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

//  glEnable(GL_BLEND);
//  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
无Blend、4倍多重采样颜色缓冲区

仔细观察可发现,无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工作方式的简要介绍

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容