roguish

Bare-Bones Shader Example in Cocos2d-x v.3.2

This is a bare-bones example of a shader in Cocos2d-x v.3.2. It swaps the color channels of the texture image: The Blue channel is drawn in the Red channel, Green in Blue, and Red in Green. See the results here: BasicShaderScreenshot.jpg

Changes that are made to a baseline Cocos2d-x project:
— Add content to HelloWorld.cpp
— Add a shader file (short text file), to the Resources folder of your project

Source files here:
https://github.com/ElliotMebane/BasicShader

HelloWorld.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Leave the visibleSize and origin variables in place in the init method of the default project. Add the following to the init method.
// Put an unmodified sprite on the left. Put another sprite that's treated with a shader on the right. Scale and place them so they both fit side-by-side. 
 
// Sprite without shader
    Sprite* spriteWithoutShader = Sprite::create("colorMarket1280_rgb.jpg");
    spriteWithoutShader->setScale( ( visibleSize.width / spriteWithoutShader->getContentSize().width ) / 2 );
    spriteWithoutShader->setPosition( Vec2( origin.x + visibleSize.width/2 - spriteWithoutShader->getBoundingBox().size.width/2, origin.y + visibleSize.height / 2 ) );
    this->addChild(spriteWithoutShader);
 
// Sprite with shader applied
    auto spriteWithShader = Sprite::create("colorMarket1280_rgb.jpg");
    spriteWithShader->setScale( ( visibleSize.width / spriteWithShader->getContentSize().width ) / 2 );
    spriteWithShader->setPosition( Vec2( origin.x + visibleSize.width/2 + spriteWithShader->getBoundingBox().size.width/2, origin.y + visibleSize.height / 2 ) );
    this->addChild(spriteWithShader);
 
// Create and apply the shader
// Load the external shader file into the application as a CString cast as a GLchar*. Note that the ChannelSwap.fsh shader file can be found in the Resources/shaders folder of our project. 
    GLchar * fragmentSource = (GLchar*) String::createWithContentsOfFile( FileUtils::getInstance()->fullPathForFilename( "shaders/ChannelSwap.fsh" ).c_str() )->getCString();
// Create a GLProgram with the shader file.
	auto p = GLProgram::createWithByteArrays( ccPositionTextureColor_noMVP_vert, fragmentSource );
// Create a GLProgramState from te GLProgram. 
	auto glProgramState = GLProgramState::getOrCreateWithGLProgram( p );
// Apply the shader to the Sprite.
	spriteWithShader->setGLProgramState( glProgramState );

ChannelSwap.fsh:

1
2
3
4
5
6
7
8
9
10
11
#ifdef GL_ES
precision mediump float;
#endif
 
varying vec2 v_texCoord;
 
void main()
{ 
	vec4 normalColor = texture2D(CC_Texture0, v_texCoord).rgba;
	gl_FragColor = vec4( normalColor.g, normalColor.b, normalColor.r, normalColor.a );
}

This shader is a great candidate for using swizzling to interchange the channels. Read about swizzling here:
https://www.opengl.org/wiki/Data_Type_%28GLSL%29
The alternate shader code in the main method would look like this:

1
gl_FragColor.rgba = texture2D(CC_Texture0, v_texCoord).gbra;

Questions and additional info:
Q: Is the use of sprite->getTexture()->setAntiAliasTexParameters() necessary for shaders?

A: Setting the texture to anti-aliasing causes the texture to be drawn with high-quality. It minimizes banding and blockiness that can happen when an image is drawn from a texture. Texels are sampled from the texture for drawing the pixels of the display image and with this setting display pixels are linearly-interpolated from the nearby texels which creates smooth transitions between the colors and minimizes problems that could happen with image scaling.
The alternative is to use aliased textures with the setAliasTexParameters() method. This setting uses nearest-neighbor texel sampling to draw the display pixels. The result can be a blocky image or noticeable banding.
If neither setting is applied, anti-aliasing (the higher quality setting) is used by default. I’ve tested and confirmed this. I did not apply any alias setting to a sprite then set its scale to 10. On another sprite I called setAntiAliasTexParameters() and set its scale to 10. The colors of both were interpolated smoothly and the images looked identical. In a third test I confirmed that setting the texture aliasing to setAliasTexParameters() causes noticeable blockiness when the image is scaled to 10.
http://www.cocos2d-x.org/reference/native-cpp/V3.2/d0/d6a/classcocos2d_1_1_texture2_d.html#aa2c9bba6d49b335816b36b60a6fcbc36

What’s next?
In the shader used in this example we apply the channel swapping to the default texture – the texture that has been applied to the sprite. Many shaders are designed to receive one or more textures instead of assuming that the default texture should be used.
To pass a texture to a shader, the first step is to declare the variable in your shader (often named “u_texture”), like this:

1
uniform sampler2D u_texture;

Then from HelloWorld.cpp, after glProgramState has been created, pass a reference to the texture to the glProgramState, like this:

1
glProgramState->setUniformTexture( "u_texture", spriteWithShader->getTexture() );

Now that we have a basic sample working we can explore other sample shaders that are included with Cocos2d-x as part of the cpp-tests project, here:
https://github.com/cocos2d/cocos2d-x/tree/v3/tests/cpp-tests/Classes/ShaderTest
Here’s the location of the associated FSH and VSH files (in the Resources/Shaders folder):
https://github.com/cocos2d/cocos2d-x/tree/v3/tests/cpp-tests/Resources/Shaders

Image Sources:
https://www.flickr.com/photos/cimmyt/7611456630
https://www.flickr.com/photos/flahertyb/10361838373

Leave a Reply