Cocos2d 4.0 create shader for Spine animation

Hello,

I have an issue with applying the custom shader to the spine animation (cocos2d v.4.0).
The shader that I’ve created works fine with Sprite but doesn’t work with Spine Animation.

Here is the sample code, (partly taken from cpp-tests):

#define SET_UNIFORM(ps, name, value)  do {   \
decltype(value) __v = value;                           \
auto __loc = (ps)->getUniformLocation(name);  \
(ps)->setUniform(__loc, &__v, sizeof(__v));  \
} while(false)

...

auto animation = spine::SkeletonAnimation::createWithFile(jsonPath, atlasPath);

animation->addAnimation(0, "idle", false);
animation->setScale(0.5f);
this->addChild(animation);

...

std::string fragment = FileUtils::getInstance()->getStringFromFile(
         FileUtils::getInstance()->fullPathForFilename("example_Blur.fsh")); // from cpp-tests
auto program = backend::Device::getInstance()->newProgram(positionTextureColor_vert, fragment.data());
auto programState = new backend::ProgramState(program);

SET_UNIFORM( programState, "resolution", cocos2d::Size(500,500));
SET_UNIFORM( programState, "blurRadius", 50.f);
SET_UNIFORM( programState, "sampleNum", 7.0f);
SET_UNIFORM( programState, "u_PMatrix", Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION));

animation->setProgramState(programState);

Thank you for your help in advance!

I don’t think it’s possible to apply a custom shader to the spine::SkeletonAnimation sprite, at least not without modifying the spine source (SkeletonBatch.cpp).

If it won’t cause any performance issues, you can render the Spine animation to a RenderTexture, apply the shader to the RenderTexture sprite, and use that sprite in your scene. You would have to do this for every update cycle.

Thank you for your response !

I can apply shader to the spine animation in cocos2d v.3.16 but with cocos2d v.4.0 it doesn’t work.

I didn’t realise it worked with 3.16, since I’ve only ever tried it on v4, and made a note to look into it later down the track. So, if it did work with v3, then it means the spine-cocos2d-x runtime that was adapted to work with v4 has had some breaking changes.

You can try asking on the Esoteric forums about this issue, since they may suggestions on how to get it working.

I just did a quick comparison of the v3 and v4 Spine cocos2dx runtime code, and as I originally mentioned above, it is the code in SkeletonBatch.cpp that stops us using the custom shader. I’ve made some modifications to it that re-enables the custom shader, and I’ll send the changes to the Spine developers, but it’s up to them to decide if they want to merge the changes into the official spine-cocos2dx runtime.

If you want to try it out yourself, then overwrite your version of the files with these:
SkeletonBatch.cpp (8.8 KB) SkeletonBatch.h (3.7 KB) SkeletonRenderer.cpp (41.7 KB)

I tested it out now by applying a custom grey-scale shader to the spine::SkeletonAnimation , and it worked perfectly.

FYI: This won’t work if you’re using two color tinting (SpineTwoColorBatch), since that has its own shader that needs to be applied. You would have to disable two color tinting before applying your own custom shader, like this: skeletonAnimation->setTwoColorTint(false)

Also, the changes I made break sprite batching for the spine animation, so it’s using more draw calls (only when using a custom shader). I’m not sure why yet, but if I figure it out I’ll post the updated code.

2 Likes

Thank you very much @R101 ! I will try your solution with my custom shaders and will let you know!

OK, so, the batching is broken because of the code in Cocos2d-x, CCTrianglesCommand.cpp:

void TrianglesCommand::init(float globalOrder, Texture2D* texture, const BlendFunc& blendType, const Triangles& triangles, const Mat4& mv, uint32_t flags)
{
    ...
        
        //since it would be too expensive to check the uniforms, simplify enable batching for built-in program.
        if(_programType == backend::ProgramType::CUSTOM_PROGRAM)
            setSkipBatching(true);
        
...
}

No idea what I can do about that. I didn’t even realise it is hard-coded to skip batching if you use a custom shader.

OK, so the batching issue turned out to be fixable, but required changes to the cocos2d-x engine code. I’ve created a pull request, which also shows an example of how custom shaders can be batched.

https://github.com/cocos2d/cocos2d-x/pull/20584

In order for the Spine animation with custom shader to be batched correctly, it will require the above changes to the V4 cocos2d-x engine code.

Example to work with the changes in the above pull request:

auto* program = Device::getInstance()->newProgram(cocos2d::positionTextureColor_vert, customFragShaderSource);
program->setProgramType(ProgramType::CUSTOM_PROGRAM | 123); // 123 = your own user-defined ID for this shader
auto* programState = new (std::nothrow) ProgramState(program);

programState->setBatchId(1);
spineAnim1->setProgramState(programState);
spineAnim2->setProgramState(programState->clone());

// Now we create another clone of ProgramState, but this time we change the batchID to 2
// since this ProgramState will have different uniform values
auto* programState2 = programState->clone();
programState2->setBatchId(2);

// These 2 spin anims use the same uniform values in the custom shader, which are different to the ProgramState uniform values used in spineAnim1 and spineAnim2
spineAnim3->setProgramState(programState2);
spineAnim4->setProgramState(programState2->clone());

Some updates to the spine runtime code I posted earlier:
SkeletonBatch.h (3.7 KB) SkeletonBatch.cpp (10.2 KB)