本節主要講利用cocos2dx機制實現opengl es shader腳本的繪制
這里先看下最終效果:
這里分別實現了灰度效果及殘影的效果。
一、繪制基類
這里主要參考了cocos2dx源碼中 RenderTexture 的實現,有興趣的可以了解下。
繪制基類RenderShader主要實現以下方法:
//****************************************************************** // 文件名: RenderShader.h // 創建人: 稀飯lei // 版 本: 1.0 // 描 述: 特效基類 //****************************************************************** #ifndef _RenderShader_H__ #define _RenderShader_H__ #include "cocos2d.h" USING_NS_CC; // GL紋理坐標組 static const GLfloat ccRenderTextcord[8] ={ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; class RenderShader : public Node { public: static RenderShader* create(); virtual bool LoadByteArray(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray); //加載 shader文件 void begin(); // 用於設置繪制開始要調用的onBegin void end(); // 用於設置繪制結束要調用的onEnd void push2Draw(Node* node); // 添加待繪制節點 virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override; // cocos2dx繪制回調 protected: virtual void onBegin(); virtual void onEnd(); //virtual void onClear(); virtual void Render(); // 繪制實現 virtual bool init(); virtual void CreateFrameBuffer(); //創建FBO及texture RenderShader(void); virtual ~RenderShader(void); GLuint m_nFrameBuffer; // 用於當前繪制的fbo GLuint m_nFrameBufferTexture; // 用於綁定在fbo中的texture GLProgram* m_glprogram; // GL繪制管理 GLint m_nOldFBO; // 原來的fbo CustomCommand _beginCommand; CustomCommand _endCommand; GroupCommand _groupCommand; std::set<Node*> sRenderChild; // 需要繪制的節點 Size m_sRenderSize; }; #endif
這里有幾個主要實現:
1、我們用push2Draw(Node* node); 方法將待繪制的節點添加到sRenderChild中等待處理。
2、參考RenderTexture 的實現,我們也利用begin,end將需要繪制的節點利用visit訪問添加到當前的RenderShader類所在的render層中進行繪制。
3、在實際繪制過程中調用的onBegin中緩存對應的坐標系,同時綁定我們創建的FBO使得visit進來的節點可以繪制到當前FBO中。然后在onEnd中調用render方法將我們的FBO內容繪制原來cocos2dx底層的FBO上。
4、在render()實現如何繪制當前FBO的內容到cocos2dx的FBO上。(實際shader作業的地方)
詳細實現代碼如下:

