WonderPlanet DEVELOPER BLOG

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

シェーダーでスプライトをひねらせよう

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

最近はシェーダープログラミングを覚えようと頑張っています。
あるアニメで出てきた演出をシェーダーで実現できないかと思い、
今回はひねる演出をシェーダーで書いていきたいと思います。

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

頂点シェーダーは前回のグレースケールと同じなので、割愛します。

フラグメントシェーダーについて書いていきます。

#ifdef GL_ES  
precision lowp float;  
#endif  
   
// Input  
varying vec4        v_fragmentColor;  
varying vec2        v_texCoord;  
  
uniform vec2 v_Center;//0.0~1.0までの間  
uniform float f_Radius;//0.0~1.0までの間  
uniform float f_SpiralStrength;//回転数  
  
void main() {  
    //--------------------------  
    //中心点からの距離が回転半径を超えたら  
    //--------------------------  
    vec2 pos =  v_texCoord - v_Center;  
    float len = length(pos);  
    if( len >= f_Radius )  
    {  
        gl_FragColor = texture2D(CC_Texture0,v_texCoord);  
        return;  
    }  
  
    //--------------------------  
    //色を取ってくる場所を計算する  
    //--------------------------  
    float spiral = min( max( 1.0 - ( len / f_Radius),0.0),1.0)  * f_SpiralStrength;  
      
    float x = pos.x * cos(spiral) - pos.y * sin(spiral); // x位置を回転  
    float y = pos.x * sin(spiral) + pos.y * cos(spiral); // y位置を回転  
      
    vec2 retPos = (vec2( x, y ) + v_Center );  
    vec4 color = texture2D(CC_Texture0, retPos );  
      
    gl_FragColor = color;  
}  

ひねる回数を計算しているのが
float spiral = min( max( 1.0 - ( len / f_Radius),0.0),1.0) * f_SpiralStrength;
の部分です。
螺旋の説明
上記画像の描画する点を取得する箇所は青矢印のように外に行くほどひねる距離が伸びていきます。
その為、中心点から描画しようとする点までの距離を回転数にかけています。

そして、回転数から実際の点を取り出しているのが、
float x = pos.x * cos(spiral) - pos.y * sin(spiral); // x位置を回転
float y = pos.x * sin(spiral) + pos.y * cos(spiral); // y位置を回転
になります。
回転数から座標を算出することで、取るべき色を割り出します。

プロジェクトへの組み込み

シェーダーが完成したので、実際にスプライトに適用してみたいと思います。

今回はより回転してることがわかりやすいように以下の画像を使いました。
warning

まずは専用のスプライトを作成します。

#ifndef __blog__SpiralSprite__  
#define __blog__SpiralSprite__  
  
#include "cocos2d.h"  
  
/**  
 * ひねるスプライト  
 */  
class SpiralSprite : public cocos2d::Sprite  
{  
    CREATE_FUNC(SpiralSprite);  
  
    //ひねり回転数加算値  
    CC_SYNTHESIZE(float, m_TwistAdd, TwistAdd);  
    //ひねり回転数終了値  
    CC_SYNTHESIZE(float, m_TwistEnd, TwistEnd);  
    //ひねり回転数開始値  
    CC_SYNTHESIZE(float, m_TwistStart, TwistStart);  
    //中心点からのひねる距離  
    CC_SYNTHESIZE(float, m_TwistRange, TwistRange);  
    //ひねる中心座標レート(UV値)  
    CC_SYNTHESIZE(cocos2d::Vec2, m_CenterRate, CenterRate);  
protected:  
    //ひねり回転  
    float m_Twist;  
      
    cocos2d::GLProgram *m_Shader;  
    cocos2d::GLProgramState *m_ShaderState;  
      
    static bool m_isMakeShader;  
public:  
    SpiralSprite();  
    virtual ~SpiralSprite();  
    /**  
     * ひねるスプライト生成  
     */  
    static SpiralSprite* create(const std::string& filename);  
      
