#include "ShaderTest.h" #include "../testResource.h" #include "cocos2d.h" static int sceneIdx = -1; #define MAX_LAYER 9 static CCLayer* createShaderLayer(int nIndex) { switch (sceneIdx) { case 0: return new ShaderMonjori(); case 1: return new ShaderMandelbrot(); case 2: return new ShaderJulia(); case 3: return new ShaderHeart(); case 4: return new ShaderFlower(); case 5: return new ShaderPlasma(); case 6: return new ShaderBlur(); case 7: return new ShaderRetroEffect(); case 8: return new ShaderFail(); } return NULL; } static CCLayer* nextAction(void) { sceneIdx++; sceneIdx = sceneIdx % MAX_LAYER; CCLayer* pLayer = createShaderLayer(sceneIdx); pLayer->autorelease(); return pLayer; } static CCLayer* backAction(void) { sceneIdx--; int total = MAX_LAYER; if( sceneIdx < 0 ) sceneIdx += total; CCLayer* pLayer = createShaderLayer(sceneIdx); pLayer->autorelease(); return pLayer; } static CCLayer* restartAction(void) { CCLayer* pLayer = createShaderLayer(sceneIdx); pLayer->autorelease(); return pLayer; } ShaderTestDemo::ShaderTestDemo() { } bool ShaderTestDemo::init() { CCSize s = CCDirector::sharedDirector()->getWinSize(); CCLabelTTF *label = CCLabelTTF::create(title().c_str(), "Arial", 26); addChild(label, 1); label->setPosition(ccp(s.width/2, s.height-50)); label->setColor(ccRED); std::string subtitle = this->subtitle(); if (subtitle.length() > 0) { CCLabelTTF *l = CCLabelTTF::create(subtitle.c_str(), "Thonburi", 16); addChild(l, 1); l->setPosition(ccp(s.width/2, s.height-80)); } CCMenuItemImage *item1 = CCMenuItemImage::create(s_pPathB1, s_pPathB2, this, menu_selector(ShaderTestDemo::backCallback)); CCMenuItemImage *item2 = CCMenuItemImage::create(s_pPathR1, s_pPathR2, this, menu_selector(ShaderTestDemo::restartCallback)); CCMenuItemImage *item3 = CCMenuItemImage::create(s_pPathF1, s_pPathF2, this, menu_selector(ShaderTestDemo::nextCallback)); CCMenu *menu = CCMenu::create(item1, item2, item3, NULL); menu->setPosition(ccp(0, 0)); item1->setPosition(ccp(VisibleRect::center().x - item2->getContentSize().width*2, VisibleRect::bottom().y+item2->getContentSize().height/2)); item2->setPosition(ccp(VisibleRect::center().x, VisibleRect::bottom().y+item2->getContentSize().height/2)); item3->setPosition(ccp(VisibleRect::center().x + item2->getContentSize().width*2, VisibleRect::bottom().y+item2->getContentSize().height/2)); addChild(menu, 1); return true; } void ShaderTestDemo::backCallback(CCObject* pSender) { CCScene* s = new ShaderTestScene(); s->addChild( backAction() ); CCDirector::sharedDirector()->replaceScene(s); s->release(); } void ShaderTestDemo::nextCallback(CCObject* pSender) { CCScene* s = new ShaderTestScene();//CCScene::create(); s->addChild( nextAction() ); CCDirector::sharedDirector()->replaceScene(s); s->release(); } std::string ShaderTestDemo::title() { return "No title"; } std::string ShaderTestDemo::subtitle() { return ""; } void ShaderTestDemo::restartCallback(CCObject* pSender) { CCScene* s = new ShaderTestScene(); s->addChild(restartAction()); CCDirector::sharedDirector()->replaceScene(s); s->release(); } ///--------------------------------------- // // ShaderNode // ///--------------------------------------- enum { SIZE_X = 256, SIZE_Y = 256, }; ShaderNode::ShaderNode() :m_center(vertex2(0.0f, 0.0f)) ,m_resolution(vertex2(0.0f, 0.0f)) ,m_time(0.0f) ,m_uniformCenter(0) ,m_uniformResolution(0) ,m_uniformTime(0) { } ShaderNode::~ShaderNode() { CCNotificationCenter::sharedNotificationCenter()->removeObserver(this, EVNET_COME_TO_FOREGROUND); } ShaderNode* ShaderNode::shaderNodeWithVertex(const char *vert, const char *frag) { ShaderNode *node = new ShaderNode(); node->initWithVertex(vert, frag); node->autorelease(); return node; } bool ShaderNode::initWithVertex(const char *vert, const char *frag) { CCNotificationCenter::sharedNotificationCenter()->addObserver(this, callfuncO_selector(ShaderNode::listenBackToForeground), EVNET_COME_TO_FOREGROUND, NULL); loadShaderVertex(vert, frag); m_time = 0; m_resolution = vertex2(SIZE_X, SIZE_Y); scheduleUpdate(); setContentSize(CCSizeMake(SIZE_X, SIZE_Y)); setAnchorPoint(ccp(0.5f, 0.5f)); m_vertFileName = vert; m_fragFileName = frag; return true; } void ShaderNode::listenBackToForeground(CCObject *obj) { this->setShaderProgram(NULL); loadShaderVertex(m_vertFileName.c_str(), m_fragFileName.c_str()); } void ShaderNode::loadShaderVertex(const char *vert, const char *frag) { CCGLProgram *shader = new CCGLProgram(); shader->initWithVertexShaderFilename(vert, frag); shader->addAttribute("aVertex", kCCVertexAttrib_Position); shader->link(); shader->updateUniforms(); m_uniformCenter = glGetUniformLocation(shader->getProgram(), "center"); m_uniformResolution = glGetUniformLocation(shader->getProgram(), "resolution"); m_uniformTime = glGetUniformLocation(shader->getProgram(), "time"); this->setShaderProgram(shader); shader->release(); } void ShaderNode::update(float dt) { m_time += dt; } void ShaderNode::setPosition(const CCPoint &newPosition) { CCNode::setPosition(newPosition); CCPoint position = getPosition(); m_center = vertex2(position.x * CC_CONTENT_SCALE_FACTOR(), position.y * CC_CONTENT_SCALE_FACTOR()); } void ShaderNode::draw() { CC_NODE_DRAW_SETUP(); float w = SIZE_X, h = SIZE_Y; GLfloat vertices[12] = {0,0, w,0, w,h, 0,0, 0,h, w,h}; // // Uniforms // getShaderProgram()->setUniformLocationWith2f(m_uniformCenter, m_center.x, m_center.y); getShaderProgram()->setUniformLocationWith2f(m_uniformResolution, m_resolution.x, m_resolution.y); // time changes all the time, so it is Ok to call OpenGL directly, and not the "cached" version glUniform1f(m_uniformTime, m_time); ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position ); glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices); glDrawArrays(GL_TRIANGLES, 0, 6); CC_INCREMENT_GL_DRAWS(1); } /// ShaderMonjori ShaderMonjori::ShaderMonjori() { init(); } bool ShaderMonjori::init() { if (ShaderTestDemo::init()) { ShaderNode *sn = ShaderNode::shaderNodeWithVertex("Shaders/example_Monjori.vsh", "Shaders/example_Monjori.fsh"); CCSize s = CCDirector::sharedDirector()->getWinSize(); sn->setPosition(ccp(s.width/2, s.height/2)); addChild(sn); return true; } return false; } std::string ShaderMonjori::title() { return "Shader: Frag shader"; } std::string ShaderMonjori::subtitle() { return "Monjori plane deformations"; } /// ShaderMandelbrot ShaderMandelbrot::ShaderMandelbrot() { init(); } bool ShaderMandelbrot::init() { if (ShaderTestDemo::init()) { ShaderNode *sn = ShaderNode::shaderNodeWithVertex("Shaders/example_Mandelbrot.vsh", "Shaders/example_Mandelbrot.fsh"); CCSize s = CCDirector::sharedDirector()->getWinSize(); sn->setPosition(ccp(s.width/2, s.height/2)); addChild(sn); return true; } return false; } std::string ShaderMandelbrot::title() { return "Shader: Frag shader"; } std::string ShaderMandelbrot::subtitle() { return "Mandelbrot shader with Zoom"; } /// ShaderJulia ShaderJulia::ShaderJulia() { init(); } bool ShaderJulia::init() { if (ShaderTestDemo::init()) { ShaderNode *sn = ShaderNode::shaderNodeWithVertex("Shaders/example_Julia.vsh", "Shaders/example_Julia.fsh"); CCSize s = CCDirector::sharedDirector()->getWinSize(); sn->setPosition(ccp(s.width/2, s.height/2)); addChild(sn); return true; } return false; } std::string ShaderJulia::title() { return "Shader: Frag shader"; } std::string ShaderJulia::subtitle() { return "Julia shader"; } /// ShaderHeart ShaderHeart::ShaderHeart() { init(); } bool ShaderHeart::init() { if (ShaderTestDemo::init()) { ShaderNode *sn = ShaderNode::shaderNodeWithVertex("Shaders/example_Heart.vsh", "Shaders/example_Heart.fsh"); CCSize s = CCDirector::sharedDirector()->getWinSize(); sn->setPosition(ccp(s.width/2, s.height/2)); addChild(sn); return true; } return false; } std::string ShaderHeart::title() { return "Shader: Frag shader"; } std::string ShaderHeart::subtitle() { return "Heart"; } /// ShaderFlower ShaderFlower::ShaderFlower() { init(); } bool ShaderFlower::init() { if (ShaderTestDemo::init()) { ShaderNode *sn = ShaderNode::shaderNodeWithVertex("Shaders/example_Flower.vsh", "Shaders/example_Flower.fsh"); CCSize s = CCDirector::sharedDirector()->getWinSize(); sn->setPosition(ccp(s.width/2, s.height/2)); addChild(sn); return true; } return false; } std::string ShaderFlower::title() { return "Shader: Frag shader"; } std::string ShaderFlower::subtitle() { return "Flower"; } /// ShaderPlasma ShaderPlasma::ShaderPlasma() { init(); } bool ShaderPlasma::init() { if (ShaderTestDemo::init()) { ShaderNode *sn = ShaderNode::shaderNodeWithVertex("Shaders/example_Plasma.vsh", "Shaders/example_Plasma.fsh"); CCSize s = CCDirector::sharedDirector()->getWinSize(); sn->setPosition(ccp(s.width/2, s.height/2)); addChild(sn); return true; } return false; } std::string ShaderPlasma::title() { return "Shader: Frag shader"; } std::string ShaderPlasma::subtitle() { return "Plasma"; } // ShaderBlur class SpriteBlur : public CCSprite { public: ~SpriteBlur(); void setBlurSize(float f); bool initWithTexture(CCTexture2D* texture, const CCRect& rect); void draw(); void initProgram(); void listenBackToForeground(CCObject *obj); static SpriteBlur* create(const char *pszFileName); CCPoint blur_; GLfloat sub_[4]; GLuint blurLocation; GLuint subLocation; }; SpriteBlur::~SpriteBlur() { CCNotificationCenter::sharedNotificationCenter()->removeObserver(this, EVNET_COME_TO_FOREGROUND); } SpriteBlur* SpriteBlur::create(const char *pszFileName) { SpriteBlur* pRet = new SpriteBlur(); if (pRet && pRet->initWithFile(pszFileName)) { pRet->autorelease(); } else { CC_SAFE_DELETE(pRet); } return pRet; } void SpriteBlur::listenBackToForeground(CCObject *obj) { setShaderProgram(NULL); initProgram(); } bool SpriteBlur::initWithTexture(CCTexture2D* texture, const CCRect& rect) { if( CCSprite::initWithTexture(texture, rect) ) { CCNotificationCenter::sharedNotificationCenter()->addObserver(this, callfuncO_selector(SpriteBlur::listenBackToForeground), EVNET_COME_TO_FOREGROUND, NULL); CCSize s = getTexture()->getContentSizeInPixels(); blur_ = ccp(1/s.width, 1/s.height); sub_[0] = sub_[1] = sub_[2] = sub_[3] = 0; this->initProgram(); return true; } return false; } void SpriteBlur::initProgram() { GLchar * fragSource = (GLchar*) CCString::createWithContentsOfFile( CCFileUtils::sharedFileUtils()->fullPathForFilename("Shaders/example_Blur.fsh").c_str())->getCString(); CCGLProgram* pProgram = new CCGLProgram(); pProgram->initWithVertexShaderByteArray(ccPositionTextureColor_vert, fragSource); setShaderProgram(pProgram); pProgram->release(); CHECK_GL_ERROR_DEBUG(); getShaderProgram()->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position); getShaderProgram()->addAttribute(kCCAttributeNameColor, kCCVertexAttrib_Color); getShaderProgram()->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords); CHECK_GL_ERROR_DEBUG(); getShaderProgram()->link(); CHECK_GL_ERROR_DEBUG(); getShaderProgram()->updateUniforms(); CHECK_GL_ERROR_DEBUG(); subLocation = glGetUniformLocation( getShaderProgram()->getProgram(), "substract"); blurLocation = glGetUniformLocation( getShaderProgram()->getProgram(), "blurSize"); CHECK_GL_ERROR_DEBUG(); } void SpriteBlur::draw() { ccGLEnableVertexAttribs(kCCVertexAttribFlag_PosColorTex ); ccBlendFunc blend = getBlendFunc(); ccGLBlendFunc(blend.src, blend.dst); getShaderProgram()->use(); getShaderProgram()->setUniformsForBuiltins(); getShaderProgram()->setUniformLocationWith2f(blurLocation, blur_.x, blur_.y); getShaderProgram()->setUniformLocationWith4fv(subLocation, sub_, 1); ccGLBindTexture2D( getTexture()->getName()); // // Attributes // #define kQuadSize sizeof(m_sQuad.bl) long offset = (long)&m_sQuad; // vertex int diff = offsetof( ccV3F_C4B_T2F, vertices); glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff)); // texCoods diff = offsetof( ccV3F_C4B_T2F, texCoords); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff)); // color diff = offsetof( ccV3F_C4B_T2F, colors); glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); CC_INCREMENT_GL_DRAWS(1); } void SpriteBlur::setBlurSize(float f) { CCSize s = getTexture()->getContentSizeInPixels(); blur_ = ccp(1/s.width, 1/s.height); blur_ = ccpMult(blur_,f); } // ShaderBlur ShaderBlur::ShaderBlur() { init(); } std::string ShaderBlur::title() { return "Shader: Frag shader"; } std::string ShaderBlur::subtitle() { return "Gaussian blur"; } CCControlSlider* ShaderBlur::createSliderCtl() { CCSize screenSize = CCDirector::sharedDirector()->getWinSize(); CCControlSlider *slider = CCControlSlider::create("extensions/sliderTrack.png","extensions/sliderProgress.png" ,"extensions/sliderThumb.png"); slider->setAnchorPoint(ccp(0.5f, 1.0f)); slider->setMinimumValue(0.0f); // Sets the min value of range slider->setMaximumValue(3.0f); // Sets the max value of range slider->setValue(1.0f); slider->setPosition(ccp(screenSize.width / 2.0f, screenSize.height / 3.0f)); // When the value of the slider will change, the given selector will be call slider->addTargetWithActionForControlEvents(this, cccontrol_selector(ShaderBlur::sliderAction), CCControlEventValueChanged); return slider; } bool ShaderBlur::init() { if( ShaderTestDemo::init() ) { m_pBlurSprite = SpriteBlur::create("Images/grossini.png"); CCSprite *sprite = CCSprite::create("Images/grossini.png"); CCSize s = CCDirector::sharedDirector()->getWinSize(); m_pBlurSprite->setPosition(ccp(s.width/3, s.height/2)); sprite->setPosition(ccp(2*s.width/3, s.height/2)); addChild(m_pBlurSprite); addChild(sprite); m_pSliderCtl = createSliderCtl(); addChild(m_pSliderCtl); return true; } return false; } void ShaderBlur::sliderAction(CCObject* sender, CCControlEvent controlEvent) { CCControlSlider* pSlider = (CCControlSlider*)sender; m_pBlurSprite->setBlurSize(pSlider->getValue()); } // ShaderRetroEffect ShaderRetroEffect::ShaderRetroEffect() : m_pLabel(NULL) , m_fAccum(0.0f) { init(); } bool ShaderRetroEffect::init() { if( ShaderTestDemo::init() ) { GLchar * fragSource = (GLchar*) CCString::createWithContentsOfFile(CCFileUtils::sharedFileUtils()->fullPathForFilename("Shaders/example_HorizontalColor.fsh").c_str())->getCString(); CCGLProgram *p = new CCGLProgram(); p->initWithVertexShaderByteArray(ccPositionTexture_vert, fragSource); p->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position); p->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords); p->link(); p->updateUniforms(); CCDirector *director = CCDirector::sharedDirector(); CCSize s = director->getWinSize(); m_pLabel = CCLabelBMFont::create("RETRO EFFECT", "fonts/west_england-64.fnt"); m_pLabel->setShaderProgram(p); p->release(); m_pLabel->setPosition(ccp(s.width/2,s.height/2)); addChild(m_pLabel); scheduleUpdate(); return true; } return false; } void ShaderRetroEffect::update(float dt) { m_fAccum += dt; CCArray* pArray = m_pLabel->getChildren(); int i=0; CCObject* pObj = NULL; CCARRAY_FOREACH(pArray, pObj) { CCSprite *sprite = (CCSprite*)pObj; i++; CCPoint oldPosition = sprite->getPosition(); sprite->setPosition(ccp( oldPosition.x, sinf( m_fAccum * 2 + i/2.0) * 20 )); // add fabs() to prevent negative scaling float scaleY = ( sinf( m_fAccum * 2 + i/2.0 + 0.707) ); sprite->setScaleY(scaleY); } } std::string ShaderRetroEffect::title() { return "Shader: Retro test"; } std::string ShaderRetroEffect::subtitle() { return "sin() effect with moving colors"; } // ShaderFail const GLchar *shader_frag_fail = "\n\ #ifdef GL_ES \n\ precision lowp float; \n\ #endif \n\ \n\ varying vec2 v_texCoord; \n\ uniform sampler2D CC_Texture0; \n\ \n\ vec4 colors[10]; \n\ \n\ void main(void) \n\ { \n\ colors[0] = vec4(1,0,0,1); \n\ colors[1] = vec4(0,1,0,1); \n\ colors[2] = vec4(0,0,1,1); \n\ colors[3] = vec4(0,1,1,1); \n\ colors[4] = vec4(1,0,1,1); \n\ colors[5] = vec4(1,1,0,1); \n\ colors[6] = vec4(1,1,1,1); \n\ colors[7] = vec4(1,0.5,0,1); \n\ colors[8] = vec4(1,0.5,0.5,1); \n\ colors[9] = vec4(0.5,0.5,1,1); \n\ \n\ int y = int( mod(gl_FragCoord.y / 3.0, 10.0 ) ); \n\ gl_FragColor = colors[z] * texture2D(CC_Texture0, v_texCoord); \n\ } \n\ \n"; ShaderFail::ShaderFail() { CCGLProgram *p = new CCGLProgram(); p->initWithVertexShaderByteArray(ccPositionTexture_vert, shader_frag_fail); p->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position); p->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords); p->link(); p->updateUniforms(); p->release(); } string ShaderFail::title() { return "Shader: Invalid shader"; } string ShaderFail::subtitle() { return "See console for output with useful error log"; } ///--------------------------------------- // // ShaderTestScene // ///--------------------------------------- void ShaderTestScene::runThisTest() { sceneIdx = -1; addChild(nextAction()); CCDirector::sharedDirector()->replaceScene(this); }