#include "RenderShader.h" #define STRINGIFY(A) #A const char* ccPositionTextureColor_frag_test = STRINGIFY( \n#ifdef GL_ES\n precision lowp float; \n#endif\n varying vec4 v_fragmentColor; varying vec2 v_texCoord; void main() { vec4 color = v_fragmentColor * texture2D(CC_Texture0, v_texCoord); float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); gl_FragColor = vec4(vec3(gray), color.a); } ); RenderShader* RenderShader::create() { auto node = new RenderShader(); if (node && node->init()) { node->autorelease(); return node; } delete node; return nullptr; } bool RenderShader::init() { if (m_glprogram) { m_glprogram->release(); m_glprogram = nullptr; } CreateFrameBuffer(); m_glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, ccPositionTextureColor_frag_test); m_glprogram->retain(); return true; } RenderShader::RenderShader(void) :m_nFrameBuffer(0), m_nFrameBufferTexture(0), m_glprogram(nullptr) { GLView* glView = Director::getInstance()->getOpenGLView(); if (!glView) { return; } //屏幕大小 m_sRenderSize = glView->getFrameSize(); } RenderShader::~RenderShader(void) { if (m_glprogram) { m_glprogram->release(); m_glprogram = nullptr; } if (m_nFrameBuffer) { glDeleteFramebuffers(1, &m_nFrameBuffer); m_nFrameBuffer = 0; } if (m_nFrameBufferTexture) { glDeleteTextures(1, &m_nFrameBufferTexture); m_nFrameBufferTexture = 0; } } bool RenderShader::LoadByteArray(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray) { if (m_glprogram) { m_glprogram->release(); m_glprogram = nullptr; } this->CreateFrameBuffer(); m_glprogram = GLProgram::createWithByteArrays(vShaderByteArray, fShaderByteArray); m_glprogram->retain(); return true; } void RenderShader::onBegin() { if (!m_nFrameBuffer) { return; } Director* director = Director::getInstance(); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); //save old fbo and bind own glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO); glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } void RenderShader::onEnd() { if (!m_nFrameBuffer) { return; } // restore viewport for render to direct fbo Director *director = Director::getInstance(); // restore viewport director->setViewport(); this->Render(); glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } void RenderShader::Render() { if (!m_glprogram || !m_nFrameBufferTexture) { return; } glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); m_glprogram->use(); m_glprogram->setUniformsForBuiltins(); //GLuint glTexture = m_glprogram->getUniformLocationForName("CC_Texture0"); //glActiveTexture(GL_TEXTURE0); //glBindTexture(GL_TEXTURE_2D, m_nFrameBufferTexture); //glUniform1i(glTexture, 0); GL::bindTexture2D(m_nFrameBufferTexture); Size sSize = Director::getInstance()->getVisibleSize(); float x = 0; float y = 0; float w = sSize.width; float h = sSize.height; GLfloat vertices[] = { x, y + h, x + w, y + h, x, y, x + w, y }; glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, ccRenderTextcord); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); /*GL::bindVAO(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);*/ } void RenderShader::CreateFrameBuffer() { glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO); if (m_nFrameBuffer == 0) { glGenFramebuffers(1, &m_nFrameBuffer); if (m_nFrameBuffer == 0) { CCLOG("m_FilterFrameBuffer == 0"); return; } } glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer); if (m_nFrameBufferTexture == 0) { glGenTextures(1, &m_nFrameBufferTexture); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_nFrameBufferTexture); GLsizei nWidth = m_sRenderSize.width; GLsizei nHeight = m_sRenderSize.height; 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); GLenum err = glGetError(); if (err != GL_NO_ERROR) { CCLOG("cocos2d: Texture2D: Error uploading compressed texture glError: 0x%04X", err); return; } } glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_nFrameBufferTexture, 0); GLenum error; if ((error = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE) { CCLOG("Failed to make complete framebuffer object 0x%X", error); return; } glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); } void RenderShader::begin() { Director* director = Director::getInstance(); CCASSERT(nullptr != director, "Director is null when seting matrix stack"); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); _groupCommand.init(_globalZOrder); Renderer *renderer = Director::getInstance()->getRenderer(); renderer->addCommand(&_groupCommand); renderer->pushGroup(_groupCommand.getRenderQueueID()); _beginCommand.init(_globalZOrder); _beginCommand.func = CC_CALLBACK_0(RenderShader::onBegin, this); renderer->addCommand(&_beginCommand); } void RenderShader::end() { _endCommand.init(_globalZOrder); _endCommand.func = CC_CALLBACK_0(RenderShader::onEnd, this); Director* director = Director::getInstance(); CCASSERT(nullptr != director, "Director is null when seting matrix stack"); Renderer *renderer = director->getRenderer(); renderer->addCommand(&_endCommand); renderer->popGroup(); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } void RenderShader::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { //Begin will create a render group using new render target begin(); ////clear screen //_clearCommand.init(_globalZOrder); //_clearCommand.func = CC_CALLBACK_0(RenderShader::onClear, this); //renderer->addCommand(&_clearCommand); //! make sure all children are drawn { auto it = sRenderChild.begin(); while (it != sRenderChild.end()) { if (Node* node = *it) { node->visit(renderer, transform, flags); node->release(); } ++it; } sRenderChild.clear(); } //End will pop the current render group end(); } void RenderShader::push2Draw(Node* node) { if (node && sRenderChild.find(node) == sRenderChild.end()) { node->retain(); sRenderChild.insert(node); } else { CCLOG(" push same!!!"); } }
這里shader代碼實現的是灰度圖的效果,其他效果可以自己修改ccPositionTextureColor_frag_test內容。
二、實際使用
1、將RenderShader當成正常的Node使用,設置父節點及index坐標等。
m_pShader = RenderShader::create(); this->addChild(m_pShader);
2、因為RenderShader在draw中對需要繪制的子節點進行了清理,所以需要每次重新對子節點進行添加,添加動畫節點如下:
void CPlayer::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if (m_pShader) { m_pShader->push2Draw(m_pNode); } }
主要事項:RenderShader在Render方法中將內容繪制到原來的FBO中,而cocos2dx對每個需要繪制的同一index層級會在開始的時候單獨創建一個FBO來處理。因而如果RenderShader單獨一個層級可能導致此時的FBO沒有創建,導致沒有繪制效果。
到這里我們就實現灰色角色的效果了,使用也依照cocos2dx的用法,相對簡單。
三、殘影效果擴展
主要實現代碼如下:

