OpenGL ES3 实现MSAA的两个坑
OpenGL ES3 实现MSAA
在OpenGL ES3上实现MSAA的主要思想是创建一个用于多采样FBO,用它来接受所有渲染指令。当需要上屏时,将多采样的FBO resolve 回默认的FBO,在这个降采样过程中得到的像素值会更平滑,从而减少锯齿感。
苹果的一片文档写的足够详细:Using Multisampling to Improve Image Quality
不过苹果用的是ES2,但MSAA的相关接口在ES3上也只是换了签名而已,迁移成本不高。
// your default FBO
glGenFramebuffers(1, &viewFrameBuffer);
glGenRenderbuffers(1, &viewRenderBuffer);
//setup MSAA Auxiliary Buffers
glGenFramebuffers(1, &msaaFrameBuffer);
glGenRenderbuffers(1, &msaaRenderBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, msaaFrameBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderBuffer);
// set MSAA sample count
int msaaSamples = 2;
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaaSamples, GL_RGBA8, bufferWidth, bufferHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRenderBuffer);
// draw calls...
// resolve MSAA FBO to your default FBO
glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFrameBuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, viewFrameBuffer);
glBlitFramebuffer(0, 0, bufferWidth, bufferHeight, 0, 0, bufferWidth, bufferHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glFlush();
// invalidate unneeded renderbuffer
GLenum msaaRenderBuffer[] = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT,GL_STENCIL_ATTACHMENT};
glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 3, msaaRenderBuffer);
// present your FBO contents
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
[glContext presentRenderbuffer:GL_RENDERBUFFER];
遇到的问题和解决方案
- 偶现的闪屏
有一些小游戏在开抗锯齿后会出现闪屏的Bug,经排查应该是FBO还没有blit完成,就被标记成了invalid,所以读不出来值了。
glInvalidateFramebuffer
可以让客户端标记哪些RBO是不需要的,这样OpenGL状态机不用再维持一些不必要的状态更新,而且在TBR/TBDR架构的GPU上,渲染时Tile不用将不再需要的数据写回显存,可以减少带宽的使用,提高性能。
但貌似在某些驱动glBlitFramebuffer
这个函数是异步的,导致FBO还没有传完,就被invalidate了,产生有偶现的黑屏。我猜测这是一些驱动的优化导致的,在iPhone 6s上会复现,在Mac和一些安卓机上没有复现。
所以为了保证多采样FBO能正确resolve,需要在glBlitFramebuffer
之后加glflush
,这样可以保证命令发送到GPU后再将一些不需要的RBO标记成invalidate。
- 黑屏
有小游戏开发商在设置WebGL时,将多采样的sampleCount设成了不支持的值,这在WebGL里是可以运行的,但在OpenGL里不支持的采样值会直接输出黑色像素。
所以在创建OpenGL时,先用glGetInternalformativ
读取一下当前驱动支持的采样值,若开发商设置的值不支持,会就近选一个近邻的采样值,不然产生黑屏的话很难查Bug。
glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 1, &counts);
GLint samples[counts];
glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, (GLsizei)counts, samples);
for (int i = 0; i < sampleCounts, i++){
printf("support sample count:%d, %d", samples[i]);
}