マスク処理について | GCREST_engineerのブログ

GCREST_engineerのブログ

ブログの説明を入力します。

マスク処理とは、画像の一部を隠すための加工処理のことを指します。
今回はcocos2dxでマスク処理の方法について書きます。

対象画像
対象画像
マスク画像
マスク画像


マスク画像に従って、画像の一部を隠したい。
暗いところ(アルファ値が小さい)は消して、
白いところ(アルファ値が大きい)は見せる。



ClippingNodeの場合

描画領域をマスクしたい場合、最初に思いつくのはClippingNodeですね。

auto sprMonster = Sprite::create("images/alpha_mask/monster.png");
auto sprMask = Sprite::create("images/alpha_mask/mask.png");

auto clippingNode = ClippingNode::create();
clippingNode->setStencil(sprMask);
clippingNode->setAlphaThreshold(0.01f);
clippingNode->addChild(sprMonster);
this->addChild(clippingNode);

表示結果


setAlphaThresholdで設定したアルファ値を境に切れたようにマスクされました。
ClippingNodeは内部ではステンシルバッファを使用しているので、ピクセル単位でみると描画するかしないかの2択になってしまいます。
そもそもClippingNodeはその名の通りクリッピングするので、マスク画像のアルファ値がゆっくり変化する場合あまり適した使い方ではないと言えます。

やりたいことは、マスク画像のアルファ値を対象の画像に合成できればいいわけです。これをアルファマップの合成と言います。



そこで2つの方法を紹介します。


アルファマップの合成:BlendFunc+RenderTextureを使う方法

BlendFuncを使ってみます。

マスク画像のスプライトに以下の設定をします。
SourceをGL_ZERO
DestinationをGL_SRC_ALPHA

auto sprMonster = Sprite::create("images/alpha_mask/monster.png");
this->addChild(sprMonster);

auto sprMask = Sprite::create("images/alpha_mask/mask.png");
sprMask->setBlendFunc({GL_ZERO, GL_SRC_ALPHA});

sprMonster->addChild(sprMask);

表示結果はこのようになりました。



アルファ値によってRGB値も0になって黒くなってしまいました。
それをRenderTextureに書き込むようにしてみます。

auto sprMonster = Sprite::create("images/alpha_mask/monster.png");

auto sprMask = Sprite::create("images/alpha_mask/mask.png");
sprMask->setBlendFunc({GL_ZERO, GL_SRC_ALPHA});

auto imgSize = sprMonster->getContentSize();
auto renderTex = RenderTexture::create(static_cast(imgSize.width), static_cast(imgSize.height));
renderTex->beginWithClear(0, 0, 0, 0);
{
sprMonster->visit();
sprMask->visit();
}
renderTex->end();

this->addChild(renderTex);

表示結果はこんな感じになりました。



意図通りなめらかにマスクできました。

ポイントは、RenderTextureを使うところです。
BlendFuncを設定して描画したものをRenderTextureに書き込んでスプライトとして使います。
こうすればBlendFuncの設定に従って計算されたアルファ値が最終結果にならずスプライトのアルファ値として使用できるのです。


アルファマップの合成:シェーダを使う方法

もう一つはシェーダを使う方法です。
マスク画像のアルファ値を反映させてなめらかにフェードさせたい場合、アルファマップをシェーダで合成します。

シェーダにアルファマップを渡します。
auto sprMask = Sprite::create("images/alpha_mask/mask.png");        

glProgramState->setUniformTexture("u_alphaMapTexture", sprMask->getTexture());

シェーダはこちら。
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform sampler2D u_alphaMapTexture;

void main()
{
vec4 alphaMap = texture2D(u_alphaMapTexture, v_texCoord);
vec4 texColor = texture2D(CC_Texture0, v_texCoord);
gl_FragColor = v_fragmentColor * vec4(texColor.x, texColor.y, texColor.z, texColor.w * alphaMap.w);
};

アルファマップのカラーを取得してアルファ値だけを乗算で合成します。



BlendFunc+RenderTextureの方法と同じ表示結果を得ることができました。

特定の領域だけ適用させたい場合、uniformでアルファマップの位置やサイズを渡して、領域判定すればいいです。

auto backContSize = sprMonster->getTexture()->getContentSize();
auto maskContSize = sprMask->getTexture()->getContentSize();
auto maskPosition = sprMask->getPosition();
Vec2 maskSize(maskContSize.width / backContSize.width, maskContSize.height / backContSize.height);
glProgramState->setUniformVec2("u_maskSize", maskSize);
glProgramState->setUniformVec2("u_maskPos", Vec2(maskPosition.x / backContSize.width, 1.0f - maskPosition.y / backContSize.height));

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform sampler2D u_alphaMapTexture;
uniform vec2 u_maskSize;
uniform vec2 u_maskPos;

void main()
{
vec4 alphaMap;
alphaMap.w = 0.0;
if (!(v_texCoord.x < u_maskPos.x
|| v_texCoord.y < u_maskPos.y
|| v_texCoord.x > u_maskPos.x + u_maskSize.x
|| v_texCoord.y > u_maskPos.y + u_maskSize.y)
) {
vec2 maskCoord = vec2((v_texCoord.x - u_maskPos.x) / u_maskSize.x,
(v_texCoord.y - u_maskPos.y) / u_maskSize.y);
alphaMap = texture2D(u_alphaMapTexture, maskCoord);
}
vec4 texColor = texture2D(CC_Texture0, v_texCoord);
gl_FragColor = v_fragmentColor * vec4(texColor.x, texColor.y, texColor.z, texColor.w * alphaMap.w);
};

アルファマップの領域外なら透過、領域内ならアルファマップのアルファ値を取得して描画します。


まとめ

マスク処理でいくつかの方法を紹介しました。

・ClippingNode
・アルファマップの合成
  ・BlendFuncとRenderTextureを使う方法
  ・シェーダを使う方法

ClippingNodeはクッキリと画像をマスクしたい場合に使えます。
なめらかにマスクしたい場合は、アルファマップの合成を行います。
BlendFunc+RenderTextureの方法を使えば可能ですが、RenderTexture分のメモリが必要になります。
シェーダを使う方法であれば、RenderTexture分のメモリなしで同じ効果が得られます。
ただ、RenderTextureを使えば、画面の一部をキャプチャして"画面の領域"に対してマスクをかけることができます。
どのようにマスクをかけたいかでそれぞれの方法を使い分けていきましょう。

"なめらかなマスク"によって表現の幅が広がります。
表現の研究というとUIチームやデザイナーの仕事じゃないの?と思われるかもしれませんが、
"ものづくり"ではそんな垣根にこだわる必要はないと考えています。
弊社では各チーム連携して、いいものができるよう常に心がけています。
一緒に"ものづくり"ができる仲間、募集しています。