【複習筆記】 cocos2d-x 2.x 渲染特效實現 四 高斯模糊效果二
在上文末,我們已經完成了高斯模糊橫向上的模糊效果,而且提到,縱向模糊要在橫向模糊的結果上進行,所以為了得到橫向模糊的貼圖,我們使用離屏渲染。顧名思義,現在渲染的目的地不是螢幕了~一般狀況下,gl是直接把渲染好的紋理繪製到螢幕緩衝區的,進而直接顯示在螢幕上。但是現在,我們要把它繪製到一張紋理中,這樣,就可以像操作一張普通的紋理一樣,對它進行再次加工了~這裡我們用到FBO技術。
FBO即Frame Buffer Object,幀快取物件,這使得我們可以建立一個由gl控制的幀快取。通常情況下,我們只繪製到作業系統提供的幀快取。
渲染到紋理的大致步驟如下:
1.建立一個空的texture
2.建立FBO
3.繫結texture到FBO上
4.使用自己建立的FBO作為當前渲染的幀快取
4.draw~
具體程式碼如下:
void CCEffectSprite::drawGaussianBlur() { CCEGLView *eglView = CCDirector::sharedDirector()->getOpenGLView(); float zoomScale = eglView->getFrameZoomFactor(); CCSize frameSize = eglView->getFrameSize(); CCSize screenBufferSize = CCSizeMake(frameSize.width*zoomScale, frameSize.height*zoomScale); //CCLOG("frameSize : %f, %f", frameSize.width, frameSize.height); float pixelSpan = 0.5f; float blursDis[2] = {pixelSpan/getTextureRect().size.width, pixelSpan/screenBufferSize.height}; GLint size = sizeof(ccV3F_C4B_T2F); long data = (long)&m_sQuad; /**/ // gen a empty texture glGenTextures(1, &_texture); glBindTexture(GL_TEXTURE_2D, _texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screenBufferSize.width, screenBufferSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // gen a off-screen framebuffer GLint oldFBO; glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldFBO); glGenFramebuffers(1, &_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); // ************ first draw ****************** CC_NODE_DRAW_SETUP(); ccGLBlendFunc(m_sBlendFunc.src, m_sBlendFunc.dst); // ccGLBindTexture2D(m_pobTexture->getName()); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_pobTexture->getName()); ccGLEnableVertexAttribs(kCCVertexAttribFlag_PosColorTex); // uniforms glUniform2f(_uniforms[kUniformBlurDis], blursDis[0], 0.00f); // attributes glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, size, (GLvoid *)data); glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, size, (GLvoid *)(data + sizeof(ccVertex3F))); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, size, (GLvoid *)(data + sizeof(ccVertex3F) + sizeof(ccColor4B))); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // ****************************************** glBindFramebuffer(GL_FRAMEBUFFER, oldFBO); // ************** second draw ***************** // reset stack kmGLMatrixMode(KM_GL_MODELVIEW); kmGLPushMatrix(); kmGLLoadIdentity(); kmGLMatrixMode(KM_GL_PROJECTION); kmGLPushMatrix(); kmGLLoadIdentity(); glViewport(0, 0, screenBufferSize.width, screenBufferSize.height); CC_NODE_DRAW_SETUP(); ccGLBlendFunc(m_sBlendFunc.src, m_sBlendFunc.dst); // ccGLBindTexture2D(m_pobTexture->getName()); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, _texture); ccGLEnableVertexAttribs(kCCVertexAttribFlag_PosColorTex); // uniforms glUniform2f(_uniforms[kUniformBlurDis], 0.00f, blursDis[1]); // attributes // do not add any other render info, just do another direction blur: ccV3F_C4B_T2F_Quad tempQuad; memset(&tempQuad, 0, sizeof(tempQuad)); // vertices are located in the 4 corners of the whole viewport. tempQuad.tl.vertices = vertex3(-1, 1, -1); tempQuad.bl.vertices = vertex3(-1, -1, -1); tempQuad.tr.vertices = vertex3(1, 1, -1); tempQuad.br.vertices = vertex3(1, -1, -1); // color is black which won't change any color info. tempQuad.tl.colors = ccc4(255, 255, 255, 255); tempQuad.bl.colors = ccc4(255, 255, 255, 255); tempQuad.tr.colors = ccc4(255, 255, 255, 255); tempQuad.br.colors = ccc4(255, 255, 255, 255); // UV y-coords is also fliped just like what CCSprite did in above step. tempQuad.tl.texCoords = tex2(0, 1); tempQuad.bl.texCoords = tex2(0, 0); tempQuad.tr.texCoords = tex2(1, 1); tempQuad.br.texCoords = tex2(1, 0); data = (long)&tempQuad; glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, size, (GLvoid *)data); glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, size, (GLvoid *)(data + sizeof(ccVertex3F))); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, size, (GLvoid *)(data + sizeof(ccVertex3F) + sizeof(ccColor4B))); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // restore stack kmGLMatrixMode(KM_GL_PROJECTION); kmGLPopMatrix(); kmGLMatrixMode(KM_GL_MODELVIEW); kmGLPopMatrix(); CCDirector::sharedDirector()->setViewport(); // ************************************** /**/ glDeleteTextures(1, &_texture); glDeleteFramebuffers(1, &_framebuffer); }
上述程式碼中值得一提的幾點是:
1.由於我們自己建立的幀快取是為了模擬另一個“螢幕”而存在的,所以FBO中繫結的texture和viewport都使用了系統幀快取的大小,所以當第一次渲染結束後,可以得到一張和螢幕大小相同的貼圖,且和直接渲染的到螢幕之後,與螢幕上顯示的畫素資訊完全相同。在第二次渲染中,因為原貼圖大小已經變化,所以不能使用正常渲染的MVP變換,而是直接把與螢幕大小相同的貼圖直接繪製出來即可。在cocos中,使用了第三方的3D數學庫kazmath來儲存各級節點的變換矩陣,所以,在第二次渲染的時候,在MV矩陣和P矩陣棧上都壓入一個單位矩陣,來保證不做任何變換,且紋理座標也根據cocos的習慣,在縱向上做了翻轉。
2.因為兩次渲染的貼圖大小是不一樣的,為了橫向和縱向都用相同的取樣標準去做高斯模糊,blurDis變數分別用原貼圖的寬和生成貼圖的高做了計算。
最終效果:
可以看到,相比於之前簡單取樣的模糊,高斯模糊有更好的模糊效果~