LoginSignup
7
8

More than 5 years have passed since last update.

cocos2d-xでETC1のアルファマスクをシェーダを使って実装する

Last updated at Posted at 2015-01-09

ETC1のアルファの抜き方に関する記事はそこそこ存在するのですが
cocos2d-xのやり方が書かれている記事が日本語では存在しませんでした。

まず最初に

アルファマスクを行うにあたり、2種類の方法にたどり着きました。
1つは通常画像と、マスク画像をそれぞれ別に持つこと。
もう1つは通常画像とマスク画像をまとめて1枚の画像に内包してしまうこと。

それぞれ一長一短があります。
・画像を分ける手法
長所:画像を最大サイズ(2048x2048)まで使える
短所:2枚の画像を管理する必要がある

・画像をまとめる手法
長所:管理が楽
短所:半分をマスク画像として扱うため、画像のサイズが最大の半分までしか使えない
(1024x2048 or 2048x1024)

なお、DrawCallについては現在調査中です。
理論上は同一手法で描画していればまとめられるはずなのですが、上記のどちらの手法をとっても
1描画毎に1Callされてしまっているので、どこかのソースを書き換える必要があると思います。

シェーダを毎回作成しているのが問題でした。
最初にシェーダインスタンスを作成し、それを使いまわすことでDrawCallが複数呼ばれることはなくなります。

ファイルを分ける手法の素材

素材はこちらからお借りしました。

ボタン画像:button.pkm
button.png
マスク画像:button_m.pkm
button_m.png

plistは前回と同じものを使用できます。

ファイルを分ける手法

最初にシェーダを書く必要があります。ここで紹介するシェーダは
リソースとして持つのではなく、プログラムに組み込んでコンパイルしてしまう方法をとります。

頂点シェーダ

AlphaMask.vert
const char* AlphaMask_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;
}
);

フラグメントシェーダ

AlphaMask.frag
const char* AlphaMask_frag = STRINGIFY(
\n#ifdef GL_ES\n
precision lowp float;
\n#endif\n

varying vec4 v_fragmentColor;       // 外部のカラー値(setColorとsetOpacityでセットされたもの)
varying vec2 v_texCoord;            // テクスチャーのUV座標
uniform sampler2D mask_texture;     // マスクテクスチャー

void main()
{
    gl_FragColor.rgb = texture2D(CC_Texture0, v_texCoord).rgb;  // RGBはテクスチャーのものを使用
    gl_FragColor.a = texture2D(mask_texture, v_texCoord).r;     // マスク画像の色をアルファとして使用
    gl_FragColor *= v_fragmentColor;                            // 外部で設定したカラーを適応
}
);

シェーダを管理するcppとhも作りましょう。

OriginalShader.cpp
#include "OriginalShader.h"

#define STRINGIFY(A)  #A

// アルファマスク
#include "AlphaMask.vert"
#include "AlphaMask.frag"
OriginalShader.h
#ifndef INCLUDE_ORIGINAL_SHADER_H
#define INCLUDE_ORIGINAL_SHADER_H

// アルファマスク
extern const char* AlphaMask_vert;  // AlphaMask.vert内で定義した文字列変数
extern const char* AlphaMask_frag;  // AlphaMask.frag内で定義した文字列変数

#endif  // INCLUDE_ORIGINAL_SHADER_H

これでシェーダ側のプログラムは終了です。

  • OriginalShader.cpp
  • OriginalShader.h
  • AlphaMask.vert
  • AlphaMask.frag

上記のファイルは同一のディレクトリに入れましょう。


次は、画面に表示するプログラムです。

SceneAlphaMask.cpp
#include "OriginalShader.h"

bool SceneAlphaMask::init()
{
    // レイヤー初期化
    if (!Layer::init())  return false;

    auto director = Director::getInstance();
    Size visibleSize = director->getVisibleSize();

    // ボタン画像を読み込んでスプライトキャッシュを生成
    auto texture = director->getTextureCache()->addImage("button.pkm");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("button.plist", texture);

    // スプライトを作成し、画面中央に配置
    auto sp = Sprite::createWithSpriteFrameName("button00.png");
    sp->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));
    this->addChild(sp);

    // 自前の頂点シェーダとフラグメントシェーダを使ってシェーダを作成
    auto shader = GLProgram::createWithByteArrays(AlphaMask_vert, AlphaMask_frag);  
    auto shader_state = GLProgramState::getOrCreateWithGLProgram(shader);

    // マスク画像を読み込む
    auto mask_texture = director->getTextureCache()->addImage("button_m.pkm");

    // シェーダ内のテクスチャーと実際のテクスチャーを紐付ける
    // 第一引数の文字列はAlphaMask.fragのテクスチャー変数名を指定
    shader_state->setUniformTexture("mask_texture", mask_texture);

    // シェーダをスプライトに登録する
    sp->setGLProgramState(shader_state);
}