    /**  
     * 回転の開始  
     */  
    virtual void OnStartTwist(float startTwist,float endTwist,float addValue);  
      
    /**  
     * アップデート処理  
     */  
    virtual void update(float delta);  
      
    /**  
     * シェーダの生成  
     */  
    static void makeAndEntryShader();  
      
    static cocos2d::GLProgram* getSpiralShader();  
      
};  
#endif /* defined(__blog__SpiralSprite__) */  
#include "SpiralSprite.h"  
  
USING_NS_CC;  
  
bool SpiralSprite::m_isMakeShader = false;  
  
void SpiralSprite::makeAndEntryShader()  
{  
    auto shader = GLProgram::createWithFilenames("shaders/spiral.vsh",  "shaders/spiral.fsh");  
    GLProgramCache::getInstance()->addGLProgram(shader, "SpiralShader");  
    SpiralSprite::m_isMakeShader = true;  
}  
GLProgram* SpiralSprite::getSpiralShader()  
{  
    return GLProgramCache::getInstance()->getGLProgram("SpiralShader");  
}  
  
SpiralSprite::SpiralSprite()  
:m_Shader(nullptr)  
,m_ShaderState(nullptr)  
{  
}  
SpiralSprite::~SpiralSprite()  
{  
}  
SpiralSprite* SpiralSprite::create(const std::string& filename)  
{  
    SpiralSprite *sprite = new (std::nothrow) SpiralSprite();  
    if (sprite && sprite->initWithFile(filename))  
    {  
        sprite->autorelease();  
        return sprite;  
    }  
    CC_SAFE_DELETE(sprite);  
    return nullptr;  
}  
  
/**  
 * 回転の開始  
 */  
void SpiralSprite::OnStartTwist(float startTwist,float endTwist,float addValue)  
{  
    if(SpiralSprite::m_isMakeShader == false)SpiralSprite::makeAndEntryShader();  
  
    this->setGLProgram(GLProgramCache::getInstance()->getGLProgram("SpiralShader"));  
    auto shader = GLProgram::createWithFilenames("shaders/spiral.vsh",  "shaders/spiral.fsh");  
    this->setGLProgram(shader);  
      
    //設定値  
    this->m_TwistStart = startTwist;  
    this->m_Twist = startTwist;  
    this->m_TwistEnd = endTwist;  
    this->m_TwistAdd = addValue;  
      
    this->scheduleUpdate();  
}  
/**  
 * アップデート処理  
 */  
void SpiralSprite::update(float delta)  
{  
      
    this->m_Twist += this->m_TwistAdd * delta;  
    if(  
       (this->m_TwistEnd < this->m_Twist && this->m_TwistAdd >= 0) ||  
       (this->m_TwistEnd > this->m_Twist && this->m_TwistAdd < 0)  
       )  
    {  
        //アップデート停止  
        this->unscheduleUpdate();  
          
        this->m_Twist = this->m_TwistEnd;  
    }  
    this->getGLProgramState()->setUniformFloat("f_Radius", this->m_TwistRange);  
    this->getGLProgramState()->setUniformFloat("f_SpiralStrength", this->m_Twist);  
    this->getGLProgramState()->setUniformVec2("v_Center", this->m_CenterRate);  
      
    Sprite::update(delta);  
}  

今回作成したスプライトでカッコイイ演出ができると思います。

iOS Simulator Screen Shot 2015.04.28 10.21.34

まとめ

今回自分が作成していてハマった箇所がシェーダー内の座標の扱いです。
テクスチャー内の中心を指定する場合、x=0.5 y=0.5を指定しなければいけませんでした。
256*128の画像を用意していたので、半分のx=128,y=64をしてしまい、思った通りの動作ができませんでした。
かっこいい演出を考えるのはデザイナだけの仕事と思われがちですが、エンジニアも出来る事を増やさないと
ゲームを作る時に演出のレパートリーが少なくなります。
そして、いきなり実務では思いつかないので思いついた演出を溜めていけたらと思っています。