图片/按钮变灰变暗变黑白是游戏UI常用的操作

之前项目使用多套资源实现

如一个按钮对应3张图,普通/选中/禁用状态

然后使用下列方法切换

setEnabled/setBright

selected/unselected

 

然后如果对象不是按钮而是一般的sprite

或包含sprite的node

并没有以上的方法可以调用

同时也出于减少资源的考虑

新项目依靠shader实现显示效果切换

 

程序原理如下

CCNode有以下3个接口

(1,2)setGLProgram/setShaderProgram

(3)setGLProgramState

接口本质都是设置CCNode的成员变量_glProgramState

从而实现显示效果的切换(包括黑白/彩色切换等等)

前两个接口一样,通过GLProgram获取state,是设置shader的主要方法

第三个接口更直接一些直接设置state

 

通过GLProgram设置shader更方便

因为cocos预设了许多可以直接用的效果

这些效果定义在CCGLProgram.h

通过CCGLProgramCache可以使用这些效果

比如我们代码里用到的(变为正常彩色)

auto programNormal = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP);

若需要自行设置shader(变为黑白灰色)

auto programGray = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert,
        ccUIGrayScale_frag);

实际上从CCGLProgramCache获取的GLProgram就是通过

createWithByteArrays/initWithByteArrays预先加载好的

再进一步看其中的两个参数

ccPositionTextureColor_noMVP_vert
ccUIGrayScale_frag

在目录cocos\renderer中我们可以看到

ccShader_PositionTextureColor_noMVP.vert

打开文件可以看到定义如下

const char* ccPositionTextureColor_noMVP_vert = STRINGIFY(
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;

\n#ifdef GL_ES\n
varying lowp vec4 v_fragmentColor;
varying mediump vec2 v_texCoord;
\n#else\n
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
\n#endif\n

void main()
{
    gl_Position = CC_PMatrix * a_position;
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
}
);

另外,在目录cocos\ui\shaders中可以看到

ccShader_grayscale.frag

const char* ccUIGrayScale_frag = STRINGIFY(

\n#ifdef GL_ES\n
\nprecision mediump float;\n
\n#endif\n
\n\n
\nvarying vec4 v_fragmentColor;\n
\nvarying vec2 v_texCoord;\n
\n\n
\nvoid main(void)\n
\n{\n
    \nvec4 c = texture2D(CC_Texture0, v_texCoord);\n
    \ngl_FragColor.xyz = vec3(0.2126*c.r + 0.7152*c.g + 0.0722*c.b);\n
    \ngl_FragColor.w = c.w;\n
\n}\n
);

具体的代码由glsg编写在外部

也可以自己手写在游戏内部

原理就是这样

 

具体的变灰操作在cocos2d-x3.7中更加方便

新加入了GLProgram ShaderUIGrayScale

可以直接从Cache中get

 

非常有趣的是

UIScale9Sprite定义了setState方法切换元素的彩色/灰色

我们的代码基本都是从那里学的

但不知道为什么cocos只在UIScale9Sprite中写了这个接口

CCNode中却没有这个接口

不过看过本文,现在变灰已经不是问题了

 

shader非常强大,变灰只是其中的一个功能

更多的还需要继续不断学习!

 

 

以下代码使用cocos2d-x3.5

点击按钮调用clickCallback切换 彩色/灰色

#include "HelloWorldScene.h"
#include "../cocos/ui/shaders/UIShaders.h"
USING_NS_CC;

Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

HelloWorld::HelloWorld()
    :m_pTitle(nullptr),
    m_pGround(nullptr),
    m_pTest(nullptr),
    m_pButton(nullptr)
{}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    /////////////////////////////
    // 2. add a menu item with "X" image, which is clicked to quit the program
    //    you may modify it.

    // add a "close" icon to exit the progress. it's an autorelease object
    auto closeItem = MenuItemImage::create(
                                           "CloseNormal.png",
                                           "CloseSelected.png",
                                           CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
    
    closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
                                origin.y + closeItem->getContentSize().height/2));


    m_pButton = MenuItemImage::create(
        "normal.png",
        "selected.png",
        CC_CALLBACK_1(HelloWorld::clickCallback, this));
    m_pButton->setPosition(Vec2(origin.x + m_pButton->getContentSize().width / 2,
        origin.y + m_pButton->getContentSize().height / 2));

    // create menu, it's an autorelease object
    auto menu = Menu::create(closeItem, m_pButton, NULL);
    menu->setPosition(Vec2::ZERO);
    this->addChild(menu, 1);

    /////////////////////////////
    // 3. add your codes below...

    // add a label shows "Hello World"
    // create and initialize a label
    
    m_pTitle = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
    
    // position the label on the center of the screen
    m_pTitle->setPosition(Vec2(origin.x + visibleSize.width / 2,
        visibleSize.height / 2 + origin.y));

    // add the label as a child to this layer
    this->addChild(m_pTitle);

    // add "HelloWorld" splash screen"
    m_pGround = Sprite::create("Test.jpg");

    // position the sprite on the center of the screen
    m_pGround->setPosition(Vec2(visibleSize.width / 3 + origin.x, visibleSize.height / 2 + origin.y));

    // add the sprite as a child to this layer
    this->addChild(m_pGround);


    m_pTest = Sprite::create("Test.jpg");
    m_pTest->setPosition(Vec2(visibleSize.width*2 / 3 + origin.x, visibleSize.height / 2 + origin.y));
    this->addChild(m_pTest);
    
    return true;
}

void HelloWorld::clickCallback(Ref* pSender)
{
    static bool isGray = false;
    isGray = !isGray;

    auto programGray = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert,
        ccUIGrayScale_frag);
    auto programNormal = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP);
    m_pTest->setGLProgram(isGray ? programGray : programNormal);
}

void HelloWorld::menuCloseCallback(Ref* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
    MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
    return;
#endif

    Director::getInstance()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}