読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

グレースケールのアニメーション表示をしてみる

今回のエンジニアブログを担当する大原です。

演出部分を作っていく中で、色付きのキャラクタからグレーに向かってアニメーションさせたいときがあります。
Cocos2d-xで簡単に実装するのであれば、色付きの画像を奥にグレースケールの画像を手前に用意することで、グレースケールを透過させるアニメーションを実装するのが一番簡単な方法です。

しかし、その場合2種類分の画像を用意しなければならず、画面全体を覆うようなテクスチャであれば、容量を圧迫します。
そこで、Shaderによるグレースケールの変換を行いたいと思います。

シェーダーのコーディング

まずは、Shaderコードを書きます。
頂点シェーダーはとても単純なものです。

attribute vec4 a_position;  
attribute vec2 a_texCoord;  
attribute vec4 a_color;  
   
#ifdef GL_ES  
varying lowp vec4 v_fragmentColor;  
varying mediump vec2 v_texCoord;  
#else  
varying vec4 v_fragmentColor;  
varying vec2 v_texCoord;  
#endif  
   
void main() {  
    gl_Position = CC_PMatrix * a_position;  
    v_fragmentColor = a_color;  
    v_texCoord = a_texCoord;  
}  

本番は、フラグメントシェーダからです。

#ifdef GL_ES  
precision lowp float;  
#endif  
   
// Input  
varying vec4        v_fragmentColor;  
varying vec2        v_texCoord;  
uniform sampler2D   u_texture;  
  
// Gray  
const float gRed    = 0.298912;  
const float gGreen  = 0.586611;  
const float gBlue   = 0.114478;  
const vec3  gScale  = vec3(gRed, gGreen, gBlue);  
  
uniform float u_Rate;  
  
void main()  
{  
    vec4  baseColor = texture2D(u_texture, v_texCoord);  
    float grayColor = dot(baseColor.rgb, gScale);  
      
    // Changes colors.  
    vec4 ansColor = vec4(vec3(grayColor), baseColor.a);  
    vec4 blendColor = vec4(baseColor.rgb * u_Rate,1.0);  
   
    ansColor = ansColor * (1.0 - u_Rate);  
    ansColor = ansColor + blendColor;  
      
    gl_FragColor = ansColor * v_fragmentColor.a;  
}  

作成した
・shaders/glayscale.vsh
・shaders/glayscale.fsh
はResource/shadersに配置してください。

Cocos2d-xのプロジェクト側の対応

いつものプロジェクトファイルを作るときに自動生成される
HelloWorldクラスにシェーダに追加します。

bool HelloWorld::init()  
{  
    if ( !Layer::init() )  
    {  
        return false;  
    }  
      
    Size visibleSize = Director::getInstance()->getVisibleSize();  
    Vec2 origin = Director::getInstance()->getVisibleOrigin();  
  
    /////////////////////////////  
    // 3. add your codes below...  
  
    // add a label shows "Hello World"  
    // create and initialize a label  
      
    auto label = LabelTTF::create("Hello World", "Arial", 24);  
      
    // position the label on the center of the screen  
    label->setPosition(Vec2(origin.x + visibleSize.width/2,  
                            origin.y + visibleSize.height - label->getContentSize().height));  
  
    // add the label as a child to this layer  
    this->addChild(label, 1);  
  
    // add "HelloWorld" splash screen"  
    auto sprite = Sprite::create("HelloWorld.png");  
  
    // position the sprite on the center of the screen  
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));  

シェーダの読み込み処理を追加します。
今回はCocos2d-x v3系で書いた場合です。
Cocos2d-x v2.2で書いた場合をコメントで記載しておきます。

  
    //=======================  
    // シェーダーの設定  
    //=======================  
      
    //ver2.2  
    //sprite->setShaderProgram(new CCGLProgram());  
    sprite->setGLProgram(new GLProgram());  
  
    //CCGLProgram *shader = sprite->getShaderProgram();  
    this->m_Shader = sprite->getGLProgram();  
    this->m_Shader->retain();  
      
    //shader->initWithVertexShaderFilename("shaders/glayscale.vsh", "shaders/glayscale.fsh");  
    this->m_Shader->initWithFilenames("shaders/glayscale.vsh",  "shaders/glayscale.fsh");  
      
    //shader->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position);  
    this->m_Shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION,GLProgram::VERTEX_ATTRIB_POSITION);  
      
    //shader->addAttribute(kCCAttributeNameColor, kCCVertexAttrib_Color);  
    this->m_Shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR,GLProgram::VERTEX_ATTRIB_COLOR);  
      
    //shader->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords);  
    this->m_Shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD,GLProgram::VERTEX_ATTRIB_TEX_COORD);  
  
    this->m_Shader->link();  
    this->m_Shader->updateUniforms();  
      
    // add the sprite as a child to this layer  
    this->addChild(sprite, 0);  
  
    this->m_grayRate = 1.0f;  
      
    //グレーに向かってアニメーションさせる  
    this->GrayChange();  
      
    return true;  
}  

色付きからグレースケールにアニメーションするため、
時間毎にグレーをどれ位適用するかを送っています。

/**  
 * グレーに向かって変更する  
 */  
void HelloWorld::GrayChange()  
{  
    if(this->m_grayRate > 0.0f)  
    {  
        this->m_grayRate -= 0.1f;  
        this->runAction(Sequence::create(DelayTime::create(0.5f),CallFunc::create(CC_CALLBACK_0(HelloWorld::GrayChange,this)), NULL));  
    }  
    else  
    {  
        this->m_grayRate = 0.0f;  
    }  
      
    //これからuniformの変数を送りますのメソッド  
    this->m_Shader->updateUniforms();  
    //グレーの合成する適用率をシェーダーのソースに送ります。  
    this->m_Shader->setUniformLocationWith1f(this->m_Shader->getUniformLocationForName("u_Rate"), this->m_grayRate);  
}  

アニメーションのスクリーンショット
色付きから
grayscale_color

グレースケールに
grayscale_gray

これで色付きからグレースケールにアニメーションする動きが確認できたのではないでしょうか。