加算(Screen/スクリーン)

 photoshop   screen   sampler2D   safari 

{{< twitter >}}

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

Photoshop のレイヤー画像に適用される「ブレンドモード( 描画モード ) 」は全種類( CS6 を参考に ) GLSL ES と GameMaker:Studio で再現可能。

GameMakerStudio 1.4 Shader :: Photoshop like BlendMode, Screen

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

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

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

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

GM:S 1.4 Shader :: Resources and Resource Tree - Screenshot -

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

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

    1. ( 暗黙の draw を利用 )

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

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

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

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

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

シェーダー名:SH_Blendmode_Screen

参考動作:Shader-BlendMode-Screen

OpenGL-ES_50px_May16 WebGL_50px_June16

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

合成色用画像

今回用意した合成色用の画像は上。これを 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,b,c;
a = SH_Blendmode_Screen;
    if !shader_is_compiled(a) {
         b = "ur hardware ain't support shader - SH_Blendmode_Screen";
         show_message(b);
    }
    else{
         /* for Shaders */
         b = "u_TexSampler";
         c = "u_ratio";
         U_sampler = shader_get_sampler_index(a , b);
         U_ratio   = shader_get_uniform(a , c);
         /* Instance Variables */
         Surf      = noone;// surface
         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();
    };
 

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

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
Init_Shader_Blendmode_Screen();

Draw Event

///noone any action

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

Draw GUI Event

///DrawGUI
DrawGUI_Blendmode_Screen();

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

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

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




次へ

前へ