オーバーレイ(Overlay) rev.2

 photoshop   overlay   sampler2D   overload 

{{< twitter >}}

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

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

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

Overlay シェーダ/右が合成結果

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

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

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

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

参考( renderingpipeline.com ) Photoshop Blendmodi in GLSL

参考( github.com : Jamie Owen ) jamieowen/glsl-blend/overlay.glsl

関連( prester.org ) Overlay -Sample- rev.1

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

スペースキーでシェーダ効果のオンオフ切り替え可能。

値は -1.0 〜 1.0 までの範囲で制限。

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

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

背景と上へレイヤー的に重ねるシャドウマップ的画像( texture ) など、グラフィックスリソースが最低二枚必要。今回は以下画像を背景としてリソースツリーに登録しました。

Overlay 用レイヤー

シェーダー名:SH_Overlay

参考動作:Shader-Overlay (rev.2)

OpenGL-ES_50px_May16 WebGL_50px_June16

Vertex Shader

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

void main()
{
    vec4 pos    = vec4(in_Position, 1.0);
    mat4 GMW    = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION];
    gl_Position = GMW * pos;
    bg_Colour   = in_TextureCoord;
}
 

Fragment Shader

 
varying vec2 bg_Colour;  //background colour

uniform sampler2D u_oTex;//foreground colour
uniform float     u_Fade;
/*
https://github.com/jamieowen/glsl-blend/blob/master/overlay.glsl

BA = base , FC = blend , OY = opacity
User defined functions "blendOverlay" >> "BOy"

関数のオーバーロード、引数を二つしか与えない場合は 3番目の関数は不要となる
*/
float BOy(float BA, float FC) {
return BA < 0.5 ? (2.0*BA*FC):(1.0-2.0*(1.0-BA)*(1.0-FC));
}
vec3  BOy(vec3 BA,  vec3 FC)  {
return vec3(BOy(BA.r,FC.r),BOy(BA.g,FC.g),BOy(BA.b,FC.b));
}
vec3  BOy(vec3 BA,  vec3 FC, float OY) {
return (BOy(BA, FC) * OY + BA * (1.0 - OY));
}

void main()
{
    vec4 BgCol   = texture2D(gm_BaseTexture , bg_Colour);
    vec4 FgCol   = texture2D(u_oTex , bg_Colour);
    vec3 Compo   = BOy(BgCol.rgb, FgCol.rgb, u_Fade);
    gl_FragColor = vec4(Compo, FgCol.a);
}
 

rev.1 と比較して大幅に書き直した部分。関数のオーバーロードを使用。

C 言語ではサポートされていないが、C++ では関数の多重定義( Overloaded function ) がサポートされています。GLSL のサンプルコードを見ていた時に関数の多重定義を使ったものがあったため、GLSL ES でも可能なものかなと試してみました。

結論、サポートされています( 再帰処理は無理だが )

オーバーレイ用の計算式は

    (base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend)))

Romain Dura が公開してくれているソースがあるので、一般的に皆それを見てるようです。しかし式は少々複雑なのでもっと別の処理ないかなとか思っていました( わがまま )

すると github.com の方では関数のオーバロード使ったサンプルが公開されていたため、今回はこちらを参考にしました。

ユーザ定義関数は三つ、そして全部同じ名前です。これが Overload であり、関数に与える引数の数や型が違えば関数名が同じでも別関数として扱われます。今回は引数の数に応じて最初に実行される関数が異なり、例えばオーバーレイの適用率を変化させたい場合は引数を三つ必要としますが、適用率を無視して良いのならば引数は二つで済むため一番最後に定義された関数が不要と成ります。

ユーザ定義関数 Init_Shader_Overlay

