本文档继续讨论上一篇文档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等后缀,甚至完全改名。
- 配置单采样(Single sampled)渲染及帧缓冲区
- 配置多重采样(Multisampled)渲染及帧缓冲区
- 绑定多重采样渲染及帧缓冲区
- 设置视口为单采样渲染缓冲区大小
- 绘制
- 绑定多重采样帧缓冲区为读缓冲区GL_READ_FRAMEBUFFER
- 绑定单采样帧缓冲区为绘制缓冲区GL_DRAW_FRAMEBUFFER
- 绑定单采样渲染缓冲区
- 交换前后帧缓冲区
在开始修改代码前,先查询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工作方式的简要介绍。