Cocos2d-x apply fragment shader to a sprite like in the tutorial

The code below is from this tutorial: http://www.raywenderlich.com/10862/how-to-create-cool-effects-with-custom-shaders-in-opengl-es-2-0-and-cocos2d-2-x

It is a very cool tutorial, but I don’t know how to do that in cocos2d-x 3. I have translated the code below

      - (id)init
      {
      self = [super init];
      if (self) {
        // 1
        sprite = [CCSprite spriteWithFile:@"Default.png"];
        sprite.anchorPoint = CGPointZero;
        sprite.rotation = 90;
        sprite.position = ccp(0, 320);
        [self addChild:sprite];
     
        // 2
        const GLchar * fragmentSource = (GLchar*) [[NSString stringWithContentsOfFile:[CCFileUtils fullPathFromRelativePath:@"CSEColorRamp.fsh"] encoding:NSUTF8StringEncoding error:nil] UTF8String];
        sprite.shaderProgram = [[CCGLProgram alloc] initWithVertexShaderByteArray:ccPositionTextureA8Color_vert
                                           fragmentShaderByteArray:fragmentSource];
        [sprite.shaderProgram addAttribute:kCCAttributeNamePosition index:kCCVertexAttrib_Position];
        [sprite.shaderProgram addAttribute:kCCAttributeNameTexCoord index:kCCVertexAttrib_TexCoords];
        [sprite.shaderProgram link];
        [sprite.shaderProgram updateUniforms];
     
        // 3
        colorRampUniformLocation = glGetUniformLocation(sprite.shaderProgram->program_, "u_colorRampTexture");
        glUniform1i(colorRampUniformLocation, 1);
     
        // 4
        colorRampTexture = [[CCTextureCache sharedTextureCache] addImage:@"colorRamp.png"];
        [colorRampTexture setAliasTexParameters];
     
        // 5
        [sprite.shaderProgram use];
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, [colorRampTexture name]);
        glActiveTexture(GL_TEXTURE0);
      }
      return self;
    }

and obtained this:

    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    
    sprite = Sprite::create("HelloWorld.png");
    sprite->setAnchorPoint(Vec2(0, 0));
    sprite->setRotation(3);
    sprite->setPosition(origin);
    addChild(sprite);
    
    const GLchar * fragmentSource = FileUtils::getInstance()->getStringFromFile("CSEColorRamp.fsh").c_str();
    GLProgram* p = GLProgram::createWithByteArrays(ccPositionTextureA8Color_vert, fragmentSource);
    sprite->setGLProgram(p);
    p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
    p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
    p->link();
    p->updateUniforms();

    
    // 3
    colorRampUniformLocation = glGetUniformLocation(sprite->getGLProgram()->getProgram(), "u_colorRampTexture");
    glUniform1i(colorRampUniformLocation, 1);
    
    // 4
    colorRampTexture = Director::getInstance()->getTextureCache()->addImage("colorRamp.png");
    colorRampTexture->setAliasTexParameters();
    
    // 5
    sprite->getGLProgram()->use();
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, colorRampTexture->getName());
    glActiveTexture(GL_TEXTURE0);

But it does not work. It shows a black screen with 2 draw calls. What is wrong? Did I pass all uniforms and attributes normally to the fragment shader. Did I initialize the program correctly?

3 Likes

OMG. In all this forum nobody knows how to apply a simple fragment shader onto the sprite?

OMG in all this forum, everyone isn’t dropping everything just to help you immediately?

I suggest learning a little patience may be in order.

OMG in all this forum someone responded, but again does not know how to apply a simple fragment shader onto the sprite :smiley:.

In cocos2d-x v3, OpenGL calls need to be wrapped in a Custom Command. If the code you posted is in the init function, the I assume this is the issue.

I used custom shaders in cocos v2.2 like this

GLProgram *shader = GLProgram::createWithFilenames("user_vertex_shader.vsh", "user_fragment_shader.fsh"); 
	shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
	shader->link();
    shader->updateUniforms();

	GLProgramCache::getInstance()->addGLProgram(shader, "user_shader");
	sprite->setGLProgram(GLProgramCache::getInstance()->getProgram("user_shader"));

Try adding your program to program cache so it is retained.

1 Like

@mmvlad It did not help me. I suspect I have done something wrong while passing uniforms of attributes to the shader.

@toojuice I don’t understand why I need to use a custom command. I assign a shader to a node so that the shader was used during the rendering of that node. Shouldn’t it be just fine?

@naghekyan - No, you’re right. I shouldn’t be so quick to reply when I’m sleep deprived.