//****************************************************************** // 文件名: RemindShader.h // 創建人: 稀飯lei // 版 本: 1.0 // 描 述: 殘影特效 //****************************************************************** #ifndef _RemindShader_H__ #define _RemindShader_H__ #include "RenderShader.h" USING_NS_CC; #define REMIND_RENDER_COUNT 5 // 殘影個數 class RemindShader :public RenderShader { public: static RemindShader* create(); virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override; private: virtual void onBegin() override; //virtual void onClear(); virtual void Render() override; virtual bool init() override; virtual void CreateFrameBuffer() override; RemindShader(void); virtual ~RemindShader(void); GLuint m_pTextureArr[REMIND_RENDER_COUNT]; int m_nRenderFrq; // 渲染間隔幀率 int m_nRenderCount; // 渲染間隔計數 int m_nCurTexutreIndex; // 當前渲染的紋理ID }; #endif #include "RemindShader.h" #define STRINGIFY(A) #A const char* ccRemind_fsh = STRINGIFY( \n#ifdef GL_ES\n precision lowp float; \n#endif\n varying vec2 v_texCoord; uniform float u_alpha; void main() { vec4 pcolor = texture2D(CC_Texture0, v_texCoord) * u_alpha; gl_FragColor = pcolor; } ); RemindShader* RemindShader::create() { auto node = new RemindShader(); if (node && node->init()) { node->autorelease(); return node; } delete node; return nullptr; } bool RemindShader::init() { if (m_glprogram) { m_glprogram->release(); m_glprogram = nullptr; } CreateFrameBuffer(); m_glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, ccRemind_fsh); m_glprogram->retain(); return true; } RemindShader::RemindShader(void) : m_nCurTexutreIndex(0), m_nRenderFrq(10), m_nRenderCount(0) { memset(m_pTextureArr, 0, REMIND_RENDER_COUNT); } RemindShader::~RemindShader(void) { if (m_pTextureArr[0]) { glDeleteTextures(REMIND_RENDER_COUNT, m_pTextureArr); memset(m_pTextureArr, 0, REMIND_RENDER_COUNT); } } void RemindShader::onBegin() { if (!m_nFrameBuffer) { return; } Director* director = Director::getInstance(); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); // director->setProjection(director->getProjection()); // GLView* glView = director->getOpenGLView(); // if (!glView) // { // return; // } // Size frameSize =glView->getFrameSize(); // { // // Calculate the adjustment ratios based on the old and new projections // Size size = director->getWinSizeInPixels(); // float widthRatio = size.width / frameSize.width; // float heightRatio = size.height / frameSize.height; // //caculate the projections of size change // Mat4 orthoMatrix; // Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix); // director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix); // } // // //calculate viewport // { // glViewport(0, 0, (GLsizei)(frameSize.width), (GLsizei)(frameSize.height)); // } glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO); // 僅在計數為0才更新新的渲染 if (m_nRenderCount == 0) { glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTextureArr[m_nCurTexutreIndex], 0); m_nCurTexutreIndex = m_nCurTexutreIndex + 1 < REMIND_RENDER_COUNT ? m_nCurTexutreIndex + 1 : 0; glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } } void RemindShader::Render() { if (!m_glprogram ) { return; } glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); m_glprogram->use(); m_glprogram->setUniformsForBuiltins(); Size sSize = Director::getInstance()->getVisibleSize(); float x = 0; float y = 0; float w = sSize.width; float h = sSize.height; // 除當前紋理外的紋理存儲的都是拖影 int nBeginIndex = m_nCurTexutreIndex; int nCurIndex = nBeginIndex; GLfloat falpha = 0.3f; float addf = 0.05f; // 渲染存在m_pTextureArr隊列里的拖影紋理到主場景 do { Point delta = _position;// this->convertToWorldSpace(_position); GLfloat vertices[] = { x - delta.x,y+ h - delta.y, x+w - delta.x,y+ h - delta.y, x - delta.x , y - delta.y, x+w - delta.x, y - delta.y }; //GLuint glTexture = m_glprogram->getUniformLocationForName("CC_Texture0"); //glActiveTexture(GL_TEXTURE0); //glBindTexture(GL_TEXTURE_2D, m_pTextureArr[nCurIndex]); //glUniform1i(glTexture, 0); GL::bindTexture2D(m_pTextureArr[nCurIndex]); GLuint glAlpha = m_glprogram->getUniformLocationForName("u_alpha"); glUniform1f(glAlpha, falpha); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, ccRenderTextcord); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); falpha += addf; addf += 0.025f; nCurIndex = ++nCurIndex<REMIND_RENDER_COUNT ? nCurIndex : 0; } while (nCurIndex != nBeginIndex); // // GL::bindVAO(0); //glBindBuffer(GL_ARRAY_BUFFER, 0); //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } void RemindShader::CreateFrameBuffer() { glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO); if (m_nFrameBuffer == 0) { glGenFramebuffers(1, &m_nFrameBuffer); if (m_nFrameBuffer == 0) { CCLOG("m_FilterFrameBuffer == 0"); return; } } GLsizei nWidth = m_sRenderSize.width; GLsizei nHeight = m_sRenderSize.height; glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer); if (m_pTextureArr[0] == 0) { glGenTextures(REMIND_RENDER_COUNT, m_pTextureArr); for (int i = 0; i < REMIND_RENDER_COUNT; i++) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_pTextureArr[i]); 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); GLenum err = glGetError(); if (err != GL_NO_ERROR) { CCLOG("cocos2d: Texture2D: Error uploading compressed texture glError: 0x%04X", err); return; } glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTextureArr[i], 0); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } } glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); } void RemindShader::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if (sRenderChild.size() <= 0 ) { if (m_nFrameBuffer) { // 清理所有緩存的紋理 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO); glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer); for (int i = 0; i < REMIND_RENDER_COUNT; i++) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTextureArr[i], 0); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); } m_nRenderCount = m_nRenderFrq - 1; return; } //Begin will create a render group using new render target begin(); if (++m_nRenderCount == m_nRenderFrq) { //! make sure all children are drawn auto it = sRenderChild.begin(); while (it != sRenderChild.end()) { if (Node* node = *it) { node->visit(renderer, transform, flags); node->release(); } ++it; } m_nRenderCount = 0; // 0 提示需要渲染進去 } sRenderChild.clear(); //End will pop the current render group end(); }
幾個關鍵點:
1、利用REMIND_RENDER_COUNT控制需要創建的Texture個數,通過每次繪制到不同的Texture上來保存原來的效果。
2、shader中利用u_alpha參數控制殘影的透明度,在render中繪制到原來的FBO上時調用不同的透明度來實現殘影漸隱的效果。
3、通過m_nRenderFrq來控制間隔的繪制次數,來實現殘影的殘留效果遠近。
這樣就實現了殘影的效果了,使用的方法還是跟基類一樣。
完整代碼地址:https://github.com/mydishes/cocos2dx-Ex/tree/master/Shader