以上でアルファマスク画像を使った描画は終わりとなります。
スプライトキャッシュに使用したテクスチャーはスプライトキャッシュを解放すれば一緒に解放されますが
mask_textureは解放されないので、どこかに保持しておき自前で解放しなくてはいけないので注意してください。

画像をまとめる手法の素材

マスク込み画像:button_mw.pkm
button_m.png

plistは同じものを使用できます。

画像をまとめる手法

まずは、シェーダを記述します。
頂点シェーダは変数名以外、AlphaMask.vertと内容はまったく同じですが一応分けておきましょう。

AlphaMaskW.vert
// 変数名が違うので注意
const char* AlphaMaskW_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;
}
);

フラグメントシェーダ

AlphaMaskW.frag
// 変数名が違うので注意
const char* AlphaMaskW_frag = STRINGIFY(
\n#ifdef GL_ES\n
precision lowp float;
\n#endif\n

varying vec4 v_fragmentColor;       // 外部のカラー値(setColorとsetOpacityでセットされたもの)
varying vec2 v_texCoord;            // テクスチャーのUV座標

void main()
{
    // RGBはテクスチャーのものを使用
    gl_FragColor.rgb = texture2D(CC_Texture0, v_texCoord).rgb;
    // 右半分のマスクの色をアルファとして使用
    gl_FragColor.a = texture2D(CC_Texture0, vec2(v_texCoord.x + 0.5, v_texCoord.y)).r;
    // 外部で設定したカラーを適応
    gl_FragColor *= v_fragmentColor;
}
);

OriginalShader.cppとOriginalShader.hにも追記

OriginalShader.cpp
#include "OriginalShader.h"

#define STRINGIFY(A)  #A

// アルファマスク
#include "AlphaMask.vert"
#include "AlphaMask.frag"

// 右半分がアルファマスク
#include "AlphaMaskW.vert"
#include "AlphaMaskW.frag"

OriginalShader.h
#ifndef INCLUDE_ORIGINAL_SHADER_H
#define INCLUDE_ORIGINAL_SHADER_H

// アルファマスク
extern const char* AlphaMask_vert;
extern const char* AlphaMask_frag;

// 画像の右半分がアルファマスク
extern const char* AlphaMaskW_vert;
extern const char* AlphaMaskW_frag;

#endif  // INCLUDE_ORIGINAL_SHADER_H

次は、画面に表示するプログラムです。

SceneAlphaMask.cpp
#include "OriginalShader.h"

bool SceneAlphaMask::init()
{
    // レイヤー初期化
    if (!Layer::init())  return false;

    auto director = Director::getInstance();
    Size visibleSize = director->getVisibleSize();

    // 通常画像とマスク画像を読み込んでスプライトキャッシュを生成
    auto texture = director->getTextureCache()->addImage("button_mw.pkm");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("button.plist", texture);

    // スプライトを作成し、画面中央に配置
    auto sp = Sprite::createWithSpriteFrameName("button00.png");
    sp->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));
    this->addChild(sp);

    // 自前の頂点シェーダとフラグメントシェーダを使ってシェーダを作成
    auto shader = GLProgram::createWithByteArrays(AlphaMaskW_vert, AlphaMaskW_frag);
    auto shader_state = GLProgramState::getOrCreateWithGLProgram(shader);

    // シェーダをスプライトに登録する
    sp->setGLProgramState(shader_state);
}

テクスチャーはスプライトキャッシュを解放すれば一緒に解放されるので手間はかかりません。
ただし最初に述べたように、使用できる画像の幅が1024までに限定されてしまいます。

最後に

以上、2つの方法を紹介しました。
管理の手間を考えると2番目の案を個人的にはお勧めします。
次はCocos Studioの検証に入るため、DrawCallの問題解決は当分後になります。
誰か教えてくれると嬉しいです!!!
次回の記事は、マスク画像を簡単に作るツールをC#で作りましたのでそちらの紹介をしたいと思います。
それではまた次回!!!

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8