Rotate Image(回転行列版)
{{< twitter >}}
Vertex Shader で表示画像の回転+反転{{< size-s “回転行列版” >}}
前回は泥臭い処理だったので、今回は回転行列を使ってシンプルに。
イベントを一つのオブジェクトへまとめ、
上記の三つのイベントへ収まるように作った。無駄な変数定義を排除、処理をユーザ定義関数化しコードの使い回し簡単。
参考( 数学とプログラミングのテクニック ) 行列計算
要 WebGL、Google Chrome/FireFox 等で動作を確認
HTML5( WebGL ) と Windows PC で描画結果が異なる場合があります。
特に今回は座標系の違いから HLSL と GLSL 系で回転方向が逆になりますが、これは仕様。
HLSL との動作に互換性を持たせるため、シェーダコードの一部に GM:S の HLSL クロスコンパイラ用コードと、Android + WebGL で動作を確認した GLSL ES 用コード、二種類を予め書いてあります。各コードは必要とするプラットフォームに応じて手動でソースコードを編集してください。
シェーダー名:SH_RotateImage
参考動作:Shader-Rotate Image
( 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 だが、非互換性はクロスコンパイラで吸収できるという話もある )参考( GLSLを使ったOpenGLプログラム ) : OpenGLの座標系
参考( Unity3d.com ) : Unity is a Left-Handed Coordinate System? Why?
次へ
前へ