GameMaker:Studio Shader Rotate Image(回転行列版)
Vertex Shader で表示画像の回転+反転、( 回転行列版 )
前回は泥臭い処理だったので、今回は回転行列を使ってシンプルに。
イベントを一つのオブジェクトへまとめ、
- Create
- Draw
- DrawGUI
上記三つのイベントへ収まるように作った。そして無駄な変数定義を全て排除。
なるべくユーザ定義関数化し、コードの使い回しが簡単。
参考動作:http://prester.org/html5/gms_shader_rotateimage/
参考(数学とプログラミングのテクニック):行列計算
要 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?
次へ
前へ