GameMaker:Studio Shader 加算(Screen/スクリーン)

 shader   screen   GLSL_ES 

Photoshop の画像合成機能から、加算処理 = Screen/スクリーンを再現するシェーダ

Photoshop のレイヤー画像に適用される「ブレンドモード( 描画モード ) 」を GLSL ES と GameMakerStudio で再現するためのシェーダ。

GameMakerStudio_1.4_Shader_effect_::_Screen

スクリーンは乗算の逆、明るい部分をより明るく、重ねれば重ねるごとに明るい部分がもっと明るく表示される効果です。

画像を二枚用いて、下地と成る画像を「基本色」、その上に重ねる画像を「合成色」として扱います。

基本色の上に合成色を重ね、合成色の暗い色は無視、明るい色だけを拾って基本色と合成色で混合された値の算出結果がブレンドモードの「加算=スクリーン」効果です。

というわけで、基本的に二枚画像が必要、GPU が扱えるテクスチャが二枚必要ってことになります。

GameMakerStudio_1.4_Shader_picture

今回テクスチャは基本色を application surface から、合成色はあえて背景( Backgrounds ) を材料とした GM:S の surface 機能で作った surface ( これもテクスチャであり、GPU 上のスロットで保持されるようにした ) を活用してます。

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

    1. Create
    2. Draw( 暗黙の draw を使ったためイベントは無い )
    3. DrawGUI

上記三つのイベントへ収まるように作った。

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

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

参考(Devmaster Forum):Shader Effects: Blend Modes

参考(mouaif.wordpress.com):Photoshop math with GLSL shaders

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

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

シェーダー名:SH_Blendmode_Screen

GameMakerStudio_1.4_background_texture_sample

今回用意した合成色用の画像は上。これを surface へ draw_background_tiled_ext 関数使ってテクスチャ全面へ貼付けました。

そして

GameMakerStudio_1.4_sprite

オブジェクトの外観としてスプライトは適当なものを適当にチョイス、これを暗黙的に実行される Draw イベントのタイミングで application surface へ描画。

GameMakerStudio_1.4_room_setting_background

背景は Room の設定から青色を選んで描画する設定で済ます。これが合成する前の「基本色」として application surface へ描画されます。

Vertex Shader

attribute vec3 in_Position;                  // (x,y,z)
attribute vec2 in_TextureCoord;              // (u,v)
// 今回、あえて頂点カラーは無視しています、必要な場合は追加
varying vec2  v_vTexcoord;

uniform float u_ratio;

void main()
{
    vec4 pos    = vec4( in_Position, u_ratio);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * pos;
    v_vTexcoord = in_TextureCoord;
}

Fragment Shader

varying vec2  v_vTexcoord;

uniform sampler2D u_TexSampler;//テクスチャを一枚読み込む

void main()
{
vec4 src     = texture2D( gm_BaseTexture , v_vTexcoord );//基本色
vec4 dst     = texture2D( u_TexSampler   , v_vTexcoord );//合成色
gl_FragColor = (src + dst) - (src * dst);
//gl_FragColor = (1.0 - ((1.0 - src)) * (1.0 - dst));
}

同時に扱うテクスチャをもう一枚サンプラーとして追加し、これを uniform 変数で受け取ります。テクスチャの UV は基本色となっている application surface と共用。

vec4 src = texture2D( gm_BaseTexture , v_vTexcoord );

こちらが基本色。これは DrawGUI から application surface をサンプラーとして指定。

vec4 dst = texture2D( u_TexSampler , v_vTexcoord );

次が合成色。surface 関数で GPU メモリ上へ作成したテクスチャをサンプラーとして GPU のスロットへ予め送信してあります。( 具体的な処理は GML によるイベントでコード記述します )

gl_FragColor = (src + dst) - (src * dst);

これは二つのサンプラーを合成して色を作成する計算式です。Devmaster Forum のサンプルの方が計算は記述がシンプルなのでこちらを採用してありますが、

//gl_FragColor = (1.0 - ((1.0 - src)) * (1.0 - dst));

mouaif.wordpress.com の GLSL 用コードも同じ結果で、計算式はちょっと違います。こちらの方が意味としては初見でも読み取りやすい。

