本文檔繼續(xù)討論上一篇文檔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
Starting with the iPhone 3GS, newer devices are equipped with the SGX series of GPUs. The SGX series features support for the OpenGL ES2.0 and newer devices support the OpenGL ES3.0 rendering API and vertex and pixel shaders. The Fixed-function pipeline is not supported natively on such GPUs, but instead is emulated by generating vertex and pixel shaders with analogous functionality on the fly.
摘自Unit手冊Unity - Manual: iOS Hardware Guide
同時,根據WWDC講座,也側面說明了在比如iPhone 6這些現代iPhone上,OpenGL ES 1.0這種固定功能渲染管線已無硬件支持,相反,當開發(fā)者運行OpenGL ES 1.0代碼時,通過可編程渲染管線模擬老版本圖形管線的行為,比如,開發(fā)者調用glEnable(GL_LIGHT),OpenGL ES驅動為之生成功能相同的著色器代碼,并緩存到一個哈希表中,在glDraw系列函數調用時才開始執(zhí)行這些著色器代碼,如下兩圖所示。
那么,提示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,簡要描述如下所示。區(qū)別是以前是蘋果等廠家自行添加的拓展函數,如今納入標準接口,故出現部分函數去除OES、APPLE等后綴,甚至完全改名。
- 配置單采樣(Single sampled)渲染及幀緩沖區(qū)
- 配置多重采樣(Multisampled)渲染及幀緩沖區(qū)
- 綁定多重采樣渲染及幀緩沖區(qū)
- 設置視口為單采樣渲染緩沖區(qū)大小
- 繪制
- 綁定多重采樣幀緩沖區(qū)為讀緩沖區(qū)GL_READ_FRAMEBUFFER
- 綁定單采樣幀緩沖區(qū)為繪制緩沖區(qū)GL_DRAW_FRAMEBUFFER
- 綁定單采樣渲染緩沖區(qū)
- 交換前后幀緩沖區(qū)
在開始修改代碼前,先查詢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)渲染及幀緩沖區(qū)
和前面開發(fā)的常規(guī)OpenGL ES程序一樣,先配置普通采樣的渲染、幀緩沖區(qū)。
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)渲染及幀緩沖區(qū)
多重采樣渲染緩沖區(qū)的內存分配與單采樣不同,在此需要手動指定其格式等信息。同時,需要知道單采樣渲染緩沖區(qū)的大小。
GLint width, height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
有了渲染緩沖區(qū)大小信息后,才開始配置多重采樣信息。然而,相應的多重采樣幀緩沖區(qū)與多重采樣渲染緩沖區(qū)的連接并無變化。
GLuint msaaRenderbuffer[1], msaaFramebuffer[1];
glGenRenderbuffers(1, msaaRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer[0]);
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
4/* 采樣倍數 */,
GL_RGBA8 /* 渲染緩沖區(qū)格式 */,
width, height);
glGenFramebuffers(1, msaaFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer[0]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRenderbuffer[0]);
為了確保多重采樣相關緩沖區(qū)是否成功,在此查詢幀緩沖區(qū)狀態(tài)。
// Test the framebuffer for completeness.
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"failed to make complete framebuffer object %x",
glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
3、綁定多重采樣渲染及幀緩沖區(qū)
在繪制前,需要將當前渲染緩沖區(qū)、幀緩沖區(qū)指定成前面給多重采樣定制的緩沖區(qū)。
glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer[0]);
glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer[0]);
4、設置視口為單采樣渲染緩沖區(qū)大小
至于視口的設置在前或在后,并不影響繪制結果。
5、繪制
繪制部分內容和正常一樣。
6、綁定讀緩沖區(qū)GL_READ_FRAMEBUFFER
由前面的繪制寫在了多重采樣幀緩沖區(qū)的顏色附著,故令其充當最終成像的數據源。
glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFramebuffer[0]);
7、綁定繪制緩沖區(qū)GL_DRAW_FRAMEBUFFER
繪制緩沖區(qū)為最終成像的緩沖區(qū),比如將圖像顯示到屏幕。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFramebuffer[0]);
接著,需要將多重采樣幀緩沖區(qū)中的顏色、深度和模版等附著拷貝到單采樣幀緩沖區(qū)。
glBlitFramebuffer(0, 0, width, height,
0, 0, width, height,
GL_COLOR_BUFFER_BIT,
GL_LINEAR);
8、綁定單采樣渲染緩沖區(qū)
至此,多重采樣相關的緩沖區(qū)都完成了它們的使命,數據也在上一步操作中拷貝到了單采樣渲染緩沖區(qū),此時,應該將它設置為使用狀態(tài)。
glBindRenderbuffer(GL_RENDERBUFFER, defaultRenderbuffer[0]);
9、交換前后幀緩沖區(qū)
和普通采樣程序一樣,為了最終顯示在屏幕上,需要通知iOS、Android等系統(tǒng)交換前后幀緩沖區(qū)。
[context presentRenderbuffer:GL_RENDERBUFFER];
2.2、成像效果比較
Renderbuffer #2確認上述代碼確實創(chuàng)建了多重采樣相關數據。同時,也表明了Renderbuffer #1是單采樣緩沖區(qū)。接下來,比較GL_RBGA8與GL_RBGA兩個internal format宏定義的區(qū)別。
1、4倍GL_RBGA8格式的多重采樣
理論上,這兩個緩沖區(qū)的數據是一樣的,因此,它們看起來也是一樣的,對我而言。
2、屏蔽混合(Blend)的多重采樣
注釋如下代碼,再次運行,比較成像質量。
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
仔細觀察可發(fā)現,無Blend情況下,圓點越大則鋸齒現象越明顯。
3、4倍GL_RBGA格式的多重采樣
修改GL_RGBA8成GL_RBGA時,glCheckFramebufferStatus檢查幀緩沖區(qū)狀態(tài)不完整,也無法進行后續(xù)有效的繪制,所以多重采樣的internal format參數與glTexImage2D顯示RGBA圖片時略有區(qū)別。
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
4,
GL_RGBA,
width,
height);
小結
從以上兩份代碼的表現可推斷,單純繪制點并配合多重采樣的實現在Retina屏上并不會像Windows默認DPI下有相對明顯的視覺改善。繼續(xù)探討多重采樣及繪制光滑圓點前,下一篇文檔OpenGL ES 3.0 數據可視化 3:多次繪制調用(glDraw*)的性能問題先談談當前代碼存在的運行性能問題及OpenGL ES工作方式的簡要介紹。