GameMaker:Studio Shader Reverse Image(反転・回転)

 shader   ReverseImage   GLSL_ES 

Vertex Shader で何かやってみようシリーズ。表示画像の反転など

頂点情報を弄って表示している画像を反転したり、90 度単位で画像を左右に回転させる。

イベントを一つのオブジェクトへまとめ、

    1. Create
    2. Draw
    3. DrawGUI

上記三つのイベントへ収まるように作った。そして無駄な変数定義を全て排除。

なるべくユーザ定義関数化し、コードの使い回しが簡単。

参考動作:http://prester.org/html5/gms_shader_reverseimage/

キーボードの H と V で画像を反転、L と R キーで左右回転。ESC キーでリセット。Space キーで描画切り替え( Shader を利用しない )

要 WebGL、Google Chrome/FireFox 等で動作を確認。

HTML5( WebGL ) と Windows PC で微妙に描画結果が異なる場合があります。

シェーダー名:SH_ReverseImage

Vertex Shader

attribute vec3 in_Position;
attribute vec2 in_TextureCoord;

varying vec2 v_texcoord;

uniform float u_xpos;
uniform float u_ypos;
uniform float u_flag;
// User-Defined Functions
vec2 Normal()            {return in_Position.xy;}
vec2 Normal_RotateLeft() {return vec2(in_Position.y          , u_xpos - in_Position.x);}
vec2 Normal_RotateRight(){return vec2(u_ypos - in_Position.y , in_Position.x);}
vec2 Normal_Vertical()   {return vec2(u_xpos - in_Position.x , u_ypos - in_Position.y);}

vec2 Flip_Horizontal()   {return vec2(u_xpos - in_Position.x , in_Position.y);}
vec2 Flip_RotateLeft()   {return in_Position.yx;}
vec2 Flip_RotateRight()  {return vec2(u_ypos - in_Position.y , u_xpos - in_Position.x);}
vec2 Flip_Vertical()     {return vec2(in_Position.x          , u_ypos - in_Position.y);}

void main()
{
    vec2 ok;
        if      (u_flag == 0.0) {ok = vec2(Normal());}
        else if (u_flag == 1.0) {ok = vec2(Normal_RotateLeft());}
        else if (u_flag == 2.0) {ok = vec2(Normal_RotateRight());}
        else if (u_flag == 3.0) {ok = vec2(Normal_Vertical());}
        else if (u_flag == 4.0) {ok = vec2(Flip_Horizontal());}
        else if (u_flag == 5.0) {ok = vec2(Flip_RotateLeft());}
        else if (u_flag == 6.0) {ok = vec2(Flip_RotateRight());}
        else if (u_flag == 7.0) {ok = vec2(Flip_Vertical());}

    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(ok, in_Position.z, 1.0);
    v_texcoord  = in_TextureCoord;
}

Vertex Shader では頂点情報を扱っています。

GLSL ES は三点ポリを扱うため、四角形も三点ポリの組み合わせで表現されています。この方法で作られた四角形はそれぞれのポリゴンで二つの頂点が重なるため、扱うべき頂点情報は二次元の場合結局四つで済みます。

gl_Position は GLSL ES の組み込み変数、行列と頂点を掛け合わせることで頂点のビュー変換/射影変換の位置が格納されます。

行列の積は原則として行・列の要素数が一致しなければ掛け算できない。この時、掛け合わせる要素の順番をあえて入れ替えることによってビュー変換の結果を操作できます。

gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(
						 in_Position.x
						 in_Position.y
						 in_Position.z
						 1.0
						);

今回はベクトルの要素 .xyz の並びを .yxz などとすることで変換結果を操作しています。

単純な方法ですが、応用すれば全八パターン作ることができ、反転だけでなく九〇度単位で左右に回転させることもできます。

Fragment Shader

precision mediump float;
varying vec2 v_texcoord;

void main()
{
    gl_FragColor = texture2D(gm_BaseTexture, v_texcoord);
}

ユーザ定義関数 Init_Shader_Reverse

///Init_Shader_Reverse();
draw_set_font(font0);

    if (!shader_is_compiled(SH_ReverseImage)) {
        show_message("Your hardware doesn't support shader - SH_ReverseImage");
    }
    else{
        /* for Debug */
        Shader_Enabled = true;
        
        /* for Fragment Shader, uniform variables */
        //                      ~~~~~~~
        uni_xpos  = shader_get_uniform(SH_ReverseImage , "u_xpos");
        var_xpos  = x+x;// 座標の正規化
        
        uni_ypos  = shader_get_uniform(SH_ReverseImage , "u_ypos");
        var_ypos  = y+y+(sprite_get_height(rb)/2);// 座標の正規化+ Y 位置の微調整
        
        uni_flag  = shader_get_uniform(SH_ReverseImage , "u_flag");
        var_flag  = 0;
        
        /* Instance Variables */
        Deg       = 0;
        Flip      = false;
    };

ユーザ定義関数 Draw_ReverseImage

