OpenGL ES3 實(shí)現(xiàn)MSAA的兩個(gè)坑
OpenGL ES3 實(shí)現(xiàn)MSAA
在OpenGL ES3上實(shí)現(xiàn)MSAA的主要思想是創(chuàng)建一個(gè)用于多采樣FBO,用它來接受所有渲染指令。當(dāng)需要上屏?xí)r,將多采樣的FBO resolve 回默認(rèn)的FBO,在這個(gè)降采樣過程中得到的像素值會(huì)更平滑,從而減少鋸齒感。

蘋果的一片文檔寫的足夠詳細(xì):Using Multisampling to Improve Image Quality
不過蘋果用的是ES2,但MSAA的相關(guān)接口在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];
遇到的問題和解決方案
- 偶現(xiàn)的閃屏
有一些小游戲在開抗鋸齒后會(huì)出現(xiàn)閃屏的Bug,經(jīng)排查應(yīng)該是FBO還沒有blit完成,就被標(biāo)記成了invalid,所以讀不出來值了。
glInvalidateFramebuffer可以讓客戶端標(biāo)記哪些RBO是不需要的,這樣OpenGL狀態(tài)機(jī)不用再維持一些不必要的狀態(tài)更新,而且在TBR/TBDR架構(gòu)的GPU上,渲染時(shí)Tile不用將不再需要的數(shù)據(jù)寫回顯存,可以減少帶寬的使用,提高性能。
但貌似在某些驅(qū)動(dòng)glBlitFramebuffer這個(gè)函數(shù)是異步的,導(dǎo)致FBO還沒有傳完,就被invalidate了,產(chǎn)生有偶現(xiàn)的黑屏。我猜測這是一些驅(qū)動(dòng)的優(yōu)化導(dǎo)致的,在iPhone 6s上會(huì)復(fù)現(xiàn),在Mac和一些安卓機(jī)上沒有復(fù)現(xiàn)。
所以為了保證多采樣FBO能正確resolve,需要在glBlitFramebuffer之后加glflush,這樣可以保證命令發(fā)送到GPU后再將一些不需要的RBO標(biāo)記成invalidate。
- 黑屏
有小游戲開發(fā)商在設(shè)置WebGL時(shí),將多采樣的sampleCount設(shè)成了不支持的值,這在WebGL里是可以運(yùn)行的,但在OpenGL里不支持的采樣值會(huì)直接輸出黑色像素。
所以在創(chuàng)建OpenGL時(shí),先用glGetInternalformativ讀取一下當(dāng)前驅(qū)動(dòng)支持的采樣值,若開發(fā)商設(shè)置的值不支持,會(huì)就近選一個(gè)近鄰的采樣值,不然產(chǎn)生黑屏的話很難查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]);
}