GameMaker:Studio Shader Convert Depth to Scale

 shader   scale   GLSL_ES 

インスタンスの描画サイズを頂点シェーダで変更する

GM:S のオブジェクト・インスタンスが持つ組み込み変数「depth」を利用し、奥行きに合わせてインスタンスのサイズを変更するシェーダ。

焦点距離が近いものは大きく、遠いものは小さく、距離に応じた重なり

今回はこれまで公開してきた他サンプルとは少しコンセプトが異なり、オブジェクトを二つ使っている点に注意。

Resource Tree

オブジェクト名
イベント
役割
ユーザ定義関数
ev
Create と Step
アクションの実行
Ev_Init_SH_gl_Pos_W/Ev_Debug_Keys
robo
Draw
スプライトの描画
Robo_ConvertDepthToScale/Robo_DrawEv_SH_gl_Pos_W
    1. Create
    2. Step
    3. Draw

オブジェクトは二つ使っているが、イベントは上記の三つへ収まるように作った。

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


キーボードの R キーでルームリスタート( インスタンスの再配置 )
スペースキー押しで描画切り替え、シェーダ機能のオン・オフ

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

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

シェーダー名:SH_gl_Pos_W

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;

uniform float U_Depth;

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

Fragment Shader

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
}

ユーザ定義関数 Ev_Init_SH_gl_Pos_W

///Ev_Init_SH_gl_Pos_W();
draw_set_font(font0);
    if (!shader_is_compiled(SH_gl_Pos_W)) {
        show_message("Your hardware doesn't support shader - SH_gl_Pos_W");
    }
    else{
        draw_set_colour(c_white);
        /* For Debug */
        Shader  = true;
        
        /* Instance Variable */
        DepRan  = 4;//2 ~ x00;
        
        var a,b,c,d,e,f;
        a = 40;    // repeat
        b = DepRan;// range
        c = 0;
        d = 100;   // offset
        e = room_width  - d;
        f = room_height - d;
            repeat a {
                with (instance_create(0, 0, robo)){
                    xstart  = random_range(c , e);
                    ystart  = random_range(c , f);
                    x       = xstart;
                    y       = ystart;
                    depth   = irandom_range(-b , b);//show_message(depth);
                    
                    /* Instance Variables for Vertex Shader */
                    U_Depth = shader_get_uniform(SH_gl_Pos_W , "U_Depth");
                    Rcdts   = Robo_ConvertDepthToScale(b , depth);
                };
            };
    };

ユーザ定義関数 Ev_Debug_Keys

///Ev_Debug_Keys();
    if keyboard_check_released(ord("R")) room_restart();
    if keyboard_check_released(vk_space) ev.Shader = !ev.Shader;

ユーザ定義関数 Robo_ConvertDepthToScale

///Robo_ConvertDepthToScale(ev.DepRan , robo.depth);
var a,b,c,d,e;
a = 100.0;
b = argument0;
c = (abs(argument1 - b) / a);
d = (b/a);
e = ((d - (d - c)) / d);// depth を視点からのキョリに置き換える
return e;

ユーザ定義関数 Robo_DrawEv_SH_gl_Pos_W

///Robo_DrawEv_SH_gl_Pos_W();
var a;
a = Rcdts;
    if ev.Shader {
        shader_set(SH_gl_Pos_W);
        shader_set_uniform_f( U_Depth , a );
        draw_self();
        draw_text(x,y, a);
        shader_reset();
    }
    else{
        draw_self();
        draw_text(x,y, a);
    };

実際のイベント処理

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

Create Event (ev)

Ev_Init_SH_gl_Pos_W();

ここではオブジェクト robo のインスタンスを 40 個作成している。

repeat 以降 with ステートメントを使ってインスタンスの初期配置を終えると同時に、今回の描画で必要と成るインスタンス変数など必要なネタを仕込む。

このスクリプトの肝は depth である。

GM:S はインスタンス同士の描画的な重なりを depth で制御している。

draw イベントにおける draw の実行順も、インスタンスが複数ある場合には depth に適用された値でソートされ、これで描画の実行順が決まる。

depth は値が小さいほど手前へ描画され、値が大きければ奥へ描画される。つまり depth で大きな値を割り当てられたインスタンスは、draw の実行順で言うと一番最初に実行され、徐々に小さい値を持つインスタンスが順番で描画されていく。

プラス値( 大きな値 ) ゼロ マイナス値( 小さな値 )
奥へ描画 デフォルト 手前へ描画
Draw 実行順の最初 デフォルト Draw 実行順の最後

インスタンスが二つあった場合を考えてみよう。

depth が -100 と depth が 100 のオブジェクト・インスタンスならば、Draw の実行順は depth == 100 のインスタンスが先、 depth == -100 のインスタンスは後。

そしてスプライトは描画位置が重なっている場合、depth == -100 のインスタンスが手前に描画され、depth == 100 のインスタンスは奥に隠れたように描画される。

2D なので描画に奥行きは無いが、スプライトの重なりは上記のごとく depth で制御されている。この depth の値を使って描画スケールを調整することによって、2D の画面に奥行きが発生するような擬似 3D 的表現を頂点シェーダを用いて実現する。

Vertex シェーダは頂点を制御するシェーダであり、頂点毎に実行される。2D スプライトの描画には 4 点が用いられる。

シェーダ使わなくともスプライトの拡縮描画は可能だが、シェーダを使った場合、GPU の行列とベクトル計算には並列処理が利用されるため、シェーダ使ったほうが恐らく GM:S の標準よりも処理速度的には速い。特にインスタンス数が多い場合には GPU 処理の方が効率良く捌けるであろうことを期待している。

( ただし、本当に速いかどうかはシラン。 )

Step Event ( ev )

Ev_Debug_Keys();

Draw Event ( robo )

Robo_DrawEv_SH_gl_Pos_W();

attribute vec3 in_Position; は vec3 の修飾子が示す通り、ベクトルには三つの要素が入っている。

.xyz である。

gl_Position は GLSL ES の Vertex シェーダ固有の組み込み変数で、この変数は行列とベクトルを掛け合わせて変換した値が格納される。

4✕4 マトリクスと掛け合わせるために用いられるベクトルも要素は四つ必要で、前述した attribute vec3 in_Position; では要素数が一個足りない。

足りない要素が即ち .w である。

今回のシェーダサンプルはこの「 .w って要素はなんやねん」という疑問からスタートしており、vertex シェーダ使って何か出来ないかなという試行錯誤の結果。

やっていることはシェーダ使ったスプライトの描画と単なる拡大縮小。処理速度的なメリットが得られなかった場合にはシェーダでやる意味も無いかと思われます。




次へ

前へ