Rotate Image(回転行列版)

 effect   rotate   vertex   matrix   safari 

{{< twitter >}}

Vertex Shader で表示画像の回転+反転{{< size-s “回転行列版” >}}

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

GameMaker:Studio GLSL ES Shader RotateImage Sample

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

上記の三つのイベントへ収まるように作った。無駄な変数定義を排除、処理をユーザ定義関数化しコードの使い回し簡単。

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

キーボードの H キーで画像を反転

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

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

特に今回は座標系の違いから HLSL と GLSL 系で回転方向が逆になりますが、これは仕様。

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

シェーダー名:SH_RotateImage

参考動作:Shader-Rotate Image

OpenGL-ES_50px_May16 WebGL_50px_June16

( MacBook Pro OS X El Capitan + Safari 11601.7.7 で正常に動作せず )

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();
    vec4 GMW    = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]; 
    gl_Position = GMW * 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);
var a,b,c,d;
a = SH_RotateImage;
    if ( !shader_is_compiled(a) ) {
	b = "ur hardware ain't support shader - SH_RotateImage";
        show_message(b);
    }
    else{
        /* for Debug */
        Shader_Enabled = true;
        
        /* for Fragment Shader, uniform variables */
        //                      ~~~~~~~
	b = "u_xpos";
	c = "u_flip";
	d = "u_angle";
        uni_xpos  = shader_get_uniform(a , b);
        var_xpos  = x+x;// 座標の正規化
        
        uni_flip  = shader_get_uniform(a , c);
        var_flip  = false;
        
        uni_angle = shader_get_uniform(a , d);
        var_angle = 0;
    };
 

ユーザ定義関数 Draw_RotateImage

///Draw_RotateImage();
    if Shader_Enabled {
        var a,b,c,d;
	a = SH_RotateImage;
	b = var_xpos;
	c = 0;
            if !var_flip { c = 1; };
	d = var_angle;
        shader_set(a);
	{
            shader_set_uniform_f(uni_xpos  , b);
            shader_set_uniform_f(uni_flip  , c);
            shader_set_uniform_f(uni_angle , d);
            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;
        if !var_flip {
           a =  0.01;
	   b =  140;// x pos
	   c =  "←";
        }else{
           a = -0.01;
	   b =  360;// x pos
	   c =  "→";
        };
    var_angle += a;
    draw_text(b,200,c);
};

/* ================================================ */
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
Init_Shader_Rotate();

Draw Event

///Draw
Draw_RotateImage();

Draw GUI Event

///DrawGUI
DrawGUI_CheckKeys();

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

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

これは座標系が異なっているのが原因。

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

( ちなみに DirectX は Left-Handed だが、XNA は Right-Handed だったらしい。Unity も Left-Handed だが、非互換性はクロスコンパイラで吸収できるという話もある )




次へ

前へ