///Init_Shader_Overlay();
draw_set_font(font0);
draw_enable_alphablend(true);
var a,b,c;
a = SH_Overlay;
    if ( !shader_is_compiled(a) ) {
        b = "Your hardware doesn't support shader - SH_Overlay";
        show_message(b);
    }
    else{
        b       = "u_oTex";
        c       = "u_Fade";
        /* Instance variables For Shader */
        U_oTex  = shader_get_sampler_index(a , b);
        U_fAde  = shader_get_uniform(a , c);
        Value   = 1.0;   // for Fade
        /* Instance variables For Surfaces */
        Stage   = noone; // #1 for for color composite
        Surf    = noone; // #0 for Base color
        /* Instance variables For Debug */
        Debug   = true;
    };
draw_enable_drawevent(true);
 

ユーザ定義関数 Draw_Set_Surfaces

///Draw_Set_Surfaces();
var a,b,c, d;
a = background;//for Base color
b = overlay;   //for color composite
c = SH_Overlay;//Shaer

    if !Surf {
        Surf  = surface_create(room_width , room_height);
        surface_set_target(Surf);
        draw_background(a,0,0);
        surface_reset_target();
    };
    
    if !Stage {
        Stage = surface_create(room_width , room_height);
        surface_set_target(Stage);
        draw_background(b,0,0);
        surface_reset_target();
        /* send texture-"Stage" to GPU Slot */
        d = surface_get_texture(Stage);
            shader_set(c);
            texture_set_stage(U_oTex, d);
            shader_reset();
    };
 

rev.1 と比較してコードにだいぶ修正を加えた部分。

texture_set_stage 関数の使い方が前回適切とは言えなかったため、Surface を作った時に一度だけ GPU へ送信するように変更。こちらの方が効率的。仕様を読むと、スロットへ送信されたテクスチャは GPU メモリ上で保持され、テクスチャの内容に変更・更新が無いのならば、その後スロットへ再送信する必要がありません。

シェーダを使った二枚のテクスチャを合成する作業は DrawGUI イベントで行います。

ユーザ定義関数 DrawGUI_SH_Overlay

///DrawGUI_SH_Overlay();
var a,b;
a = Surf;
b = Value;

    if Debug {
        shader_set(SH_Overlay);
        shader_set_uniform_f(U_fAde, b);
        draw_surface(a,0,0);
        shader_reset();
    }
    else{
        draw_surface(a,0,0);
    };
 

rev.1 で複雑な処理を作ったことは反省し、今度はなるべくシンプルを心がけました。

合成用のサンプラーは surface を作った時点で GPU へ送信済み。

ユーザ定義関数 DrawGUI_CheckKeys

///DrawGUI_CheckKeys();
var a,b,c,d,e,f,g,h;
a = 10;
b = room_height;
c = "Shader SH_Overlay";
d = "Press Q & A, Ajust Overlay";
e = "press Space key, toggle Shader";
f = "u_Fade = " + string_format(Value,3,3);
g = Value;
h = 0.02;

/* draw normal message */
draw_set_colour(c_red);
draw_text(a,0,     c);
/* draw for Debug */
draw_set_colour(c_purple);
draw_text(a,b-150, d);
draw_set_colour(c_yellow);
draw_text(a,b-100, e);
draw_set_colour(c_blue);
draw_text(a,b-50,  f);

/* Check keys */
if keyboard_check(ord("Q")) g += h;
if keyboard_check(ord("A")) g -= h;
if keyboard_check_pressed(vk_space) Debug = !Debug;

/* finalize */
Value = clamp(g, -1.0 , 1.0);
 

イベント処理

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

Create Event

///Init
Init_Shader_Overlay();

Draw Event

///Draw
Draw_Set_Surfaces();

DrawGUI Event

///DrawGUI
DrawGUI_SH_Overlay();
DrawGUI_CheckKeys();

テクスチャを二枚用意して色合成する Overlay は派生効果を作りやすいシェーダです。

マップを工夫したり、フラグメント内の計算式を Overlay 以外のものへ変更したり、色々楽しめます。

rev.1 は複雑な処理に加え、HTML5 でコードに非互換性が現れるなど問題ありましたが、rev.2 の今回は HTML5( WebGL ) でも問題発生せず。




次へ

前へ