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]);
}