ちなみにこの計算式をほかのものへ入れ替えするだけで photoshop のブレンドモード風シェーダ効果が色々作れます。

サンプラーを2つ用意して合成するためリソースを用意して GML でコードを書く、手間は少しかかりますが一度雛形を作れば多数応用が効く。

ユーザ定義関数 Init_Shader_Blendmode_Screen

///Init_Shader_Blendmode_Screen();
draw_set_font(font0);
var a = SH_Blendmode_Screen;

if !shader_is_compiled(a){
     show_message("Ur hardware ain't support shader - SH_Blendmode_Screen");
}
else{
     U_sampler = shader_get_sampler_index(a, "u_TexSampler");
     Surf      = noone;
     U_ratio   = shader_get_uniform(a, "u_ratio");
     Ratio     = 1.0;
};

ユーザ定義関数 DrawGUI_Blendmode_Screen

///DrawGUI_Blendmode_Screen();
var a,b, z;
a = Ratio;
b = Screen_Scale_Ratio();/* Check Window Size */

    if (a != b){
        a     = b;
        Ratio = a;
        /* 
        Window サイズが変更された場合、
        surface は自動的に破棄されるため
        明示的な開放処理は不要.
        それでも心配な場合、明示的に破棄すればよい
        */
        //surface_free(Surf);
    };

z = SH_Blendmode_Screen;

    if !surface_exists(Surf){
        var c,d,e, f;
        c    = window_get_width()  * a;
        d    = window_get_height() * a;
        e    = star2;// Background image
        Surf = surface_create(c , d);
        /* surface */
        surface_set_target(Surf);
            draw_clear_alpha(c_black, 0);
            draw_background_tiled_ext(e ,0,0, a,a,-1,1);
        surface_reset_target();
        /* shader , add a texture to GPU's texture slot */
        shader_set(z);
            f = surface_get_texture(Surf);
            texture_set_stage(U_sampler, f);
        shader_reset();
    }
    else{
        /* shader draw */
        shader_set(z);
            shader_set_uniform_f(U_ratio , a);
            draw_surface(application_surface, 0,0);
        shader_reset();
    };

ちょっといつもより長めのコード。

( surface 関数を使うとどうしてもコードは若干長くなってしまう )

shader へのバインド処理としてサンプラーを GPU のスロットへ送信する作業もこなしています。

あとはウィンドウサイズが変更された時、テクスチャサイズを変更する必要があったので、拡大縮小率を再計算する処理があえて含まれています。

二枚のテクスチャを合成する上で重要なのは、サンプラーとしてテクスチャを GPU メモリ上のスロットへ追加する処理、つまり

texture_set_stage(U_sampler, f);

この部分です。

texture_set_stage 関数は GPU メモリ上のスロットへ、指定したテクスチャを送信する処理を行う関数。スロットへ送信されたテクスチャはサンプラーとして、テクスチャをキャッシュする GPU のテクスチャプール上でデータが保管されます。これは Surface が破棄されない限り同じ画像が利用される前提の場合、適切なタイミングで一度送信すれば良い。

GML のリファレンスによると GPU の扱うスロットは数に限りがあって、Windows PC など比較的スロット数に余裕がある環境ではスロット数が 8 つくらい、モバイルでは 2 つとされています。互換性を考慮する場合は低い方へ合わせるのが無難なので、安心して使えるのは二つ迄ということになる。

ユーザ定義関数 Screen_Scale_Ratio

///Screen_Scale_Ratio();
var b,c;
b = view_wview[0] / window_get_width();
c = view_hview[0] / window_get_height();
    if (b < c) b = c;
return b;

これはウィンドウサイズが変更された時、破棄された surface を新しく作る際に必要なテクスチャサイズを再計算し、標準のウィンドウサイズに対してどの程度拡大縮小されたのか倍率を測り Vertex shader へ送信するための値を算出する働きを持つ関数。

この関数を利用するためには Room のプロパティから Views 0 の利用を「可」とする設定が必要。

実際のイベント処理

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

Create Event

Init_Shader_Blendmode_Screen();

Draw Event

//

オブジェクトの外観として設定されたスプライトは暗黙の Draw で自動的に描画される設定となっているので、今回はカスタムイベント無し。

Draw GUI Event

DrawGUI_Blendmode_Screen();

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

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

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




次へ

前へ