GameMaker:Studio Shader モザイク/高品質版

 shader   mosaic   GLSL_ES 

wgld.org で公開されていたサンプルを元に GM:Studio 用に筆者が作り直したもの

画像にモザイク処理をかける効果。類似したシェーダがすでにあるが、こちらは高品質版。計算コストが高いため大きな画面の描画には本来向かない。しかしモザイク化処理の品質について比較すると、こちらのほうが品質は高い。

関連:http://prester.org/static/obake/post/shader_mosaic/

参考:https://wgld.org/d/webgl/w066.html

スクリーンショット:通常版のモザイク処理における不具合

スクリーンショット:高品質版のモザイク処理は均一

イベントを一つのオブジェクトへまとめるようにし、

    1. Create
    2. Draw
    3. DrawGUI

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

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

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

キーボードの Q と A キーで値を変更可能。

値は -100 〜 100 までの範囲で制限。要 WebGL、Google Chrome/FireFox 等で動作を確認。

HTML5( WebGL ) と Windows PC で微妙に描画結果が異なる場合があります。特に引数値が 0 の場合の描画結果に注意。

高品質版は範囲から色の平均を求め、その色を元にボックスで塗りつぶします。この平均化処理があるため通常版のモザイク処理よりも計算量が多く、大きな画像をモザイク化する場合は計算に依る負荷も比例して大きくなります。しかしその分精度が上がるため、均一なモザイク処理結果を得られます。
シェーダー名:SH_mosaicHQ

Vertex Shader

attribute vec3 in_Position;   // (x,y,z)

varying vec2 FragCoord;

void main()
{
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position, 1.0);
    FragCoord   = in_Position.xy;
}

FragCoord について、GM:S の仕様らしいのだが、GLSL ES の全ビルトインをサポートしているわけではなく、特に今回は gl_FragCoord が GM:S から利用することは出来なかった。

このため

varying vec2 FragCoord;

として void main() 内では

FragCoord   = in_Position.xy;

とすることでフラグメントシェーダに頂点データを渡す処理を追加してある。この辺りは GM:S 独自の仕様。

Fragment Shader

precision mediump float;

varying vec2  FragCoord;

uniform float resolution_x;//画面の幅
uniform float resolution_y;//画面の高さ
uniform float pixel_amount;//ボックスのサイズを設定(可変)

float tFrag       = 1.0 / resolution_x;
float nFrag       = 1.0 / 64.0; //色を出力する際に正規化するため、事前に nFrag を計算しておく
const float vmax  = 7.0;    //8*8の行列に対する計算は64回のテクセル参照と加算処理が必要になる

void main(void){
    vec4  destColor   = vec4(0.0);
    float aspect      = resolution_x / resolution_y;
    vec2  fc          = vec2(FragCoord.s, aspect * FragCoord.t);
    float offsetX     = mod(fc.s, pixel_amount);//テクセルがボックス内のどの位置にあるのかを算出する計算
    float offsetY     = mod(fc.t, pixel_amount * aspect);//画面比率に応じて値を調整する処理を加えてある

    for(float x = 0.0; x <= vmax; x += 1.0){//ループ回数を指定している vmax は定数であること(変数は不可)
        for(float y = 0.0; y <= vmax; y += 1.0){
            //テクセルを参照しながら、ループ処理で色を全て加算
            destColor += texture2D(gm_BaseTexture, (fc + vec2(x - offsetX, y - offsetY)) * tFrag);
        }
    }
    gl_FragColor = destColor * nFrag;
}

ボックスのサイズを可変とした。値が 0 の場合でもややぼやけた感じで出力される。

ユーザ定義関数 Init_Shader_MosaicHQ

///Init_Shader_MosaicHQ();

    if (!shader_is_compiled(SH_mosaicHQ)) {
        show_message("Your hardware doesn't support shader - SH_mosaicHQ");
    }
    else{
        uni_resolution_x = shader_get_uniform(SH_mosaicHQ,"resolution_x");
        var_resolution_x = display_get_gui_width()*1.0;
        uni_resolution_y = shader_get_uniform(SH_mosaicHQ,"resolution_y");
        var_resolution_y = display_get_gui_height()*1.0;
        
        uni_pixel_amount = shader_get_uniform(SH_mosaicHQ,"pixel_amount");
        var_pixel_amount = 18.0;
    };

ユーザ定義関数 DrawEv_WalkingRobo

///DrawEv_WalkingRobo();
var a,b,c,d;
a = sprite_get_number(rb);
b = Robot_ImageIndex;
b++;
   if (b > a) b = 0;
Robot_ImageIndex = b;
c                = room_width>>1;
d                = room_height>>1;
draw_sprite(rb,b,c,d);

ユーザ定義関数 Debug_ShowStatus

///Debug_ShowStatus();
var v,w,z;
v = 100;
w = var_pixel_amount;
z = 0.1;
if keyboard_check(ord('Q')) and w< v var_pixel_amount += z;
if keyboard_check(ord('A')) and w>-v var_pixel_amount -= z;

var a,b,c;
a  = 0;
b  = 40;
c  = "Pixel amount (Q & A to adjust): ";
c += string(var_pixel_amount);

draw_set_colour(c_red);
draw_text(a,0,"Real FPS: "+string(fps_real));
draw_set_colour(c_white);
draw_text(a,b,c);

ユーザ定義関数 DrawGUIEv_MosaicHQ

///DrawGUIEv_MosaicHQ();

shader_set(SH_mosaicHQ);
shader_set_uniform_f(uni_resolution_x, var_resolution_x);
shader_set_uniform_f(uni_resolution_y, var_resolution_y);
shader_set_uniform_f(uni_pixel_amount, var_pixel_amount);
draw_surface_ext(application_surface, 0,0, 1,1, 0, -1,1); 
shader_reset();

実際のイベント処理

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

Create Event

draw_set_font(font0);

Init_Shader_MosaicHQ();

/* Instance Variable for draw_sprite */
Robot_ImageIndex = 0;

Draw Event

draw_self();
DrawEv_WalkingRobo();//draw with application surface

Draw GUI Event

DrawGUIEv_MosaicHQ();

Debug_ShowStatus();

DrawEv_WalkingRobo();//for GUI

application_surface を対象としてシェーダでエフェクトをかけます。

Draw イベントで描画した画面を DrawGUI へ描画するという手順。シェーダで application_surface 全体を対象にする場合は DrawGUI のタイミングで描画するのが無難。

ユーザ定義関数である DrawGUIEv_MosaicHQ でシェーダ処理を行っている。アクションの実行されるタイミングなどを色々変えて描画結果の違いを試すと良い。




次へ

前へ