昨晚花費了我2個多小時的時間終於把OpenGL ES3.0中的MSAA給搞定了。在OpenGL ES2.0中,Khronos官方沒有引入標准的MSAA全屏抗鋸齒的方法,而Apple則采用了自己的GL_APPLE_framebuffer_multisample的擴展來實現MSAA。在iOS中,OpenGL ES3.0之前使用MSAA的方法可以參見Apple的官方OpenGL ES開發者指南,寫得非常詳細:
而對於OpenGL ES3.0,GL_APPLE_framebuffer_multisample擴展已經失效,不能再使用了。於是我在網上搜了許多資料,不過有幫助的不多,比較有方向性的文章是OpenGL官方wiki上關於多重采樣的介紹:https://www.opengl.org/wiki/Multisampling
不過這篇文章針對的是OpenGL,與OpenGL ES稍微有些差異。於是本人借助Apple的文檔結合這篇官維,終於把它搗鼓出來了。
其實,大部分代碼與Apple官方所描述的差不多,有幾個需要改動的地方:
1、要包含頭文件<OpenGLES/ES3/gl.h>。如果是之前的OpenGL ES2.0,那么所包含的是<OpenGLES/ES2/gl.h>和<OpenGLES/ES2/glext.h>。
2、帶‘APPLE’、‘EXT’以及‘OES’后綴的函數以及常量都沒有了。改起來非常簡單,直接把后綴給刪了即可,比如原來的‘glRenderbufferStorageMultisampleAPPLE’改為‘glRenderbufferStorageMultisample’;原來的‘GL_RGBA8_OES’改為‘GL_RGBA8’。
3、在繪制時,用‘glBlitFramebuffer’來取代‘glResolveMultisampleFramebufferAPPLE’。
4、用‘glInvalidateFramebuffer’來取代‘glDiscardFramebufferEXT’。這個接口非常有用!使用和沒使用速度能相差1倍之多!這里得感謝Apple的Xcode以及OpenGL ES Analysis的profile工具,使得我能查到之前的glDiscardFramebufferEXT被啥取代了……否則,如果包含<OpenGLES/ES2/glext.h>然后調用glDiscardFramebufferEXT也沒啥問題。不過直接用官方標准的接口會更可靠些,至少更有可移植性些,呵呵。
下面我提供比較完整的使用范例(帶有部分的Objective-C代碼):
先是頭文件
// MyGLLayer.h // CADemo // // Created by Zenny Chen on 14-8-19. // Copyright (c) 2014年 Adwo. All rights reserved. // @import QuartzCore; #import <OpenGLES/ES3/gl.h> @interface MyGLLayer : CAEAGLLayer { @private /* The pixel dimensions of the backbuffer */ GLint mBackingWidth; GLint mBackingHeight; EAGLContext *mContext; /* OpenGL names for the renderbuffer and framebuffers used to render to this view */ GLuint mFramebuffer, mRenderbuffer, mDepthRenderbuffer; GLuint mMSAAFramebuffer, mMSAARenderbuffer, mMSAADepthRenderbuffer; CADisplayLink *mDisplayLink; }
我們看到以上代碼定義了兩組FBO和RBO,一組是用於繪制到目標窗口的(不帶MSAA的),另一組是用於圖形渲染的,采用MSAA。在最后繪制時會把MSAA的FBO像素拷貝到單樣本的FBO,用於顯示。
以下是源文件的主要代碼片段:
- (instancetype)init { self = [super init]; self.opaque = YES; self.contentsScale = [UIScreen mainScreen].scale; // Optionally configure the surface properties of the rendering surface by assigning a new dictionary of // values to the drawableProperties property of the CAEAGLLayer object. self.drawableProperties = @{ kEAGLDrawablePropertyRetainedBacking : @NO, kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8 }; // Set OpenGL ES context,use GL ES3 profile mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; return self; } - (BOOL)createFramebuffer { // Create the framebuffer and bind it so that future OpenGL ES framebuffer commands are directed to it. glGenFramebuffers(1, &mFramebuffer); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); // Create a color renderbuffer, allocate storage for it, and attach it to the framebuffer. glGenRenderbuffers(1, &mRenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer); // Create the color renderbuffer and call the rendering context to allocate the storage on our Core Animation layer. // The width, height, and format of the renderbuffer storage are derived from the bounds and properties of the CAEAGLLayer object // at the moment the renderbufferStorage:fromDrawable: method is called. [mContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self]; glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mRenderbuffer); // Retrieve the height and width of the color renderbuffer. glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &mBackingWidth); glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &mBackingHeight); // Perform similar steps to create and attach a depth renderbuffer. glGenRenderbuffers(1, &mDepthRenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, mDepthRenderbuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, mBackingWidth, mBackingHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthRenderbuffer); // The following is MSAA settings glGenFramebuffers(1, &mMSAAFramebuffer); glBindFramebuffer(GL_FRAMEBUFFER, mMSAAFramebuffer); glGenRenderbuffers(1, &mMSAARenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, mMSAARenderbuffer); // 4 samples for color glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, mBackingWidth, mBackingHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mMSAARenderbuffer); glGenRenderbuffers(1, &mMSAADepthRenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, mMSAADepthRenderbuffer); // 4 samples for depth glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, mBackingWidth, mBackingHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mMSAADepthRenderbuffer); // Test the framebuffer for completeness. if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER)); return NO; } glViewport(0, 0, mBackingWidth, mBackingHeight); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Do other settings... return YES; } - (void)drawLayer:(CADisplayLink*)link { glBindFramebuffer(GL_FRAMEBUFFER, mMSAAFramebuffer); glBindRenderbuffer(GL_RENDERBUFFER, mMSAARenderbuffer); // Draw something here... [self drawModels]; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, mMSAAFramebuffer); #if 0 // OpenGL ES 2.0 Apple multisampling // Discard the depth buffer from the read fbo. It is no more necessary. glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_DEPTH_ATTACHMENT}); glResolveMultisampleFramebufferAPPLE(); glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_COLOR_ATTACHMENT0}); #else // OpenGL ES3.0 Core multisampling // Discard the depth buffer from the read fbo. It is no more necessary. glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_DEPTH_ATTACHMENT}); // Copy the read fbo(multisampled framebuffer) to the draw fbo(single-sampled framebuffer) glBlitFramebuffer(0, 0, mBackingWidth, mBackingHeight, 0, 0, mBackingWidth, mBackingHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_COLOR_ATTACHMENT0}); #endif glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer); // Assuming you allocated a color renderbuffer to point at a Core Animation layer, you present its contents by making it the current renderbuffer // and calling the presentRenderbuffer: method on your rendering context. [mContext presentRenderbuffer:GL_RENDERBUFFER]; }
大致使用流程如上述代碼所示。我用11寸的MacBook Air上模擬器看,效果十分明顯(因為MacBook Air不是retina屏)。上述demo中使用了4個樣本,基本夠用了。
如果各位要看非MSAA版本,只需要把drawLayer:方法下面第一行代碼改為:‘glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);’;然后把對glBlitFramebuffer的調用給注釋掉即可,非常方便~
