モザイク(Mosaic)/高品質版

 effect   mosaic   gl_FragCoord   safari 

{{< twitter >}}

wgld.org の公開サンプルを元に GM:Studio 用に作り直したもの

画像にモザイク処理をかける効果。

類似したシェーダはすでにあるが、こちらのモザイク処理は高品質版、ただし計算コストが高いので大きな画面サイズの描画には本来向かない。精度高いけれど一定のループ回数を超えると非現実的な重い処理となるため、高品質と言いつつ、実際は 8*8 固定のループ処理。必要に応じてループ回数はソースコードを書き換えるか、すこし別のアプローチが必要となる。

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

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

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

    1. ( 暗黙の draw なのでアクションは無し )

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

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

値は 0 〜 100 までの範囲で制限。

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

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



高品質版は領域から色の平均を求め、その色で領域を塗りつぶします。この平均化処理は通常版モザイク処理よりも計算量が多くなり、大きな画像をモザイク化する場合は計算に依る負荷も比例して大きくなります。しかし精度は上がり、常に正確なモザイクを得ることができます。

このサンプルではループ処理回数を固定化してあるため、領域から色の平均化を求める処理がそれほど重くなりません。その代わり精度については無視しています。

シェーダー名:SH_mosaicHQ

参考動作:高品質版モザイク

OpenGL-ES_50px_May16 WebGL_50px_June16

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

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 2.0 の全ビルトインをサポートしていません。gl_FragCoord が GM:S から利用することは出来なかったため、varying を使って自力で Fragment へ値を渡しています。

このため

varying vec2 FragCoord;

として void main() 内では

FragCoord   = in_Position.xy;

フラグメントシェーダへ頂点データを渡す処理を自分で記述。

Fragment Shader

 
precision mediump float;

varying vec2  FragCoord;

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

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

void main(){
    vec2  fc      = vec2(FragCoord.x, aspect * FragCoord.y);
    float offsetX = mod(fc.x, pixel_amount);//テクセルがボックス内のどの位置にあるのかを算出
    float offsetY = mod(fc.y, pixel_amount * aspect);// 画面比率に応じて値を調整する処理
    vec2  ok;
/*
    ループ回数を指定、vmax は定数であること(変数は不可)
    
    ここで問題点、ループ回数はスカラーか定数しか利用できない仕様がある。
    本来はテクセル参照回数を増減したいが、仕様があるためそれもできない。
    ここでは 8*8 固定の64回ループになっていて、正規化も 64.0 で固定だが
    例えば 20 * 20 の場合、400 回ループ、正規化も 400.0 にしないと
    本当の意味で精度の高い「高品質」なモザイクは得られない。
    
    pixel_amount の値に応じてループ回数を変えたいが、できないので
    処理が比較的軽くて精度もまぁまぁだった 8*8 ループで固定してある。
*/
    for(float x = 0.0; x <= vmax; x += 1.0)
    {
        for(float y = 0.0; y <= vmax; y += 1.0)
        {
            // テクセル参照しながら、ループ処理で色を全て加算
            ok = (fc + vec2(x - offsetX, y - offsetY)) * tFrag;
            destColor += texture2D(gm_BaseTexture, ok);
        }
    }
    gl_FragColor = destColor * nFrag;
}
 

サンプリングする領域サイズを可変としたが、ループ回数は常に 8*8=64 回で固定したから、領域を拡大しても純粋に全ての色を拾って来るわけではない。ループ回数を固定とすることでサンプリングする領域が大きくなっても計算量は変わらない。このままでは精度的に問題あるが、ゲーム用として妥協した方が良い。

ループ回数を固定化したもうひとつの弊害としてサンプリングする領域サイズの値が 0.0 でも出力結果はぼやける。

ユーザ定義関数 Init_Shader_MosaicHQ

///Init_Shader_MosaicHQ();
draw_set_font(font0);
var a,b,c,d;
a = SH_mosaicHQ;
    if !shader_is_compiled(a) {
        b = "ur hardware ain't support shader - SH_mosaicHQ"
        show_message(b);
    }
    else{
        /* Instance variables for Uniform  */
        b = "resolution_x";
        c = "resolution_y";
        d = "pixel_amount";
        uni_resolution_x = shader_get_uniform(a, b);
        var_resolution_x = display_get_gui_width()*1.0;
        uni_resolution_y = shader_get_uniform(a, c);
        var_resolution_y = display_get_gui_height()*1.0;
        uni_pixel_amount = shader_get_uniform(a, d);
        var_pixel_amount = 18.0;

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

        /* For Debug */
        Debug = false;
    };
 

ユーザ定義関数 DrawGUI_MosaicHQ

///DrawGUI_MosaicHQ();
if !Debug {
    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();
};
DrawGUI_WalkingRobo();
Debug_ShowStatus();
 

ユーザ定義関数 DrawGUI_WalkingRobo

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

ユーザ定義関数 Debug_ShowStatus

///Debug_ShowStatus();
if keyboard_check_pressed(ord("R")) room_restart();
if keyboard_check_pressed(vk_space) Debug = !Debug;

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 > 0) 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);
 

イベント処理

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

Create Event

///Init
Init_Shader_MosaicHQ();

Draw GUI Event

///DrawGUI
DrawGUI_MosaicHQ();

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

ユーザ定義関数である DrawGUI_MosaicHQ でシェーダ処理を行っている。

精度的にはまだ問題があるけれど、現実的に実行可能なループ回数に抑えないと単なるモザイク処理も相当重い処理になります。




次へ

前へ