GameMaker:Studio Shader オーバーレイ(Overlay)

 shader   overlay   GLSL_ES 

PhotoShop の描画モードにある「乗算」と「スクリーン」とを掛け合わせた効果を持つ「オーバーレイ」を GLSL ES で再現。

二枚のイメージを用いてそれらを重ねあわせた時、乗算の場合は暗いところが暗くなり、スクリーンの場合は明るいところが明るくなるといった特徴がある。

オーバーレイは暗いところは暗く、明るいところは明るくと、上記二つのモードを合わせたような効果を持つ。これを GameMakerStudio の Shader として GLSL ES で再現する。

Overlay シェーダ

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

    1. Create
    2. Alarm
    3. Draw

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

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

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

参考( YoYoGames旧フォーラム: ) Trying to do a photoshop effect over a …

参考:Photoshop math with GLSL shaders

参考:Photoshop Blendmodi in GLSL

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

値は 0 〜 1 までの範囲で制限。要 WebGL、Google Chrome/FireFox 等で動作を確認。 スペースキーでシェーダ効果のオンオフ切り替え可能。

HTML5( WebGL ) と Windows PC で描画結果が異なる場合があります。古いグラフィックスドライバ上でオーバーレイの色が適用されないケースを確認済み。Android でも動きます。

グラフィックスリソースが複数必要に成ります。最低でも背景とその上へレイヤー的に重ねるシャドウマップ的な画像が必要。ただし単純なグラデーションならば予め用意しなくとも、Surface を使って Shader でグラデーションマップを動的に作ることなども可能。 今回はリソースツリーに背景として以下画像を利用しています。

Overlay シェーダ用レイヤー画像

シェーダー名:SH_Overlay

Vertex Shader

attribute vec3 in_Position;                  // (x,y,z)
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position, 1.0);
    v_vColour   = in_Colour;
    v_vTexcoord = in_TextureCoord;
}

Fragment Shader

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform sampler2D texOverlay;//foreground colour ( luminance value )
float val = 2.0;// f(a, b) is 2ab
float num = 1.0;// 1 - 2(1 - a)(1 - b)
float cut = 0.5;// if a < 0.5
//luminance is 0.5 and ignore the linear blend

void main()
{
    vec4 inColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
    vec4 overlay = texture2D(texOverlay, v_vTexcoord);
    vec3 aa      = ((val * inColor.xyz) * overlay.xyz);// 2.0 * a * b 
    vec3 bb      = (num - ((val * (num - inColor.xyz))*(num - overlay.xyz)));
    gl_FragColor = vec4(mix(aa,bb, floor(inColor.xyz + cut)), inColor.a);
    //gl_FragColor = vec4(mix(aa,bb, floor(inColor.xyz - cut)), inColor.a);
    //gl_FragColor = vec4(mix(bb,aa, floor(inColor.xyz - cut)), inColor.a);
    //gl_FragColor = vec4(mix(aa,bb, abs(inColor.xyz + cut)), inColor.a);
    //上記三行は派生効果としてのサンプル、それぞれ微妙に描画結果は異なる
}

ユーザ定義関数 Init_Shader_Overlay

///Init_Shader_Overlay();
    if (!shader_is_compiled(SH_Overlay)) {
        show_message("Your hardware doesn't support shader - SH_Overlay");
    }
    else{
        /* 
        For Debug
        */
        Debug   = true;
        Value   = 0.5;
        /* 
        For Shader
        */
        stage   = shader_get_sampler_index(SH_Overlay, "texOverlay");
        Rotate  = 0;
        SRotate = 360;
        //alarm[0]  = HOLY;//alarm_set(0, HOLY);
        /* 
        For Surfaces
        */
        Surf_Copy    = noone;
        Surf         = noone;
    };

ユーザ定義関数 DrawEv_Shader_Overlay

///DrawEv_Shader_Overlay();
draw_background_stretched(background,0,0,room_width,room_height);

var a,b,c,d,e,f, g,h,j, z;
a = room_width>>1; 
b = room_height>>1;
c = .2
d = 360;
e = 1.;
f = Value;

Rotate++;
SRotate -= c;
    if Rotate == d Rotate = 0;
    if SRotate == 0 Rotate = d;
g = d-Rotate;
h = d-SRotate;
j = alarm[0];//alarm_get(0);// alarm_get関数はHTML5でまだ未実装
//---------------------------------------------------------------
/*
Surf_Copy へ Overlay 用の bg_grad を描画する手順について

surface_get_texture は background_get_textureでもポインタ値を得られる。
ただし、その場合はリソースツリー上の bg_grad に対する個別の設定項目として
Used for 3D チェックオプションを有効にすることが必須と成る。

分かり難い設定項目で、トラブルを避ける意味でも surface 使った方が無難と考える
*/
    if !surface_exists(Surf_Copy) {
        Surf_Copy = surface_create(room_width,room_height);
        surface_set_target(Surf_Copy);
        draw_background(bg_grad,0,0);
        surface_reset_target();
    }
    else{
        if Debug {
            shader_set(SH_Overlay);
            texture_set_stage(stage, surface_get_texture(Surf_Copy));
            /*
            Surface 上の画像をオーバレイに利用して
            二枚の絵を合成した結果を application_surface 上へ描画する
            */
            draw_surface_ext(application_surface, 0,0, e,e,0,-1, f);
            shader_reset();
        };
    };