But, doing a quick test… changing the color value the fragment shader returns to a constant, causes the screen to be set to that color. So the output side works. It seems the input side is not working, as you suggested. I’ll play around a little more and report back if I find anything out.

@toojuice thank you very much!

@naghekyan - Hey sorry, I haven’t had much time to look into it and probably won’t have more time until next week.

I did download the original tutorial’s project and tested it out and saw that it work. I don’t see anything off the bat that’s different in your port, so I’m guessing there’s a setting or operational difference between the two engines (cocos2d-x and cocos2d).

Using Xcode’s OpenGL debugger, I see an error and a warning:

ERROR: Mipmapping enabled without a complete mip-chain Texture #0 has mipmapping enabled, but is missing levels of the mip-chain

WARNING: Uninitialized texture data Texture #0 is being used for a drawing call while it contains uninitialized data

I’d guess these are causing the problem. Will look more into it later

@toojuice thank for your time. I am starting to think that you were right about CustomCommand. @owen gave me a link to his tutorial: http://4gamers.cn/archives/131 This is the only tutorial that teaches how to use OpenGL with cocos2d-x 3.1 and it is in Chinese. Rare resource, huh? :smile: And here is the Google translation: https://translate.google.com/translate?sl=auto&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=http%3A%2F%2F4gamers.cn%2Farchives%2F131&edit-text=&act=url

Here you can see how he explains why we need CustomCommand and also there is a good link about cocos2d-x 3 rendering pipeline: http://cocos2d-x.org/wiki/Cocos2d_v30_renderer_pipeline_roadmap. So I start to think, that the cocos2d/cocos2d-x-2.2.x approach is obsolete. And this is my mistake. I need to think in therms of rendering commands.

Ok, it’s taken me a while to get back to this, but I have an answer, of sorts. Some things that I learned along the way:

  1. You don’t need to wrap the code in a custom command
  2. It seems to be easier to use GLProgramState to interface with the shader

First, here’s the code I used:

    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("HelloWorld.png");
    sprite->getTexture()->setAntiAliasTexParameters();
    
    // position the sprite on the center of the screen
    sprite->setPosition(_visibleSize / 2);
    
    // add the sprite as a child to this layer
    this->addChild(sprite);    

    const GLchar * fragmentSource = FileUtils::getInstance()->getStringFromFile("CSEColorRamp.fsh").c_str();
    GLProgram* p = GLProgram::createWithByteArrays(ccPositionTextureA8Color_vert, fragmentSource);
    p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
    p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
    p->link();
    CHECK_GL_ERROR_DEBUG();
    p->updateUniforms();
    CHECK_GL_ERROR_DEBUG();
    sprite->setGLProgram(p);
    
    Texture2D *_colorRampTexture = Director::getInstance()->getTextureCache()->addImage("colorRamp.png");
    _colorRampTexture->setAliasTexParameters();
    _colorRampTexture->retain();
    
    // Here we set up which textures are associated with which shader variables
    auto glProgramState = GLProgramState::getOrCreateWithGLProgram(p);
    sprite->setGLProgramState(glProgramState);
    
    glProgramState->setUniformTexture("u_texture", sprite->getTexture());
    glProgramState->setUniformTexture("u_colorRampTexture", _colorRampTexture);

    sprite->getGLProgram()->use();

Here are screenshots of with and without the shader:

WITHOUT:

WITH:

You may have noticed that using the shader translates the position of the image for some reason :slight_smile: I have not looked into why this is, but I hope it’s not terribly hard to debug.

EDIT:

This post talks about a post-shader translation problem: http://gamedev.stackexchange.com/questions/76124/weird-y-offset-when-using-custom-frag-shader-cocos2d-x

2 Likes

Thank you very much @toojuice ! One difference I see is that I do sprite->setGLProgram(p); right after creating the program, but you have done that after binding attribute locations. Is this important?

Also you call sprite->getGLProgram()->use(); which is missing in my case and you use GLProgramState, which, indeed makes easier to pass uniforms to the program!

Thank you for your great help.

@naghekyan - sorry for the late reply. I’ve been really busy lately and completely missed it. I don’t think the location of sprite->setGLProgram(p); is very important. But you can experiment if you feel like it :slight_smile:

@toojuice sure thank you! :smile:

I am still getting the transilation issue anyway to get around it? :smiley:

in vertex shader use gl_Position = CC_PMatrix * a_position

Ya got it thanks. :smile: D

can you help me explain why need this CC_PMatrix? im newbie so just want to know the details, :smiley:

CC_PMatrix is the projection matrix, where as the CC_MVPMatrix is the model, view, projection matrix. Since in 2d we are using ortho camera CC_PMatrix is enough to do calculations. I understand this way, if wrong correct me guys.