日記

cocos2d-x v3.0 シェーダを使ってみる

シェーダなんて今まで触ったことないような私ですが、
使ってみたくなったので挑戦してみました。
ちゃんと理解してない状態で書いてるんで、間違ったこと言ってるかもしれません。
こんな感じで書けば一応動くんだなぁ程度の気持ちでご覧ください。

今回やってみるのはモザイクです。
OpenGLのシェーダはGLSLという言語で書くそうです。
そして、頂点シェーダとフラブメントシェーダの2つのファイルが必要です。
細かいことはあんまりよくわかってないですけど、適当にググりながら試行錯誤したら↓のコードが出来上がりました。

mosaic.vsh

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

mosaic.fsh

varying vec2        v_texCoord;
uniform sampler2D   u_texture;
uniform vec2        u_texSize;
uniform int         u_mosaicLevel;

void main() {
	vec4 color;
	if(u_mosaicLevel > 1){
    		vec2 target;
    		target.x = float(int(v_texCoord.x / float(u_mosaicLevel) * u_texSize.x + 0.5)) * float(u_mosaicLevel) / u_texSize.x;
    		target.y = float(int(v_texCoord.y / float(u_mosaicLevel) * u_texSize.y + 0.5)) * float(u_mosaicLevel) / u_texSize.y;
		color = texture2D(u_texture, target);
    	}else{
    		color = texture2D(u_texture, v_texCoord);
	}
    	gl_FragColor = color;
}

cocos2d-xで使う部分。

#define VISIBLE_SIZE	cocos2d::Director::getInstance()->getVisibleSize()
auto sp = Sprite::create("hoge.png");
sp->setPosition(VISIBLE_SIZE.width / 2, VISIBLE_SIZE.height / 2);

auto shader = new GLProgram();
shader->initWithFilenames("mosaic.vsh", "mosaic.fsh");
shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS);
shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
shader->link();
shader->updateUniforms();
shader->setUniformLocationWith1i(shader->getUniformLocationForName("u_mosaicLevel"), 10);
shader->setUniformLocationWith2f(shader->getUniformLocationForName("u_texSize"), VISIBLE_SIZE.width, VISIBLE_SIZE.height);
sp->setShaderProgram(shader);

this->addChild(sp);

とりあえずこれで画像にモザイクをかけることができました。
細かい解説できるほど理解してないので、詰まった部分だけざっくりと
mosaic.vshの

gl_Position = CC_PMatrix * a_position;

という部分ですが、調べてみると”CC_PMatrix”ではなく、”CC_MVPMatrix”を使っていました。
しかし、それだとスプライトの位置がずれちゃったのでこうしたらうまくいきました。具体的にどういう処理をしているか理解してないので、わかったら追記します。

mosaic.fshでモザイクの処理を書いています。
v_texCoordには、テクスチャの描画する座標が入っています。

color = texture2D(u_texture, v_texCoord);

でテクスチャからその座標の色を取得し、それを返すことで描画されるっぽいです。(たぶん)
この座標を、ある数で割って、四捨五入して、同じ数をかけてあげればモザイクになるという寸法です。(わかりにくい)
四捨五入は0.5を足してからintにキャストしちゃえばOKです。別に四捨五入じゃなくて切り捨てでもいいんですが、なんとなく。

target.x = float(int(v_texCoord.x / float(u_mosaicLevel) * u_texSize.x + 0.5)) * float(u_mosaicLevel) / u_texSize.x;

この部分、floatでキャストしまくってますね。
Windows上では、こんなことする必要なかったんですが、Androidではこうしないと動きません。
スマホだとGLSL ESを使っていて、それだと暗黙変換ができないらしいです。

cocos2d-xでこれを使うには、まず、mosaic.vshとmosaic.fshをResourceフォルダの中に入れます。
そして、GLProgramクラスで設定を読み込んで、それをスプライトにセットすれば使えます。
GLSLで書いたプログラムは単なるテキストファイルで、実行時にコンパイルされるらしいです。(あってる?)

shader->initWithFilenames("mosaic.vsh", "mosaic.fsh");

の部分で読み込んでます。中身の文字列を読めればいいので、拡張子とか変えても大丈夫そうです。
FileUtilesのgetData関数を使ってデータの読み込みをしているので、以前の記事で書いた方法を使えば暗号化もできます。
また、最終的にデータを読み込んだ後は
GLProgram::initWithByteArrays(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray)
に値を渡しているので、c++中にGLSLのコードを直接ハードコーディングしちゃっても動きそうですね。やりませんけど。

shader->setUniformLocationWith1i(shader->getUniformLocationForName("u_mosaicLevel"), 10);

ここでGLSL中の変数に値を渡しています。このコードでは”u_mosaicLevel”の値を変えることでモザイクの粗さをコントロールできます。
今回はsetUniformLocationWith1i()とsetUniformLocationWith2f()を使っていますが、似たものとして
*1i ~ *4i
*1f ~ *4f
があります。(名前長いんで省略)これらはそれぞれ
int, ivec2, ivec3, ivec4
float, vec2, vec3, vec4
に対応しているっぽいです。
この値は、後から変更することが出来ます。
その際は、”updateUniforms()”をセットで使う必要があります。


シェーダを使えばラスタースクロールとか色々出来そうで楽しそうですね。

2014/03/30



CAPTCHA