///Draw_ReverseImage();
    if Shader_Enabled {
        shader_set(SH_ReverseImage);
            shader_set_uniform_f(uni_xpos , var_xpos);
            shader_set_uniform_f(uni_ypos , var_ypos);
            shader_set_uniform_f(uni_flag , var_flag);
            draw_self();
        shader_reset();
    }
    else{
        draw_self();
    };

ユーザ定義関数 DrawGUI_CheckKeys

///DrawGUI_CheckKeys();
if keyboard_check_pressed(vk_escape) room_restart();
if keyboard_check_pressed(vk_space) Shader_Enabled = !Shader_Enabled;
if keyboard_check_pressed(ord("L")) FlagSwitch(1);
if keyboard_check_pressed(ord("R")) FlagSwitch(2);
if keyboard_check_pressed(ord("V")) FlagSwitch(4);
if keyboard_check_pressed(ord("H")) FlagSwitch(8);

/* ================================================ */

draw_set_colour(c_red);
draw_text(0, 0,"Real FPS: " + string(fps_real));
draw_set_colour(c_blue);
draw_text(0, 30,"Press ESC key to Reset");
/* ================================================ */
draw_set_colour(c_green);
draw_text(0,room_height-30,"Shader SH_ReverseImage");

ユーザ定義関数 FlagSwitch

///FlagSwitch( val );
var a,b,c, z;
a = argument0;
b = Deg;
c = 0.0;

if !Flip {
    if (Deg == 0){
        switch(a){
        case  8: c = 4.0; b  =   0; Flip = !Flip; break; // H
        case  4: c = 7.0; b  = 180; Flip = !Flip; break; // V
        case  2: c = 2.0; b -=  90; break; // R
        case  1: c = 1.0; b +=  90; break; // L
        };
    }
    else if (Deg == 90){
        switch(a){
        case  8: c = 6.0; b  = 270; Flip = !Flip; break; // H
        case  4: c = 5.0; b  =  90; Flip = !Flip; break; // V
        case  2: c = 0.0; b -=  90; break; // R
        case  1: c = 3.0; b +=  90; break; // L
        };
    }
    else if (Deg == 180){
        switch(a){
        case  8: c = 7.0; b  = 180; Flip = !Flip; break; // H
        case  4: c = 4.0; b  =   0; Flip = !Flip; break; // V
        case  2: c = 1.0; b -=  90; break; // R
        case  1: c = 2.0; b +=  90; break; // L
        };
    };
    else if (Deg == 270){
        switch(a){
        case  8: c = 5.0; b  =  90; Flip = !Flip; break; // H
        case  4: c = 6.0; b  = 270; Flip = !Flip; break; // V
        case  2: c = 3.0; b -=  90; break; // R
        case  1: c = 0.0; b +=  90; break; // L
        };
    };
}
else{//=========================================================
    if      (Deg == 0){
        switch(a){
        case  8: c = 0.0; b  =   0; Flip = !Flip; break; // H
        case  4: c = 3.0; b  = 180; Flip = !Flip; break; // V
        case  2: c = 6.0; b -=  90; break; // R
        case  1: c = 5.0; b +=  90; break; // L
        };
    }
    else if (Deg == 90){
        switch(a){
        case  8: c = 2.0; b  = 270; Flip = !Flip; break; // H
        case  4: c = 1.0; b  =  90; Flip = !Flip; break; // V
        case  2: c = 4.0; b -=  90; break; // R
        case  1: c = 7.0; b +=  90; break; // L
        };
    }
    else if (Deg == 180){
        switch(a){
        case  8: c = 3.0; b  = 180; Flip = !Flip; break; // H
        case  4: c = 0.0; b  =   0; Flip = !Flip; break; // V
        case  2: c = 5.0; b -=  90; break; // R
        case  1: c = 6.0; b +=  90; break; // L
        };
    }
    else if (Deg == 270){
        switch(a){
        case  8: c = 1.0; b  =  90; Flip = !Flip; break; // H
        case  4: c = 2.0; b  = 270; Flip = !Flip; break; // V
        case  2: c = 7.0; b -=  90; break; // R
        case  1: c = 4.0; b +=  90; break; // L
        };
    };
};

z = 360;
    if      (b  < 0) b += z;
    else if (b  > z) b -= z;
    else if (b == z) b  = 0;

var_flag = c;
Deg      = b; 

ハードコードです……計算コストは低いが、条件分岐だらけに。

シェーダ側の処理は八パターンあって、キー押しは四つなので

8 * 4 = 32

全32パターン、これで全てを網羅。力技です。

実際のイベント処理

シェーダと4つのユーザ定義関数を使って簡潔なイベントを作成。

Create Event

Init_Shader_Reverse();

Draw Event

Draw_ReverseImage();

Draw GUI Event

DrawGUI_CheckKeys();

WindowsPC で筆者が愛用している画像閲覧ソフト、IrfanView の操作性を真似しました。




次へ

前へ