GameMaker:Studio Shader Rotate Image(回転行列版)

 shader   rotate   GLSL_ES 

Vertex Shader で表示画像の回転+反転、( 回転行列版 )

前回は泥臭い処理だったので、今回は回転行列を使ってシンプルに。

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

    1. Create
    2. Draw
    3. DrawGUI

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

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

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

参考(数学とプログラミングのテクニック):行列計算

キーボードの H キーで反転。

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

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

HTML5 と Android で動作確認した際に、Windows PC で利用されている HLSL への変換結果と GLSL ES での動作が異なっていることが判明。

( 原因は座標系の違い、たぶんバグではなく仕様 )

HLSL との動作に互換性を持たせるため、シェーダコードの一部に GM:S の HLSL クロスコンパイラ用コードと、Android + WebGL で動作を確認した GLSL ES 用コード、二種類を予め書いてあります。各コードは必要とするプラットフォームに応じて手動でソースコードを編集してください。

シェーダー名:SH_RotateImage

Vertex Shader

attribute vec3 in_Position;
attribute vec2 in_TextureCoord;

varying vec2  v_texcoord;

uniform float u_xpos;
uniform float u_flip;
uniform float u_angle;
/* for Windows PC (HLSL) で使う場合はこちらのコードを */
/*
mat4 RotationMatrix = mat4( cos( u_angle ), -sin( u_angle ), 0.0, 0.0,
                            sin( u_angle ),  cos( u_angle ), 0.0, 0.0,
                                       0.0,             0.0, 1.0, 0.0,
                                       0.0,             0.0, 0.0, 1.0
                          );
*/
/* for GLSL ES (HTML5 | Android) で使う場合はこちらのコードを */
mat4 RotationMatrix = mat4(  cos( u_angle ), sin( u_angle ), 0.0, 0.0,
                            -sin( u_angle ), cos( u_angle ), 0.0, 0.0,
                                        0.0,            0.0, 1.0, 0.0,
                                        0.0,            0.0, 0.0, 1.0
                          );

// User-Defined Functions
vec4 Flip_H()
{
    vec4 dir;
    const float l = 1.0;
    if  (u_flip == l) {dir = vec4(in_Position.xyz , l);}
    else              {dir = vec4(u_xpos - in_Position.x, in_Position.yz, l);}
    return dir;
}

void main()
{
    vec4 ok     = Flip_H();
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * ok * RotationMatrix;
    v_texcoord  = in_TextureCoord;
}
回転行列を使って頂点座標を変換し、表示される画像を指定した角度で回転させます。

mat4 RotationMatrix は 4✕4 の正方行列( 4次正方行列 ) 、これは回転変換のために利用します。同じ値( 角度 ) を与えても回転方向は Windows PC(HLSL) と GLSL ES で逆になります。

本来 Windows PC(HLSL) 用のコード
mat4 RotationMatrix = mat4( cos( u_angle ), -sin( u_angle ), 0.0, 0.0,
                            sin( u_angle ),  cos( u_angle ), 0.0, 0.0,
                                       0.0,             0.0, 1.0, 0.0,
                                       0.0,             0.0, 0.0, 1.0
                          );

上記を常に使いたいのですが、GLSL ES でこのコードを利用すると HLSL とは逆方向へ回転しちゃう。そこで対処法

mat4 RotationMatrix = mat4(  cos( u_angle ), sin( u_angle ), 0.0, 0.0,
                            -sin( u_angle ), cos( u_angle ), 0.0, 0.0,
                                        0.0,            0.0, 1.0, 0.0,
                                        0.0,            0.0, 0.0, 1.0
                          );

雑ですが、とりあえずこれで。

回転行列のおかげで前回よりも条件分岐が一気に減り、コードが見やすくなりました。前回は基本的に 90 度単位でしか回転させることもできませんでしたが、今回は自由に角度を指定できます。

Fragment Shader

precision mediump float;
varying vec2 v_texcoord;

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

precision mediump float; は GLSL ES 用のオマジナイです。

別に無くても良い局面なので、消しても問題なく動きます。

ユーザ定義関数 Init_Shader_Rotate

///Init_Shader_Rotate();
draw_set_font(font0);

    if (!shader_is_compiled(SH_RotateImage)) {
        show_message("Your hardware doesn't support shader - SH_RotateImage");
    }
    else{
        /* for Debug */
        Shader_Enabled = true;
        
        /* for Fragment Shader, uniform variables */
        //                      ~~~~~~~
        uni_xpos  = shader_get_uniform(SH_RotateImage , "u_xpos");
        var_xpos  = x+x;// 座標の正規化
        
        uni_flip  = shader_get_uniform(SH_RotateImage , "u_flip");
        var_flip  = false;
        
        uni_angle = shader_get_uniform(SH_RotateImage , "u_angle");
        var_angle = 0;
    };

ユーザ定義関数 Draw_RotateImage

///Draw_RotateImage();
    if Shader_Enabled {
        var a = 0;
            if !var_flip a = 1;
        shader_set(SH_RotateImage);
            shader_set_uniform_f(uni_xpos  , var_xpos);
            shader_set_uniform_f(uni_flip  , a);
            shader_set_uniform_f(uni_angle , var_angle);
            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("H"))  var_flip       = !var_flip;

if Shader_Enabled {
    var a,b,c,d;
    c = 200;
        if !var_flip {
           a =  0.01; b = 140; d = "←";
        }else{
           a = -0.01; b = 360; d = "→";
        };
    var_angle += a;
    draw_text(b,c,d);
};

/* ================================================ */
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_text(0, 60,"Angle = " + string(var_angle));
/* ================================================ */
draw_set_colour(c_green);
draw_text(0,room_height-30,"Shader SH_RotateImage");

実際のイベント処理

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

Create Event

Init_Shader_Rotate();

Draw Event

Draw_RotateImage();

Draw GUI Event

DrawGUI_CheckKeys();

Windows PC (HLSL)と GLSL ES でシェーダコードの非互換性が現れたのは当初想定外だった。

本来、回転行列で正の方向へ増加する値を角度として指定する場合には counter-clockwise ( 時計の針と反対回り、すなわり左回り ) で正しいはず。しかし GLSL ES では逆方向の右回りになる。

少し考えた結果、座標系異なっているのが原因か?

Direct3d は Left-Handed Coordinates System、OpenGL は right-handed coordinates Rule なので、この部分に元から非互換性があるってことかな。

ちなみに DirectX は Left-Handed だけど、XNA は Right-Handed の座標系だったらしい。Unity も Left-Handed ね。

参考( GLSLを使ったOpenGLプログラム )OpenGLの座標系

参考( Unity3d.com )Unity is a Left-Handed Coordinate System? Why?




次へ

前へ