Search code examples
openglopengl-esluacocos2d-xshader

how to set different texture to sprites sharing one shader in cocos2d


I'm making a rain scene with 4 different rain drop textures, each rain drop sprite randomly choose a rain drop texture when it's created and added to the screen. All the rain drop sprite share a same shader.

The problem is, when I add a new rain drop onto the screen, all former rain drops that have been added to the scene change its texture to be the same as the new rain drop.

my code is as follows:

local function addOneRainDrop()
    local rainStyleNumber = math.random(1,4)
    local rainDrop = cc.Sprite:create("rainDrop"..tostring(rainStyleNumber)..".png")
    rainShader:use()
    rainShader:updateUniforms()
    rainShader:setUniformsForBuiltins()
    gl.activeTexture(GL_TEXTURE1)    -- here may be the reason
    gl.bindTexture(GL_TEXTURE_2D, rainDrop:getTexture():getName())
    gl.activeTexture(GL_TEXTURE2)
    gl.bindTexture(GL_TEXTURE_2D, rainNormal[rainStyleNumber]:getName())
    gl.activeTexture(GL_TEXTURE0)

    rainDrop:setGLProgram(rainShader)
    rainDropLayer:addChild(rainDrop)
end

In my shader, I just sample CC_Texture0, CC_Texture1, CC_Texture2 and output color. It's not the shader's problem, I think. I don't know how Cocos2d manage it's shader's state and uniforms for different sprites, maybe directly inherit from CCSprite, overwrite "draw" and manage the texture by my self can solve the problem but it's a little bit complicated.

Any better ideas?

=============================================================================

UPDATE:

I find out that I can use GLProgramState to store uniforms for each sprite. Quote from cocos2d's website

A GLProgram can be used by thousands of Nodes, but if different uniform values are going to be used, then each node will need its own GLProgramState

So I changed my code to following:

local function addOneRainDrop()
    local rainStyleNumber = math.random(1,4)
    local rainDrop = cc.Sprite:create("rainDrop"..tostring(rainStyleNumber)..".png")
    local glprogramstate = cc.GLProgramState:getOrCreateWithGLProgram(rainShader);
    glprogramstate:setUniformTexture("rainDrop", rainDrop:getTexture():getName());
    glprogramstate:setUniformTexture("textureBackground", bg:getTexture():getName());
    glprogramstate:setUniformTexture("rainDropNormals", rainNormal[rainStyleNumber]:getName());
    rainDrop:setGLProgramState(glprogramstate);
    rainDropLayer:addChild(rainDrop)
end

Unfortunately, all the sprites still use the same texture. Does this have something to do with batchedNode?


Solution

  • Your issue is with you utilisation of getOrCreateWithGLProgram() which always returns the same GLProgramState because it caches using your rainShader as key.


    What I've done is:

    1) Create a method that will create or return your shader program

        static GLProgram* getOrCreateShader(std::string name, const GLchar* vert, const GLchar* frag)
        {
            auto cache = GLProgramCache::getInstance();
            auto prog = cache->getGLProgram(name);
            if(prog == nullptr)
            {
                prog = GLProgram::createWithByteArrays(vert, frag);
                cache->addGLProgram(prog, name);
            }
            return prog;
        }
    

    My method creates a program from a GLchar buffer, but you can create a new one which uses disk files.

    2) For each of your sprites, you create a new GLProgramState from your GLProgram

            auto program = getOrCreateShader("rainShaderName", vertexProgram, fragmentProgram);
            auto programState = GLProgramState::create(program);
    
            Sprite* sprite = Sprite::create("yourSpriteImage.png");
            programState->setUniformTxeture("u_texture", sprite->getTexture());
            sprite->setGLProgramState(programState);
    

    This way, the shader program will only be created once, and you'll have a unique GLProgramState per sprite.