//---------------------------------------------------------------
/*
ブレンドモードに関する覚書
draw_set_blend_mode_ext(bm_src_alpha, bm_one); == draw_set_blend_mode(bm_add);

draw_set_blend_mode_ext(bm_src_alpha, bm_inv_src_alpha); //  == bm_normal
draw_set_blend_mode_ext(bm_src_alpha, bm_one); //            == bm_add
draw_set_blend_mode_ext(bm_zero,      bm_inv_src_colour); // == bm_subtract
draw_set_blend_mode_ext(bm_src_alpha, bm_inv_src_colour); // == bm_max

bm_zero            (0, 0, 0, 0) 
bm_one             (1, 1, 1, 1) 
bm_src_colour      (Rs, Gs, Bs, As) 
bm_inv_src_colour  (1-Rs, 1-Gs, 1-Bs, 1-As) 
bm_src_alpha       (As, As, As, As) 
bm_inv_src_alpha   (1-As, 1-As, 1-As, 1-As) 
bm_dest_alpha      (Ad, Ad, Ad, Ad) 
bm_inv_dest_alpha  (1-Ad, 1-Ad, 1-Ad, 1-Ad) 
bm_dest_colour     (Rd, Gd, Bd, Ad) 
bm_inv_dest_colour (1-Rd, 1-Gd, 1-Bd, 1-Ad) 
bm_src_alpha_sat   (f, f, f, 1) where f = min(As, 1-Ad)
*/
draw_set_blend_mode(bm_add);// 描画を加算モードへ変更
z = random_range(.3 , f);
draw_sprite_ext(ef_starnoize,0,a,b,e,e,SRotate,-1, z);
draw_sprite_ext(ef_light,0,a,b,e,e,g,-1,1);
//---------------------------------------------------------------
    if !surface_exists(Surf) {
       Surf     = surface_create(room_width,room_height);
       alarm[0] = HOLY;// HOLY はマクロに登録された定数
    }
    else{
       surface_set_target(Surf);
           if (j > 0){
               draw_sprite_ext(ef_logo_base,0,a,b,e,e,h,-1,.6);
           };
       surface_reset_target();
       draw_surface(Surf,0,0);// Draw to Application_surface
    };
//---------------------------------------------------------------
z = random_range(f , .8);
    if !j draw_sprite_ext(ef_logo_base,0, a,b,e,e, SRotate,-1, z);
draw_sprite_ext(ef_lihgtstar,0, a,b,e,e, 180-Rotate, -1, 1);
draw_set_blend_mode(bm_normal);// モードをノーマルへ戻す
draw_sprite(logo_body,0,a,b);

ユーザ定義関数 Show_Status

///Show_Status();
draw_set_colour(c_red);
draw_text(10,10,"Draw_HolyLight_TEST");
draw_text(10,60,"Shader Overlay TEST");
/*
draw_set_colour(c_lime);
draw_text(10,room_height-200,string(alarm_get(0)));
*/
draw_set_colour(c_purple);
draw_text(10,room_height-150,"Press Q & A, Ajust Overlay");
draw_set_colour(c_yellow);
draw_text(10,room_height-100, "press Space key, toggle Shader");
draw_set_colour(c_blue);
draw_text(10,room_height-50, "press R key, Restart the Room");

if keyboard_check(ord("Q")) Value += 0.01;
if keyboard_check(ord("A")) Value -= 0.01;
Value = clamp(Value, .0 , 1.);
if keyboard_check_pressed(vk_space) Debug = !Debug;
if keyboard_check_pressed(ord("R")) room_restart();

実際のイベント処理

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

Create Event

draw_set_font(font0);
draw_enable_alphablend(true);
Init_Shader_Overlay();

Alarm 0

///Alarm[0]
//show_message("TE");

Draw Event

DrawEv_Shader_Overlay();
Show_Status();

application_surface を対象としてシェーダでエフェクトをかけます。利用されている Surface はオーバレイ用のマップです。

シェーダで application_surface 全体を対象にする場合は DrawGUI のタイミングで描画するのが無難だけど、今回は Draw Event のタイミングで実行してます。

派生効果を作りやすいシェーダ。マップを工夫したり、フラグメント内のロジックを変更したりで色々楽しめます。




次へ

前へ